UNPKG

@testboxlab/react-bubble-chart-d3

Version:

ReactJS component to display data as a bubble chart using D3.

324 lines (308 loc) 13.2 kB
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