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