Skip to content

Instantly share code, notes, and snippets.

@sandinosaso
Created Jun 12, 2021
Embed
What would you like to do?
Sunburst Chart Using D3 and React
import React from 'react';
import Sunburst from './sunburst';
import data from './data';
const App = () => {
return <Sunburst
data={chartData.data}
keyId="Sunburst"
width={600}
/>
}
export default App;
export default {
"name": "flare",
"children": [
{
"name": "analytics",
"children": [
{
"name": "cluster",
"children": [
{"name": "AgglomerativeCluster", "value": 3938},
{"name": "CommunityStructure", "value": 3812},
{"name": "HierarchicalCluster", "value": 6714},
{"name": "MergeEdge", "value": 743}
]
},
{
"name": "graph",
"children": [
{"name": "BetweennessCentrality", "value": 3534},
{"name": "LinkDistance", "value": 5731},
{"name": "MaxFlowMinCut", "value": 7840},
{"name": "ShortestPaths", "value": 5914},
{"name": "SpanningTree", "value": 3416}
]
},
{
"name": "optimization",
"children": [
{"name": "AspectRatioBanker", "value": 7074}
]
}
]
},
{
"name": "animate",
"children": [
{"name": "Easing", "value": 17010},
{"name": "FunctionSequence", "value": 5842},
{
"name": "interpolate",
"children": [
{"name": "ArrayInterpolator", "value": 1983},
{"name": "ColorInterpolator", "value": 2047},
{"name": "DateInterpolator", "value": 1375},
{"name": "Interpolator", "value": 8746},
{"name": "MatrixInterpolator", "value": 2202},
{"name": "NumberInterpolator", "value": 1382},
{"name": "ObjectInterpolator", "value": 1629},
{"name": "PointInterpolator", "value": 1675},
{"name": "RectangleInterpolator", "value": 2042}
]
},
{"name": "ISchedulable", "value": 1041},
{"name": "Parallel", "value": 5176},
{"name": "Pause", "value": 449},
{"name": "Scheduler", "value": 5593},
{"name": "Sequence", "value": 5534},
{"name": "Transition", "value": 9201},
{"name": "Transitioner", "value": 19975},
{"name": "TransitionEvent", "value": 1116},
{"name": "Tween", "value": 6006}
]
},
{
"name": "data",
"children": [
{
"name": "converters",
"children": [
{"name": "Converters", "value": 721},
{"name": "DelimitedTextConverter", "value": 4294},
{"name": "GraphMLConverter", "value": 9800},
{"name": "IDataConverter", "value": 1314},
{"name": "JSONConverter", "value": 2220}
]
},
{"name": "DataField", "value": 1759},
{"name": "DataSchema", "value": 2165},
{"name": "DataSet", "value": 586},
{"name": "DataSource", "value": 3331},
{"name": "DataTable", "value": 772},
{"name": "DataUtil", "value": 3322}
]
},
{
"name": "display",
"children": [
{"name": "DirtySprite", "value": 8833},
{"name": "LineSprite", "value": 1732},
{"name": "RectSprite", "value": 3623},
{"name": "TextSprite", "value": 10066}
]
},
{
"name": "flex",
"children": [
{"name": "FlareVis", "value": 4116}
]
},
{
"name": "physics",
"children": [
{"name": "DragForce", "value": 1082},
{"name": "GravityForce", "value": 1336},
{"name": "IForce", "value": 319},
{"name": "NBodyForce", "value": 10498},
{"name": "Particle", "value": 2822},
{"name": "Simulation", "value": 9983},
{"name": "Spring", "value": 2213},
{"name": "SpringForce", "value": 1681}
]
},
{
"name": "query",
"children": [
{"name": "AggregateExpression", "value": 1616},
{"name": "And", "value": 1027},
{"name": "Arithmetic", "value": 3891},
{"name": "Average", "value": 891},
{"name": "BinaryExpression", "value": 2893},
{"name": "Comparison", "value": 5103},
{"name": "CompositeExpression", "value": 3677},
{"name": "Count", "value": 781},
{"name": "DateUtil", "value": 4141},
{"name": "Distinct", "value": 933},
{"name": "Expression", "value": 5130},
{"name": "ExpressionIterator", "value": 3617},
{"name": "Fn", "value": 3240},
{"name": "If", "value": 2732},
{"name": "IsA", "value": 2039},
{"name": "Literal", "value": 1214},
{"name": "Match", "value": 3748},
{"name": "Maximum", "value": 843},
{
"name": "methods",
"children": [
{"name": "add", "value": 593},
{"name": "and", "value": 330},
{"name": "average", "value": 287},
{"name": "count", "value": 277},
{"name": "distinct", "value": 292},
{"name": "div", "value": 595},
{"name": "eq", "value": 594},
{"name": "fn", "value": 460},
{"name": "gt", "value": 603},
{"name": "gte", "value": 625},
{"name": "iff", "value": 748},
{"name": "isa", "value": 461},
{"name": "lt", "value": 597},
{"name": "lte", "value": 619},
{"name": "max", "value": 283},
{"name": "min", "value": 283},
{"name": "mod", "value": 591},
{"name": "mul", "value": 603},
{"name": "neq", "value": 599},
{"name": "not", "value": 386},
{"name": "or", "value": 323},
{"name": "orderby", "value": 307},
{"name": "range", "value": 772},
{"name": "select", "value": 296},
{"name": "stddev", "value": 363},
{"name": "sub", "value": 600},
{"name": "sum", "value": 280},
{"name": "update", "value": 307},
{"name": "variance", "value": 335},
{"name": "where", "value": 299},
{"name": "xor", "value": 354},
{"name": "_", "value": 264}
]
},
{"name": "Minimum", "value": 843},
{"name": "Not", "value": 1554},
{"name": "Or", "value": 970},
{"name": "Query", "value": 13896},
{"name": "Range", "value": 1594},
{"name": "StringUtil", "value": 4130},
{"name": "Sum", "value": 791},
{"name": "Variable", "value": 1124},
{"name": "Variance", "value": 1876},
{"name": "Xor", "value": 1101}
]
},
{
"name": "scale",
"children": [
{"name": "IScaleMap", "value": 2105},
{"name": "LinearScale", "value": 1316},
{"name": "LogScale", "value": 3151},
{"name": "OrdinalScale", "value": 3770},
{"name": "QuantileScale", "value": 2435},
{"name": "QuantitativeScale", "value": 4839},
{"name": "RootScale", "value": 1756},
{"name": "Scale", "value": 4268},
{"name": "ScaleType", "value": 1821},
{"name": "TimeScale", "value": 5833}
]
},
{
"name": "util",
"children": [
{"name": "Arrays", "value": 8258},
{"name": "Colors", "value": 10001},
{"name": "Dates", "value": 8217},
{"name": "Displays", "value": 12555},
{"name": "Filter", "value": 2324},
{"name": "Geometry", "value": 10993},
{
"name": "heap",
"children": [
{"name": "FibonacciHeap", "value": 9354},
{"name": "HeapNode", "value": 1233}
]
},
{"name": "IEvaluable", "value": 335},
{"name": "IPredicate", "value": 383},
{"name": "IValueProxy", "value": 874},
{
"name": "math",
"children": [
{"name": "DenseMatrix", "value": 3165},
{"name": "IMatrix", "value": 2815},
{"name": "SparseMatrix", "value": 3366}
]
},
{"name": "Maths", "value": 17705},
{"name": "Orientation", "value": 1486},
{
"name": "palette",
"children": [
{"name": "ColorPalette", "value": 6367},
{"name": "Palette", "value": 1229},
{"name": "ShapePalette", "value": 2059},
{"name": "SizePalette", "value": 2291}
]
},
{"name": "Property", "value": 5559},
{"name": "Shapes", "value": 19118},
{"name": "Sort", "value": 6887},
{"name": "Stats", "value": 6557},
{"name": "Strings", "value": 22026}
]
},
{
"name": "vis",
"children": [
{
"name": "axis",
"children": [
{"name": "Axes", "value": 1302},
{"name": "Axis", "value": 24593},
{"name": "AxisGridLine", "value": 652},
{"name": "AxisLabel", "value": 636},
{"name": "CartesianAxes", "value": 6703}
]
},
{
"name": "controls",
"children": [
{"name": "AnchorControl", "value": 2138},
{"name": "ClickControl", "value": 3824},
{"name": "Control", "value": 1353},
{"name": "ControlList", "value": 4665},
{"name": "DragControl", "value": 2649},
{"name": "ExpandControl", "value": 2832},
{"name": "HoverControl", "value": 4896},
{"name": "IControl", "value": 763},
{"name": "PanZoomControl", "value": 5222},
{"name": "SelectionControl", "value": 7862},
{"name": "TooltipControl", "value": 8435}
]
},
{
"name": "data",
"children": [
{"name": "Data", "value": 20544},
{"name": "DataList", "value": 19788},
{"name": "DataSprite", "value": 10349},
{"name": "EdgeSprite", "value": 3301},
{"name": "NodeSprite", "value": 19382},
{
"name": "render",
"children": [
{"name": "ArrowType", "value": 698},
{"name": "EdgeRenderer", "value": 5569},
{"name": "IRenderer", "value": 353},
{"name": "ShapeRenderer", "value": 2247}
]
},
{"name": "ScaleBinding", "value": 11275},
{"name": "Tree", "value": 7147},
{"name": "TreeBuilder", "value": 9930}
]
},
{
"name": "events",
"children": [
{"name": "DataEvent", "value": 2313},
{"name": "SelectionEvent", "value": 1880},
{"name": "TooltipEvent", "value": 1701},
{"name": "VisualizationEvent", "value": 1117}
]
},
{
"name": "legend",
"children": [
{"name": "Legend", "value": 20859},
{"name": "LegendItem", "value": 4614},
{"name": "LegendRange", "value": 10530}
]
},
{
"name": "operator",
"children": [
{
"name": "distortion",
"children": [
{"name": "BifocalDistortion", "value": 4461},
{"name": "Distortion", "value": 6314},
{"name": "FisheyeDistortion", "value": 3444}
]
},
{
"name": "encoder",
"children": [
{"name": "ColorEncoder", "value": 3179},
{"name": "Encoder", "value": 4060},
{"name": "PropertyEncoder", "value": 4138},
{"name": "ShapeEncoder", "value": 1690},
{"name": "SizeEncoder", "value": 1830}
]
},
{
"name": "filter",
"children": [
{"name": "FisheyeTreeFilter", "value": 5219},
{"name": "GraphDistanceFilter", "value": 3165},
{"name": "VisibilityFilter", "value": 3509}
]
},
{"name": "IOperator", "value": 1286},
{
"name": "label",
"children": [
{"name": "Labeler", "value": 9956},
{"name": "RadialLabeler", "value": 3899},
{"name": "StackedAreaLabeler", "value": 3202}
]
},
{
"name": "layout",
"children": [
{"name": "AxisLayout", "value": 6725},
{"name": "BundledEdgeRouter", "value": 3727},
{"name": "CircleLayout", "value": 9317},
{"name": "CirclePackingLayout", "value": 12003},
{"name": "DendrogramLayout", "value": 4853},
{"name": "ForceDirectedLayout", "value": 8411},
{"name": "IcicleTreeLayout", "value": 4864},
{"name": "IndentedTreeLayout", "value": 3174},
{"name": "Layout", "value": 7881},
{"name": "NodeLinkTreeLayout", "value": 12870},
{"name": "PieLayout", "value": 2728},
{"name": "RadialTreeLayout", "value": 12348},
{"name": "RandomLayout", "value": 870},
{"name": "StackedAreaLayout", "value": 9121},
{"name": "TreeMapLayout", "value": 9191}
]
},
{"name": "Operator", "value": 2490},
{"name": "OperatorList", "value": 5248},
{"name": "OperatorSequence", "value": 4190},
{"name": "OperatorSwitch", "value": 2581},
{"name": "SortOperator", "value": 2023}
]
},
{"name": "Visualization", "value": 16540}
]
}
]
}
import React, {useEffect, useRef} from 'react';
import { isEqual } from 'lodash/lang';
import * as d3 from 'd3';
const partition = (data) => {
const root = d3.hierarchy(data)
.sum(d => d.value)
.sort((a, b) => b.value - a.value);
const partition = d3.partition()
.size([2 * Math.PI, root.height + 1])(root);
return partition;
}
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
const Sunburst = (props) => {
const svgRef = useRef()
const prevProps = usePrevious(props);
useEffect(() => {
if (!isEqual(prevProps, props)) {
renderSunburst();
}
// eslint-disable-next-line
}, [props])
const renderSunburst = () => {
const { data, width } = props;
const color = d3.scaleOrdinal(d3.quantize(d3.interpolateRainbow, data.children.length + 1));
if (data) {
document.querySelectorAll("g").forEach((node) => {
node.remove()
});
const radius = width / 6;
const svg = d3.select(svgRef.current)
.attr("viewBox", [0, 0, width, width])
.style("font", "10px sans-serif");
const g = svg.append('g')
.attr('transform', `translate(${width / 2},${width / 2})`);
const root = partition(data);
root.each(d => d.current = d);
console.log('Root:', root);
const path = g.append("g")
.selectAll("path")
.data(root.descendants().slice(1))
.join("path")
.attr("fill", d => {
while (d.depth > 1) d = d.parent;
return color(d.data.name);
})
.attr("fill-opacity", d => arcVisible(d.current) ? (d.children ? 0.6 : 0.4) : 0)
.attr("d", d => {
return d3.arc()
.startAngle(d => { return d.x0 })
.endAngle(d => d.x1)
.padAngle(d => Math.min((d.x1 - d.x0) / 2, 0.005))
.padRadius(radius * 1.5)
.innerRadius(d => d.y0 * radius)
.outerRadius(d => Math.max(d.y0 * radius, d.y1 * radius - 1))(d.current);
});
path.filter(d => d.children)
.style("cursor", "pointer")
.on("click", clicked);
path.append("title")
.text(d => `${d.ancestors().map(d => d.data.name).reverse().join("/")}\n${d3.format(",d")(d.value)}`);
const label = g.append("g")
.attr("pointer-events", "none")
.attr("text-anchor", "middle")
.style("user-select", "none")
.selectAll("text")
.data(root.descendants().slice(1))
.join("text")
.attr("dy", "0.35em")
.attr("fill-opacity", d => +labelVisible(d.current))
.attr("transform", d => labelTransform(d.current))
.text(d => d.data.name);
const parent = g.append("circle")
.datum(root)
.attr("r", radius)
.attr("fill", "none")
.attr("pointer-events", "all")
.on("click", clicked);
g.append("text")
.datum(root)
.attr("id", "mainCircleText")
.text(d => d.data.name)
.attr('font-size', 40)//font size
.attr("x", 0)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.on("click", clicked);
function clicked(event, p) {
parent.datum(p.parent || root);
g.select('#mainCircleText')
.text(p.data.name);
root.each(d => d.target = {
x0: Math.max(0, Math.min(1, (d.x0 - p.x0) / (p.x1 - p.x0))) * 2 * Math.PI,
x1: Math.max(0, Math.min(1, (d.x1 - p.x0) / (p.x1 - p.x0))) * 2 * Math.PI,
y0: Math.max(0, d.y0 - p.depth),
y1: Math.max(0, d.y1 - p.depth)
});
const t = g.transition().duration(750);
// Transition the data on all arcs, even the ones that aren’t visible,
// so that if this transition is interrupted, entering arcs will start
// the next transition from the desired position.
path.transition(t)
.tween("data", d => {
const i = d3.interpolate(d.current, d.target);
return t => d.current = i(t);
})
.filter(function(d) {
return +this.getAttribute("fill-opacity") || arcVisible(d.target);
})
.attr("fill-opacity", d => arcVisible(d.target) ? (d.children ? 0.6 : 0.4) : 0)
.attrTween("d", d => () => {
return d3.arc()
.startAngle(d => d.x0)
.endAngle(d => d.x1)
.padAngle(d => Math.min((d.x1 - d.x0) / 2, 0.005))
.padRadius(radius * 1.5)
.innerRadius(d => d.y0 * radius)
.outerRadius(d => Math.max(d.y0 * radius, d.y1 * radius - 1))(d.current)
});
label.filter(function(d) {
return +this.getAttribute("fill-opacity") || labelVisible(d.target);
}).transition(t)
.attr("fill-opacity", d => +labelVisible(d.target))
.attrTween("transform", d => () => labelTransform(d.current));
}
function arcVisible(d) {
return d.y1 <= 3 && d.y0 >= 1 && d.x1 > d.x0;
}
function labelVisible(d) {
return d.y1 <= 3 && d.y0 >= 1 && (d.y1 - d.y0) * (d.x1 - d.x0) > 0.03;
}
function labelTransform(d) {
const x = (d.x0 + d.x1) / 2 * 180 / Math.PI;
const y = (d.y0 + d.y1) / 2 * radius;
return `rotate(${x - 90}) translate(${y},0) rotate(${x < 180 ? 0 : 180})`;
}
}
}
return (
<div id={props.keyId} className="text-center">
<svg
ref={svgRef}
id={`${props.keyId}-svg`}
/>
</div>
);
}
export default Sunburst;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment