Demonstration of d3-parent, a plugin that makes it easier to navigate around hierarchical selections and adds a more stable API around parentNode.
Last active
November 12, 2016 22:17
-
-
Save vijithassar/2b1bf939d3c5a4ae2a824f3973364363 to your computer and use it in GitHub Desktop.
d3-parent example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function (global, factory) { | |
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-selection')) : | |
typeof define === 'function' && define.amd ? define(['exports', 'd3-selection'], factory) : | |
(factory((global.d3 = global.d3 || {}),global.d3)); | |
}(this, function (exports,d3Selection) { 'use strict'; | |
var all_parents; | |
var direct_parent; | |
var closest_parent; | |
var single_parent; | |
var iterator; | |
var selector_to_nodes; | |
var parent; | |
// match all parents with linear iteration | |
all_parents = function(node, candidates) { | |
var candidate, | |
results, | |
i, | |
ilength; | |
results = []; | |
for (i = 0, ilength = candidates.length; i < ilength; i++) { | |
candidate = candidates[i]; | |
if (candidate.contains(node)) { | |
results.push(candidate); | |
} | |
} | |
return results; | |
}; | |
// return the immediate parentNode | |
direct_parent = function(node) { | |
if (node && node.parentNode) { | |
return node.parentNode; | |
} else { | |
return null; | |
} | |
}; | |
// match by iterating upward through the DOM. this is an | |
// expensive operation and should be avoided when another | |
// method will suffice. | |
closest_parent = function(node, candidates) { | |
var results, | |
match, | |
end; | |
results = []; | |
// iterate upward from starting node | |
while (!match && !end && node.parentNode) { | |
// reassign node to its own parent | |
node = node.parentNode; | |
end = !node.parentNode; | |
// test selection for current iterator node | |
match = candidates.indexOf(node) !== -1; | |
} | |
// if there is a match, add it to the results | |
if (match) { | |
results.push(node); | |
} | |
return results; | |
}; | |
// find exactly one parent node, optimizing iteration when possible | |
single_parent = function(node, candidates) { | |
var results, | |
result; | |
// try linear search first | |
results = all_parents(node, candidates); | |
// if linear search matches multiple candidates, | |
// retry hierarchically to find the single closest parent | |
if (results.length && results.length > 1) { | |
results = closest_parent(node, candidates); | |
} | |
// if the results are in array format, take the first | |
if (results.length > 0) { | |
result = results[0]; | |
} else { | |
result = results; | |
} | |
return result; | |
}; | |
// reformat a multidimensional array of nodes as a d3 selection | |
// optionally inserting a processing function along the way | |
iterator = function(input_selection, processor) { | |
var output_selection, | |
group, | |
node, | |
parent, | |
i, | |
ilength, | |
j, | |
jlength; | |
// create a new selection and copy the existing nodes | |
output_selection = d3Selection.selection(); | |
output_selection._parents = input_selection._parents; | |
// loop through groups | |
for (i = 0, ilength = input_selection._groups.length; i < ilength; i++) { | |
group = input_selection._groups[i]; | |
if (typeof output_selection._groups[i] === 'undefined') { | |
output_selection._groups[i] = []; | |
} | |
// loop through nodes | |
for (j = 0, jlength = group.length; j < jlength; j++) { | |
// process nodes | |
node = group[j]; | |
if (node) { | |
parent = processor(node); | |
output_selection._groups[i][j] = parent; | |
} | |
} | |
} | |
return output_selection; | |
}; | |
// fetch an array of matching nodes for a DOM selector string | |
selector_to_nodes = function(selector) { | |
var node_list, | |
nodes; | |
if (!selector) { | |
console.error('missing DOM selector string'); | |
return; | |
} | |
node_list = document.querySelectorAll(selector); | |
// convert to array | |
nodes = Array.prototype.slice.call(node_list); | |
return nodes; | |
}; | |
// public function to select single parent matching a selector | |
parent = function(selector) { | |
var candidates, | |
processor, | |
results; | |
if (!selector) { | |
processor = function(node) { | |
return direct_parent(node); | |
}; | |
} else { | |
candidates = selector_to_nodes(selector); | |
processor = function(node) { | |
return single_parent(node, candidates); | |
}; | |
} | |
results = iterator(this, processor); | |
return results; | |
}; | |
var parent$1 = parent; | |
d3Selection.selection.prototype.parent = parent$1 | |
exports.selection = d3Selection.selection; | |
exports.select = d3Selection.select; | |
Object.defineProperty(exports, '__esModule', { value: true }); | |
})); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<title>d3-parent example</title> | |
</head> | |
<body> | |
<div class="target"></div> | |
<script type="text/javascript" src="https://d3js.org/d3.v4.min.js"></script> | |
<script type="text/javascript" src="./d3-parent.js"></script> | |
<script type="text/javascript" src="./magnetic-poetry.js"></script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function(d3) { | |
'use strict' | |
var svg, | |
wrapper, | |
height, | |
width, | |
increment, | |
x_increment, | |
y_increment, | |
highlight, | |
text, | |
menu, | |
sentence_count, | |
switcher, | |
mode, | |
modes, | |
drag, | |
move, | |
nest, | |
create_menu, | |
magnetic_poetry | |
// configuration values | |
width = 960 | |
height = 480 | |
increment = 15 | |
x_increment = increment | |
y_increment = x_increment * 2 | |
highlight = 'red' | |
modes = ['character', 'word', 'sentence', 'text'] | |
mode = modes[0] | |
// behaviors | |
move = function() { | |
var transition, | |
target, | |
node, | |
nodes, | |
index, | |
word_offset | |
if (!mode) { | |
mode = modes[0] | |
} | |
node = this | |
target = d3.select(node).parent('g.' + mode) | |
nodes = d3.select(node) | |
.parent('g.sentence') | |
.selectAll('g.character') | |
.nodes() | |
index = nodes.indexOf(node) | |
word_offset = mode === 'word' ? index * x_increment * -1 : 0 | |
target.transition().ease(d3.easeLinear).attr('transform', function() { | |
var parent, | |
container, | |
coordinates, | |
x, | |
y | |
if (mode === 'text') { | |
parent = 'wrapper' | |
} else { | |
parent = modes[modes.indexOf(mode) + 1] | |
} | |
container = d3.select(this).parent('g.' + parent).node() | |
coordinates = d3.mouse(container) | |
x = coordinates[0] + word_offset | |
y = coordinates[1] | |
return 'translate(' + x + ',' + y + ')' | |
}) | |
} | |
drag = d3.drag().on('drag.text', move) | |
// set up dom | |
svg = d3.select('body') | |
.append('svg:svg') | |
.attr('height', height) | |
.attr('width', width) | |
wrapper = svg.append('g') | |
.classed('wrapper', true) | |
.attr('transform', 'translate(' + increment + ',' + increment + ')') | |
text = wrapper.append('g').classed('text', true) | |
// use a monospaced font to simplify position calculations | |
text | |
.style('font-family', 'Courier') | |
.style('text-anchor', 'middle') | |
.style('font-size', increment * 1.5) | |
.attr('transform', 'translate(' + increment * 5 + ',' + increment * 5 + ')') | |
menu = wrapper.append('g').classed('menu', true) | |
// toggle the menu options | |
switcher = function() { | |
svg.select('g.options').selectAll('text') | |
.on('mouseenter', function(d, i) { | |
svg.select('g.options').selectAll('text') | |
.style('fill', 'black') | |
d3.select(this) | |
.style('fill', highlight) | |
mode = d | |
}) | |
} | |
// menu to toggle between operational modes | |
create_menu = function() { | |
var instruction, | |
options, | |
option, | |
label | |
menu = wrapper.append('g').classed('menu', true) | |
instruction = menu.append('g').classed('instruction', true) | |
instruction.append('text').text('move:') | |
options = menu.append('g').classed('options', true) | |
options.attr('transform', 'translate(' + increment + ',' + increment + ')') | |
option = options.selectAll('g.option') | |
.data(modes) | |
.enter() | |
.append('g') | |
.attr('class', function(d) { | |
return ['option', d].join(' ') | |
}) | |
option.attr('transform', function(d, i) { | |
return 'translate(0,' + i * increment + ')' | |
}) | |
label = option | |
.append('text') | |
label | |
.text(function(d) { | |
return d === 'text' ? 'all ' + d : d + 's' | |
}) | |
options.selectAll('g.option.' + mode) | |
.style('fill', highlight) | |
} | |
// convert text into deeply nested arrays which can be bound | |
nest = function(sentences) { | |
var nested_sentences | |
nested_sentences = sentences.map(function(sentence) { | |
var words, | |
nested_words | |
words = sentence.split(' ') | |
nested_words = words.map(function(word) { | |
var characters | |
characters = word.split('') | |
return characters | |
}) | |
return nested_words | |
}) | |
return nested_sentences | |
} | |
// wrapper function to execute all text content | |
magnetic_poetry = function(sentences) { | |
var nested_sentences, | |
word_count, | |
character_count, | |
sentence, | |
word, | |
character, | |
handle | |
nested_sentences = nest(sentences) | |
sentence_count = d3.local() | |
word_count = d3.local() | |
character_count = d3.local() | |
sentence = text.selectAll('g.sentence') | |
.data(nested_sentences) | |
.enter() | |
.append('g') | |
.classed('sentence', true) | |
sentence.attr('transform', function(d, i) { | |
var y | |
y = i * y_increment | |
return 'translate(0,' + y + ')' | |
}) | |
sentence.each(function(d, i) { | |
sentence_count.set(this, i) | |
character_count.set(this, 0) | |
}) | |
word = sentence.selectAll('g.word') | |
.data(function(d) { | |
return d | |
}) | |
.enter() | |
.append('g') | |
.classed('word', true) | |
word.each(function(d, i) { | |
word_count.set(this, i) | |
}) | |
character = word.selectAll('g.character') | |
.data(function(d) { | |
return d | |
}) | |
.enter() | |
.append('g') | |
.classed('character', true) | |
character.each(function(d, i, a, b) { | |
var sentence_node, | |
word_node, | |
current | |
sentence_node = d3.select(this).parent('.sentence').node() | |
word_node = d3.select(this).parent('.word').node() | |
current = character_count.get(sentence_node) | |
character_count.set(sentence_node, current + 1) | |
d3.select(this).attr('transform', function(d, i) { | |
var x_offset, | |
x | |
x_offset = character_count.get(sentence_node) + word_count.get(word_node) | |
x = x_offset * x_increment | |
return 'translate(' + x + ',0)' | |
}) | |
}) | |
character | |
.append('text') | |
.text(function(d) { | |
return d | |
}) | |
handle = character.append('rect') | |
handle | |
.attr('height', function() { | |
return this.parentNode.getBoundingClientRect().height | |
}) | |
.attr('width', function() { | |
return this.parentNode.getBoundingClientRect().width | |
}) | |
.attr('x', x_increment * -1 * 0.5) | |
.attr('y', y_increment * -1 * 0.5) | |
.style('opacity', 0.0001) | |
character | |
.style('cursor', 'pointer') | |
.call(drag) | |
} | |
// fetch data file | |
d3.json('./sentences.json', function(error, results) { | |
if (error) { | |
console.error('one or more data files could not be found') | |
} | |
// render | |
create_menu() | |
switcher() | |
magnetic_poetry(results) | |
}) | |
}).call(this, d3) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[ | |
"It's like raaaaiiiiaaaaiiin on your wedding day", | |
"It's a freeeeee riiiiiide when you've already paid", | |
"It's the good advice that you just didn't take", | |
"Who would have thought, it figures" | |
] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment