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