import { select, event } from 'd3-selection'; import { scaleLinear } from 'd3-scale'; import { hierarchy, pack } from 'd3-hierarchy'; import { transition } from 'd3-transition'; import { interpolate } from 'd3-interpolate'; import zoomable from 'd3-zoomable'; import Kapsule from 'kapsule'; import tinycolor from 'tinycolor2'; import accessorFn from 'accessor-fn'; function styleInject(css, ref) { if (ref === void 0) ref = {}; var insertAt = ref.insertAt; if (!css || typeof document === 'undefined') { return; } var head = document.head || document.getElementsByTagName('head')[0]; var style = document.createElement('style'); style.type = 'text/css'; if (insertAt === 'top') { if (head.firstChild) { head.insertBefore(style, head.firstChild); } else { head.appendChild(style); } } else { head.appendChild(style); } if (style.styleSheet) { style.styleSheet.cssText = css; } else { style.appendChild(document.createTextNode(css)); } } var css = ".circlepack-viz {\n cursor: move;\n}\n\n.circlepack-viz circle {\n cursor: pointer;\n stroke: lightgrey;\n stroke-opacity: .4;\n opacity: .85;\n transition-property: stroke-opacity, opacity;\n transition-duration: .4s;\n}\n\n.circlepack-viz circle:hover {\n stroke-opacity: 1;\n opacity: 1;\n transition-duration: .05s;\n}\n\n.circlepack-viz text {\n font-size: 12px;\n font-family: sans-serif;\n pointer-events: none;\n dominant-baseline: middle;\n text-anchor: middle;\n fill: #404041;\n}\n\n.circlepack-viz text.light {\n fill: #F7F7F7;\n}\n\n.circlepack-tooltip {\n display: none;\n position: absolute;\n max-width: 320px;\n white-space: nowrap;\n padding: 5px;\n border-radius: 3px;\n font: 12px sans-serif;\n color: #eee;\n background: rgba(0,0,0,0.65);\n pointer-events: none;\n}\n\n.circlepack-tooltip .tooltip-title {\n font-weight: bold;\n text-align: center;\n margin-bottom: 5px;\n}"; styleInject(css); var LABELS_WIDTH_OPACITY_SCALE = scaleLinear().domain([4, 8]).clamp(true); // px per char var TRANSITION_DURATION = 800; var circlepack = Kapsule({ props: { width: { "default": window.innerWidth, onChange: function onChange(_, state) { state.needsReparse = true; } }, height: { "default": window.innerHeight, onChange: function onChange(_, state) { state.needsReparse = true; } }, data: { onChange: function onChange(_, state) { state.needsReparse = true; } }, children: { "default": 'children', onChange: function onChange(_, state) { state.needsReparse = true; } }, sort: { onChange: function onChange(_, state) { state.needsReparse = true; } }, label: { "default": function _default(d) { return d.name; } }, size: { "default": 'value', onChange: function onChange(_, state) { this.zoomReset(); state.needsReparse = true; } }, padding: { "default": 4, onChange: function onChange(_, state) { state.needsReparse = true; } }, color: { "default": function _default(d) { return 'lightgrey'; } }, minCircleRadius: { "default": 3 }, excludeRoot: { "default": false, onChange: function onChange(_, state) { state.needsReparse = true; } }, showLabels: { "default": true }, showTooltip: { "default": function _default(d) { return true; }, triggerUpdate: false }, tooltipTitle: { "default": null, triggerUpdate: false }, tooltipContent: { "default": function _default(d) { return ''; }, triggerUpdate: false }, onClick: { triggerUpdate: false }, onHover: { triggerUpdate: false } }, methods: { zoomBy: function zoomBy(state, k) { state.zoom.zoomBy(k, TRANSITION_DURATION); return this; }, zoomReset: function zoomReset(state) { state.zoom.zoomReset(TRANSITION_DURATION); return this; }, zoomToNode: function zoomToNode(state) { var d = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var node = d.__dataNode; if (node) { var ZOOM_REL_PADDING = 0.12; var k = Math.max(1, Math.min(state.width, state.height) / (node.r * 2) * (1 - ZOOM_REL_PADDING)); var tr = { k: k, x: -Math.max(0, Math.min(state.width * (1 - 1 / k), // Don't pan out of chart boundaries node.x - state.width / k / 2 // Center circle in view )), y: -Math.max(0, Math.min(state.height * (1 - 1 / k), node.y - state.height / k / 2)) }; state.zoom.zoomTo(tr, TRANSITION_DURATION); } return this; }, _parseData: function _parseData(state) { if (state.data) { var hierData = hierarchy(state.data, accessorFn(state.children)).sum(accessorFn(state.size)); if (state.sort) { hierData.sort(state.sort); } pack().padding(state.padding).size([state.width, state.height])(hierData); hierData.descendants().forEach(function (d, i) { d.id = i; // Mark each node with a unique ID d.data.__dataNode = d; // Dual-link data nodes }); state.layoutData = hierData.descendants().filter(state.excludeRoot ? function (d) { return d.depth > 0; } : function () { return true; }); } } }, stateInit: function stateInit() { return { zoom: zoomable() }; }, init: function init(domNode, state) { var _this = this; var el = select(domNode).append('div').attr('class', 'circlepack-viz'); state.svg = el.append('svg'); state.canvas = state.svg.append('g'); // tooltips state.tooltip = select('body').append('div').attr('class', 'chart-tooltip circlepack-tooltip'); // tooltip cleanup on unmount domNode.addEventListener('DOMNodeRemoved', function (e) { if (e.target === this) { state.tooltip.remove(); } }); state.canvas.on('mousemove', function () { state.tooltip.style('left', event.pageX + 'px').style('top', event.pageY + 'px').style('transform', "translate(-".concat(event.offsetX / state.width * 100, "%, 21px)")); // adjust horizontal position to not exceed canvas boundaries }); // zoom/pan state.zoom(state.svg).svgEl(state.canvas).onChange(function (tr, prevTr, duration) { if (state.showLabels && !duration) { // Scale labels immediately if not animating state.canvas.selectAll('text').attr('transform', "scale(".concat(1 / tr.k, ")")); } // Prevent using transitions when using mouse wheel to zoom state.skipTransitionsOnce = !duration; state._rerender(); }); state.svg.on('click', function () { return (state.onClick || _this.zoomReset)(null); }) // By default reset zoom when clicking on canvas .on('mouseover', function () { return state.onHover && state.onHover(null); }); }, update: function update(state) { var _this2 = this; if (state.needsReparse) { this._parseData(); state.needsReparse = false; } state.svg.style('width', state.width + 'px').style('height', state.height + 'px'); state.zoom.translateExtent([[0, 0], [state.width, state.height]]); if (!state.layoutData) return; var zoomTr = state.zoom.current(); var cell = state.canvas.selectAll('.node').data(state.layoutData.filter(function (d) { return (// Show only circles in scene that are larger than the threshold d.x + d.r > -zoomTr.x / zoomTr.k && d.x - d.r < (state.width - zoomTr.x) / zoomTr.k && d.y + d.r > -zoomTr.y / zoomTr.k && d.y - d.r < (state.height - zoomTr.y) / zoomTr.k && d.r >= state.minCircleRadius / zoomTr.k ); }), function (d) { return d.id; }); var nameOf = accessorFn(state.label); var colorOf = accessorFn(state.color); var animate = !state.skipTransitionsOnce; state.skipTransitionsOnce = false; var transition$1 = transition().duration(animate ? TRANSITION_DURATION : 0); // Exiting cell.exit().transition(transition$1).remove(); // Entering var newCell = cell.enter().append('g').attr('class', 'node').attr('transform', function (d) { return "translate(".concat(d.x, ",").concat(d.y, ")"); }); newCell.append('circle').attr('id', function (d) { return "circle-".concat(d.id); }).attr('r', 0).style('stroke-width', 1).on('click', function (d) { event.stopPropagation(); (state.onClick || _this2.zoomToNode)(d.data); }).on('mouseover', function (d) { event.stopPropagation(); state.onHover && state.onHover(d.data); state.tooltip.style('display', state.showTooltip(d.data, d) ? 'inline' : 'none'); state.tooltip.html("\n