@testboxlab/react-bubble-chart-d3
Version:
ReactJS component to display data as a bubble chart using D3.
324 lines (308 loc) • 13.2 kB
JavaScript
import {jsx as $zIto6$jsx} from "react/jsx-runtime";
import {createRef as $zIto6$createRef, Component as $zIto6$Component} from "react";
import "react-dom";
import $zIto6$proptypes from "prop-types";
import {scaleOrdinal as $zIto6$scaleOrdinal, schemeCategory20c as $zIto6$schemeCategory20c, pack as $zIto6$pack, hierarchy as $zIto6$hierarchy, select as $zIto6$select, selectAll as $zIto6$selectAll} from "d3";
function $parcel$export(e, n, v, s) {
Object.defineProperty(e, n, {get: v, set: s, enumerable: true, configurable: true});
}
var $parcel$global =
typeof globalThis !== 'undefined'
? globalThis
: typeof self !== 'undefined'
? self
: typeof window !== 'undefined'
? window
: typeof global !== 'undefined'
? global
: {};
var $parcel$modules = {};
var $parcel$inits = {};
var parcelRequire = $parcel$global["parcelRequire5493"];
if (parcelRequire == null) {
parcelRequire = function(id) {
if (id in $parcel$modules) {
return $parcel$modules[id].exports;
}
if (id in $parcel$inits) {
var init = $parcel$inits[id];
delete $parcel$inits[id];
var module = {id: id, exports: {}};
$parcel$modules[id] = module;
init.call(module.exports, module, module.exports);
return module.exports;
}
var err = new Error("Cannot find module '" + id + "'");
err.code = 'MODULE_NOT_FOUND';
throw err;
};
parcelRequire.register = function register(id, init) {
$parcel$inits[id] = init;
};
$parcel$global["parcelRequire5493"] = parcelRequire;
}
parcelRequire.register("j9IVI", function(module, exports) {
$parcel$export(module.exports, "default", () => $df21b5b58bbd6a03$export$2e2bcd8739ae039);
class $df21b5b58bbd6a03$export$2e2bcd8739ae039 extends (0, $zIto6$Component) {
constructor(props){
super(props);
this.renderChart = this.renderChart.bind(this);
this.renderBubbles = this.renderBubbles.bind(this);
this.renderLegend = this.renderLegend.bind(this);
this.svg = /*#__PURE__*/ (0, $zIto6$createRef)();
}
componentDidMount() {
this.renderChart();
}
componentDidUpdate() {
const { width: width , height: height } = this.props;
if (width !== 0 && height !== 0) this.renderChart();
}
render() {
const { width: width , height: height } = this.props;
return /*#__PURE__*/ (0, $zIto6$jsx)("svg", {
width: width,
height: height,
ref: this.svg
});
}
renderChart() {
const { overflow: overflow , graph: graph , data: data , height: height , width: width , padding: padding , showLegend: showLegend , showValue: showValue , legendPercentage: legendPercentage , charsBeforeSplit: charsBeforeSplit } = this.props;
// Reset the svg element to a empty state.
this.svg.current.innerHTML = "";
// Allow bubbles overflowing its SVG container in visual aspect if props(overflow) is true.
if (overflow) this.svg.current.style.overflow = "visible";
const bubblesWidth = showLegend ? width * (1 - legendPercentage / 100) : width;
const legendWidth = width - bubblesWidth;
const color = $zIto6$scaleOrdinal($zIto6$schemeCategory20c);
const pack = $zIto6$pack().size([
bubblesWidth * graph.zoom,
bubblesWidth * graph.zoom
]).padding(padding);
// Process the data to have a hierarchy structure;
const root = $zIto6$hierarchy({
children: data
}).sum(function(d) {
return d.value;
}).sort(function(a, b) {
return b.value - a.value;
}).each((d)=>{
if (d.data.label) {
d.label = d.data.label;
d.id = d.data.label.toLowerCase().replace(/ |\//g, "-");
}
});
// Pass the data to the pack layout to calculate the distribution.
const nodes = pack(root).leaves();
// Call to the function that draw the bubbles.
this.renderBubbles(bubblesWidth, height, nodes, color);
// Call to the function that draw the legend.
if (showLegend) this.renderLegend(legendWidth, height, bubblesWidth, nodes, color);
}
renderBubbles(width, height, nodes, color) {
const { graph: graph , data: data , bubbleClickFun: bubbleClickFun , valueFont: valueFont , labelFont: labelFont , showValue: showValue , charsBeforeSplit: charsBeforeSplit } = this.props;
const splitByFirstSpace = (str)=>{
const index = str.indexOf(" ");
if (index === -1 || str.length <= charsBeforeSplit) return [
str
];
else return [
str.slice(0, index),
str.slice(index + 1)
];
};
var insertLinebreaks = function(d) {
var text = $zIto6$select(this);
var words = splitByFirstSpace(text.text());
text.text("");
for(var i = 0; i < words.length; i++){
var tspan = text.append("tspan").text(words[i]);
if (i >= 0) tspan.attr("x", 0).attr("dy", "15");
}
};
const bubbleChart = $zIto6$select(this.svg.current).append("g").attr("class", "bubble-chart").attr("transform", function(d) {
return "translate(" + (width - width * graph.zoom) / 2 + "," + (height - height * graph.zoom) / 2 + ")";
});
const node = bubbleChart.selectAll(".node").data(nodes).enter().append("g").attr("class", "node").attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
}).on("click", function(d) {
bubbleClickFun(d.label);
});
node.append("circle").attr("id", function(d) {
return d.id;
}).attr("r", function(d) {
return d.r;
}).style("fill", function(d) {
return d.data.color ? d.data.color : color(nodes.indexOf(d));
}).style("z-index", 1);
node.append("clipPath").attr("id", function(d) {
return "clip-" + d.id;
}).append("use").attr("xlink:href", function(d) {
return "#" + d.id;
});
if (showValue) node.append("text").attr("class", "value-text").style("font-size", `${valueFont.size}px`).attr("clip-path", function(d) {
return "url(#clip-" + d.id + ")";
}).style("font-weight", (d)=>{
return valueFont.weight ? valueFont.weight : 600;
}).style("font-family", valueFont.family).style("fill", ()=>{
return valueFont.color ? valueFont.color : "#000";
}).style("stroke", ()=>{
return valueFont.lineColor ? valueFont.lineColor : "#000";
}).style("stroke-width", ()=>{
return valueFont.lineWeight ? valueFont.lineWeight : 0;
}).text(function(d) {
return d.value;
});
node.append("text").attr("class", "label-text").attr("text-anchor", "middle").attr("dominant-baseline", "middle").style("font-size", `${labelFont.size}px`).attr("clip-path", function(d) {
return "url(#clip-" + d.id + ")";
}).style("font-weight", (d)=>{
return labelFont.weight ? labelFont.weight : 600;
}).style("font-family", labelFont.family).style("fill", ()=>{
return labelFont.color ? labelFont.color : "#000";
}).style("stroke", ()=>{
return labelFont.lineColor ? labelFont.lineColor : "#000";
}).style("stroke-width", ()=>{
return labelFont.lineWeight ? labelFont.lineWeight : 0;
}).text(function(d) {
return d.label;
}).each(insertLinebreaks);
// Center the texts inside the circles.
$zIto6$selectAll(".label-text").attr("x", function(d) {
const self = $zIto6$select(this);
const width = self.node().getBBox().width;
return -(width / 2);
}).style("opacity", function(d) {
const self = $zIto6$select(this);
const width = self.node().getBBox().width;
d.hideLabel = width * 1.05 > d.r * 2;
return d.hideLabel ? 0 : 1;
}).attr("y", function(d) {
const self = $zIto6$select(this);
const height = self.node().getBBox().height;
return -(height / 2 + 10);
});
// Center the texts inside the circles.
$zIto6$selectAll(".value-text").attr("x", function(d) {
const self = $zIto6$select(this);
const width = self.node().getBBox().width;
return -(width / 2);
}).attr("y", function(d) {
if (d.hideLabel) return valueFont.size / 3;
else return -valueFont.size * 0.5;
});
node.append("title").text(function(d) {
return d.label;
});
}
renderLegend(width, height, offset, nodes, color) {
const { data: data , legendClickFun: legendClickFun , legendFont: legendFont } = this.props;
const bubble = $zIto6$select(".bubble-chart");
const bubbleHeight = bubble.node().getBBox().height;
const legend = $zIto6$select(this.svg.current).append("g").attr("transform", function() {
return `translate(${offset},${bubbleHeight * 0.05})`;
}).attr("class", "legend");
let textOffset = 0;
const texts = legend.selectAll(".legend-text").data(nodes).enter().append("g").attr("transform", (d, i)=>{
const offset = textOffset;
textOffset += legendFont.size + 10;
return `translate(0,${offset})`;
}).on("mouseover", function(d) {
$zIto6$select("#" + d.id).attr("r", d.r * 1.04);
}).on("mouseout", function(d) {
const r = d.r - d.r * 0.04;
$zIto6$select("#" + d.id).attr("r", r);
}).on("click", function(d) {
legendClickFun(d.label);
});
texts.append("rect").attr("width", 30).attr("height", legendFont.size).attr("x", 0).attr("y", -legendFont.size).style("fill", "transparent");
texts.append("rect").attr("width", legendFont.size).attr("height", legendFont.size).attr("x", 0).attr("y", -legendFont.size).style("fill", function(d) {
return d.data.color ? d.data.color : color(nodes.indexOf(d));
});
texts.append("text").style("font-size", `${legendFont.size}px`).style("font-weight", (d)=>{
return legendFont.weight ? legendFont.weight : 600;
}).style("font-family", legendFont.family).style("fill", ()=>{
return legendFont.color ? legendFont.color : "#000";
}).style("stroke", ()=>{
return legendFont.lineColor ? legendFont.lineColor : "#000";
}).style("stroke-width", ()=>{
return legendFont.lineWeight ? legendFont.lineWeight : 0;
}).attr("x", (d)=>{
return legendFont.size + 10;
}).attr("y", 0).text((d)=>{
return d.label;
});
}
}
$df21b5b58bbd6a03$export$2e2bcd8739ae039.propTypes = {
overflow: (0, $zIto6$proptypes).bool,
graph: (0, $zIto6$proptypes).shape({
zoom: (0, $zIto6$proptypes).number,
offsetX: (0, $zIto6$proptypes).number,
offsetY: (0, $zIto6$proptypes).number
}),
width: (0, $zIto6$proptypes).number,
height: (0, $zIto6$proptypes).number,
padding: (0, $zIto6$proptypes).number,
showLegend: (0, $zIto6$proptypes).bool,
legendPercentage: (0, $zIto6$proptypes).number,
legendFont: (0, $zIto6$proptypes).shape({
family: (0, $zIto6$proptypes).string,
size: (0, $zIto6$proptypes).number,
color: (0, $zIto6$proptypes).string,
weight: (0, $zIto6$proptypes).string
}),
valueFont: (0, $zIto6$proptypes).shape({
family: (0, $zIto6$proptypes).string,
size: (0, $zIto6$proptypes).number,
color: (0, $zIto6$proptypes).string,
weight: (0, $zIto6$proptypes).string
}),
labelFont: (0, $zIto6$proptypes).shape({
family: (0, $zIto6$proptypes).string,
size: (0, $zIto6$proptypes).number,
color: (0, $zIto6$proptypes).string,
weight: (0, $zIto6$proptypes).string
})
};
$df21b5b58bbd6a03$export$2e2bcd8739ae039.defaultProps = {
overflow: false,
graph: {
zoom: 1.1,
offsetX: -0.05,
offsetY: -0.01
},
width: 1000,
height: 800,
padding: 0,
showLegend: true,
legendPercentage: 20,
legendFont: {
family: "Arial",
size: 12,
color: "#000",
weight: "bold"
},
valueFont: {
family: "Arial",
size: 16,
color: "#fff",
weight: "bold"
},
labelFont: {
family: "Arial",
size: 11,
color: "#fff",
weight: "normal"
},
bubbleClickFun: (label)=>{
console.log(`Bubble ${label} is clicked ...`);
},
legendClickFun: (label)=>{
console.log(`Legend ${label} is clicked ...`);
}
};
});
var $cf838c15c8b009ba$exports = {};
$cf838c15c8b009ba$exports = (parcelRequire("j9IVI")).default;
export {$cf838c15c8b009ba$exports as default};
//# sourceMappingURL=module.js.map