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