@fluentui/react-charting 5.19.25 → 5.19.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.json +28 -1
- package/CHANGELOG.md +13 -2
- package/dist/react-charting.d.ts +15 -0
- package/dist/react-charting.js +1 -1
- package/dist/react-charting.min.js +1 -1
- package/lib/components/SankeyChart/SankeyChart.base.d.ts +16 -19
- package/lib/components/SankeyChart/SankeyChart.base.js +295 -186
- package/lib/components/SankeyChart/SankeyChart.base.js.map +1 -1
- package/lib/types/IDataPoint.d.ts +16 -1
- package/lib/types/IDataPoint.js.map +1 -1
- package/lib/version.js +1 -1
- package/lib/version.js.map +1 -1
- package/lib-amd/components/SankeyChart/SankeyChart.base.d.ts +16 -19
- package/lib-amd/components/SankeyChart/SankeyChart.base.js +294 -185
- package/lib-amd/components/SankeyChart/SankeyChart.base.js.map +1 -1
- package/lib-amd/types/IDataPoint.d.ts +16 -1
- package/lib-amd/types/IDataPoint.js.map +1 -1
- package/lib-amd/version.js +1 -1
- package/lib-amd/version.js.map +1 -1
- package/lib-commonjs/components/SankeyChart/SankeyChart.base.d.ts +16 -19
- package/lib-commonjs/components/SankeyChart/SankeyChart.base.js +294 -185
- package/lib-commonjs/components/SankeyChart/SankeyChart.base.js.map +1 -1
- package/lib-commonjs/types/IDataPoint.d.ts +16 -1
- package/lib-commonjs/types/IDataPoint.js.map +1 -1
- package/lib-commonjs/version.js +1 -1
- package/lib-commonjs/version.js.map +1 -1
- package/package.json +3 -3
|
@@ -273,6 +273,176 @@ function preRenderLayout(margins, containerWidth, containerHeight, isRtl) {
|
|
|
273
273
|
return { sankey: sankey, height: height, width: width };
|
|
274
274
|
}
|
|
275
275
|
exports.preRenderLayout = preRenderLayout;
|
|
276
|
+
var elipsis = '...';
|
|
277
|
+
/**
|
|
278
|
+
* This is used to assign node fillcolors and borderColor cyclically when the user doesnt
|
|
279
|
+
* provide color to individual node.
|
|
280
|
+
*/
|
|
281
|
+
function assignNodeColors(nodes, colorsForNodes, borderColorsForNodes) {
|
|
282
|
+
var colors;
|
|
283
|
+
var borders;
|
|
284
|
+
if (colorsForNodes && borderColorsForNodes) {
|
|
285
|
+
colors = colorsForNodes;
|
|
286
|
+
borders = borderColorsForNodes;
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
colors = DEFAULT_NODE_COLORS.map(function (color) { return color.fillColor; });
|
|
290
|
+
borders = DEFAULT_NODE_COLORS.map(function (color) { return color.borderColor; });
|
|
291
|
+
}
|
|
292
|
+
var currentIndex = 0;
|
|
293
|
+
nodes.forEach(function (node) {
|
|
294
|
+
if (!node.color && !node.borderColor) {
|
|
295
|
+
node.color = colors[currentIndex];
|
|
296
|
+
node.borderColor = borders[currentIndex];
|
|
297
|
+
}
|
|
298
|
+
else if (node.color && !node.borderColor) {
|
|
299
|
+
node.borderColor = '#757575';
|
|
300
|
+
}
|
|
301
|
+
else if (node.borderColor && !node.color) {
|
|
302
|
+
node.color = '#F5F5F5';
|
|
303
|
+
}
|
|
304
|
+
currentIndex = (currentIndex + 1) % colors.length;
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Takes in the display name for the node and potentially returns a trimmed version of the name.
|
|
309
|
+
* @param tspan the `tspan` element to use for text visual length measurement
|
|
310
|
+
* @param text is the text which we will potentially truncate
|
|
311
|
+
* @param rectangleWidth is the width of the rectangle which will contain the text
|
|
312
|
+
* @param padding is the space we need to leave between the rect lines and other text
|
|
313
|
+
* @returns the name to show on the node which might be the truncated `text` if the `text` is too long
|
|
314
|
+
*/
|
|
315
|
+
function truncateText(tspan, text, rectangleWidth, padding) {
|
|
316
|
+
// NOTE: This method is the most-expensive in terms of rerendering components.
|
|
317
|
+
var textLengthForNodeName = rectangleWidth - padding; // This can likely be computed once and passed in.
|
|
318
|
+
// The following `select` statement injects a `tempText` element into the DOM. This injection
|
|
319
|
+
// (and subsequent removal) is causing a layout recalculation. This is a performance issue.
|
|
320
|
+
// Note that this code will always inject a `tempText` element, but doesn't always remove it. This is a bug.
|
|
321
|
+
if (fitsWithinNode(tspan, text, textLengthForNodeName)) {
|
|
322
|
+
return text;
|
|
323
|
+
}
|
|
324
|
+
// Computing the size of elipsis is performed with each node. This should be computed once and used everywhere.
|
|
325
|
+
// TODO: Compute the size of the elipsis once and use it everywhere.
|
|
326
|
+
var elipsisLength = computeElipsisLength(tspan);
|
|
327
|
+
var line = '';
|
|
328
|
+
// Calculate how much of the original text to show.
|
|
329
|
+
// TODO: The folllowing is O(n). We could use a binary search to make this faster: O(log(n)).
|
|
330
|
+
for (var i = 0; i < text.length; i++) {
|
|
331
|
+
line += text[i];
|
|
332
|
+
tspan.text(line);
|
|
333
|
+
var currentNode = tspan.node();
|
|
334
|
+
if (currentNode !== null) {
|
|
335
|
+
var w = currentNode.getComputedTextLength();
|
|
336
|
+
if (w >= textLengthForNodeName - elipsisLength) {
|
|
337
|
+
line = line.slice(0, -1);
|
|
338
|
+
line += elipsis;
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
tspan.text(null);
|
|
344
|
+
return line;
|
|
345
|
+
}
|
|
346
|
+
function fitsWithinNode(tspan, text, textLengthForNodeName) {
|
|
347
|
+
var measurement = measureText(tspan, text);
|
|
348
|
+
if (measurement === undefined) {
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
return measurement <= textLengthForNodeName;
|
|
352
|
+
}
|
|
353
|
+
function measureText(tspan, text) {
|
|
354
|
+
var _a;
|
|
355
|
+
try {
|
|
356
|
+
tspan.text(text);
|
|
357
|
+
return (_a = tspan.node()) === null || _a === void 0 ? void 0 : _a.getComputedTextLength();
|
|
358
|
+
}
|
|
359
|
+
finally {
|
|
360
|
+
tspan.text(null);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function computeElipsisLength(tspan) {
|
|
364
|
+
var measurement = measureText(tspan, elipsis);
|
|
365
|
+
return measurement === undefined ? 0 : measurement;
|
|
366
|
+
}
|
|
367
|
+
function computeNodeAttributes(nodes) {
|
|
368
|
+
var result = {};
|
|
369
|
+
var weightSpan = (0, d3_selection_1.select)('.nodeName').append('text').attr('class', 'tempText').append('tspan').text(null);
|
|
370
|
+
var nameSpan = (0, d3_selection_1.select)('.nodeName')
|
|
371
|
+
.append('text')
|
|
372
|
+
.attr('class', 'tempText')
|
|
373
|
+
.attr('font-size', '10')
|
|
374
|
+
.append('tspan')
|
|
375
|
+
.text(null);
|
|
376
|
+
nodes.forEach(function (singleNode) {
|
|
377
|
+
var height = Math.max(singleNode.y1 - singleNode.y0, 0);
|
|
378
|
+
var padding = 8;
|
|
379
|
+
var textLengthForNodeWeight = 0;
|
|
380
|
+
// If the nodeWeight is in the same line as node description an extra padding
|
|
381
|
+
// of 6 px is required between node description and node weight.
|
|
382
|
+
if (height < MIN_HEIGHT_FOR_DOUBLINE_TYPE) {
|
|
383
|
+
padding = padding + 6;
|
|
384
|
+
// The following `select` statement injects a `tempText` element into the DOM. This injection
|
|
385
|
+
// (and subsequent removal) is causing a layout recalculation. This is a performance issue.
|
|
386
|
+
var measurement = measureText(weightSpan, singleNode.actualValue);
|
|
387
|
+
if (measurement !== undefined) {
|
|
388
|
+
textLengthForNodeWeight = measurement;
|
|
389
|
+
padding = padding + textLengthForNodeWeight;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
// Since the total width of the node is 124 and we are giving margin of 8px from the left .
|
|
393
|
+
// So the actual value on which it will be truncated is 124-8=116.
|
|
394
|
+
var truncatedname = truncateText(nameSpan, singleNode.name, 116, padding);
|
|
395
|
+
var isTruncated = truncatedname.slice(-3) === elipsis;
|
|
396
|
+
result[singleNode.nodeId] = {
|
|
397
|
+
reactId: (0, Utilities_1.getId)('nodeBar'),
|
|
398
|
+
gElementId: (0, Utilities_1.getId)('nodeGElement'),
|
|
399
|
+
name: truncatedname,
|
|
400
|
+
trimmed: isTruncated,
|
|
401
|
+
height: height,
|
|
402
|
+
weightOffset: textLengthForNodeWeight,
|
|
403
|
+
};
|
|
404
|
+
});
|
|
405
|
+
(0, d3_selection_1.selectAll)('.tempText').remove();
|
|
406
|
+
return result;
|
|
407
|
+
}
|
|
408
|
+
function computeLinkAttributes(links) {
|
|
409
|
+
var result = {};
|
|
410
|
+
links.forEach(function (link) {
|
|
411
|
+
var sourceId = idFromNumberOrSNode(link.source);
|
|
412
|
+
var sourceToTarget = result[sourceId];
|
|
413
|
+
if (!sourceToTarget) {
|
|
414
|
+
sourceToTarget = {};
|
|
415
|
+
result[sourceId] = sourceToTarget;
|
|
416
|
+
}
|
|
417
|
+
sourceToTarget[idFromNumberOrSNode(link.target)] = { reactId: (0, Utilities_1.getId)('link') };
|
|
418
|
+
});
|
|
419
|
+
return result;
|
|
420
|
+
}
|
|
421
|
+
var linkToDataPoints = function (d) {
|
|
422
|
+
var halfWidth = d.width * 0.5;
|
|
423
|
+
var y0 = d.y0;
|
|
424
|
+
var y1 = d.y1;
|
|
425
|
+
return [
|
|
426
|
+
{ x: d.source.x1, y0: y0 + halfWidth, y1: y0 - halfWidth },
|
|
427
|
+
{ x: d.target.x0, y0: y1 + halfWidth, y1: y1 - halfWidth },
|
|
428
|
+
];
|
|
429
|
+
};
|
|
430
|
+
var linkArea = (0, d3_shape_1.area)()
|
|
431
|
+
.x(function (p) { return p.x; })
|
|
432
|
+
.y0(function (p) { return p.y0; })
|
|
433
|
+
.y1(function (p) { return p.y1; })
|
|
434
|
+
.curve(d3_shape_1.curveBumpX);
|
|
435
|
+
function nodeTextColor(state, singleNode) {
|
|
436
|
+
return !(!state.selectedState ||
|
|
437
|
+
(state.selectedNodes.has(singleNode.index) && state.selectedNode) ||
|
|
438
|
+
!state.selectedNode)
|
|
439
|
+
? DEFAULT_TEXT_COLOR
|
|
440
|
+
: NON_SELECTED_TEXT_COLOR;
|
|
441
|
+
}
|
|
442
|
+
// NOTE: To start employing React.useMemo properly, we need to convert this code from a React.Component
|
|
443
|
+
// to a function component. This will require a significant refactor of the code in this file.
|
|
444
|
+
// https://stackoverflow.com/questions/60223362/fast-way-to-convert-react-class-component-to-functional-component
|
|
445
|
+
// I am concerned that doing so would break this contract, making it difficult for consuming code.
|
|
276
446
|
var SankeyChartBase = /** @class */ (function (_super) {
|
|
277
447
|
tslib_1.__extends(SankeyChartBase, _super);
|
|
278
448
|
function SankeyChartBase(props) {
|
|
@@ -293,11 +463,14 @@ var SankeyChartBase = /** @class */ (function (_super) {
|
|
|
293
463
|
if (!state.selectedState) {
|
|
294
464
|
return singleNode.color;
|
|
295
465
|
}
|
|
296
|
-
else
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
466
|
+
else {
|
|
467
|
+
var selectedNode = state.selectedNode;
|
|
468
|
+
if (selectedNode && state.selectedNodes.has(singleNode.index)) {
|
|
469
|
+
return selectedNode.color;
|
|
470
|
+
}
|
|
471
|
+
else if (!selectedNode) {
|
|
472
|
+
return singleNode.color;
|
|
473
|
+
}
|
|
301
474
|
}
|
|
302
475
|
};
|
|
303
476
|
_this._fillNodeBorder = function (singleNode) {
|
|
@@ -305,13 +478,13 @@ var SankeyChartBase = /** @class */ (function (_super) {
|
|
|
305
478
|
if (!state.selectedState) {
|
|
306
479
|
return singleNode.borderColor;
|
|
307
480
|
}
|
|
308
|
-
else
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
481
|
+
else {
|
|
482
|
+
if (state.selectedNodes.has(singleNode.index)) {
|
|
483
|
+
var selectedNode = state.selectedNode;
|
|
484
|
+
return selectedNode ? selectedNode.borderColor : singleNode.borderColor;
|
|
485
|
+
}
|
|
312
486
|
return singleNode.borderColor;
|
|
313
487
|
}
|
|
314
|
-
return singleNode.borderColor;
|
|
315
488
|
};
|
|
316
489
|
_this.state = {
|
|
317
490
|
containerHeight: 468,
|
|
@@ -324,12 +497,40 @@ var SankeyChartBase = /** @class */ (function (_super) {
|
|
|
324
497
|
};
|
|
325
498
|
_this._calloutId = (0, Utilities_1.getId)('callout');
|
|
326
499
|
_this._linkId = (0, Utilities_1.getId)('link');
|
|
500
|
+
_this._chartId = (0, Utilities_1.getId)('sankeyChart');
|
|
501
|
+
_this._emptyChartId = (0, Utilities_1.getId)('_SankeyChart_empty');
|
|
502
|
+
_this._labelTooltipId = (0, Utilities_1.getId)('tooltip');
|
|
327
503
|
_this._margins = { top: 36, right: 48, bottom: 32, left: 48 };
|
|
328
|
-
|
|
329
|
-
|
|
504
|
+
// We memo-ize creation so that we only create a new object when any of the fields change.
|
|
505
|
+
_this._computeClassNamesProps = (0, Utilities_1.memoizeFunction)(function (theme, pathColor, className, containerWidth, containerHeight) { return ({
|
|
506
|
+
theme: theme,
|
|
507
|
+
width: containerWidth,
|
|
508
|
+
height: containerHeight,
|
|
509
|
+
pathColor: pathColor,
|
|
510
|
+
className: className,
|
|
511
|
+
}); });
|
|
512
|
+
// `getClassNames` is memoized underneath, so it only recomputes when the `styles` or `classNamesProps` change.
|
|
513
|
+
// We memoize `classNamesProps` so that we only create new class names when absolutely necessary,
|
|
514
|
+
// and so that we can memoize the `_createNodes` method.
|
|
515
|
+
_this._computeClassNames = (0, Utilities_1.memoizeFunction)(function (styles, classNamesProps) { return getClassNames(styles, classNamesProps); });
|
|
516
|
+
_this._normalizeData = (0, Utilities_1.memoizeFunction)(function (data, containerWidth, containerHeight, colorsForNodes, borderColorsForNodes) { return _this._normalizeSankeyData(data, containerWidth, containerHeight, colorsForNodes, borderColorsForNodes); });
|
|
517
|
+
_this._fetchTooltip = (0, Utilities_1.memoizeFunction)(function (classNames) {
|
|
518
|
+
return _this._labelTooltipDiv(classNames);
|
|
330
519
|
});
|
|
331
|
-
|
|
520
|
+
// NOTE: Memoizing the `_createNodes` and `_createLinks` methods breaks the hoverability of the chart
|
|
521
|
+
// because the nodes are currently created differently based on the layout information.
|
|
522
|
+
_this._nodeAttributes = (0, Utilities_1.memoizeFunction)(function (nodes) { return computeNodeAttributes(nodes); });
|
|
523
|
+
_this._fetchNodes = function (classNames, nodes, nodeAttributes, tooltipDiv) { return _this._createNodes(classNames, nodes, nodeAttributes, tooltipDiv); };
|
|
524
|
+
_this._linkAttributes = (0, Utilities_1.memoizeFunction)(function (links) { return computeLinkAttributes(links); });
|
|
525
|
+
_this._fetchLinks = function (links, linkAttributes) {
|
|
526
|
+
return _this._createLinks(links, linkAttributes);
|
|
527
|
+
};
|
|
332
528
|
return _this;
|
|
529
|
+
// Our shorter path to performance is to pre-compute the truncated labels of each node because
|
|
530
|
+
// that should not change based on the position of the mouse. This is a shorter path becase the code which
|
|
531
|
+
// computes the truncated labels creates and destroys a `tempText` element in the DOM. This is causing a
|
|
532
|
+
// reflow and repaint of the entire chart. This is a performance issue, and so computing the truncated labels
|
|
533
|
+
// once will help to mitigate this issue.
|
|
333
534
|
}
|
|
334
535
|
SankeyChartBase.prototype.componentDidMount = function () {
|
|
335
536
|
this._fitParentContainer();
|
|
@@ -345,22 +546,22 @@ var SankeyChartBase = /** @class */ (function (_super) {
|
|
|
345
546
|
SankeyChartBase.prototype.render = function () {
|
|
346
547
|
var _this = this;
|
|
347
548
|
if (!this._isChartEmpty()) {
|
|
348
|
-
var _a = this.props, theme = _a.theme, className = _a.className, styles = _a.styles, pathColor = _a.pathColor;
|
|
549
|
+
var _a = this.props, theme = _a.theme, className = _a.className, styles = _a.styles, pathColor = _a.pathColor, colorsForNodes = _a.colorsForNodes, borderColorsForNodes = _a.borderColorsForNodes;
|
|
349
550
|
var state = this.state;
|
|
350
|
-
var
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
var
|
|
358
|
-
//
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
this.
|
|
362
|
-
var
|
|
363
|
-
var linkData = this.
|
|
551
|
+
var classNamesProps = this._computeClassNamesProps(theme, pathColor, className, state.containerWidth, state.containerHeight);
|
|
552
|
+
var classNames = this._computeClassNames(styles, classNamesProps);
|
|
553
|
+
// Compute the position of each node and link
|
|
554
|
+
var _b = this._normalizeData(this.props.data.SankeyChartData, state.containerWidth, state.containerHeight, colorsForNodes, borderColorsForNodes), nodes = _b.nodes, links = _b.links, width = _b.width, height = _b.height;
|
|
555
|
+
// NOTE: I don't love this approach to caching the "select" result. Is it still valid from render-to-render?
|
|
556
|
+
// although local testing seems to indicate so, I do not trust that React will always support that instance.
|
|
557
|
+
// It might be better to perform this `fetch` within the `_showTooltip` and `_hideTooltip` methods.
|
|
558
|
+
var tooltipDiv = this._fetchTooltip(classNames);
|
|
559
|
+
// Pre-compute some important attributes about nodes, specifically text
|
|
560
|
+
var nodeAttributes = this._nodeAttributes(nodes);
|
|
561
|
+
// Build the nodes and links as rendered in the UX.
|
|
562
|
+
var nodeData = this._fetchNodes(classNames, nodes, nodeAttributes, tooltipDiv);
|
|
563
|
+
var linkAttributes = this._linkAttributes(links);
|
|
564
|
+
var linkData = this._fetchLinks(links, linkAttributes);
|
|
364
565
|
var calloutProps = {
|
|
365
566
|
isCalloutVisible: state.isCalloutVisible,
|
|
366
567
|
directionalHint: Callout_1.DirectionalHint.topAutoEdge,
|
|
@@ -378,7 +579,7 @@ var SankeyChartBase = /** @class */ (function (_super) {
|
|
|
378
579
|
};
|
|
379
580
|
return (React.createElement("div", { className: classNames.root, role: 'presentation', ref: function (rootElem) { return (_this.chartContainer = rootElem); }, onMouseLeave: this._onCloseCallout },
|
|
380
581
|
React.createElement(react_focus_1.FocusZone, { direction: react_focus_1.FocusZoneDirection.bidirectional, isCircularNavigation: true, handleTabKey: react_focus_1.FocusZoneTabbableElements.all },
|
|
381
|
-
React.createElement("svg", { width: width, height: height, id:
|
|
582
|
+
React.createElement("svg", { width: width, height: height, id: this._chartId },
|
|
382
583
|
React.createElement("g", { className: classNames.links, strokeOpacity: 1 }, linkData),
|
|
383
584
|
React.createElement("g", { className: classNames.nodes }, nodeData),
|
|
384
585
|
calloutProps.isCalloutVisible && (React.createElement(Callout_1.Callout, tslib_1.__assign({}, calloutProps),
|
|
@@ -386,11 +587,19 @@ var SankeyChartBase = /** @class */ (function (_super) {
|
|
|
386
587
|
}
|
|
387
588
|
return (React.createElement("div", { id: this._emptyChartId, role: 'alert', style: { opacity: '0' }, "aria-label": 'Graph has no data to display' }));
|
|
388
589
|
};
|
|
389
|
-
SankeyChartBase.prototype._normalizeSankeyData = function (data, containerWidth, containerHeight) {
|
|
590
|
+
SankeyChartBase.prototype._normalizeSankeyData = function (data, containerWidth, containerHeight, colorsForNodes, borderColorsForNodes) {
|
|
390
591
|
var _a = preRenderLayout(this._margins, containerWidth, containerHeight, this._isRtl), sankey = _a.sankey, height = _a.height, width = _a.width;
|
|
391
592
|
// Clone the data before mutating it (via the SankeyLayoutGenerator) so that we don't mutate the original data.
|
|
392
593
|
var transformed = duplicateData(data);
|
|
393
594
|
sankey(transformed);
|
|
595
|
+
// NOTE: After the prior line, `transformed` is now a more-complex object than the incoming `ISankeyChartData`.
|
|
596
|
+
// `transformed` should be cast to a more-specific type. This is a breaking change because we would be eliminating
|
|
597
|
+
// fields from `ISankeyChartData` and putting those fields on a now-local type. But doing so makes it clearer what
|
|
598
|
+
// the caller needs to supply and why. For example, the `actualValue` and `layer` fields of `ISNodeExtra` should
|
|
599
|
+
// both be moved. Similarly for `unnormalizedValue` in `ISLinkExtra`.
|
|
600
|
+
// `SankeyNodeMinimal` and `SankeyLinkMinimal` are both the types after `sankey(transformed)`, but have almost no
|
|
601
|
+
// bearing on the data before `sankey(transformed)` (which is basically nodes with ids and names along with links
|
|
602
|
+
// with source index, target index, and value).
|
|
394
603
|
var nodesInColumn = groupNodesByColumn(transformed);
|
|
395
604
|
// Keep track of the original values of the links and their acccumulated values in the nodes
|
|
396
605
|
// Setting these in external objects so they cannot be mutated by other code.
|
|
@@ -407,99 +616,75 @@ var SankeyChartBase = /** @class */ (function (_super) {
|
|
|
407
616
|
// Without the second call, the links are not hoverable.
|
|
408
617
|
sankey(transformed);
|
|
409
618
|
populateNodeActualValue(transformed, nodeValues, linkValues);
|
|
619
|
+
assignNodeColors(transformed.nodes, colorsForNodes, borderColorsForNodes);
|
|
410
620
|
return {
|
|
411
621
|
width: width,
|
|
412
622
|
height: height,
|
|
413
|
-
|
|
623
|
+
nodes: transformed.nodes,
|
|
624
|
+
links: transformed.links,
|
|
414
625
|
};
|
|
415
626
|
};
|
|
416
|
-
SankeyChartBase.prototype.
|
|
627
|
+
SankeyChartBase.prototype._labelTooltipDiv = function (classNames) {
|
|
628
|
+
// find the tooltip div. If it doesn't exist, then create it.
|
|
629
|
+
var tooltipDiv = (0, d3_selection_1.select)("#".concat(this._labelTooltipId));
|
|
630
|
+
if (tooltipDiv.empty()) {
|
|
631
|
+
tooltipDiv = (0, d3_selection_1.select)('body')
|
|
632
|
+
.append('div')
|
|
633
|
+
.attr('id', this._labelTooltipId)
|
|
634
|
+
.attr('class', classNames.toolTip)
|
|
635
|
+
.style('opacity', 0);
|
|
636
|
+
}
|
|
637
|
+
// If the div exists, then `classNames` has changed; update the `class` on the `div`.
|
|
638
|
+
tooltipDiv.attr('class', classNames.toolTip);
|
|
639
|
+
return tooltipDiv;
|
|
640
|
+
};
|
|
641
|
+
SankeyChartBase.prototype._createLinks = function (dataLinks, linkAttributes) {
|
|
417
642
|
var _this = this;
|
|
418
|
-
var links = [];
|
|
419
643
|
if (dataLinks) {
|
|
420
644
|
var linkId_1 = this._linkId;
|
|
421
|
-
dataLinks.
|
|
645
|
+
return dataLinks.map(function (singleLink, index) {
|
|
422
646
|
var onMouseOut = function () {
|
|
423
647
|
_this._onStreamLeave(singleLink);
|
|
424
648
|
};
|
|
649
|
+
var reactId = linkValue(linkAttributes, singleLink).reactId;
|
|
425
650
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
426
|
-
var
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
//
|
|
433
|
-
|
|
434
|
-
var linkArea = (0, d3_shape_1.area)()
|
|
435
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
436
|
-
.x(function (p) { return p.x; })
|
|
437
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
438
|
-
.y0(function (p) { return p.y0; })
|
|
439
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
440
|
-
.y1(function (p) { return p.y1; })
|
|
441
|
-
.curve(d3_shape_1.curveBumpX);
|
|
442
|
-
var gradientUrl = "url(#gradient-".concat(linkId_1, "-").concat(index, ")");
|
|
443
|
-
var link = (React.createElement("g", { key: "".concat(linkId_1, "-").concat(index) },
|
|
651
|
+
var dataPoints = linkToDataPoints(singleLink);
|
|
652
|
+
var key = "".concat(linkId_1, "-").concat(index);
|
|
653
|
+
var gradientId = "gradient-".concat(key);
|
|
654
|
+
var gradientUrl = "url(#".concat(gradientId, ")");
|
|
655
|
+
var source = singleLink.source;
|
|
656
|
+
var target = singleLink.target;
|
|
657
|
+
// TODO: localize the aria-label string
|
|
658
|
+
return (React.createElement("g", { key: key },
|
|
444
659
|
React.createElement("defs", null,
|
|
445
|
-
React.createElement("linearGradient", { id:
|
|
446
|
-
React.createElement("stop", { offset: "0", stopColor:
|
|
447
|
-
React.createElement("stop", { offset: "100%", stopColor:
|
|
448
|
-
React.createElement("path", { d: linkArea(dataPoints), id:
|
|
449
|
-
links.push(link);
|
|
660
|
+
React.createElement("linearGradient", { id: gradientId, x1: "0%", y1: "0%", x2: "100%", y2: "0%" },
|
|
661
|
+
React.createElement("stop", { offset: "0", stopColor: source.color }),
|
|
662
|
+
React.createElement("stop", { offset: "100%", stopColor: target.color }))),
|
|
663
|
+
React.createElement("path", { d: linkArea(dataPoints), id: reactId, fill: _this._fillStreamColors(singleLink, gradientUrl), stroke: _this._fillStreamBorder(singleLink, gradientUrl), strokeWidth: "2", strokeOpacity: _this._getOpacityStreamBorder(singleLink), onMouseOver: _this._onStreamHover.bind(_this, singleLink), onMouseOut: onMouseOut, onFocus: _this._onFocusLink.bind(_this, singleLink), onBlur: _this._onBlur, fillOpacity: _this._getOpacityStream(singleLink), "data-is-focusable": true, "aria-label": "link from ".concat(source.name, " to ").concat(target.name, " with weight ").concat(singleLink.unnormalizedValue), role: "img" })));
|
|
450
664
|
});
|
|
451
665
|
}
|
|
452
|
-
return
|
|
666
|
+
return [];
|
|
453
667
|
};
|
|
454
|
-
SankeyChartBase.prototype._createNodes = function (classNames, dataNodes) {
|
|
668
|
+
SankeyChartBase.prototype._createNodes = function (classNames, dataNodes, nodeAttributes, tooltipDiv) {
|
|
455
669
|
var _this = this;
|
|
456
|
-
var nodes = [];
|
|
457
670
|
if (dataNodes) {
|
|
458
671
|
var state_1 = this.state;
|
|
459
|
-
|
|
672
|
+
var textAnchor_1 = this._isRtl ? 'end' : 'start';
|
|
673
|
+
return dataNodes.map(function (singleNode, index) {
|
|
460
674
|
var onMouseOut = function () {
|
|
461
675
|
_this._onLeave(singleNode);
|
|
462
676
|
};
|
|
463
|
-
var
|
|
464
|
-
var
|
|
465
|
-
var
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
var tspan = (0, d3_selection_1.select)('.nodeName').append('text').attr('class', 'tempText').append('tspan').text(null);
|
|
471
|
-
tspan.text(singleNode.actualValue);
|
|
472
|
-
if (tspan.node() !== null) {
|
|
473
|
-
textLengthForNodeWeight = tspan.node().getComputedTextLength();
|
|
474
|
-
padding = padding + textLengthForNodeWeight;
|
|
475
|
-
}
|
|
476
|
-
tspan.text(null);
|
|
477
|
-
(0, d3_selection_1.selectAll)('.tempText').remove();
|
|
478
|
-
}
|
|
479
|
-
// Since the total width of the node is 124 and we are giving margin of 8px from the left .
|
|
480
|
-
//So the actual value on which it will be truncated is 124-8=116.
|
|
481
|
-
var truncatedname = _this._truncateText(singleNode.name, 116, padding);
|
|
482
|
-
var isTruncated = truncatedname.slice(-3) === '...';
|
|
483
|
-
var id = (0, Utilities_1.getId)('tooltip');
|
|
484
|
-
var div = (0, d3_selection_1.select)('body').append('div').attr('id', id).attr('class', classNames.toolTip).style('opacity', 0);
|
|
485
|
-
var nodeId = (0, Utilities_1.getId)('nodeBar');
|
|
486
|
-
var node = (React.createElement("g", { key: index, id: (0, Utilities_1.getId)('nodeGElement') },
|
|
487
|
-
React.createElement("rect", { x: singleNode.x0, y: singleNode.y0, height: height, width: singleNode.x1 - singleNode.x0, fill: _this._fillNodeColors(singleNode), id: nodeId, onMouseOver: _this._onHover.bind(_this, singleNode), onMouseOut: onMouseOut, onFocus: _this._onCloseCallout.bind(_this), stroke: _this._fillNodeBorder(singleNode), strokeWidth: "2", opacity: "1", "data-is-focusable": true, "aria-label": "node ".concat(singleNode.name, " with weight ").concat(singleNode.actualValue), role: "img" }),
|
|
488
|
-
singleNode.y1 - singleNode.y0 > MIN_HEIGHT_FOR_TYPE && (React.createElement("g", { className: classNames.nodeTextContainer },
|
|
677
|
+
var _a = nodeAttributes[singleNode.nodeId], nodeId = _a.reactId, gElementId = _a.gElementId, height = _a.height, isTruncated = _a.trimmed, truncatedName = _a.name, textLengthForNodeWeight = _a.weightOffset;
|
|
678
|
+
var tooTall = height > MIN_HEIGHT_FOR_DOUBLINE_TYPE;
|
|
679
|
+
var name = singleNode.name, actualValue = singleNode.actualValue, x0 = singleNode.x0, x1 = singleNode.x1, y0 = singleNode.y0;
|
|
680
|
+
var textColor = nodeTextColor(state_1, singleNode);
|
|
681
|
+
return (React.createElement("g", { key: index, id: gElementId },
|
|
682
|
+
React.createElement("rect", { x: x0, y: y0, height: height, width: x1 - x0, fill: _this._fillNodeColors(singleNode), id: nodeId, onMouseOver: _this._onHover.bind(_this, singleNode), onMouseOut: onMouseOut, onFocus: _this._onCloseCallout.bind(_this), stroke: _this._fillNodeBorder(singleNode), strokeWidth: "2", opacity: "1", "data-is-focusable": true, "aria-label": "node ".concat(name, " with weight ").concat(actualValue), role: "img" }),
|
|
683
|
+
height > MIN_HEIGHT_FOR_TYPE && (React.createElement("g", { className: classNames.nodeTextContainer },
|
|
489
684
|
React.createElement("g", { className: "nodeName" },
|
|
490
|
-
React.createElement("text", { id: "".concat(nodeId, "-name"), x:
|
|
491
|
-
|
|
492
|
-
(state_1.selectedState && !state_1.selectedNode))
|
|
493
|
-
? DEFAULT_TEXT_COLOR
|
|
494
|
-
: NON_SELECTED_TEXT_COLOR, fontSize: 10, onMouseOver: _this._showTooltip.bind(_this, singleNode.name, isTruncated, div), onMouseOut: _this._hideTooltip.bind(_this, div) }, truncatedname)),
|
|
495
|
-
React.createElement("text", { x: height > MIN_HEIGHT_FOR_DOUBLINE_TYPE ? singleNode.x0 : singleNode.x1 - textLengthForNodeWeight - 8, y: singleNode.y0, dy: height > MIN_HEIGHT_FOR_DOUBLINE_TYPE ? '2em' : '1em', dx: height > MIN_HEIGHT_FOR_DOUBLINE_TYPE ? '0.4em' : '0em', textAnchor: _this._isRtl ? 'end' : 'start', fontWeight: "bold", "aria-hidden": "true", fill: !(!state_1.selectedState ||
|
|
496
|
-
(state_1.selectedState && state_1.selectedNodes.has(singleNode.index) && state_1.selectedNode) ||
|
|
497
|
-
(state_1.selectedState && !state_1.selectedNode))
|
|
498
|
-
? DEFAULT_TEXT_COLOR
|
|
499
|
-
: NON_SELECTED_TEXT_COLOR, fontSize: 14 }, singleNode.actualValue)))));
|
|
500
|
-
nodes.push(node);
|
|
685
|
+
React.createElement("text", { id: "".concat(nodeId, "-name"), x: x0, y: y0, dy: '1.2em', dx: '0.4em', textAnchor: textAnchor_1, fontWeight: "regular", "aria-hidden": "true", fill: textColor, fontSize: 10, onMouseOver: _this._showTooltip.bind(_this, name, isTruncated, tooltipDiv), onMouseOut: _this._hideTooltip.bind(_this, tooltipDiv) }, truncatedName)),
|
|
686
|
+
React.createElement("text", { x: tooTall ? x0 : x1 - textLengthForNodeWeight - 8, y: y0, dy: tooTall ? '2em' : '1em', dx: tooTall ? '0.4em' : '0em', textAnchor: textAnchor_1, fontWeight: "bold", "aria-hidden": "true", fill: textColor, fontSize: 14 }, actualValue)))));
|
|
501
687
|
});
|
|
502
|
-
return nodes;
|
|
503
688
|
}
|
|
504
689
|
};
|
|
505
690
|
SankeyChartBase.prototype._onLeave = function (singleNode) {
|
|
@@ -576,44 +761,11 @@ var SankeyChartBase = /** @class */ (function (_super) {
|
|
|
576
761
|
descriptionMessage: 'from ' + singleLink.source.name, // TODO: Does this need to be localized?
|
|
577
762
|
});
|
|
578
763
|
};
|
|
579
|
-
/**
|
|
580
|
-
* This is used to assign node fillcolors and borderColor cyclically when the user doesnt
|
|
581
|
-
* provide color to individual node.
|
|
582
|
-
*/
|
|
583
|
-
SankeyChartBase.prototype._assignNodeColors = function (nodes) {
|
|
584
|
-
var colors;
|
|
585
|
-
var borders;
|
|
586
|
-
var _a = this.props, colorsForNodes = _a.colorsForNodes, borderColorsForNodes = _a.borderColorsForNodes;
|
|
587
|
-
if (colorsForNodes && borderColorsForNodes) {
|
|
588
|
-
colors = colorsForNodes;
|
|
589
|
-
borders = borderColorsForNodes;
|
|
590
|
-
}
|
|
591
|
-
else {
|
|
592
|
-
colors = DEFAULT_NODE_COLORS.map(function (color) { return color.fillColor; });
|
|
593
|
-
borders = DEFAULT_NODE_COLORS.map(function (color) { return color.borderColor; });
|
|
594
|
-
}
|
|
595
|
-
var currentIndex = 0;
|
|
596
|
-
nodes.forEach(function (node) {
|
|
597
|
-
if (!node.color && !node.borderColor) {
|
|
598
|
-
node.color = colors[currentIndex];
|
|
599
|
-
node.borderColor = borders[currentIndex];
|
|
600
|
-
}
|
|
601
|
-
else if (node.color && !node.borderColor) {
|
|
602
|
-
node.borderColor = '#757575';
|
|
603
|
-
}
|
|
604
|
-
else if (node.borderColor && !node.color) {
|
|
605
|
-
node.color = '#F5F5F5';
|
|
606
|
-
}
|
|
607
|
-
currentIndex = (currentIndex + 1) % colors.length;
|
|
608
|
-
});
|
|
609
|
-
};
|
|
610
764
|
SankeyChartBase.prototype._fillStreamColors = function (singleLink, gradientUrl) {
|
|
611
765
|
var state = this.state;
|
|
612
|
-
if (state.selectedState && state.selectedLinks.has(singleLink.index)
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
else if (state.selectedState && state.selectedLinks.has(singleLink.index)) {
|
|
616
|
-
return gradientUrl;
|
|
766
|
+
if (state.selectedState && state.selectedLinks.has(singleLink.index)) {
|
|
767
|
+
var selectedNode = state.selectedNode;
|
|
768
|
+
return selectedNode ? selectedNode.color : gradientUrl;
|
|
617
769
|
}
|
|
618
770
|
};
|
|
619
771
|
SankeyChartBase.prototype._fillStreamBorder = function (singleLink, gradientUrl) {
|
|
@@ -621,13 +773,13 @@ var SankeyChartBase = /** @class */ (function (_super) {
|
|
|
621
773
|
if (!state.selectedState) {
|
|
622
774
|
return NON_SELECTED_NODE_AND_STREAM_COLOR;
|
|
623
775
|
}
|
|
624
|
-
else
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
776
|
+
else {
|
|
777
|
+
if (state.selectedLinks.has(singleLink.index)) {
|
|
778
|
+
var selectedNode = state.selectedNode;
|
|
779
|
+
return selectedNode ? selectedNode.borderColor : gradientUrl;
|
|
780
|
+
}
|
|
781
|
+
return NON_SELECTED_NODE_AND_STREAM_COLOR;
|
|
629
782
|
}
|
|
630
|
-
return NON_SELECTED_NODE_AND_STREAM_COLOR;
|
|
631
783
|
};
|
|
632
784
|
SankeyChartBase.prototype._getOpacityStream = function (singleLink) {
|
|
633
785
|
var state = this.state;
|
|
@@ -635,7 +787,7 @@ var SankeyChartBase = /** @class */ (function (_super) {
|
|
|
635
787
|
if (!state.selectedLinks.has(singleLink.index)) {
|
|
636
788
|
return NON_SELECTED_OPACITY;
|
|
637
789
|
}
|
|
638
|
-
else if (
|
|
790
|
+
else if (!state.selectedNode) {
|
|
639
791
|
return SELECTED_STREAM_OPACITY;
|
|
640
792
|
}
|
|
641
793
|
}
|
|
@@ -665,49 +817,6 @@ var SankeyChartBase = /** @class */ (function (_super) {
|
|
|
665
817
|
}
|
|
666
818
|
});
|
|
667
819
|
};
|
|
668
|
-
/**
|
|
669
|
-
* @param text is the text which we are trying to truncate
|
|
670
|
-
* @param rectangleWidth is the width of the rectangle which will contain the text
|
|
671
|
-
* @param padding is the space we need to leave between the rect lines and other text
|
|
672
|
-
* @param nodeWeight is the text if present needs to be accomodate in the same line as text.
|
|
673
|
-
* @returns the truncated text , if truncated given the above parameters.
|
|
674
|
-
*/
|
|
675
|
-
SankeyChartBase.prototype._truncateText = function (text, rectangleWidth, padding) {
|
|
676
|
-
var textLengthForNodeName = rectangleWidth - padding;
|
|
677
|
-
var elipsisLength = 0;
|
|
678
|
-
var tspan = (0, d3_selection_1.select)('.nodeName')
|
|
679
|
-
.append('text')
|
|
680
|
-
.attr('class', 'tempText')
|
|
681
|
-
.attr('font-size', '10')
|
|
682
|
-
.append('tspan')
|
|
683
|
-
.text(null);
|
|
684
|
-
tspan.text(text);
|
|
685
|
-
if (tspan.node() !== null && tspan.node().getComputedTextLength() <= textLengthForNodeName) {
|
|
686
|
-
return text;
|
|
687
|
-
}
|
|
688
|
-
tspan.text(null);
|
|
689
|
-
tspan.text('...');
|
|
690
|
-
if (tspan.node() !== null) {
|
|
691
|
-
elipsisLength = tspan.node().getComputedTextLength();
|
|
692
|
-
}
|
|
693
|
-
tspan.text(null);
|
|
694
|
-
var line = '';
|
|
695
|
-
for (var i = 0; i < text.length; i++) {
|
|
696
|
-
line += text[i];
|
|
697
|
-
tspan.text(line);
|
|
698
|
-
if (tspan.node() !== null) {
|
|
699
|
-
var w = tspan.node().getComputedTextLength();
|
|
700
|
-
if (w >= textLengthForNodeName - elipsisLength) {
|
|
701
|
-
line = line.slice(0, -1);
|
|
702
|
-
line += '...';
|
|
703
|
-
break;
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
tspan.text(null);
|
|
708
|
-
(0, d3_selection_1.selectAll)('.tempText').remove();
|
|
709
|
-
return line;
|
|
710
|
-
};
|
|
711
820
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
712
821
|
SankeyChartBase.prototype._showTooltip = function (text, checkTrcuncated, div, evt) {
|
|
713
822
|
if (checkTrcuncated) {
|