block by vijithassar 2b1bf939d3c5a4ae2a824f3973364363

d3-parent example

Full Screen

Demonstration of d3-parent, a plugin that makes it easier to navigate around hierarchical selections and adds a more stable API around parentNode.

index.html

<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>

d3-parent.js

(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 });

}));

magnetic-poetry.js

(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)

sentences.json

[
  "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"
]