@fluentui/react-charting 5.19.23 → 5.19.24

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.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SankeyChartBase = void 0;
3
+ exports.SankeyChartBase = exports.preRenderLayout = exports.adjustPadding = exports.groupNodesByColumn = void 0;
4
4
  var tslib_1 = require("tslib");
5
5
  var react_focus_1 = require("@fluentui/react-focus");
6
6
  var Callout_1 = require("@fluentui/react/lib/Callout");
@@ -10,7 +10,7 @@ var d3_sankey_1 = require("d3-sankey");
10
10
  var d3_selection_1 = require("d3-selection");
11
11
  var d3_shape_1 = require("d3-shape");
12
12
  var React = require("react");
13
- var index_1 = require("../../index");
13
+ var ChartHoverCard_1 = require("../../utilities/ChartHoverCard/ChartHoverCard");
14
14
  var getClassNames = (0, Utilities_1.classNamesFunction)();
15
15
  var PADDING_PERCENTAGE = 0.3;
16
16
  var NON_SELECTED_NODE_AND_STREAM_COLOR = '#757575';
@@ -117,8 +117,8 @@ function getSelectedLinksforStreamHover(singleLink) {
117
117
  /**
118
118
  * This is used to group nodes by column index.
119
119
  */
120
- function populateNodeInColumns(graph, sankey) {
121
- sankey(graph);
120
+ // This is exported for unit tests.
121
+ function groupNodesByColumn(graph) {
122
122
  var nodesInColumn = {};
123
123
  graph.nodes.forEach(function (node) {
124
124
  var columnId = node.layer;
@@ -131,21 +131,25 @@ function populateNodeInColumns(graph, sankey) {
131
131
  });
132
132
  return nodesInColumn;
133
133
  }
134
+ exports.groupNodesByColumn = groupNodesByColumn;
134
135
  /**
135
136
  * This is used to normalize the nodes value whose value is less than 1% of the total column value.
136
137
  */
137
- function adjustOnePercentHeightNodes(nodesInColumn) {
138
+ function adjustOnePercentHeightNodes(nodesInColumn, computedNodes, originalLinks) {
138
139
  var totalColumnValue = Object.values(nodesInColumn).map(function (column) {
139
140
  return (0, d3_array_1.sum)(column, function (node) { return node.value; });
140
141
  });
141
142
  totalColumnValue.forEach(function (columnValue, index) {
142
143
  var totalPercentage = 0;
143
- nodesInColumn[index].forEach(function (node) {
144
- var nodePercentage = (node.value / columnValue) * 100;
145
- node.actualValue = node.value;
144
+ var onePercent = 0.01 * columnValue;
145
+ var columnNodes = nodesInColumn[index];
146
+ columnNodes.forEach(function (node) {
147
+ var value = computedNodes[node.nodeId];
148
+ var nodePercentage = (value / columnValue) * 100;
149
+ node.actualValue = value;
146
150
  //if the value is less than 1% then we are making it as 1% of total .
147
151
  if (nodePercentage < 1) {
148
- node.value = 0.01 * columnValue;
152
+ node.value = onePercent;
149
153
  totalPercentage = totalPercentage + 1;
150
154
  }
151
155
  else {
@@ -155,41 +159,120 @@ function adjustOnePercentHeightNodes(nodesInColumn) {
155
159
  //since we have adjusted the value to be 1% but we need to keep the sum of the percentage value under 100.
156
160
  var scalingRatio = totalPercentage !== 0 ? totalPercentage / 100 : 1;
157
161
  if (scalingRatio > 1) {
158
- nodesInColumn[index].forEach(function (node) {
159
- node.value = node.value / scalingRatio;
160
- changeColumnValue(node, node.actualValue, node.value);
162
+ // Loop through each node in that column and scale that node--and its incoming and outgoing links--by the
163
+ // scaling ratio. We need the sankey diagram to re-layout the nodes and links after we do this.
164
+ columnNodes.forEach(function (node) {
165
+ var normalized = (node.value = node.value / scalingRatio);
166
+ // Which Original Value? and Which Normalized Value is needed, here? The Node? The Link? Both?
167
+ changeColumnValue(node, computedNodes[node.nodeId], normalized, originalLinks);
161
168
  });
162
169
  }
163
170
  });
164
171
  }
165
172
  /**
166
- * This is used for normalizing each links value for reflecting the normalized node value.
173
+ * This is used for normalizing each link's value to reflect the normalized node value.
167
174
  */
168
- function changeColumnValue(node, originalValue, normalizedValue) {
169
- node.sourceLinks.forEach(function (link) {
170
- link.unnormalizedValue = link.value;
171
- var linkRatio = link.value / originalValue;
172
- link.value = normalizedValue * linkRatio;
173
- });
174
- node.targetLinks.forEach(function (link) {
175
- link.unnormalizedValue = link.value;
176
- var linkRatio = link.value / originalValue;
177
- link.value = normalizedValue * linkRatio;
178
- });
175
+ function changeColumnValue(node, originalNodeValue, normalizedNodeValue, linkValues) {
176
+ // For each link in the source and target, compute the proportion that this link contributes to the total
177
+ // then adjust the link's value to reflect its proportion of the normalized node value.
178
+ var updateLinkValue = function (link) {
179
+ var value = linkValue(linkValues, link);
180
+ link.unnormalizedValue = value;
181
+ var linkRatio = value / originalNodeValue;
182
+ link.value = Math.max(normalizedNodeValue * linkRatio, link.value);
183
+ };
184
+ node.sourceLinks.forEach(updateLinkValue);
185
+ node.targetLinks.forEach(updateLinkValue);
179
186
  }
180
187
  /**
181
188
  * This is used for calculating the node non normalized value based on link non normalized value.
189
+ * The links have the original weights. Computed nodes have the total weight of all incoming and outgoing links.
182
190
  */
183
- function populateNodeActualValue(data) {
191
+ function populateNodeActualValue(data, computedNodes, originalLinks) {
184
192
  data.links.forEach(function (link) {
185
193
  if (!link.unnormalizedValue) {
186
- link.unnormalizedValue = link.value;
194
+ link.unnormalizedValue = linkValue(originalLinks, link);
187
195
  }
188
196
  });
189
197
  data.nodes.forEach(function (node) {
190
- node.actualValue = Math.max((0, d3_array_1.sum)(node.sourceLinks, function (link) { return link.unnormalizedValue; }), (0, d3_array_1.sum)(node.targetLinks, function (link) { return link.unnormalizedValue; }));
198
+ node.actualValue = computedNodes[node.nodeId];
199
+ });
200
+ }
201
+ /**
202
+ * This is used to introduce dynamic padding for cases where the number of nodes in a column is huge
203
+ * so that we maintain a node to space ratio for such columns as if we fail to do so the
204
+ * chart is devoid of nodes and only shows links.
205
+ */
206
+ // This is exported for unit tests
207
+ function adjustPadding(sankey, height, nodesInColumn) {
208
+ var padding = sankey.nodePadding();
209
+ var minPadding = PADDING_PERCENTAGE * height;
210
+ Object.values(nodesInColumn).forEach(function (column) {
211
+ var totalPaddingInColumn = height - (0, d3_array_1.sum)(column, function (node) { return node.y1 - node.y0; });
212
+ if (minPadding < totalPaddingInColumn) {
213
+ // Here we are calculating the min of default and calculated padding, we will not increase the padding
214
+ // in any scenario.
215
+ padding = Math.min(padding, minPadding / (column.length - 1));
216
+ }
191
217
  });
218
+ sankey.nodePadding(padding);
192
219
  }
220
+ exports.adjustPadding = adjustPadding;
221
+ function idFromNumberOrSNode(node) {
222
+ if (typeof node === 'number') {
223
+ return node;
224
+ }
225
+ return node.nodeId;
226
+ }
227
+ /**
228
+ * Duplicates the supplied chart data so that we do not alter the original.
229
+ * @param data The data to duplicate.
230
+ * @returns The duplicated data.
231
+ */
232
+ function duplicateData(data) {
233
+ return {
234
+ nodes: data.nodes.map(function (node) { return (tslib_1.__assign({}, node)); }),
235
+ links: data.links.map(function (link) { return (tslib_1.__assign({}, link)); }),
236
+ };
237
+ }
238
+ function valuesOfNodes(nodes) {
239
+ var result = {};
240
+ nodes.forEach(function (node) {
241
+ result[node.nodeId] = node.value;
242
+ });
243
+ return result;
244
+ }
245
+ function valuesOfLinks(links) {
246
+ var result = {};
247
+ links.forEach(function (link) {
248
+ var sourceId = idFromNumberOrSNode(link.source);
249
+ var sourceToTarget = result[sourceId];
250
+ if (!sourceToTarget) {
251
+ sourceToTarget = {};
252
+ result[sourceId] = sourceToTarget;
253
+ }
254
+ sourceToTarget[idFromNumberOrSNode(link.target)] = link.value;
255
+ });
256
+ return result;
257
+ }
258
+ function linkValue(originalLinks, link) {
259
+ return originalLinks[idFromNumberOrSNode(link.source)][idFromNumberOrSNode(link.target)];
260
+ }
261
+ // This is exported for unit tests.
262
+ function preRenderLayout(margins, containerWidth, containerHeight, isRtl) {
263
+ var left = margins.left, right = margins.right, top = margins.top, bottom = margins.bottom;
264
+ var width = containerWidth - right;
265
+ var height = containerHeight - bottom > 0 ? containerHeight - bottom : 0;
266
+ var sankey = (0, d3_sankey_1.sankey)()
267
+ .nodeWidth(124)
268
+ .extent([
269
+ [left, top],
270
+ [width - 1, height - 6],
271
+ ])
272
+ .nodeAlign(isRtl ? d3_sankey_1.sankeyRight : d3_sankey_1.sankeyJustify);
273
+ return { sankey: sankey, height: height, width: width };
274
+ }
275
+ exports.preRenderLayout = preRenderLayout;
193
276
  var SankeyChartBase = /** @class */ (function (_super) {
194
277
  tslib_1.__extends(SankeyChartBase, _super);
195
278
  function SankeyChartBase(props) {
@@ -242,8 +325,9 @@ var SankeyChartBase = /** @class */ (function (_super) {
242
325
  _this._calloutId = (0, Utilities_1.getId)('callout');
243
326
  _this._linkId = (0, Utilities_1.getId)('link');
244
327
  _this._margins = { top: 36, right: 48, bottom: 32, left: 48 };
245
- _this._preRenderLayout();
246
- _this._normalizeData = (0, Utilities_1.memoizeFunction)(function (data) { return _this._normalizeSankeyData(data); });
328
+ _this._normalizeData = (0, Utilities_1.memoizeFunction)(function (data, containerWidth, containerHeight) {
329
+ return _this._normalizeSankeyData(data, containerWidth, containerHeight);
330
+ });
247
331
  _this._emptyChartId = (0, Utilities_1.getId)('_SankeyChart_empty');
248
332
  return _this;
249
333
  }
@@ -270,18 +354,13 @@ var SankeyChartBase = /** @class */ (function (_super) {
270
354
  pathColor: pathColor,
271
355
  className: className,
272
356
  });
273
- // We are using the this._margins.left and this._margins.top in sankey extent while constructing the layout
274
- var _b = this._preRenderLayout(), height = _b.height, width = _b.width;
275
- var sankeyChartData = this.props.data.SankeyChartData;
276
- this._normalizeData(sankeyChartData);
277
- var nodePadding = 8;
278
- nodePadding = this._adjustPadding(this._sankey, height - 6, this._nodesInColumn);
279
- this._sankey.nodePadding(nodePadding);
280
- this._sankey(sankeyChartData);
281
- populateNodeActualValue(sankeyChartData);
282
- this._assignNodeColors();
283
- var nodeData = this._createNodes(classNames);
284
- var linkData = this._createLinks();
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);
285
364
  var calloutProps = {
286
365
  isCalloutVisible: state.isCalloutVisible,
287
366
  directionalHint: Callout_1.DirectionalHint.topAutoEdge,
@@ -303,54 +382,43 @@ var SankeyChartBase = /** @class */ (function (_super) {
303
382
  React.createElement("g", { className: classNames.links, strokeOpacity: 1 }, linkData),
304
383
  React.createElement("g", { className: classNames.nodes }, nodeData),
305
384
  calloutProps.isCalloutVisible && (React.createElement(Callout_1.Callout, tslib_1.__assign({}, calloutProps),
306
- React.createElement(index_1.ChartHoverCard, { XValue: calloutProps.XValue, YValue: calloutProps.YValue, color: calloutProps.color, descriptionMessage: calloutProps.descriptionMessage ? calloutProps.descriptionMessage : '' })))))));
385
+ React.createElement(ChartHoverCard_1.ChartHoverCard, { XValue: calloutProps.XValue, YValue: calloutProps.YValue, color: calloutProps.color, descriptionMessage: calloutProps.descriptionMessage ? calloutProps.descriptionMessage : '' })))))));
307
386
  }
308
387
  return (React.createElement("div", { id: this._emptyChartId, role: 'alert', style: { opacity: '0' }, "aria-label": 'Graph has no data to display' }));
309
388
  };
310
- SankeyChartBase.prototype._preRenderLayout = function () {
311
- var _a = this.state, containerHeight = _a.containerHeight, containerWidth = _a.containerWidth;
312
- var _b = this._margins, left = _b.left, right = _b.right, top = _b.top, bottom = _b.bottom;
313
- var width = containerWidth - right;
314
- var height = containerHeight - bottom > 0 ? containerHeight - bottom : 0;
315
- this._sankey = (0, d3_sankey_1.sankey)()
316
- .nodeWidth(124)
317
- .extent([
318
- [left, top],
319
- [width - 1, height - 6],
320
- ])
321
- .nodeAlign(this._isRtl ? d3_sankey_1.sankeyRight : d3_sankey_1.sankeyJustify);
322
- return { height: height, width: width };
323
- };
324
- SankeyChartBase.prototype._normalizeSankeyData = function (data) {
325
- var nodesInColumn = (this._nodesInColumn = populateNodeInColumns(data, this._sankey));
326
- adjustOnePercentHeightNodes(nodesInColumn);
327
- };
328
- /**
329
- * This is used to introduce dynamic padding for cases where the number of nodes in a column is huge
330
- * so that we maintain a node to space ratio for such columns as if we fail to do so the
331
- * chart is devoid of nodes and only shows links.
332
- */
333
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
334
- SankeyChartBase.prototype._adjustPadding = function (sankey, height, nodesInColumn) {
335
- var padding = this._sankey.nodePadding();
336
- Object.values(nodesInColumn).forEach(function (column) {
337
- var minPadding = PADDING_PERCENTAGE * height;
338
- var toatlPaddingInColumn = height - (0, d3_array_1.sum)(column, function (node) { return node.y1 - node.y0; });
339
- if (minPadding < toatlPaddingInColumn) {
340
- //Here we are calculating the min of default and calculated padding, we will not increase the padding
341
- //in any scenario.
342
- padding = Math.min(padding, minPadding / (column.length - 1));
343
- }
344
- });
345
- return padding;
389
+ SankeyChartBase.prototype._normalizeSankeyData = function (data, containerWidth, containerHeight) {
390
+ var _a = preRenderLayout(this._margins, containerWidth, containerHeight, this._isRtl), sankey = _a.sankey, height = _a.height, width = _a.width;
391
+ // Clone the data before mutating it (via the SankeyLayoutGenerator) so that we don't mutate the original data.
392
+ var transformed = duplicateData(data);
393
+ sankey(transformed);
394
+ var nodesInColumn = groupNodesByColumn(transformed);
395
+ // Keep track of the original values of the links and their acccumulated values in the nodes
396
+ // Setting these in external objects so they cannot be mutated by other code.
397
+ // The IDs of nodes can be numbers or strings. But, the IDs of links are always the index into the "nodes" array.
398
+ // After the sankey layout is computed, the each link's `source` and `target` will have the ID of the node in the
399
+ // type originally specified in the Nodes array. Consequently, we get the values of those links after the sankey
400
+ // transformation.
401
+ var nodeValues = valuesOfNodes(transformed.nodes);
402
+ var linkValues = valuesOfLinks(transformed.links);
403
+ adjustOnePercentHeightNodes(nodesInColumn, nodeValues, linkValues);
404
+ adjustPadding(sankey, height - 6, nodesInColumn);
405
+ // `sankey` is called a second time, probably to re-layout the nodes with the one-percent adjusted weights.
406
+ // NOTE: The second call to `sankey` is required to allow links to be hoverable.
407
+ // Without the second call, the links are not hoverable.
408
+ sankey(transformed);
409
+ populateNodeActualValue(transformed, nodeValues, linkValues);
410
+ return {
411
+ width: width,
412
+ height: height,
413
+ data: transformed,
414
+ };
346
415
  };
347
- SankeyChartBase.prototype._createLinks = function () {
416
+ SankeyChartBase.prototype._createLinks = function (dataLinks) {
348
417
  var _this = this;
349
418
  var links = [];
350
- var sankeyChartData = this.props.data.SankeyChartData;
351
- if (sankeyChartData) {
419
+ if (dataLinks) {
352
420
  var linkId_1 = this._linkId;
353
- sankeyChartData.links.forEach(function (singleLink, index) {
421
+ dataLinks.forEach(function (singleLink, index) {
354
422
  var onMouseOut = function () {
355
423
  _this._onStreamLeave(singleLink);
356
424
  };
@@ -383,13 +451,12 @@ var SankeyChartBase = /** @class */ (function (_super) {
383
451
  }
384
452
  return links;
385
453
  };
386
- SankeyChartBase.prototype._createNodes = function (classNames) {
454
+ SankeyChartBase.prototype._createNodes = function (classNames, dataNodes) {
387
455
  var _this = this;
388
456
  var nodes = [];
389
- var sankeyChartData = this.props.data.SankeyChartData;
390
- if (sankeyChartData) {
457
+ if (dataNodes) {
391
458
  var state_1 = this.state;
392
- sankeyChartData.nodes.forEach(function (singleNode, index) {
459
+ dataNodes.forEach(function (singleNode, index) {
393
460
  var onMouseOut = function () {
394
461
  _this._onLeave(singleNode);
395
462
  };
@@ -513,7 +580,7 @@ var SankeyChartBase = /** @class */ (function (_super) {
513
580
  * This is used to assign node fillcolors and borderColor cyclically when the user doesnt
514
581
  * provide color to individual node.
515
582
  */
516
- SankeyChartBase.prototype._assignNodeColors = function () {
583
+ SankeyChartBase.prototype._assignNodeColors = function (nodes) {
517
584
  var colors;
518
585
  var borders;
519
586
  var _a = this.props, colorsForNodes = _a.colorsForNodes, borderColorsForNodes = _a.borderColorsForNodes;
@@ -526,7 +593,7 @@ var SankeyChartBase = /** @class */ (function (_super) {
526
593
  borders = DEFAULT_NODE_COLORS.map(function (color) { return color.borderColor; });
527
594
  }
528
595
  var currentIndex = 0;
529
- this.props.data.SankeyChartData.nodes.forEach(function (node) {
596
+ nodes.forEach(function (node) {
530
597
  if (!node.color && !node.borderColor) {
531
598
  node.color = colors[currentIndex];
532
599
  node.borderColor = borders[currentIndex];
@@ -585,6 +652,7 @@ var SankeyChartBase = /** @class */ (function (_super) {
585
652
  var _this = this;
586
653
  var _a = this.state, containerWidth = _a.containerWidth, containerHeight = _a.containerHeight;
587
654
  this._reqID = requestAnimationFrame(function () {
655
+ // NOTE: Calls to this method trigger a re-render.
588
656
  var container = _this.props.parentRef ? _this.props.parentRef : _this.chartContainer;
589
657
  var currentContainerWidth = container && container.getBoundingClientRect().width;
590
658
  var currentContainerHeight = container && container.getBoundingClientRect().height;