@fluentui/react-charting 5.19.27 → 5.19.28

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 (29) hide show
  1. package/CHANGELOG.json +16 -1
  2. package/CHANGELOG.md +11 -2
  3. package/dist/react-charting.d.ts +40 -0
  4. package/dist/react-charting.js +1 -1
  5. package/dist/react-charting.min.js +1 -1
  6. package/etc/react-charting.api.md +14 -0
  7. package/lib/components/SankeyChart/SankeyChart.base.d.ts +2 -0
  8. package/lib/components/SankeyChart/SankeyChart.base.js +64 -40
  9. package/lib/components/SankeyChart/SankeyChart.base.js.map +1 -1
  10. package/lib/components/SankeyChart/SankeyChart.types.d.ts +39 -1
  11. package/lib/components/SankeyChart/SankeyChart.types.js.map +1 -1
  12. package/lib/version.js +1 -1
  13. package/lib/version.js.map +1 -1
  14. package/lib-amd/components/SankeyChart/SankeyChart.base.d.ts +2 -0
  15. package/lib-amd/components/SankeyChart/SankeyChart.base.js +63 -39
  16. package/lib-amd/components/SankeyChart/SankeyChart.base.js.map +1 -1
  17. package/lib-amd/components/SankeyChart/SankeyChart.types.d.ts +39 -1
  18. package/lib-amd/components/SankeyChart/SankeyChart.types.js.map +1 -1
  19. package/lib-amd/version.js +1 -1
  20. package/lib-amd/version.js.map +1 -1
  21. package/lib-commonjs/components/SankeyChart/SankeyChart.base.d.ts +2 -0
  22. package/lib-commonjs/components/SankeyChart/SankeyChart.base.js +63 -39
  23. package/lib-commonjs/components/SankeyChart/SankeyChart.base.js.map +1 -1
  24. package/lib-commonjs/components/SankeyChart/SankeyChart.types.d.ts +39 -1
  25. package/lib-commonjs/components/SankeyChart/SankeyChart.types.js.map +1 -1
  26. package/lib-commonjs/version.js +1 -1
  27. package/lib-commonjs/version.js.map +1 -1
  28. package/package.json +1 -1
  29. package/src/components/SankeyChart/SankeyChart.types.ts +43 -1
@@ -1094,6 +1094,13 @@ export interface IRefArrayData {
1094
1094
  refElement?: SVGGElement;
1095
1095
  }
1096
1096
 
1097
+ // @public
1098
+ export interface ISankeyChartAccessibilityProps {
1099
+ emptyAriaLabel?: string;
1100
+ linkAriaLabel?: string;
1101
+ nodeAriaLabel?: string;
1102
+ }
1103
+
1097
1104
  // @public (undocumented)
1098
1105
  export interface ISankeyChartData {
1099
1106
  // (undocumented)
@@ -1104,6 +1111,7 @@ export interface ISankeyChartData {
1104
1111
 
1105
1112
  // @public
1106
1113
  export interface ISankeyChartProps {
1114
+ accessibility?: ISankeyChartAccessibilityProps;
1107
1115
  borderColorsForNodes?: string[];
1108
1116
  className?: string;
1109
1117
  colorsForNodes?: string[];
@@ -1112,11 +1120,17 @@ export interface ISankeyChartProps {
1112
1120
  parentRef?: HTMLElement | null;
1113
1121
  pathColor?: string;
1114
1122
  shouldResize?: number;
1123
+ strings?: ISankeyChartStrings;
1115
1124
  styles?: IStyleFunctionOrObject<ISankeyChartStyleProps, ISankeyChartStyles>;
1116
1125
  theme?: ITheme;
1117
1126
  width?: number;
1118
1127
  }
1119
1128
 
1129
+ // @public
1130
+ export interface ISankeyChartStrings {
1131
+ linkFrom?: string;
1132
+ }
1133
+
1120
1134
  // @public
1121
1135
  export interface ISankeyChartStyleProps {
1122
1136
  // (undocumented)
@@ -52,6 +52,8 @@ export declare class SankeyChartBase extends React.Component<ISankeyChartProps,
52
52
  private readonly _linkAttributes;
53
53
  private readonly _fetchNodes;
54
54
  private readonly _fetchLinks;
55
+ private readonly _strings;
56
+ private readonly _accessibility;
55
57
  constructor(props: ISankeyChartProps);
56
58
  componentDidMount(): void;
57
59
  componentDidUpdate(prevProps: ISankeyChartProps): void;
@@ -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, format, 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';
@@ -358,7 +358,7 @@ function computeElipsisLength(tspan) {
358
358
  var measurement = measureText(tspan, elipsis);
359
359
  return measurement === undefined ? 0 : measurement;
360
360
  }
361
- function computeNodeAttributes(nodes) {
361
+ function computeNodeAttributes(nodes, nodeAriaLabel) {
362
362
  var result = {};
363
363
  var weightSpan = select('.nodeName').append('text').attr('class', 'tempText').append('tspan').text(null);
364
364
  var nameSpan = select('.nodeName')
@@ -371,13 +371,14 @@ function computeNodeAttributes(nodes) {
371
371
  var height = Math.max(singleNode.y1 - singleNode.y0, 0);
372
372
  var padding = 8;
373
373
  var textLengthForNodeWeight = 0;
374
+ var nodeValue = singleNode.actualValue;
374
375
  // If the nodeWeight is in the same line as node description an extra padding
375
376
  // of 6 px is required between node description and node weight.
376
377
  if (height < MIN_HEIGHT_FOR_DOUBLINE_TYPE) {
377
378
  padding = padding + 6;
378
379
  // The following `select` statement injects a `tempText` element into the DOM. This injection
379
380
  // (and subsequent removal) is causing a layout recalculation. This is a performance issue.
380
- var measurement = measureText(weightSpan, singleNode.actualValue);
381
+ var measurement = measureText(weightSpan, nodeValue);
381
382
  if (measurement !== undefined) {
382
383
  textLengthForNodeWeight = measurement;
383
384
  padding = padding + textLengthForNodeWeight;
@@ -391,6 +392,7 @@ function computeNodeAttributes(nodes) {
391
392
  reactId: getId('nodeBar'),
392
393
  gElementId: getId('nodeGElement'),
393
394
  name: truncatedname,
395
+ aria: nodeAriaLabel(singleNode, nodeValue),
394
396
  trimmed: isTruncated,
395
397
  height: height,
396
398
  weightOffset: textLengthForNodeWeight,
@@ -399,7 +401,7 @@ function computeNodeAttributes(nodes) {
399
401
  selectAll('.tempText').remove();
400
402
  return result;
401
403
  }
402
- function computeLinkAttributes(links) {
404
+ function computeLinkAttributes(links, linkFrom, linkAriaLabel) {
403
405
  var result = {};
404
406
  links.forEach(function (link) {
405
407
  var sourceId = idFromNumberOrSNode(link.source);
@@ -408,7 +410,11 @@ function computeLinkAttributes(links) {
408
410
  sourceToTarget = {};
409
411
  result[sourceId] = sourceToTarget;
410
412
  }
411
- sourceToTarget[idFromNumberOrSNode(link.target)] = { reactId: getId('link') };
413
+ sourceToTarget[idFromNumberOrSNode(link.target)] = {
414
+ reactId: getId('link'),
415
+ from: linkFrom(link.source),
416
+ aria: linkAriaLabel(link),
417
+ };
412
418
  });
413
419
  return result;
414
420
  }
@@ -433,6 +439,16 @@ function nodeTextColor(state, singleNode) {
433
439
  ? DEFAULT_TEXT_COLOR
434
440
  : NON_SELECTED_TEXT_COLOR;
435
441
  }
442
+ function linkCalloutAttributes(singleLink, from) {
443
+ return {
444
+ selectedLink: singleLink,
445
+ isCalloutVisible: true,
446
+ color: singleLink.source.color,
447
+ xCalloutValue: singleLink.target.name,
448
+ yCalloutValue: singleLink.unnormalizedValue.toString(),
449
+ descriptionMessage: from,
450
+ };
451
+ }
436
452
  // NOTE: To start employing React.useMemo properly, we need to convert this code from a React.Component
437
453
  // to a function component. This will require a significant refactor of the code in this file.
438
454
  // https://stackoverflow.com/questions/60223362/fast-way-to-convert-react-class-component-to-functional-component
@@ -511,11 +527,36 @@ var SankeyChartBase = /** @class */ (function (_super) {
511
527
  _this._fetchTooltip = memoizeFunction(function (classNames) {
512
528
  return _this._labelTooltipDiv(classNames);
513
529
  });
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); });
530
+ // Prepare the localization utilities
531
+ _this._strings = memoizeFunction(function (strings) {
532
+ var fromString = (strings === null || strings === void 0 ? void 0 : strings.linkFrom) || 'From {0}';
533
+ // NOTE: The `node` parameter is the sankey-generated node on the link, and not the original `node` supplied
534
+ // by the caller.
535
+ return {
536
+ linkFrom: function (node) { return format(fromString, node.name); },
537
+ };
538
+ })(props.strings);
539
+ _this._accessibility = memoizeFunction(function (accessibility) {
540
+ var linkString = (accessibility === null || accessibility === void 0 ? void 0 : accessibility.linkAriaLabel) || 'link from {0} to {1} with weight {2}';
541
+ var nodeString = (accessibility === null || accessibility === void 0 ? void 0 : accessibility.nodeAriaLabel) || 'node {0} with weight {1}';
542
+ return {
543
+ emptyAriaLabel: (accessibility === null || accessibility === void 0 ? void 0 : accessibility.emptyAriaLabel) || 'Graph has no data to display',
544
+ linkAriaLabel: function (link) {
545
+ return format(linkString, link.source.name, link.target.name, link.unnormalizedValue);
546
+ },
547
+ nodeAriaLabel: function (node, weight) { return format(nodeString, node.name, weight); },
548
+ };
549
+ })(props.accessibility);
550
+ // NOTE: Memoizing the `_createNodes` and `_createLinks` methods would break the hoverability of the chart
551
+ // because the nodes are currently created differently based on the layout information. Hence why we do not
552
+ // memoize these methods (but have stubs for memoizing as the `_fetchNodes` and `_fetchLinks` methods).
553
+ _this._nodeAttributes = memoizeFunction(function (nodes, nodeAriaLabel) {
554
+ return computeNodeAttributes(nodes, nodeAriaLabel);
555
+ });
517
556
  _this._fetchNodes = function (classNames, nodes, nodeAttributes, tooltipDiv) { return _this._createNodes(classNames, nodes, nodeAttributes, tooltipDiv); };
518
- _this._linkAttributes = memoizeFunction(function (links) { return computeLinkAttributes(links); });
557
+ _this._linkAttributes = memoizeFunction(function (links, linkFrom, linkAriaLabel) {
558
+ return computeLinkAttributes(links, linkFrom, linkAriaLabel);
559
+ });
519
560
  _this._fetchLinks = function (links, linkAttributes) {
520
561
  return _this._createLinks(links, linkAttributes);
521
562
  };
@@ -551,10 +592,10 @@ var SankeyChartBase = /** @class */ (function (_super) {
551
592
  // It might be better to perform this `fetch` within the `_showTooltip` and `_hideTooltip` methods.
552
593
  var tooltipDiv = this._fetchTooltip(classNames);
553
594
  // Pre-compute some important attributes about nodes, specifically text
554
- var nodeAttributes = this._nodeAttributes(nodes);
595
+ var nodeAttributes = this._nodeAttributes(nodes, this._accessibility.nodeAriaLabel);
555
596
  // Build the nodes and links as rendered in the UX.
556
597
  var nodeData = this._fetchNodes(classNames, nodes, nodeAttributes, tooltipDiv);
557
- var linkAttributes = this._linkAttributes(links);
598
+ var linkAttributes = this._linkAttributes(links, this._strings.linkFrom, this._accessibility.linkAriaLabel);
558
599
  var linkData = this._fetchLinks(links, linkAttributes);
559
600
  var calloutProps = {
560
601
  isCalloutVisible: state.isCalloutVisible,
@@ -579,7 +620,7 @@ var SankeyChartBase = /** @class */ (function (_super) {
579
620
  calloutProps.isCalloutVisible && (React.createElement(Callout, __assign({}, calloutProps),
580
621
  React.createElement(ChartHoverCard, { XValue: calloutProps.XValue, YValue: calloutProps.YValue, color: calloutProps.color, descriptionMessage: calloutProps.descriptionMessage ? calloutProps.descriptionMessage : '' })))))));
581
622
  }
582
- return (React.createElement("div", { id: this._emptyChartId, role: 'alert', style: { opacity: '0' }, "aria-label": 'Graph has no data to display' }));
623
+ return (React.createElement("div", { id: this._emptyChartId, role: 'alert', style: { opacity: '0' }, "aria-label": this._accessibility.emptyAriaLabel }));
583
624
  };
584
625
  SankeyChartBase.prototype._normalizeSankeyData = function (data, containerWidth, containerHeight, colorsForNodes, borderColorsForNodes) {
585
626
  var _a = preRenderLayout(this._margins, containerWidth, containerHeight, this._isRtl), sankey = _a.sankey, height = _a.height, width = _a.width;
@@ -640,7 +681,7 @@ var SankeyChartBase = /** @class */ (function (_super) {
640
681
  var onMouseOut = function () {
641
682
  _this._onStreamLeave(singleLink);
642
683
  };
643
- var reactId = linkValue(linkAttributes, singleLink).reactId;
684
+ var _a = linkValue(linkAttributes, singleLink), reactId = _a.reactId, from = _a.from, aria = _a.aria;
644
685
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
645
686
  var dataPoints = linkToDataPoints(singleLink);
646
687
  var key = "".concat(linkId_1, "-").concat(index);
@@ -654,7 +695,7 @@ var SankeyChartBase = /** @class */ (function (_super) {
654
695
  React.createElement("linearGradient", { id: gradientId, x1: "0%", y1: "0%", x2: "100%", y2: "0%" },
655
696
  React.createElement("stop", { offset: "0", stopColor: source.color }),
656
697
  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" })));
698
+ 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: function (event) { return _this._onStreamHover(event, singleLink, from); }, onMouseOut: onMouseOut, onFocus: function (event) { return _this._onFocusLink(event, singleLink, from); }, onBlur: _this._onBlur, fillOpacity: _this._getOpacityStream(singleLink), "data-is-focusable": true, "aria-label": aria, role: "img" })));
658
699
  });
659
700
  }
660
701
  return [];
@@ -668,12 +709,12 @@ var SankeyChartBase = /** @class */ (function (_super) {
668
709
  var onMouseOut = function () {
669
710
  _this._onLeave(singleNode);
670
711
  };
671
- var _a = nodeAttributes[singleNode.nodeId], nodeId = _a.reactId, gElementId = _a.gElementId, height = _a.height, isTruncated = _a.trimmed, truncatedName = _a.name, textLengthForNodeWeight = _a.weightOffset;
712
+ var _a = nodeAttributes[singleNode.nodeId], nodeId = _a.reactId, gElementId = _a.gElementId, height = _a.height, isTruncated = _a.trimmed, truncatedName = _a.name, textLengthForNodeWeight = _a.weightOffset, aria = _a.aria;
672
713
  var tooTall = height > MIN_HEIGHT_FOR_DOUBLINE_TYPE;
673
714
  var name = singleNode.name, actualValue = singleNode.actualValue, x0 = singleNode.x0, x1 = singleNode.x1, y0 = singleNode.y0;
674
715
  var textColor = nodeTextColor(state_1, singleNode);
675
716
  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" }),
717
+ 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": aria, role: "img" }),
677
718
  height > MIN_HEIGHT_FOR_TYPE && (React.createElement("g", { className: classNames.nodeTextContainer },
678
719
  React.createElement("g", { className: "nodeName" },
679
720
  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)),
@@ -712,23 +753,12 @@ var SankeyChartBase = /** @class */ (function (_super) {
712
753
  });
713
754
  }
714
755
  };
715
- SankeyChartBase.prototype._onStreamHover = function (singleLink, mouseEvent) {
756
+ SankeyChartBase.prototype._onStreamHover = function (mouseEvent, singleLink, from) {
716
757
  mouseEvent.persist();
717
758
  this._onCloseCallout();
718
759
  if (!this.state.selectedState) {
719
760
  var _a = getSelectedLinksforStreamHover(singleLink), selectedLinks = _a.selectedLinks, selectedNodes = _a.selectedNodes;
720
- this.setState({
721
- selectedState: true,
722
- selectedNodes: new Set(Array.from(selectedNodes).map(function (node) { return node.index; })),
723
- selectedLinks: new Set(Array.from(selectedLinks).map(function (link) { return link.index; })),
724
- refSelected: mouseEvent,
725
- selectedLink: singleLink,
726
- isCalloutVisible: true,
727
- color: singleLink.source.color,
728
- xCalloutValue: singleLink.target.name,
729
- yCalloutValue: singleLink.unnormalizedValue.toString(),
730
- descriptionMessage: 'from ' + singleLink.source.name, // TODO: Does this need to be localized?
731
- });
761
+ this.setState(__assign({ selectedState: true, selectedNodes: new Set(Array.from(selectedNodes).map(function (node) { return node.index; })), selectedLinks: new Set(Array.from(selectedLinks).map(function (link) { return link.index; })), refSelected: mouseEvent }, linkCalloutAttributes(singleLink, from)));
732
762
  }
733
763
  };
734
764
  SankeyChartBase.prototype._onStreamLeave = function (singleLink) {
@@ -741,19 +771,13 @@ var SankeyChartBase = /** @class */ (function (_super) {
741
771
  });
742
772
  }
743
773
  };
744
- SankeyChartBase.prototype._onFocusLink = function (singleLink, element) {
774
+ SankeyChartBase.prototype._onFocusLink = function (element, singleLink, from) {
775
+ // There is a big difference in how "Tab" and the "Arrow keys" are handled in this diagram.
776
+ // In particular, I would expect the "Down" key to be like "Tab", but it jumps a little wildly. I'm not sure
777
+ // if this behavior is an accessiblity violation, but it we might want to investigate it.
745
778
  element.persist();
746
779
  this._onCloseCallout();
747
- this.setState({
748
- refSelected: element.currentTarget,
749
- selectedLink: singleLink,
750
- isCalloutVisible: true,
751
- color: singleLink.source.color,
752
- xCalloutValue: singleLink.target.name,
753
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
754
- yCalloutValue: singleLink.source.actualValue,
755
- descriptionMessage: 'from ' + singleLink.source.name, // TODO: Does this need to be localized?
756
- });
780
+ this.setState(__assign({ refSelected: element.currentTarget }, linkCalloutAttributes(singleLink, from)));
757
781
  };
758
782
  SankeyChartBase.prototype._fillStreamColors = function (singleLink, gradientUrl) {
759
783
  var state = this.state;