Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

D3-Sankey Error: Missing: (nodename)

import React, { Component } from 'react';

import { json } from 'd3-request';
import { rgb } from 'd3-color';
import { interpolateHcl } from 'd3-interpolate';
import { scaleLinear, scaleOrdinal } from 'd3-scale';
import { arc, line, pie, curveMonotoneX } from 'd3-shape';
import { format } from 'd3-format';
import { min, max } from 'd3-array';
import { select } from 'd3-selection';
import { sankey as sankeyGraph, sankeyLinkHorizontal } from 'd3-sankey';

class Graph extends React.Component {
    constructor(props) {
        super(props);

        this.createLineGraph = this.createLineGraph.bind(this);
        this.createBarChart = this.createBarChart.bind(this);
        this.createPieChart = this.createPieChart.bind(this);
        this.createSankeyGraph = this.createSankeyGraph.bind(this);
        // this.createRadialChart = this.createRadialChart.bind(this);

        this.createTheGraphs = this.createTheGraphs.bind(this);

        this.state = {
            loading: false
        };
    }

    getDimensions() {
        const margin = {top: 20, right: 20, bottom: 20, left: 20},
            padding = {top: 40, right: 40, bottom: 40, left: 40},
            outerWidth = parseInt(this.props.size[0]),
            outerHeight = parseInt(this.props.size[1]),
            innerWidth = outerWidth - margin.left - margin.right,
            innerHeight = outerHeight - margin.top - margin.bottom,
            width = innerWidth - padding.left - padding.right,
            height = innerHeight - padding.top - padding.botto,
            radius = parseInt(min([innerWidth, innerHeight]) / 2),
            donutHole = this.props.type === "DONUT" ? radius / 2 : 0,
            color = scaleLinear()
                .domain([1, this.props.data.length])
                .interpolate(interpolateHcl)
                .range([rgb("#AF2192"), rgb("#98D719")]);

        // DON'T DO DATA MAPPING ON SANKEY GRAPH SINCE DATA STRUCTURE IS DIFFERENT
        if (this.props.type !== "SANKEY") {

            // HIGHEST VALUE OF ITEMS IN DATA ARRAY
            const dataMax = max(this.props.data.map(item => item.value)),
                dataSpread = (innerWidth / this.props.data.length),
                // DEPEND SCALE OF ITEMS ON THE Y AXIS BASED ON HIGHEST VALUE
                yScale = scaleLinear()
                    .domain([0, dataMax])
                    .range([0, innerHeight]),
                // GENERATE THE LINE USING THE TOTAL SPACE AVAILABLE FROM THE SIZE PROP DIVIDED BY THE LENGTH OF THE DATA ARRAY
                lineGen = line()
                    .x((d, i) => i * dataSpread)
                    .y(d => innerHeight - yScale(d))
                    // CURVEMONOTONEX GAVE THE BEST RESULTS
                    .curve(curveMonotoneX);

            dimensions = {margin, padding, outerWidth, outerHeight, innerWidth, innerHeight, radius, donutHole, color, dataMax, dataSpread, yScale, lineGen};

        } else {

            dimensions = {margin, padding, outerWidth, outerHeight, innerWidth, innerHeight, radius, donutHole, color};

        }

    }

    createSankeyGraph(data) {
        const sankeyNode = this.node;

        let graphData = this.props.data;

        // console.log(graphData);
        // console.log(graphData.links);
        // console.log(graphData.nodes);

        // console.log(dimensions.outerWidth, dimensions.outerHeight);

        // GET DIMENSIONS IN A GLOBAL-VAR-LIKE WAY
        this.getDimensions();

        const formatNumber = format('.1f');
        const formatted = function(d) {return formatNumber(d) + " Potential Guests"};
        const color = scaleLinear()
            .domain([1, 3])
            .interpolate(interpolateHcl)
            .range([rgb('#126382'), rgb('#417685')]);

        var sankey = sankeyGraph()
            .nodeWidth(15)
            .nodePadding(10)
            .extent([1, 1], [parseInt(dimensions.outerWidth) - 1, parseInt(dimensions.outerHeight) - 6]);

        var SVG = select(sankeyNode)
            .append('g')
            .attr('transform', 'translate(' + dimensions.margin.left + ',' + dimensions.margin.top +')');

        var link = SVG.append('g')
            .attr('class', 'links')
            .attr("fill", "none")
            .attr("stroke", "#000")
            .attr("stroke-opacity", 0.2)
            .selectAll('path')

        var node = SVG.append('g')
            .attr('class', 'nodes')
            .attr("font-family", "sans-serif")
            .attr("font-size", 10)
            .selectAll('g')

        // json('https://api.myjson.com/bins/15xgsd', function(error, graphData){

            sankey(graphData);

            // console.log(graphData.nodes, graphData.links);

            link = link
                .data(graphData.links)
                .enter()
                .append('path')
                .attr('d', sankeyLinkHorizontal())
                .attr('stroke-width', function(d) { return Math.max(1, d.width); });

            link.append('title')
                .text(function(d) { return d.source.name + " → " + d.target.name + "\n" + formatted(d.value); });

            node = node
                .data(graphData.nodes)
                .enter()
                .append('g')

            node.append('rect')
                .attr('x', function(d) { return d.x0; }) 
                .attr('y', function(d) { return d.y0; }) 
                .attr('height', function(d) { return d.y1 - d.y0}) 
                .attr('width', function(d) { return d.x1 - d.x0}) 
                .attr("fill", function(d, i) { return color(i); })
                .attr('stroke', 'black');

            node.append('text')
                .attr('x', function(d) {return d.x0 - 6})
                .attr('y', function(d) {return (d.y1 + d.y0) / 2})
                .attr('dy', '.35em')
                .attr('text-anchor', 'end')
                .text(function(d) { return d.name; })
                .filter(function(d) { return d.x0 < dimensions.innerWidth / 2; })
                .attr('x', function(d) { return d.x1 + 6; })
                .attr('text-anchor', 'start');

            node.append('title')
                .text(d => d.name + "\n" + formatted(d.value));

        // });

    }

    createTheGraphs() {
        (this.props.type === "LINE") ? this.createLineGraph() : "";
        (this.props.type === "BAR") ? this.createBarChart() : "";
        (this.props.type === "PIE" || this.props.type === "DONUT") ? this.createPieChart() : "";
        (this.props.type === "SANKEY") ? this.createSankeyGraph() : "";
        (this.props.type === "RADIAL") ? this.createRadialChart() : "";
    }

    componentWillMount() {
        this.setState({ loading: true });
    }

    componentDidMount() {
        this.createTheGraphs();
    }

    componentDidUpdate() {
        this.createTheGraphs();
    }

    render() {
        return(
            <div className="Graph">
                <svg className='Graph_Container' ref={node => this.node = node}></svg>
                <h2>{this.props.type} Placeholder</h2>
            </div>
        );
    }
}

Graph.propTypes = {

};

export default Graph;

What's happening? Well, basically the console is outputting

Error: missing: Peter  modules.js:54276:20
find http://localhost:3000/packages/modules.js:54276:20
computeNodeLinks/< http://localhost:3000/packages/modules.js:54353:62
forEach self-hosted:267:13
computeNodeLinks http://localhost:3000/packages/modules.js:54350:5
sankey http://localhost:3000/packages/modules.js:54292:5
createSankeyGraph http://localhost:3000/app/app.js:554:13
createSankeyGraph self-hosted:941:17
createTheGraphs http://localhost:3000/app/app.js:598:44
createTheGraphs self-hosted:941:17
componentDidMount http://localhost:3000/app/app.js:617:13
mountComponent/</< http://localhost:3000/packages/modules.js:17838:20
measureLifeCyclePerf http://localhost:3000/packages/modules.js:17649:12
mountComponent/< http://localhost:3000/packages/modules.js:17837:11
notifyAll http://localhost:3000/packages/modules.js:10464:9
close http://localhost:3000/packages/modules.js:20865:5
closeAll http://localhost:3000/packages/modules.js:11623:11
perform http://localhost:3000/packages/modules.js:11570:11
batchedMountComponentIntoNode http://localhost:3000/packages/modules.js:22897:3
perform http://localhost:3000/packages/modules.js:11557:13
batchedUpdates http://localhost:3000/packages/modules.js:20563:14
batchedUpdates http://localhost:3000/packages/modules.js:10225:10
_renderNewRootComponent http://localhost:3000/packages/modules.js:23090:5
_renderSubtreeIntoContainer http://localhost:3000/packages/modules.js:23172:21
render http://localhost:3000/packages/modules.js:23193:12
routes.js/< http://localhost:3000/app/app.js:1504:3
maybeReady http://localhost:3000/packages/meteor.js:821:6
loadingCompleted http://localhost:3000/packages/meteor.js:833:5

Which results in the graph not rendering the nodes it needs to base the lines (paths) on. The only HTML I get back is:

<svg class="Graph_Container">
    <g transform="translate(20,20)">
        <g class="links" fill="none" stroke="#000" stroke-opacity="0.2"></g>
        <g class="nodes" font-family="sans-serif" font-size="10"></g>
    </g>
</svg>

No nodes in the 'g.nodes' thus no links in the 'g.links'. The data structure this graph should be processing looks like:

<Graph type="SANKEY"
data={{
    nodes: [
        {name: "Peter"},
        {name: "Test.com"},
        {name: "Thing.com"},
        {name: "AnotherName"}
    ], links: [
        {source: "Peter", target: "Test.com", value: 50},
        {source: "Peter", target: "Thing.com", value: 50},
        {source: "Test.com", target: "AnotherName", value: 50},
        {source: "Thing.com", target: "AnotherName", value: 50}
    ]
}}
size={[500, 500]} />

I don't know where to go from here. With this package I jumped from issue to issue altering the code line by line. The original issue looked like this.

like image 933
Casimir de Bruijn Avatar asked Sep 15 '17 13:09

Casimir de Bruijn


1 Answers

In case links are specified using a source and target name (i.e. a string) instead of node indices, the solution is to specify a nodeId mapping function:

d3.sankey()
  .nodeId(d => d.name) // Needed to avoid "Error: Missing: myNode"

Of course one may have to adjust the function d => d.name to correspond to the actual data.

like image 172
gogo Avatar answered Oct 16 '22 05:10

gogo