@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.
@@ -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 if (state.selectedState && state.selectedNodes.has(singleNode.index) && state.selectedNode) {
288
- return state.selectedNode.color;
289
- }
290
- else if (state.selectedState && !state.selectedNode) {
291
- return singleNode.color;
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 if (state.selectedState && state.selectedNodes.has(singleNode.index) && state.selectedNode) {
300
- return state.selectedNode.borderColor;
301
- }
302
- else if (state.selectedState && state.selectedNodes.has(singleNode.index)) {
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
- _this._normalizeData = (0, Utilities_1.memoizeFunction)(function (data, containerWidth, containerHeight) {
320
- return _this._normalizeSankeyData(data, containerWidth, containerHeight);
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
- _this._emptyChartId = (0, Utilities_1.getId)('_SankeyChart_empty');
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 classNames = getClassNames(styles, {
342
- theme: theme,
343
- width: state.containerWidth,
344
- height: state.containerHeight,
345
- pathColor: pathColor,
346
- className: className,
347
- });
348
- var _b = this._normalizeData(this.props.data.SankeyChartData, state.containerWidth, state.containerHeight), sankeyChartData = _b.data, width = _b.width, height = _b.height;
349
- // NEXT: Move the following into _normalizeSankeyData to see if we can cache computation of
350
- // colors, nodes, and links.
351
- var dataNodes = sankeyChartData.nodes;
352
- this._assignNodeColors(dataNodes); // This can probably safely move.
353
- var nodeData = this._createNodes(classNames, dataNodes);
354
- var linkData = this._createLinks(sankeyChartData.links);
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: (0, Utilities_1.getId)('sankeyChart') },
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
- data: transformed,
614
+ nodes: transformed.nodes,
615
+ links: transformed.links,
405
616
  };
406
617
  };
407
- SankeyChartBase.prototype._createLinks = function (dataLinks) {
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.forEach(function (singleLink, index) {
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 data = function (d) {
418
- return [
419
- { x: d.source.x1, y0: d.y0 + d.width / 2, y1: d.y0 - d.width / 2 },
420
- { x: d.target.x0, y0: d.y1 + d.width / 2, y1: d.y1 - d.width / 2 },
421
- ];
422
- };
423
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
424
- var dataPoints = data(singleLink);
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: "gradient-".concat(linkId_1, "-").concat(index), x1: "0%", y1: "0%", x2: "100%", y2: "0%" },
437
- React.createElement("stop", { offset: "0", stopColor: singleLink.source.color }),
438
- React.createElement("stop", { offset: "100%", stopColor: singleLink.target.color }))),
439
- 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" })));
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 links;
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
- dataNodes.forEach(function (singleNode, index) {
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 height = singleNode.y1 - singleNode.y0 > 0 ? singleNode.y1 - singleNode.y0 : 0;
455
- var padding = 8;
456
- var textLengthForNodeWeight = 0;
457
- // If the nodeWeight is in the same line as node description an extra padding
458
- // of 6 px is required between node description and node weight.
459
- if (height < MIN_HEIGHT_FOR_DOUBLINE_TYPE) {
460
- padding = padding + 6;
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: 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 ||
482
- (state_1.selectedState && state_1.selectedNodes.has(singleNode.index) && state_1.selectedNode) ||
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) && state.selectedNode) {
604
- return state.selectedNode.color;
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 if (state.selectedState && state.selectedLinks.has(singleLink.index) && state.selectedNode) {
616
- return state.selectedNode.borderColor;
617
- }
618
- else if (state.selectedState && state.selectedLinks.has(singleLink.index)) {
619
- return gradientUrl;
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 (state.selectedLinks.has(singleLink.index) && !state.selectedNode) {
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) {