@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.
Files changed (65) hide show
  1. package/CHANGELOG.json +43 -1
  2. package/CHANGELOG.md +22 -2
  3. package/bundle-size/AreaChart.fixture.js +0 -2
  4. package/bundle-size/ChartHoverCard.fixture.js +7 -0
  5. package/bundle-size/DonutChart.fixture.js +7 -0
  6. package/bundle-size/GaugeChart.fixture.js +7 -0
  7. package/bundle-size/GroupedVerticalBarChart.fixture.js +7 -0
  8. package/bundle-size/HeatMapChart.fixture.js +7 -0
  9. package/bundle-size/HorizontalBarChart.fixture.js +7 -0
  10. package/bundle-size/HorizontalBarChartWithAxis.fixture.js +7 -0
  11. package/bundle-size/Legends.fixture.js +7 -0
  12. package/bundle-size/LineChart.fixture.js +7 -0
  13. package/bundle-size/MultiStackedBarChart.fixture.js +7 -0
  14. package/bundle-size/PieChart.fixture.js +7 -0
  15. package/bundle-size/SankeyChart.fixture.js +7 -0
  16. package/bundle-size/Sparkline.fixture.js +7 -0
  17. package/bundle-size/StackedBarChart.fixture.js +7 -0
  18. package/bundle-size/TreeChart.fixture.js +7 -0
  19. package/bundle-size/VerticalBarChart.fixture.js +0 -2
  20. package/bundle-size/VerticalStackedBarChart.fixture.js +7 -0
  21. package/dist/react-charting.d.ts +15 -0
  22. package/dist/react-charting.js +1 -1
  23. package/dist/react-charting.min.js +1 -1
  24. package/lib/components/SankeyChart/SankeyChart.base.d.ts +16 -19
  25. package/lib/components/SankeyChart/SankeyChart.base.js +295 -186
  26. package/lib/components/SankeyChart/SankeyChart.base.js.map +1 -1
  27. package/lib/components/VerticalBarChart/VerticalBarChart.base.js +2 -2
  28. package/lib/components/VerticalBarChart/VerticalBarChart.base.js.map +1 -1
  29. package/lib/components/VerticalStackedBarChart/VerticalStackedBarChart.base.js +2 -2
  30. package/lib/components/VerticalStackedBarChart/VerticalStackedBarChart.base.js.map +1 -1
  31. package/lib/types/IDataPoint.d.ts +16 -1
  32. package/lib/types/IDataPoint.js.map +1 -1
  33. package/lib/utilities/utilities.js +2 -2
  34. package/lib/utilities/utilities.js.map +1 -1
  35. package/lib/version.js +1 -1
  36. package/lib/version.js.map +1 -1
  37. package/lib-amd/components/SankeyChart/SankeyChart.base.d.ts +16 -19
  38. package/lib-amd/components/SankeyChart/SankeyChart.base.js +294 -185
  39. package/lib-amd/components/SankeyChart/SankeyChart.base.js.map +1 -1
  40. package/lib-amd/components/VerticalBarChart/VerticalBarChart.base.js +1 -1
  41. package/lib-amd/components/VerticalBarChart/VerticalBarChart.base.js.map +1 -1
  42. package/lib-amd/components/VerticalStackedBarChart/VerticalStackedBarChart.base.js +1 -1
  43. package/lib-amd/components/VerticalStackedBarChart/VerticalStackedBarChart.base.js.map +1 -1
  44. package/lib-amd/types/IDataPoint.d.ts +16 -1
  45. package/lib-amd/types/IDataPoint.js.map +1 -1
  46. package/lib-amd/utilities/utilities.js +1 -1
  47. package/lib-amd/utilities/utilities.js.map +1 -1
  48. package/lib-amd/version.js +1 -1
  49. package/lib-amd/version.js.map +1 -1
  50. package/lib-commonjs/components/SankeyChart/SankeyChart.base.d.ts +16 -19
  51. package/lib-commonjs/components/SankeyChart/SankeyChart.base.js +294 -185
  52. package/lib-commonjs/components/SankeyChart/SankeyChart.base.js.map +1 -1
  53. package/lib-commonjs/components/VerticalBarChart/VerticalBarChart.base.js +1 -1
  54. package/lib-commonjs/components/VerticalBarChart/VerticalBarChart.base.js.map +1 -1
  55. package/lib-commonjs/components/VerticalStackedBarChart/VerticalStackedBarChart.base.js +1 -1
  56. package/lib-commonjs/components/VerticalStackedBarChart/VerticalStackedBarChart.base.js.map +1 -1
  57. package/lib-commonjs/types/IDataPoint.d.ts +16 -1
  58. package/lib-commonjs/types/IDataPoint.js.map +1 -1
  59. package/lib-commonjs/utilities/utilities.js +1 -1
  60. package/lib-commonjs/utilities/utilities.js.map +1 -1
  61. package/lib-commonjs/version.js +1 -1
  62. package/lib-commonjs/version.js.map +1 -1
  63. package/package.json +6 -5
  64. package/scripts/constants.js +7 -0
  65. package/scripts/run-tests-in-timezone.js +11 -0
@@ -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 if (state.selectedState && state.selectedNodes.has(singleNode.index) && state.selectedNode) {
291
- return state.selectedNode.color;
292
- }
293
- else if (state.selectedState && !state.selectedNode) {
294
- return singleNode.color;
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 if (state.selectedState && state.selectedNodes.has(singleNode.index) && state.selectedNode) {
303
- return state.selectedNode.borderColor;
304
- }
305
- else if (state.selectedState && state.selectedNodes.has(singleNode.index)) {
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
- _this._normalizeData = memoizeFunction(function (data, containerWidth, containerHeight) {
323
- return _this._normalizeSankeyData(data, containerWidth, containerHeight);
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
- _this._emptyChartId = getId('_SankeyChart_empty');
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 classNames = getClassNames(styles, {
345
- theme: theme,
346
- width: state.containerWidth,
347
- height: state.containerHeight,
348
- pathColor: pathColor,
349
- className: className,
350
- });
351
- var _b = this._normalizeData(this.props.data.SankeyChartData, state.containerWidth, state.containerHeight), sankeyChartData = _b.data, width = _b.width, height = _b.height;
352
- // NEXT: Move the following into _normalizeSankeyData to see if we can cache computation of
353
- // colors, nodes, and links.
354
- var dataNodes = sankeyChartData.nodes;
355
- this._assignNodeColors(dataNodes); // This can probably safely move.
356
- var nodeData = this._createNodes(classNames, dataNodes);
357
- var linkData = this._createLinks(sankeyChartData.links);
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: getId('sankeyChart') },
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
- data: transformed,
617
+ nodes: transformed.nodes,
618
+ links: transformed.links,
408
619
  };
409
620
  };
410
- SankeyChartBase.prototype._createLinks = function (dataLinks) {
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.forEach(function (singleLink, index) {
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 data = function (d) {
421
- return [
422
- { x: d.source.x1, y0: d.y0 + d.width / 2, y1: d.y0 - d.width / 2 },
423
- { x: d.target.x0, y0: d.y1 + d.width / 2, y1: d.y1 - d.width / 2 },
424
- ];
425
- };
426
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
427
- var dataPoints = data(singleLink);
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: "gradient-".concat(linkId_1, "-").concat(index), x1: "0%", y1: "0%", x2: "100%", y2: "0%" },
440
- React.createElement("stop", { offset: "0", stopColor: singleLink.source.color }),
441
- React.createElement("stop", { offset: "100%", stopColor: singleLink.target.color }))),
442
- React.createElement("path", { d: linkArea(dataPoints), id: getId('link'), 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(singleLink.source.name, " to ").concat(singleLink.target.name, " with weight ").concat(singleLink.unnormalizedValue), role: "img" })));
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 links;
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
- dataNodes.forEach(function (singleNode, index) {
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 height = singleNode.y1 - singleNode.y0 > 0 ? singleNode.y1 - singleNode.y0 : 0;
458
- var padding = 8;
459
- var textLengthForNodeWeight = 0;
460
- // If the nodeWeight is in the same line as node description an extra padding
461
- // of 6 px is required between node description and node weight.
462
- if (height < MIN_HEIGHT_FOR_DOUBLINE_TYPE) {
463
- padding = padding + 6;
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: singleNode.x0, y: singleNode.y0, dy: '1.2em', dx: '0.4em', textAnchor: _this._isRtl ? 'end' : 'start', fontWeight: "regular", "aria-hidden": "true", fill: !(!state_1.selectedState ||
485
- (state_1.selectedState && state_1.selectedNodes.has(singleNode.index) && state_1.selectedNode) ||
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) && state.selectedNode) {
607
- return state.selectedNode.color;
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 if (state.selectedState && state.selectedLinks.has(singleLink.index) && state.selectedNode) {
619
- return state.selectedNode.borderColor;
620
- }
621
- else if (state.selectedState && state.selectedLinks.has(singleLink.index)) {
622
- return gradientUrl;
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 (state.selectedLinks.has(singleLink.index) && !state.selectedNode) {
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) {