D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
djokicx
Full window
Github gist
Yodlee Datasets
Built with
blockbuilder.org
<!DOCTYPE html> <head> <meta charset="utf-8"> <link href="style.css" rel="stylesheet" type="text/css"> <title>SF Arrests</title> </head> <body> <!-- hard-code the svg and various g layers so that even if the streets are loaded and drawn last, they will still show up behind the symbols --> <svg width="960" height="700" id="vis"> <g id="details" pointer-events="none"></g> </svg> <script src="https://d3js.org/d3.v5.min.js"></script> <script> const data = "./datasets.csv"; const diameter = 500; const pad = 14; const height = 500; const width = 960; const r = 13; const radialLine = d3.linkRadial() .angle(d => d.theta + Math.PI / 2) // rotate, 0 angle is mapped differently here .radius(d => d.radial); const numberFormat = d3.format(".2~s"); let body; let details; d3.csv(data).then(function(original) { nodes = new Set(original.map(row => row.dataset)) let processed = original.map(function(row) { let parent = row.dataset.substring(0, row.dataset.lastIndexOf("-")); return { name : row.dataset, parent: nodes.has(parent) ? parent : "Datasets", } }) processed.push( { name: "Datasets", parent: "", } ) let root = d3.stratify() .id(function(row) { return row.name; }) .parentId(function(row) {return row.parent;}) (processed); root.count(); root.each(function(node) { // copy this calculation since value is sometimes overwritten node.data.leaves = node.value; }) root.sum(row => row.count) root.each(function(node) { // copy this calculation since value is sometimes overwritten node.data.total = node.value; }) let data = root; data.sort(function(a, b) { return b.height - a.height || b.count - a.count; }); color = d3.scaleSequential([0, root.height], d3.interpolateBlues) let layout = d3.cluster().size([2 * Math.PI, (diameter / 2) - pad]); layout(data); data.each(function(node) { node.theta = node.x; node.radial = node.y; var point = toCartesian(node.radial, node.theta); node.x = point.x; node.y = point.y; }); let svg = d3.select("#vis") .style("width", width) .style("height", height); let plot = svg.append("g") .attr("id", "plot") .attr("transform", `translate(${width/ 3}, ${height/2})`); const extraInfo = svg.select("g#details") details = extraInfo.append("foreignObject") .attr("id", "details") .attr("width", 960) .attr("height", 600) .attr("x", width/ 2 + 150) .attr("y", height/5); body = details.append("xhtml:body") .style("text-align", "left") .style("background", "none") .html("<p>N/A</p>"); details.style("visibility", "hidden"); drawNodes(plot.append("g"), data.descendants(), true); drawLinks(plot.append("g"), data.links(), radialLine); }); function toCartesian(r, theta) { return { x: r * Math.cos(theta), y: r * Math.sin(theta) }; } function drawLinks(g, links, generator) { let paths = g.selectAll('path') .data(links) .enter() .append('path') .attr('d', generator) .attr('class', 'link'); } function drawNodes(g, nodes, raise) { let circles = g.selectAll('circle') .data(nodes, node => node.data.name) .enter() .append('circle') .attr('r', d => d.r ? d.r : r) .attr('cx', d => d.x) .attr('cy', d => d.y) .attr('id', d => d.data.name) .attr('class', 'node') .style('fill', d => color(d.depth)); setupEvents(g, circles, raise); } function setupEvents(g, selection, raise) { selection.on('mouseover.highlight', function(d) { // https://github.com/d3/d3-hierarchy#node_path // returns path from d3.select(this) node to selection.data()[0] root node let path = d3.select(this).datum().path(selection.data()[0]); // select all of the nodes on the shortest path let update = selection.data(path, node => node.data.name); // highlight the selected nodes update.classed('selected', true); if (raise) { update.raise(); } }); selection.on('mouseout.highlight', function(d) { let path = d3.select(this).datum().path(selection.data()[0]); let update = selection.data(path, node => node.data.name); update.classed('selected', false); }); // show tooltip text on mouseover (hover) selection.on('mouseover.tooltip', function(d) { let node = d3.select(this); let nodeData = node.datum(); let nodeChildren = nodeData.children; console.log(node); console.log(nodeChildren); if(nodeChildren) { nodeChildren.forEach(element => { let childSelector = '#' + element.data.name; showTooltip(g, d3.select(childSelector)); }); } console.log(node.datum()); let parentName = node.datum().id.substring(0, node.datum().id.lastIndexOf("-")); if(!parentName.length == 0) { let parent = d3.select("circle#" + parentName + ".node") showTooltip(g, parent); const html = ` <table border="0" cellspacing="0" cellpadding="2"> <tbody> <tr> <th>Attribute:</th> <td id="attribute">${node.datum().id.substring(node.datum().id.lastIndexOf("-") + 1)}</td> </tr> <tr> <th>Dataset:</th> <td>${parentName}</td> </tr> <tr> <th>Description:</th> <td>Full account number</td> </tr> <tr> <th>Qualities:</th> <td>Sensitive information</td> </tr> <tr> <th>Containers:</th> <td> <ul> <li>Bank</li> <li>Investment</li> <li>Credit Card</li> <li>Insurance</li> <li>Loan</li> <li>Reward</li> </ul> </td> </tr> <tr> <th>Regions:</th> <td> <ul> <li>US</li> <li>UK</li> <li>Canada</li> <li>Insurance</li> <li>Australia</li> <li>India</li> </ul> </td> </tr> </tbody> </table> `; body.html(html); details.style("visibility", "visible"); } showTooltip(g, node); }); // remove tooltip text on mouseout selection.on('mouseout.tooltip', function(d) { g.selectAll("#tooltip").remove(); details.style("visibility", "hidden"); }); } function showTooltip(g, node) { let gbox = g.node().getBBox(); // get bounding box of group BEFORE adding text let nbox = node.node().getBBox(); // get bounding box of node // calculate shift amount let dx = nbox.width / 2; let dy = nbox.height / 2; // retrieve node attributes (calculate middle point) let x = nbox.x + dx; let y = nbox.y + dy; // get data for node let datum = node.datum(); let name = node.datum().id.substring(node.datum().id.lastIndexOf("-") + 1); // let name = datum.data.name.substring(datum.data.name) text = `${name}`; // create tooltip let tooltip = g.append('text') .text(text) .attr('x', x) .attr('y', y) .attr('dy', -dy - 4) // shift upward above circle .attr('text-anchor', 'middle') // anchor in the middle .attr('id', 'tooltip'); // it is possible the tooltip will fall off the edge of the // plot area. we can detect when this happens, and set the // text anchor appropriately // get bounding box for the text let tbox = tooltip.node().getBBox(); // if text will fall off left side, anchor at start if (tbox.x < gbox.x) { tooltip.attr('text-anchor', 'start'); tooltip.attr('dx', -dx); // nudge text over from center } // if text will fall off right side, anchor at end else if ((tbox.x + tbox.width) > (gbox.x + gbox.width)) { tooltip.attr('text-anchor', 'end'); tooltip.attr('dx', dx); } // if text will fall off top side, place below circle instead if (tbox.y < gbox.y) { tooltip.attr('dy', dy + tbox.height); } } </script> </body>
https://d3js.org/d3.v5.min.js