Why are x, y in a twin d3v7 force graph NaN? Why doesn’t the data work properly?

I want to implement a dual-panel set of D3 force graphs (see below).

I use an adjacency list to split my original data into two graphs. There are two copies of everything, so individual elements can be retrieved or selected in future feature development

I have four steps in the graph setup corresponding to four functions in the namespace:

  1. Function ns.initSVG(data, inputOptions) -> sets up the two svg’s
  2. Function ns.updateGraph(graph, options) -> splits the original graph into two graphs
  3. Function ns.showGraphs(options) -> sets up all of the lines, labels and nodes
  4. Function ns.simGraph(options) -> runs the simulations

For some reason x and y are both NaN. Why?

I have tried everything i can think of, including logging out data at various steps, but I can’t find where my setup error is.

Can you help me find it please?

I have a working copy on VizHub here, but i also present the code and data below.

The namespace and adjacency list that drives the two graphs are


//----------------------------------------
//------------------------------------------
// Adjaceny List Class
//-------------------------------------------
class Graph {
  constructor() {
    this.t = new Map();
  }
  addEdge(node1, node2) {
    const s = this.t.get(node1);
    if (s == null) this.t.set(node1, new Set([node2]));
    else s.add(node2);
  }
  getAdjacencies(node) {
    var z = this.t.get(node);
    if (z == null) {
      z = new Set();
    }
    return z;
  }
  *dir(node, path = Array(), visited = new Set()) {
    yield [...path, node];
    path.push(node);
    visited.add(node);
    for (const adj of this.getAdjacencies(node)) {
      if (!visited.has(adj)) {
        yield* this.dir(adj, path, visited);
      }
    }
    path.pop();
  }
  *dirs(nodes) {
    for (const node of nodes) {
      yield* this.dir(node);
    }
  }
}

//------------------------------------------
// Reference Component Namesapce
//-----------------------------------------
//namespace for all of the object functions and variables
//will be passed to the declared function
// window.workP = {};
// (function (ns) {
//   console.group('testObject');

//   //declare a variable for this namespace
//   ns.testValue = 'value';

//   ns.testFunction = function () {
//     return 'function value';
//   };
//   console.log(['testObject namespace contents', ns]);

//   console.groupEnd();
// })(window.testObject); //execte declaration and pass namespace

// console.log([
//   'testObject',
//   testObject.testValue,
//   testObject.testFunction(),
// ]);

//------------------------------------------
// visualisation component
//-----------------------------------------
window.workP = {};
(function (ns) {
  // A. Initialise the object
  ns.initSVG = async function (data, inputOptions) {
    console.group('Step 1 initSVG');
    console.log(['config', data, inputOptions]);

    //copy inputOptions into options
    let options = Object.assign({}, inputOptions);

    if (options.theme === 'light') {
      options.theme = options.light_theme;
    } else {
      options.theme = options.dark_theme;
    }

    options.steps = [];
    options.steps.push('initSVG');
    //
    // Step 1: Setup the 3 svg's and the tooltip
    options.promotable_svg = d3
      .select('#working_svg')
      .append('svg')
      .attr('width', options.working_width)
      .attr('height', options.svg_height / 2)
      .attr('class', 'promotable_svg');
    options.scratch_svg = d3
      .select('#working_svg')
      .append('svg')
      .attr('width', options.working_width)
      .attr('height', options.svg_height / 2)
      .attr('class', 'scratch_svg');
    options.tooltip = d3
      .select('body')
      .append('div')
      .attr('class', 'tooltip')
      .style('opacity', 0);

    // Step 2: Setup the 3 rectangles
    options.promotable_rect = options.promotable_svg
      .append('rect')
      .attr('width', options.working_width)
      .attr('height', options.svg_height / 2)
      .attr('x', 0)
      .attr('y', 0)
      .attr('stroke', options.theme.svgBorder)
      .attr('fill', options.theme.promoFill);
    options.scratch_rect = options.scratch_svg
      .append('rect')
      .attr('width', options.working_width)
      .attr('height', options.svg_height / 2)
      .attr('x', 0)
      .attr('y', 0)
      .attr('stroke', options.theme.svgBorder)
      .attr('fill', options.theme.scratchFill);
    options.promotable_label = options.promotable_svg
      .append('g')
      .attr('transform', 'translate(' + 10 + ',' + 20 + ')')
      .append('text')
      .text('Promotable')
      .style('fill', options.theme.svgName);
    options.scratch_label = options.scratch_svg
      .append('g')
      .attr('transform', 'translate(' + 10 + ',' + 20 + ')')
      .append('text')
      .text('Scratch Pad')
      .style('fill', options.theme.svgName);

    // Step 3. Connect the svg's to the function object and set the zoom
    options.promo_svg = options.promotable_svg
      .call(
        d3.zoom().on('zoom', function () {
          options.promotable_svg.attr(
            'transform',
            event.transform,
          );
        }),
      )
      .append('g');
    options.scratch_svg = options.scratch_svg
      .call(
        d3.zoom().on('zoom', function () {
          options.scratch_svg.attr(
            'transform',
            event.transform,
          );
        }),
      )
      .append('g');

    // Append the Arrowhead in Promo and Scratch SVG's
    options.promo_svg = options.promo_svg
      .append('defs')
      .append('marker')
      .attr('id', 'parrowhead')
      .attr('viewBox', '-0 -5 10 10') //the bound of the SVG viewport for the current SVG fragment. defines a coordinate system 10 wide and 10 high starting on (0,-5)
      .attr('refX', 50) // x coordinate for the reference point of the marker. If circle is bigger, this need to be bigger.
      .attr('refY', 0)
      .attr('orient', 'auto')
      .attr('markerWidth', 10)
      .attr('markerHeight', 10)
      .attr('xoverflow', 'visible')
      .append('svg:path')
      .attr('d', 'M 0,-5 L 10 ,0 L 0,5')
      .attr('fill', options.checkColour)
      .style('stroke', 'none');

    options.scratch_svg = options.scratch_svg
      .append('defs')
      .append('marker')
      .attr('id', 'sarrowhead')
      .attr('viewBox', '-0 -5 10 10') //the bound of the SVG viewport for the current SVG fragment. defines a coordinate system 10 wide and 10 high starting on (0,-5)
      .attr('refX', 50) // x coordinate for the reference point of the marker. If circle is bigger, this need to be bigger.
      .attr('refY', 0)
      .attr('orient', 'auto')
      .attr('markerWidth', 10)
      .attr('markerHeight', 10)
      .attr('xoverflow', 'visible')
      .append('svg:path')
      .attr('d', 'M 0,-5 L 10 ,0 L 0,5')
      .attr('fill', options.checkColour)
      .style('stroke', 'none');

    // Step 4. Setup the data, first separate the nodes and edges into two groups
    console.groupEnd();
    return options;
  };

  // Step 2: Split the Graph Data
  ns.updateGraph = async function (graph, options) {
    console.group('Step 2 updateGraph');
    console.log('options->', options);

    // let options = Object.assign({}, inputOptions);

    options.steps.push('updateGraph');
    // 1. Setup variables and define promotable types
    options.prom_types = [
      'incident',
      'task',
      'impact',
      'event',
      'sighting',
    ];
    let nodes = graph.nodes;
    let edges = graph.edges;

    options.split = {};
    options.split.promo = {};
    options.split.promo.nodes = [];
    options.split.promo.edges = [];
    options.split.scratch = {};
    options.split.scratch.nodes = [];
    options.split.scratch.edges = [];
    options.adjacency = new Graph();
    options.prom_node_IDs = [];

    // 2. Fill adjacency list with edges
    edges.forEach(function (edge) {
      options.adjacency.addEdge(
        edge['source'],
        edge['target'],
      );
    });
    //3. Find first the promotable node ID's and collect all sub-graphs into promID's
    nodes.forEach(function (node) {
      if (options.prom_types.includes(node.type)) {
        options.prom_node_IDs.push(node.id);
      }
    });
    options.prom_IDs = Array.from(
      options.adjacency.dirs(options.prom_node_IDs),
      (path) => path.at(-1),
    );
    // 4. Now split the Graphs and update the
    nodes.forEach(function (node) {
      if (options.prom_IDs.includes(node.id)) {
        options.split.promo.nodes.push(node);
      } else {
        options.split.scratch.nodes.push(node);
      }
    });
    edges.forEach(function (edge) {
      if (
        options.prom_IDs.includes(edge.source) &&
        options.prom_IDs.includes(edge.target)
      ) {
        options.split.promo.edges.push(edge);
      } else {
        options.split.scratch.edges.push(edge);
      }
    });
    // 5. Setup the namesspace data
    options.rawData = graph;

    console.log('options->', options);
    console.log('options.split->', options.split);
    console.log(
      'options.split.scratch->',
      options.split.scratch,
    );
    console.log(
      'options.split.promo->',
      options.split.promo,
    );

    console.groupEnd();
    return options;
  };

  // Step 3. Show Graph
  ns.showGraphs = async function (options) {
    console.group('Step 3 showGraphs');
    console.log('Showing the Graph');
    console.log('options->', options);
    options.steps.push('showGraphs');
    // Initialize the links, EdgePaths
    options.promoLink = options.promo_svg
      .selectAll('.plinks')
      .data(options.split.promo.edges)
      .join('line')
      .attr('class', 'plinks')
      .attr('source', (d) => d.source)
      .attr('target', (d) => d.target)
      .attr('stroke-width', 0.75)
      .attr('stroke', 'grey')
      .attr('marker-end', 'url(#parrowhead)'); //The marker-end attribute defines the arrowhead or polymarker that will be drawn at the final vertex of the given shape.

    options.scratchLink = options.scratch_svg
      .selectAll('.slinks')
      .data(options.split.scratch.edges)
      .join('line')
      .attr('class', 'slinks')
      .attr('source', (d) => d.source)
      .attr('target', (d) => d.target)
      .attr('stroke-width', 0.75)
      .attr('stroke', 'grey')
      .attr('marker-end', 'url(#sarrowhead)'); //The marker-end attribute defines the arrowhead or polymarker that will be drawn at the final vertex of the given shape.

    options.promoEdgepaths = options.promo_svg
      .selectAll('.pedgepath') //make path go along with the link provide position for link labels
      .data(options.split.promo.edges)
      .join('path')
      .attr('class', 'pedgepath')
      .attr('fill-opacity', 0)
      .attr('stroke-opacity', 0)
      .attr('id', function (d, i) {
        return 'pedgepath' + i;
      })
      .style('pointer-events', 'none');

    options.scratchEdgepaths = options.scratch_svg
      .selectAll('.sedgepath') //make path go along with the link provide position for link labels
      .data(options.split.scratch.edges)
      .join('path')
      .attr('class', 'sedgepath')
      .attr('fill-opacity', 0)
      .attr('stroke-opacity', 0)
      .attr('id', function (d, i) {
        return 'sedgepath' + i;
      })
      .style('pointer-events', 'none');

    options.promoEdgelabels = options.promo_svg
      .selectAll('.pedgelabel')
      .data(options.split.promo.edges)
      .join('text')
      .style('pointer-events', 'none')
      .attr('class', 'pedgelabel')
      .attr('id', function (d, i) {
        return 'pedgelabel' + i;
      })
      .attr('font-size', 18)
      .attr('fill', options.theme.edges);

    options.scratchEdgelabels = options.scratch_svg
      .selectAll('.sedgelabel')
      .data(options.split.scratch.edges)
      .join('text')
      .style('pointer-events', 'none')
      .attr('class', 'sedgelabel')
      .attr('id', function (d, i) {
        return 'sedgelabel' + i;
      })
      .attr('font-size', 18)
      .attr('fill', options.theme.edges);

    options.promoEdgelabels
      .append('textPath') //To render text along the shape of a <path>, enclose the text in a <textPath> element that has an href attribute with a reference to the <path> element.
      .attr('xlink:href', function (d, i) {
        return '#pedgepath' + i;
      })
      .style('text-anchor', 'middle')
      .style('pointer-events', 'none')
      .attr('startOffset', '50%')
      .text((d) => d.name);

    options.scratchEdgelabels
      .append('textPath') //To render text along the shape of a <path>, enclose the text in a <textPath> element that has an href attribute with a reference to the <path> element.
      .attr('xlink:href', function (d, i) {
        return '#sedgepath' + i;
      })
      .style('text-anchor', 'middle')
      .style('pointer-events', 'none')
      .attr('startOffset', '50%')
      .text((d) => d.name);

    // Initialize the nodes with attached image, changed to join data
    // add hover over effect
    // for promo
    options.promoNode = options.promo_svg
      .append('g')
      .selectAll('.pnodes')
      .data(options.split.promo.nodes)
      .join('image')
      .attr('class', 'pnodes')
      .attr('xlink:href', function (d) {
        return (
          options.prefix + options.shape + d.icon + '.svg'
        );
      })
      .attr('width', options.icon_size + 5)
      .attr('height', options.icon_size + 5);
    // .on('mouseover', function(d) {
    //   d3.select(this)
    //     .transition()
    //     .duration(options.duration)
    //     .attr('width', 70)
    //     .attr('height', 70);
    // })
    // .on('mouseout', function(d) {
    //   d3.select(this)
    //     .transition()
    //     .duration(options.duration)
    //     .attr('width', function(d) {
    //       return options.radius;
    //     })
    //     .attr('height', function(d) {
    //       return options.radius;
    //     });
    // })
    // .on('mouseover.tooltip', function(d) {
    //   ns.tooltip
    //     .transition()
    //     .duration(options.duration)
    //     .style('opacity', 0.8);
    //   ns.tooltip
    //     .html(
    //       '<h1>' +
    //         d.heading +
    //         '</h1>' +
    //         '<p> ' +
    //         d.description +
    //         '</p>',
    //     )
    //     .style('left', d3.event.pageX + 'px')
    //     .style('top', d3.event.pageY + 10 + 'px');
    // })
    // .on('mouseout.tooltip', function() {
    //   ns.tooltip
    //     .transition()
    //     .duration(100)
    //     .style('opacity', 0);
    // })
    // .on('mousemove', function() {
    //   ns.tooltip
    //     .style('left', d3.event.pageX + 'px')
    //     .style('top', d3.event.pageY + 10 + 'px');
    // })
    // .call(
    //   d3
    //     .drag() //sets the event listener for the specified typenames and returns the drag behavior.
    //     .on('start', pDragstarted) //start - after a new pointer becomes active (on mousedown or touchstart).
    //     .on('drag', pDragged) //drag - after an active pointer moves (on mousemove or touchmove).
    //     .on('end', pDragended), //end - after an active pointer becomes inactive (on mouseup, touchend or touchcancel).
    // );

    // for scratch
    options.scratchNode = options.scratch_svg
      .append('g')
      .selectAll('.snodes')
      .data(options.split.scratch.nodes)
      .join('image')
      .attr('class', 'snodes')
      .attr('xlink:href', function (d) {
        return (
          options.prefix + options.shape + d.icon + '.svg'
        );
      })
      .attr('width', options.icon_size + 5)
      .attr('height', options.icon_size + 5);
    // .on('mouseover', function(d) {
    //   d3.select(this)
    //     .transition()
    //     .duration(options.duration)
    //     .attr('width', 70)
    //     .attr('height', 70);
    // })
    // .on('mouseout', function(d) {
    //   d3.select(this)
    //     .transition()
    //     .duration(options.duration)
    //     .attr('width', function(d) {
    //       return options.icon_size;
    //     })
    //     .attr('height', function(d) {
    //       return options.icon_size;
    //     });
    // })
    // .on('mouseover.tooltip', function(d) {
    //   ns.tooltip
    //     .transition()
    //     .duration(options.duration)
    //     .style('opacity', 0.8);
    //   ns.tooltip
    //     .html(
    //       '<h1>' +
    //         d.heading +
    //         '</h1>' +
    //         '<p> ' +
    //         d.description +
    //         '</p>',
    //     )
    //     .style('left', d3.event.pageX + 'px')
    //     .style('top', d3.event.pageY + 10 + 'px');
    // })
    // .on('mouseout.tooltip', function() {
    //   ns.tooltip
    //     .transition()
    //     .duration(100)
    //     .style('opacity', 0);
    // })
    // .on('mousemove', function() {
    //   ns.tooltip
    //     .style('left', d3.event.pageX + 'px')
    //     .style('top', d3.event.pageY + 10 + 'px');
    // })
    // .call(
    //   d3
    //     .drag() //sets the event listener for the specified typenames and returns the drag behavior.
    //     .on('start', sDragstarted) //start - after a new pointer becomes active (on mousedown or touchstart).
    //     .on('drag', sDragged) //drag - after an active pointer moves (on mousemove or touchmove).
    //     .on('end', sDragended), //end - after an active pointer becomes inactive (on mouseup, touchend or touchcancel).
    // );

    //create zoom handler  for each
    let pZoom_handler = d3
      .zoom()
      .on('zoom', function (options) {
        pzoom_actions(options);
      });
    let sZoom_handler = d3
      .zoom()
      .on('zoom', function (options) {
        szoom_actions(options);
      });

    //specify what to do when zoom event listener is triggered foreach
    function pzoom_actions(options) {
      options.promo_svg.attr(
        'transform',
        d3.event.transform,
      );
    }
    function szoom_actions(options) {
      options.scratch_svg.attr(
        'transform',
        d3.event.transform,
      );
    }

    //add zoom behaviour to the svg element
    //same as svg.call(zoom_handler);
    pZoom_handler(options.promo_svg);
    sZoom_handler(options.scratch_svg);

    //The simulation is temporarily “heated” during interaction by setting the target alpha to a non-zero value.
    function pDragstarted(d) {
      if (!d3.event.active)
        options.promotable_sim.alphaTarget(0.3).restart(); //sets the current target alpha to the specified number in the range [0,1].
      d.fy = d.y; //fx - the node’s fixed x-position. Original is null.
      d.fx = d.x; //fy - the node’s fixed y-position. Original is null.
    }

    //When the drag gesture starts, the targeted node is fixed to the pointer
    function pDragged(d) {
      d.fx = d3.event.x;
      d.fy = d3.event.y;
    }

    //the targeted node is released when the gesture ends
    function pDragended(d) {
      if (!d3.event.active)
        options.promotable_sim.alphaTarget(0);
      d.fx = null;
      d.fy = null;
    }

    function sDragstarted(d) {
      if (!d3.event.active)
        options.scratch_sim.alphaTarget(0.3).restart(); //sets the current target alpha to the specified number in the range [0,1].
      d.fy = d.y; //fx - the node’s fixed x-position. Original is null.
      d.fx = d.x; //fy - the node’s fixed y-position. Original is null.
    }

    //When the drag gesture starts, the targeted node is fixed to the pointer
    function sDragged(d) {
      d.fx = d3.event.x;
      d.fy = d3.event.y;
    }

    //the targeted node is released when the gesture ends
    function sDragended(d) {
      if (!d3.event.active)
        options.scratch_sim.alphaTarget(0);
      d.fx = null;
      d.fy = null;
    }
    console.groupEnd();
    return options;
  };

  // Step 4: Ryn the simulation
  ns.simGraph = async function (options) {
    console.group('Step 4 simGraph');
    console.log('Simulating Graph');
    // console.log('inputOptions->', inputOptions);
    // let options = Object.assign({}, inputOptions);
    console.log('options->', options);
    options.steps.push('simGraph');
    console.log(
      'options.split.scratch->',
      options.split.promo.edges,
    );
    // Step 6. Setup the simulations
    // first the variables centreStrength
    options.pforceNode = d3.forceManyBody();
    options.sforceNode = d3.forceManyBody();
    options.pforceLink = d3
      .forceLink(options.split.promo.edges)
      .id((d) => d.id)
      .distance(4 * options.icon_size);
    options.sforceLink = d3
      .forceLink(options.split.scratch.edges)
      .id((d) => d.id)
      .distance(4 * options.icon_size);
    options.sforceCentre = d3.forceCenter(
      options.working_width / 2,
      options.svg_height / 4,
    );
    options.pforceCentre = d3.forceCenter(
      options.working_width / 2,
      options.svg_height / 4,
    );
    if (options.nodeStrength !== undefined)
      options.pforceNode.strength(options.nodeStrength);
    if (options.nodeStrength !== undefined)
      options.sforceNode.strength(options.nodeStrength);
    if (options.linkStrength !== undefined)
      options.pforceLink.strength(options.linkStrength);
    if (options.linkStrength !== undefined)
      options.sforceLink.strength(options.linkStrength);
    if (options.centreStrength !== undefined)
      options.sforceCentre.strength(options.centreStrength);
    if (options.centreStrength !== undefined)
      options.pforceCentre.strength(options.centreStrength);

    console.log(
      'options.split.promo.nodes',
      options.split.promo.nodes,
    );
    console.log(
      'options.split.promo.edges',
      options.split.promo.edges,
    );
    console.log(
      'options.split.scratch.nodes',
      options.split.scratch.nodes,
    );
    console.log(
      'options.split.scratch.edges',
      options.split.scratch.edges,
    );
    //

    options.promotable_sim = d3
      .forceSimulation(
        options.working_width,
        options.svg_height / 2,
      )
      .nodes(options.split.promo.nodes)
      //   .on('end', function() {
      //     console.log(["promotable_sim",this]);
      //         this.force('link',   options.pforceLink)
      //         .force('charge',   options.pforceNode)
      //         .force('center',   options.pforceCentre);
      //   });
      .force('link', options.pforceLink)
      .force('charge', options.pforceNode)
      .force('center', options.pforceCentre)
      .on('tick', function () {
        // console.log(['promotable_sim ticked',this, options.promo_svg
        // .selectAll('.plinks')]);
        // options.promo_svg
        options.promoLink
          // .selectAll('.plinks')
          .attr('x1', (d) => d.source.x)
          .attr('y1', (d) => d.source.y)
          .attr('x2', (d) => d.target.x)
          .attr('y2', (d) => d.target.y);

        // options.promo_svg
        options.promoNode
          // .selectAll('.pnodes')
          // .attr('x', (d) => d.x - 6 / 2)
          .attr('x', function (d) {
            // console.log('promoNode->', d);
            return d.x - 6 / 2;
          })
          .attr('y', (d) => d.y - 6 / 2);

        // options.promo_svg.selectAll('.pedgepath')
        options.promoEdgepaths.attr(
          'd',
          function (d) {
            //console.log('pedgepath->', d);
            return (
              'M ' +
              d.source.x +
              ' ' +
              d.source.y +
              ' L ' +
              d.target.x +
              ' ' +
              d.target.y
            );
          },
          // (d) =>
          //   'M ' +
          //   d.source.x +
          //   ' ' +
          //   d.source.y +
          //   ' L ' +
          //   d.target.x +
          //   ' ' +
          //   d.target.y,
        );
      }); //use simulation.on to listen for tick events as the simulation runs.

    options.scratch_sim = d3
      .forceSimulation(
        options.working_width,
        options.svg_height / 2,
      )
      .nodes(options.split.scratch.nodes)
      //   .on('end', function() {
      //     console.log(this);
      //     console.log(["scratch_sim",this]);
      //     this.force('link', options.sforceLink)
      //         .force('charge', options.sforceNode)
      //         .force('center', options.sforceCentre);
      //   });
      .force('link', options.sforceLink)
      .force('charge', options.sforceNode)
      .force('center', options.sforceCentre)
      .on('tick', function () {
        // options.scratch_svg
        //   .selectAll('.slinks')
        options.scratchLink
          // .attr('d',
          //   function(d) {
          // console.log('slinks->', d);})
          .attr('x1', (d) => d.source.x)
          .attr('y1', (d) => d.source.y)
          .attr('x2', (d) => d.target.x)
          .attr('y2', (d) => d.target.y);

        // options.scratch_svg
        //   .selectAll('.snodes')
        options.scratchNode
          .attr('x', (d) => d.x - options.radius / 2)
          .attr('y', (d) => d.y - options.radius / 2);

        // options.scratch_svg.selectAll('.sedgepath')
        options.scratchEdgepaths.attr(
          'd',
          function (d) {
            // console.log('sedgepath->', d);
            return (
              'M ' +
              d.source.x +
              ' ' +
              d.source.y +
              ' L ' +
              d.target.x +
              ' ' +
              d.target.y
            );
          },
          // (d) =>
          //   'M ' +
          //   d.source.x +
          //   ' ' +
          //   d.source.y +
          //   ' L ' +
          //   d.target.x +
          //   ' ' +
          //   d.target.y,
        );
      });

    // 7. Now show split graphs
    console.groupEnd();
    return options;
  };

  console.groupEnd();
  return 'function value';
})(workP); //execute declaration and pass namespace

I call the four functions using the code and options data below


const options = {
  duration: 350,
  radius: 6, // radius of curve for links
  barHeight: 40,
  margin: {
    top: 100,
    left: 30,
    bottom: 50,
    right: 30,
  },
  index_width: 400, // this svg
  working_width: 800, // next door svg
  svg_height: 800,
  svg_spacing: 500,
  // Icons
  prefix:
    'https://raw.githubusercontent.com/os-threat/images/main/img/',
  shape: 'rect-',
  icon_size: 36,
  textPadding: 8,
  corner: 5,
  // the tree view
  minHeight: 20,
  width: 400,
  height: 800,
  lineSpacing: 50,
  indentSpacing: 50,
  itemFont: '18px',
  boxSize: 10,
  tree_edge_thickness: 0.75,
  graph_edge_thickness: 1,
  linkStrength: 200,
  nodeStrength: -100,
  centreStrength: 80,
  theme: 'light',
  light_theme: {
    treeFill: 'white',
    scratchFill: 'ivory',
    promoFill: 'blanchedalmond',
    svgName: 'black',
    svgBorder: 'black',
    checkColour: 'gray',
    checkText: 'white',
    select: 'yellow',
    edges: 'black',
  },
  dark_theme: {
    treeFill: 'gray',
    scratchFill: 'dimgray',
    promoFill: 'gray',
    svgName: 'white',
    svgBorder: 'white',
    checkColour: 'white',
    checkText: 'gray',
    select: 'yellow',
    edges: 'white',
  },
};


const scratch = 'data/scratch.json';
d3.json(scratch).then(function (data) {
  console.log('scratch->', data);
  console.log('I am rendering the working page');

  //Step 1 - setup the SVG's

  let graphInitialiseOptions = window.workP.initSVG(
    data,
    options,
  );

  console.log(graphInitialiseOptions);

  //Return Step 1 Promise
  graphInitialiseOptions.then((initialiseResults) => {
    //Step 2 - split graph into PROMO and SCRATCH
    let updateDataOptions = window.workP.updateGraph(
      data,
      initialiseResults,
    );
    console.log(initialiseResults);

    // Return Step 2 Promise
    updateDataOptions.then((updateResults) => {
      //Step 3 - D3 draw
      let showGraphOptions =
        window.workP.showGraphs(updateResults);
      console.log(updateResults);

      // Return Step 3 Promise
      showGraphOptions.then((showResults) => {
        //Step 4 - D3 simulate graph
        let simOptions = window.workP.simGraph(showResults);

        simOptions.then((simResults) => {
          console.log(simResults);
        });
      });
    });
  });
});

The scratch.json data file that I use is too big to post here

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật