1 | 'use strict';
|
2 |
|
3 | Object.defineProperty(exports, '__esModule', { value: true });
|
4 |
|
5 | var _toConsumableArray = require('@babel/runtime/helpers/toConsumableArray');
|
6 | var _defineProperty = require('@babel/runtime/helpers/defineProperty');
|
7 | var d3 = require('d3');
|
8 | var ramda = require('ramda');
|
9 | var map2tree = require('map2tree');
|
10 | var deepmerge = require('deepmerge');
|
11 | var _typeof = require('@babel/runtime/helpers/typeof');
|
12 | var d3tooltip = require('d3tooltip');
|
13 |
|
14 | function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
15 |
|
16 | var _toConsumableArray__default = _interopDefaultLegacy(_toConsumableArray);
|
17 | var _defineProperty__default = _interopDefaultLegacy(_defineProperty);
|
18 | var d3__default = _interopDefaultLegacy(d3);
|
19 | var deepmerge__default = _interopDefaultLegacy(deepmerge);
|
20 | var _typeof__default = _interopDefaultLegacy(_typeof);
|
21 |
|
22 | function sortObject(obj, strict) {
|
23 | if (obj instanceof Array) {
|
24 | var ary;
|
25 |
|
26 | if (strict) {
|
27 | ary = obj.sort();
|
28 | } else {
|
29 | ary = obj;
|
30 | }
|
31 |
|
32 | return ary;
|
33 | }
|
34 |
|
35 | if (obj && _typeof__default["default"](obj) === 'object') {
|
36 | var tObj = {};
|
37 | Object.keys(obj).sort().forEach(function (key) {
|
38 | return tObj[key] = sortObject(obj[key]);
|
39 | });
|
40 | return tObj;
|
41 | }
|
42 |
|
43 | return obj;
|
44 | }
|
45 |
|
46 | function sortAndSerialize(obj) {
|
47 | return JSON.stringify(sortObject(obj, true), undefined, 2);
|
48 | }
|
49 |
|
50 | function toggleChildren(node) {
|
51 | if (node.children) {
|
52 | node._children = node.children;
|
53 | node.children = null;
|
54 | } else if (node._children) {
|
55 | node.children = node._children;
|
56 | node._children = null;
|
57 | }
|
58 |
|
59 | return node;
|
60 | }
|
61 | function visit(parent, visitFn, childrenFn) {
|
62 | if (!parent) {
|
63 | return;
|
64 | }
|
65 |
|
66 | visitFn(parent);
|
67 | var children = childrenFn(parent);
|
68 |
|
69 | if (children) {
|
70 | var count = children.length;
|
71 |
|
72 | for (var i = 0; i < count; i++) {
|
73 | visit(children[i], visitFn, childrenFn);
|
74 | }
|
75 | }
|
76 | }
|
77 | function getNodeGroupByDepthCount(rootNode) {
|
78 | var nodeGroupByDepthCount = [1];
|
79 |
|
80 | var traverseFrom = function traverseFrom(node) {
|
81 | var depth = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
|
82 |
|
83 | if (!node.children || node.children.length === 0) {
|
84 | return 0;
|
85 | }
|
86 |
|
87 | if (nodeGroupByDepthCount.length <= depth + 1) {
|
88 | nodeGroupByDepthCount.push(0);
|
89 | }
|
90 |
|
91 | nodeGroupByDepthCount[depth + 1] += node.children.length;
|
92 | node.children.forEach(function (childNode) {
|
93 | traverseFrom(childNode, depth + 1);
|
94 | });
|
95 | };
|
96 |
|
97 | traverseFrom(rootNode);
|
98 | return nodeGroupByDepthCount;
|
99 | }
|
100 | function getTooltipString(node, i, _ref) {
|
101 | var _ref$indentationSize = _ref.indentationSize,
|
102 | indentationSize = _ref$indentationSize === void 0 ? 4 : _ref$indentationSize;
|
103 | if (!ramda.is(Object, node)) return '';
|
104 | var spacer = ramda.join(' ');
|
105 | var cr2br = ramda.replace(/\n/g, '<br/>');
|
106 | var spaces2nbsp = ramda.replace(/\s{2}/g, spacer(new Array(indentationSize)));
|
107 | var json2html = ramda.pipe(sortAndSerialize, cr2br, spaces2nbsp);
|
108 | var children = node.children || node._children;
|
109 | if (typeof node.value !== 'undefined') return json2html(node.value);
|
110 | if (typeof node.object !== 'undefined') return json2html(node.object);
|
111 | if (children && children.length) return "childrenCount: ".concat(children.length);
|
112 | return 'empty';
|
113 | }
|
114 |
|
115 | function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
|
116 |
|
117 | function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty__default["default"](target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
|
118 | var defaultOptions = {
|
119 | state: undefined,
|
120 | rootKeyName: 'state',
|
121 | pushMethod: 'push',
|
122 | tree: undefined,
|
123 | id: 'd3svg',
|
124 | style: {
|
125 | node: {
|
126 | colors: {
|
127 | default: '#ccc',
|
128 | collapsed: 'lightsteelblue',
|
129 | parent: 'white'
|
130 | },
|
131 | radius: 7
|
132 | },
|
133 | text: {
|
134 | colors: {
|
135 | default: 'black',
|
136 | hover: 'skyblue'
|
137 | }
|
138 | },
|
139 | link: {
|
140 | stroke: '#000',
|
141 | fill: 'none'
|
142 | }
|
143 | },
|
144 | size: 500,
|
145 | aspectRatio: 1.0,
|
146 | initialZoom: 1,
|
147 | margin: {
|
148 | top: 10,
|
149 | right: 10,
|
150 | bottom: 10,
|
151 | left: 50
|
152 | },
|
153 | isSorted: false,
|
154 | heightBetweenNodesCoeff: 2,
|
155 | widthBetweenNodesCoeff: 1,
|
156 | transitionDuration: 750,
|
157 | blinkDuration: 100,
|
158 | onClickText: function onClickText() {
|
159 | },
|
160 | tooltipOptions: {
|
161 | disabled: false,
|
162 | left: undefined,
|
163 | top: undefined,
|
164 | offset: {
|
165 | left: 0,
|
166 | top: 0
|
167 | },
|
168 | style: undefined
|
169 | }
|
170 | };
|
171 | function tree (DOMNode) {
|
172 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
173 |
|
174 | var _deepmerge = deepmerge__default["default"](defaultOptions, options),
|
175 | id = _deepmerge.id,
|
176 | style = _deepmerge.style,
|
177 | size = _deepmerge.size,
|
178 | aspectRatio = _deepmerge.aspectRatio,
|
179 | initialZoom = _deepmerge.initialZoom,
|
180 | margin = _deepmerge.margin,
|
181 | isSorted = _deepmerge.isSorted,
|
182 | widthBetweenNodesCoeff = _deepmerge.widthBetweenNodesCoeff,
|
183 | heightBetweenNodesCoeff = _deepmerge.heightBetweenNodesCoeff,
|
184 | transitionDuration = _deepmerge.transitionDuration,
|
185 | blinkDuration = _deepmerge.blinkDuration,
|
186 | state = _deepmerge.state,
|
187 | rootKeyName = _deepmerge.rootKeyName,
|
188 | pushMethod = _deepmerge.pushMethod,
|
189 | tree = _deepmerge.tree,
|
190 | tooltipOptions = _deepmerge.tooltipOptions,
|
191 | onClickText = _deepmerge.onClickText;
|
192 |
|
193 | var width = size - margin.left - margin.right;
|
194 | var height = size * aspectRatio - margin.top - margin.bottom;
|
195 | var fullWidth = size;
|
196 | var fullHeight = size * aspectRatio;
|
197 | var attr = {
|
198 | id: id,
|
199 | preserveAspectRatio: 'xMinYMin slice'
|
200 | };
|
201 |
|
202 | if (!style.width) {
|
203 | attr.width = fullWidth;
|
204 | }
|
205 |
|
206 | if (!style.width || !style.height) {
|
207 | attr.viewBox = "0 0 ".concat(fullWidth, " ").concat(fullHeight);
|
208 | }
|
209 |
|
210 | var root = d3__default["default"].select(DOMNode);
|
211 | var zoom = d3__default["default"].behavior.zoom().scaleExtent([0.1, 3]).scale(initialZoom);
|
212 | var vis = root.append('svg').attr(attr).style(_objectSpread({
|
213 | cursor: '-webkit-grab'
|
214 | }, style)).call(zoom.on('zoom', function () {
|
215 | var _d3$event = d3__default["default"].event,
|
216 | translate = _d3$event.translate,
|
217 | scale = _d3$event.scale;
|
218 | vis.attr('transform', "translate(".concat(translate.toString(), ")scale(").concat(scale, ")"));
|
219 | })).append('g').attr({
|
220 | transform: "translate(".concat(margin.left + style.node.radius, ", ").concat(margin.top, ") scale(").concat(initialZoom, ")")
|
221 | });
|
222 | var layout = d3__default["default"].layout.tree().size([width, height]);
|
223 | var data;
|
224 |
|
225 | if (isSorted) {
|
226 | layout.sort(function (a, b) {
|
227 | return b.name.toLowerCase() < a.name.toLowerCase() ? 1 : -1;
|
228 | });
|
229 | }
|
230 |
|
231 |
|
232 |
|
233 |
|
234 | var previousNodePositionsById = {
|
235 | root: {
|
236 | id: 'root',
|
237 | parentId: null,
|
238 | x: height / 2,
|
239 | y: 0
|
240 | }
|
241 | };
|
242 |
|
243 |
|
244 |
|
245 | function findParentNodePosition(nodePositionsById, nodeId, filter) {
|
246 | var currentPosition = nodePositionsById[nodeId];
|
247 |
|
248 | while (currentPosition) {
|
249 | currentPosition = nodePositionsById[currentPosition.parentId];
|
250 |
|
251 | if (!currentPosition) {
|
252 | return null;
|
253 | }
|
254 |
|
255 | if (!filter || filter(currentPosition)) {
|
256 | return currentPosition;
|
257 | }
|
258 | }
|
259 | }
|
260 |
|
261 | return function renderChart() {
|
262 | var nextState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : tree || state;
|
263 | data = !tree ?
|
264 | map2tree.map2tree(nextState, {
|
265 | key: rootKeyName,
|
266 | pushMethod: pushMethod
|
267 | }) : nextState;
|
268 |
|
269 | if (ramda.isEmpty(data) || !data.name) {
|
270 | data = {
|
271 | name: 'error',
|
272 | message: 'Please provide a state map or a tree structure'
|
273 | };
|
274 | }
|
275 |
|
276 | var nodeIndex = 0;
|
277 | var maxLabelLength = 0;
|
278 |
|
279 |
|
280 |
|
281 | visit(data, function (node) {
|
282 | maxLabelLength = Math.max(node.name.length, maxLabelLength);
|
283 | node.id = node.id || 'root';
|
284 | }, function (node) {
|
285 | return node.children && node.children.length > 0 ? node.children.map(function (c) {
|
286 | c.id = "".concat(node.id || '', "|").concat(c.name);
|
287 | return c;
|
288 | }) : null;
|
289 | });
|
290 | update();
|
291 |
|
292 | function update() {
|
293 |
|
294 | var diagonal = d3__default["default"].svg.diagonal().projection(function (d) {
|
295 | return [d.y, d.x];
|
296 | });
|
297 |
|
298 | var maxNodeCountByLevel = Math.max.apply(Math, _toConsumableArray__default["default"](getNodeGroupByDepthCount(data)));
|
299 | layout = layout.size([maxNodeCountByLevel * 25 * heightBetweenNodesCoeff, width]);
|
300 | var nodes = layout.nodes(data);
|
301 | var links = layout.links(nodes);
|
302 | nodes.forEach(function (node) {
|
303 | return node.y = node.depth * (maxLabelLength * 7 * widthBetweenNodesCoeff);
|
304 | });
|
305 | var nodePositions = nodes.map(function (n) {
|
306 | return {
|
307 | parentId: n.parent && n.parent.id,
|
308 | id: n.id,
|
309 | x: n.x,
|
310 | y: n.y
|
311 | };
|
312 | });
|
313 | var nodePositionsById = {};
|
314 | nodePositions.forEach(function (node) {
|
315 | return nodePositionsById[node.id] = node;
|
316 | });
|
317 |
|
318 | var node = vis.selectAll('g.node').property('__oldData__', function (d) {
|
319 | return d;
|
320 | }).data(nodes, function (d) {
|
321 | return d.id || (d.id = ++nodeIndex);
|
322 | });
|
323 | var nodeEnter = node.enter().append('g').attr({
|
324 | class: 'node',
|
325 | transform: function transform(d) {
|
326 | var position = findParentNodePosition(nodePositionsById, d.id, function (n) {
|
327 | return !!previousNodePositionsById[n.id];
|
328 | });
|
329 | var previousPosition = position && previousNodePositionsById[position.id] || previousNodePositionsById.root;
|
330 | return "translate(".concat(previousPosition.y, ",").concat(previousPosition.x, ")");
|
331 | }
|
332 | }).style({
|
333 | fill: style.text.colors.default,
|
334 | cursor: 'pointer'
|
335 | }).on('mouseover', function mouseover() {
|
336 | d3__default["default"].select(this).style({
|
337 | fill: style.text.colors.hover
|
338 | });
|
339 | }).on('mouseout', function mouseout() {
|
340 | d3__default["default"].select(this).style({
|
341 | fill: style.text.colors.default
|
342 | });
|
343 | });
|
344 |
|
345 | if (!tooltipOptions.disabled) {
|
346 | nodeEnter.call(d3tooltip.tooltip(d3__default["default"], 'tooltip', _objectSpread(_objectSpread({}, tooltipOptions), {}, {
|
347 | root: root
|
348 | })).text(function (d, i) {
|
349 | return getTooltipString(d, i, tooltipOptions);
|
350 | }).style(tooltipOptions.style));
|
351 | }
|
352 |
|
353 |
|
354 |
|
355 | var nodeEnterInnerGroup = nodeEnter.append('g');
|
356 | nodeEnterInnerGroup.append('circle').attr({
|
357 | class: 'nodeCircle',
|
358 | r: 0
|
359 | }).on('click', function (clickedNode) {
|
360 | if (d3__default["default"].event.defaultPrevented) return;
|
361 | toggleChildren(clickedNode);
|
362 | update();
|
363 | });
|
364 | nodeEnterInnerGroup.append('text').attr({
|
365 | class: 'nodeText',
|
366 | 'text-anchor': 'middle',
|
367 | transform: 'translate(0,0)',
|
368 | dy: '.35em'
|
369 | }).style({
|
370 | 'fill-opacity': 0
|
371 | }).text(function (d) {
|
372 | return d.name;
|
373 | }).on('click', onClickText);
|
374 |
|
375 | node.select('text').text(function (d) {
|
376 | return d.name;
|
377 | });
|
378 |
|
379 | node.select('circle').style({
|
380 | stroke: 'black',
|
381 | 'stroke-width': '1.5px',
|
382 | fill: function fill(d) {
|
383 | return d._children ? style.node.colors.collapsed : d.children ? style.node.colors.parent : style.node.colors.default;
|
384 | }
|
385 | });
|
386 |
|
387 | var nodeUpdate = node.transition().duration(transitionDuration).attr({
|
388 | transform: function transform(d) {
|
389 | return "translate(".concat(d.y, ",").concat(d.x, ")");
|
390 | }
|
391 | });
|
392 |
|
393 | nodeUpdate.select('circle').attr('r', style.node.radius);
|
394 |
|
395 | nodeUpdate.select('text').style('fill-opacity', 1).attr({
|
396 | transform: function transform(d) {
|
397 | var x = (d.children || d._children ? -1 : 1) * (this.getBBox().width / 2 + style.node.radius + 5);
|
398 | return "translate(".concat(x, ",0)");
|
399 | }
|
400 | });
|
401 |
|
402 | node.filter(function flick(d) {
|
403 |
|
404 |
|
405 |
|
406 |
|
407 | return this.__oldData__ && d.value !== this.__oldData__.value;
|
408 | }).select('g').style('opacity', '0.3').transition().duration(blinkDuration).style('opacity', '1');
|
409 |
|
410 | var nodeExit = node.exit().transition().duration(transitionDuration).attr({
|
411 | transform: function transform(d) {
|
412 | var position = findParentNodePosition(previousNodePositionsById, d.id, function (n) {
|
413 | return !!nodePositionsById[n.id];
|
414 | });
|
415 | var futurePosition = position && nodePositionsById[position.id] || nodePositionsById.root;
|
416 | return "translate(".concat(futurePosition.y, ",").concat(futurePosition.x, ")");
|
417 | }
|
418 | }).remove();
|
419 | nodeExit.select('circle').attr('r', 0);
|
420 | nodeExit.select('text').style('fill-opacity', 0);
|
421 |
|
422 | var link = vis.selectAll('path.link').data(links, function (d) {
|
423 | return d.target.id;
|
424 | });
|
425 |
|
426 | link.enter().insert('path', 'g').attr({
|
427 | class: 'link',
|
428 | d: function d(_d) {
|
429 | var position = findParentNodePosition(nodePositionsById, _d.target.id, function (n) {
|
430 | return !!previousNodePositionsById[n.id];
|
431 | });
|
432 | var previousPosition = position && previousNodePositionsById[position.id] || previousNodePositionsById.root;
|
433 | return diagonal({
|
434 | source: previousPosition,
|
435 | target: previousPosition
|
436 | });
|
437 | }
|
438 | }).style(style.link);
|
439 |
|
440 | link.transition().duration(transitionDuration).attr({
|
441 | d: diagonal
|
442 | });
|
443 |
|
444 | link.exit().transition().duration(transitionDuration).attr({
|
445 | d: function d(_d2) {
|
446 | var position = findParentNodePosition(previousNodePositionsById, _d2.target.id, function (n) {
|
447 | return !!nodePositionsById[n.id];
|
448 | });
|
449 | var futurePosition = position && nodePositionsById[position.id] || nodePositionsById.root;
|
450 | return diagonal({
|
451 | source: futurePosition,
|
452 | target: futurePosition
|
453 | });
|
454 | }
|
455 | }).remove();
|
456 |
|
457 | node.property('__oldData__', null);
|
458 |
|
459 | previousNodePositionsById = nodePositionsById;
|
460 | }
|
461 | };
|
462 | }
|
463 |
|
464 | exports.tree = tree;
|