UNPKG

@progress/kendo-charts

Version:

Kendo UI platform-independent Charts library

1,022 lines (837 loc) 35.7 kB
import { geometry, drawing } from '@progress/kendo-drawing'; import { deepExtend, addClass, Observable, setDefaultOptions } from '../common'; import { calculateSankey, crossesValue } from './calculation'; import { Node, resolveNodeOptions } from './node'; import { Link, resolveLinkOptions } from './link'; import { Label, resolveLabelOptions } from './label'; import { Title } from './title'; import { BLACK, BOTTOM, LEFT, RIGHT, TOP } from '../common/constants'; import { Box, rectToBox } from '../core'; import { Legend } from './legend'; import { defined } from '../drawing-utils'; var LINK = 'link'; var NODE = 'node'; var toRtl = function (sankey) { var nodes = sankey.nodes; var links = sankey.links; var startX = Math.min.apply(Math, nodes.map(function (node) { return node.x0; })); var endX = Math.max.apply(Math, nodes.map(function (node) { return node.x1; })); var width = endX - startX; nodes.forEach(function (node) { var x0 = width - (node.x1 - 2 * startX); var x1 = width - (node.x0 - 2 * startX); node.x0 = x0; node.x1 = x1; }); links.forEach(function (link) { var x0 = width - (link.x1 - 2 * startX); var x1 = width - (link.x0 - 2 * startX); link.x1 = x0; link.x0 = x1; }); }; export var Sankey = (function (Observable) { function Sankey(element, options, theme) { Observable.call(this); this._initTheme(theme); this._setOptions(options); this._initElement(element); this._initSurface(); if (options && options.data) { this._redraw(); this._initResizeObserver(); this._initNavigation(element); } } if ( Observable ) Sankey.__proto__ = Observable; Sankey.prototype = Object.create( Observable && Observable.prototype ); Sankey.prototype.constructor = Sankey; Sankey.prototype.destroy = function destroy () { this.unbind(); this._destroySurface(); this._destroyResizeObserver(); if (this.element) { this.element.removeEventListener('keydown', this._keydownHandler); this.element.removeEventListener('focus', this._focusHandler); this.element.removeEventListener('mousedown', this._onDownHandler); this.element.removeEventListener('touchstart', this._onDownHandler); this.element.removeEventListener('pointerdown', this._onDownHandler); } this._focusState = null; this.element = null; }; Sankey.prototype._initElement = function _initElement (element) { this.element = element; addClass(element, [ "k-chart", "k-sankey" ]); element.setAttribute('role', 'graphics-document'); var ref = this.options; var title = ref.title; if (title.text) { element.setAttribute('aria-label', title.text); } if (title.description) { element.setAttribute("aria-roledescription", title.description); } }; Sankey.prototype._initSurface = function _initSurface () { if (!this.surface) { this._destroySurface(); this._initSurfaceElement(); this.surface = this._createSurface(); } }; Sankey.prototype._initNavigation = function _initNavigation (element) { element.tabIndex = element.getAttribute("tabindex") || 0; if (this.options.disableKeyboardNavigation) { return; } this._keydownHandler = this._keydown.bind(this); this._focusHandler = this._focus.bind(this); this._blurHandler = this._blur.bind(this); this._onDownHandler = this._onDown.bind(this); element.addEventListener('keydown', this._keydownHandler); element.addEventListener('focus', this._focusHandler); element.addEventListener('blur', this._blurHandler); element.addEventListener('mousedown', this._onDownHandler); element.addEventListener('touchstart', this._onDownHandler); element.addEventListener('pointerdown', this._onDownHandler); this._focusState = { node: this.firstFocusableNode(), link: null }; }; Sankey.prototype.firstFocusableNode = function firstFocusableNode () { return this.columns[0][0]; }; Sankey.prototype._initResizeObserver = function _initResizeObserver () { var this$1 = this; var observer = new ResizeObserver(function (entries) { entries.forEach(function (entry) { var ref = entry.contentRect; var width = ref.width; var height = ref.height; if (entry.target !== this$1.element || (this$1.size && this$1.size.width === width && this$1.size.height === height)) { return; } this$1.size = { width: width, height: height }; this$1.surface.setSize(this$1.size); this$1.resize = true; this$1._redraw(); }); }); this._resizeObserver = observer; observer.observe(this.element); }; Sankey.prototype._createSurface = function _createSurface () { return drawing.Surface.create(this.surfaceElement, { mouseenter: this._mouseenter.bind(this), mouseleave: this._mouseleave.bind(this), mousemove: this._mousemove.bind(this), click: this._click.bind(this) }); }; Sankey.prototype._initTheme = function _initTheme (theme) { var currentTheme = theme || this.theme || {}; this.theme = currentTheme; this.options = deepExtend({}, currentTheme, this.options); }; Sankey.prototype.setLinksOpacity = function setLinksOpacity (opacity) { var this$1 = this; this.linksVisuals.forEach(function (link) { this$1.setOpacity(link, opacity, link.linkOptions.opacity); }); }; Sankey.prototype.setLinksInactivityOpacity = function setLinksInactivityOpacity (inactiveOpacity) { var this$1 = this; this.linksVisuals.forEach(function (link) { this$1.setOpacity(link, inactiveOpacity, link.linkOptions.highlight.inactiveOpacity); }); }; Sankey.prototype.setOpacity = function setOpacity (link, opacity, linkValue) { link.options.set('stroke', Object.assign({}, link.options.stroke, {opacity: defined(linkValue) ? linkValue : opacity})); }; Sankey.prototype.trigger = function trigger (name, ev) { var dataItem = ev.element.dataItem; var targetType = ev.element.type; var event = Object.assign({}, ev, {type: name, targetType: targetType, dataItem: dataItem}); return Observable.prototype.trigger.call(this, name, event); }; Sankey.prototype._mouseenter = function _mouseenter (ev) { var element = ev.element; var isLink = element.type === LINK; var isNode = element.type === NODE; var isLegendItem = Boolean(element.chartElement && element.chartElement.options.node); if ((isLink && this.trigger('linkEnter', ev)) || (isNode && this.trigger('nodeEnter', ev))) { return; } var ref = this.options.links; var highlight = ref.highlight; if (isLink) { this.setLinksInactivityOpacity(highlight.inactiveOpacity); this.setOpacity(element, highlight.opacity, element.linkOptions.highlight.opacity); } else if (isNode) { this.highlightLinks(element, highlight); } else if (isLegendItem) { var nodeVisual = this.nodesVisuals.get(element.chartElement.options.node.id); this.highlightLinks(nodeVisual, highlight); } }; Sankey.prototype._mouseleave = function _mouseleave (ev) { var this$1 = this; var element = ev.element; var isLink = element.type === LINK; var isNode = element.type === NODE; var isLegendItem = Boolean(element.chartElement && element.chartElement.options.node); var target = ev.originalEvent.relatedTarget; if (isLink && target && target.nodeName === 'text') { return; } if (isLink || isNode) { if (this.tooltipTimeOut) { clearTimeout(this.tooltipTimeOut); this.tooltipTimeOut = null; } this.tooltipShown = false; this.trigger('tooltipHide', ev); } if ((isLink && this.trigger('linkLeave', ev)) || (isNode && this.trigger('nodeLeave', ev))) { return; } if (isLink || isNode || isLegendItem) { this.linksVisuals.forEach(function (link) { this$1.setOpacity(link, this$1.options.links.opacity, link.linkOptions.opacity); }); } }; Sankey.prototype._mousemove = function _mousemove (ev) { var this$1 = this; var ref = this.options.tooltip; var followPointer = ref.followPointer; var delay = ref.delay; var element = ev.element; var tooltipElType = element.type; if ((tooltipElType !== LINK && tooltipElType !== NODE) || (this.tooltipShown && !followPointer)) { return; } var mouseEvent = ev.originalEvent; var rect = this.element.getBoundingClientRect(); var isLeft = mouseEvent.clientX - rect.left < rect.width / 2; var isTop = mouseEvent.clientY - rect.top < rect.height / 2; ev.tooltipData = { popupOffset: { left: mouseEvent.pageX, top: mouseEvent.pageY }, popupAlign: { horizontal: isLeft ? 'left' : 'right', vertical: isTop ? 'top' : 'bottom' } }; if (tooltipElType === NODE) { var ref$1 = element.dataItem; var sourceLinks = ref$1.sourceLinks; var targetLinks = ref$1.targetLinks; var links = targetLinks.length ? targetLinks : sourceLinks; ev.nodeValue = links.reduce(function (acc, link) { return acc + link.value; }, 0); } if (this.tooltipTimeOut) { clearTimeout(this.tooltipTimeOut); this.tooltipTimeOut = null; } var nextDelay = followPointer && this.tooltipShown ? 0 : delay; this.tooltipTimeOut = setTimeout(function () { this$1.trigger('tooltipShow', ev); this$1.tooltipShown = true; this$1.tooltipTimeOut = null; }, nextDelay); }; Sankey.prototype._click = function _click (ev) { var element = ev.element; var dataItem = element.dataItem; var isLink = element.type === LINK; var isNode = element.type === NODE; var focusState = this._focusState || {}; if (isNode) { var focusedNodeClicked = !focusState.link && this.sameNode(focusState.node, dataItem); if (!focusedNodeClicked) { this._focusState = { node: dataItem, link: null }; this._focusNode({ highlight: false }); } this.trigger('nodeClick', ev); } else if (isLink) { var link = { sourceId: dataItem.source.id, targetId: dataItem.target.id, value: dataItem.value }; var focusedLinkClicked = this.sameLink(focusState.link, link); if (!focusedLinkClicked) { this._focusState = { node: dataItem.source, link: link }; this._focusLink({ highlight: false }); } this.trigger('linkClick', ev); } }; Sankey.prototype.sameNode = function sameNode (node1, node2) { return node1 && node2 && node1.id === node2.id; }; Sankey.prototype.sameLink = function sameLink (link1, link2) { return link1 && link2 && link1.sourceId === link2.sourceId && link1.targetId === link2.targetId; }; Sankey.prototype._focusNode = function _focusNode (options) { this._cleanFocusHighlight(); var nodeData = this._focusState.node; var node = this.models.map.get(nodeData.id); node.focus(options); }; Sankey.prototype._focusLink = function _focusLink (options) { this._cleanFocusHighlight(); var linkData = this._focusState.link; var link = this.models.map.get(((linkData.sourceId) + "-" + (linkData.targetId))); link.focus(options); }; Sankey.prototype._focusNextNode = function _focusNextNode (direction) { if ( direction === void 0 ) direction = 1; var current = this._focusState.node; var columnIndex = this.columns.findIndex(function (column) { return column.find(function (n) { return n.id === current.id; }); }); var columnNodes = this.columns[columnIndex]; var nodeIndex = columnNodes.findIndex(function (n) { return n.id === current.id; }); var nextNode = columnNodes[nodeIndex + direction]; if (nextNode) { this._focusState.node = nextNode; this._focusNode(); } }; Sankey.prototype._focusNextLink = function _focusNextLink (direction) { if ( direction === void 0 ) direction = 1; var node = this._focusState.node; var link = this._focusState.link; var sourceLinkIndex = node.sourceLinks.findIndex(function (l) { return l.sourceId === link.sourceId && l.targetId === link.targetId; }); var targetLinkIndex = node.targetLinks.findIndex(function (l) { return l.sourceId === link.sourceId && l.targetId === link.targetId; }); if (sourceLinkIndex !== -1) { var nextLink = node.sourceLinks[sourceLinkIndex + direction]; if (nextLink) { this._focusState.link = nextLink; this._focusLink(); } } else if (targetLinkIndex !== -1) { var nextLink$1 = node.targetLinks[targetLinkIndex + direction]; if (nextLink$1) { this._focusState.link = nextLink$1; this._focusLink(); } } }; Sankey.prototype._focusSourceNode = function _focusSourceNode () { var linkData = this._focusState.link; var sourceNode = this.models.map.get(linkData.sourceId); this._focusState = { node: sourceNode.options.node, link: null }; this._focusNode(); }; Sankey.prototype._focusTargetNode = function _focusTargetNode () { var linkData = this._focusState.link; var targetNode = this.models.map.get(linkData.targetId); this._focusState = { node: targetNode.options.node, link: null }; this._focusNode(); }; Sankey.prototype._focusSourceLink = function _focusSourceLink () { var nodeData = this._focusState.node; var sourceLinks = nodeData.sourceLinks; var linkData = sourceLinks[0]; if (linkData) { this._focusState.link = linkData; this._focusLink(); } }; Sankey.prototype._focusTargetLink = function _focusTargetLink () { var nodeData = this._focusState.node; var targetLinks = nodeData.targetLinks; var linkData = targetLinks[0]; if (linkData) { this._focusState.link = linkData; this._focusLink(); } }; Sankey.prototype._focus = function _focus () { if (!this._skipFocusHighlight) { if (this._focusState.link) { this._focusLink(); } else { this._focusNode(); } } this._skipFocusHighlight = false; }; Sankey.prototype._blur = function _blur () { this._cleanFocusHighlight(); }; Sankey.prototype._onDown = function _onDown () { if (!this._hasFocus()) { this._skipFocusHighlight = true; } }; Sankey.prototype._hasFocus = function _hasFocus () { return this.element.ownerDocument.activeElement === this.element; }; Sankey.prototype._cleanFocusHighlight = function _cleanFocusHighlight () { this.models.nodes.forEach(function (node) { return node.blur(); }); this.models.links.forEach(function (link) { return link.blur(); }); }; Sankey.prototype._keydown = function _keydown (ev) { var handler = this['on' + ev.key]; var rtl = this.options.rtl; if (rtl && ev.key === 'ArrowLeft') { handler = this.onArrowRight; } else if (rtl && ev.key === 'ArrowRight') { handler = this.onArrowLeft; } if (handler) { handler.call(this, ev); } }; Sankey.prototype.onEscape = function onEscape (ev) { ev.preventDefault(); this._focusState = { node: this.firstFocusableNode(), link: null }; this._focusNode(); }; Sankey.prototype.onArrowDown = function onArrowDown (ev) { ev.preventDefault(); if (this._focusState.link) { this._focusNextLink(1); } else { this._focusNextNode(1); } }; Sankey.prototype.onArrowUp = function onArrowUp (ev) { ev.preventDefault(); if (this._focusState.link) { this._focusNextLink(-1); } else { this._focusNextNode(-1); } }; Sankey.prototype.onArrowLeft = function onArrowLeft (ev) { ev.preventDefault(); if (this._focusState.link) { this._focusSourceNode(); } else { this._focusTargetLink(); } }; Sankey.prototype.onArrowRight = function onArrowRight (ev) { ev.preventDefault(); if (this._focusState.link) { this._focusTargetNode(); } else { this._focusSourceLink(); } }; Sankey.prototype.highlightLinks = function highlightLinks (node, highlight) { var this$1 = this; if (node) { this.setLinksInactivityOpacity(highlight.inactiveOpacity); node.links.forEach(function (link) { this$1.setOpacity(link, highlight.opacity, link.linkOptions.highlight.opacity); }); } }; Sankey.prototype._destroySurface = function _destroySurface () { if (this.surface) { this.surface.destroy(); this.surface = null; this._destroySurfaceElement(); } }; Sankey.prototype._destroyResizeObserver = function _destroyResizeObserver () { if (this._resizeObserver) { this._resizeObserver.disconnect(); this._resizeObserver = null; } }; Sankey.prototype._initSurfaceElement = function _initSurfaceElement () { if (!this.surfaceElement) { this.surfaceElement = document.createElement('div'); this.element.appendChild(this.surfaceElement); } }; Sankey.prototype._destroySurfaceElement = function _destroySurfaceElement () { if (this.surfaceElement && this.surfaceElement.parentNode) { this.surfaceElement.parentNode.removeChild(this.surfaceElement); this.surfaceElement = null; } }; Sankey.prototype.setOptions = function setOptions (options, theme) { this._setOptions(options); this._initTheme(theme); this._initSurface(); this._redraw(); }; Sankey.prototype._redraw = function _redraw () { this.surface.clear(); var ref = this._getSize(); var width = ref.width; var height = ref.height; this.size = { width: width, height: height }; this.surface.setSize(this.size); this.createVisual(); this.surface.draw(this.visual); }; Sankey.prototype._getSize = function _getSize () { return this.element.getBoundingClientRect(); }; Sankey.prototype.createVisual = function createVisual () { this.visual = this._render(); }; Sankey.prototype.titleBox = function titleBox (title, drawingRect) { if (!title || title.visible === false || !title.text) { return null; } var titleElement = new Title(Object.assign({}, {drawingRect: drawingRect}, title)); var titleVisual = titleElement.exportVisual(); return titleVisual.chartElement.box; }; Sankey.prototype.legendBox = function legendBox (options, nodes, drawingRect) { if (!options || options.visible === false) { return null; } var legend = new Legend(Object.assign({}, {nodes: nodes}, options, {drawingRect: drawingRect})); var legendVisual = legend.exportVisual(); return legendVisual.chartElement.box; }; Sankey.prototype.calculateSankey = function calculateSankey$1 (calcOptions, sankeyOptions) { var title = sankeyOptions.title; var legend = sankeyOptions.legend; var data = sankeyOptions.data; var nodes = sankeyOptions.nodes; var labels = sankeyOptions.labels; var nodeColors = sankeyOptions.nodeColors; var disableAutoLayout = sankeyOptions.disableAutoLayout; var disableKeyboardNavigation = sankeyOptions.disableKeyboardNavigation; var rtl = sankeyOptions.rtl; var autoLayout = !disableAutoLayout; var focusHighlightWidth = ((nodes.focusHighlight || {}).border || {}).width || 0; var padding = disableKeyboardNavigation ? 0 : focusHighlightWidth / 2; var sankeyBox = new Box(0, 0, calcOptions.width, calcOptions.height); sankeyBox.unpad(padding); var titleBox = this.titleBox(title, sankeyBox); var legendArea = sankeyBox.clone(); if (titleBox) { var titleHeight = titleBox.height(); if (title.position === TOP) { sankeyBox.unpad({ top: titleHeight }); legendArea = new Box(0, titleHeight, calcOptions.width, calcOptions.height); } else { sankeyBox.shrink(0, titleHeight); legendArea = new Box(0, 0, calcOptions.width, calcOptions.height - titleHeight); } } var legendBox = this.legendBox(legend, data.nodes, legendArea); var legendPosition = (legend && legend.position) || Legend.prototype.options.position; if (legendBox) { if (legendPosition === LEFT) { sankeyBox.unpad({ left: legendBox.width() }); } if (legendPosition === RIGHT) { sankeyBox.shrink(legendBox.width(), 0); } if (legendPosition === TOP) { sankeyBox.unpad({ top: legendBox.height() }); } if (legendPosition === BOTTOM) { sankeyBox.shrink(0, legendBox.height()); } } var ref = calculateSankey(Object.assign({}, calcOptions, {offsetX: 0, offsetY: 0, width: sankeyBox.width(), height: sankeyBox.height()})); var calculatedNodes = ref.nodes; var circularLinks = ref.circularLinks; if (circularLinks) { console.warn('Circular links detected. Kendo Sankey diagram does not support circular links.'); return { sankey: { nodes: [], links: [], columns: [[]], circularLinks: circularLinks }, legendBox: legendBox, titleBox: titleBox }; } var box = new Box(); var diagramMinX = calculatedNodes.reduce(function (acc, node) { return Math.min(acc, node.x0); }, Infinity); var diagramMaxX = calculatedNodes.reduce(function (acc, node) { return Math.max(acc, node.x1); }, 0); calculatedNodes.forEach(function (nodeEl, i) { if (rtl) { var x0 = nodeEl.x0; var x1 = nodeEl.x1; nodeEl.x0 = diagramMaxX - x1; nodeEl.x1 = diagramMaxX - x0; } var nodeOps = resolveNodeOptions(nodeEl, nodes, nodeColors, i); var nodeInstance = new Node(nodeOps); box.wrap(rectToBox(nodeInstance.exportVisual().rawBBox())); var labelInstance = new Label(resolveLabelOptions(nodeEl, labels, rtl, diagramMinX, diagramMaxX)); var labelVisual = labelInstance.exportVisual(); if (labelVisual) { box.wrap(rectToBox(labelVisual.rawBBox())); } }); var offsetX = sankeyBox.x1; var offsetY = sankeyBox.y1; var width = sankeyBox.width() + offsetX; var height = sankeyBox.height() + offsetY; width -= box.x2 > sankeyBox.width() ? box.x2 - sankeyBox.width() : 0; height -= box.y2 > sankeyBox.height() ? box.y2 - sankeyBox.height() : 0; offsetX += box.x1 < 0 ? -box.x1 : 0; offsetY += box.y1 < 0 ? -box.y1 : 0; if (autoLayout === false) { return { sankey: calculateSankey(Object.assign({}, calcOptions, {offsetX: offsetX, offsetY: offsetY, width: width, height: height, autoLayout: false})), legendBox: legendBox, titleBox: titleBox }; } if (this.resize && autoLayout && this.permutation) { this.resize = false; return { sankey: calculateSankey(Object.assign({}, calcOptions, {offsetX: offsetX, offsetY: offsetY, width: width, height: height}, this.permutation)), legendBox: legendBox, titleBox: titleBox }; } var startColumn = 1; var loops = 2; var columnsLength = calculateSankey(Object.assign({}, calcOptions, {offsetX: offsetX, offsetY: offsetY, width: width, height: height, autoLayout: false})).columns.length; var results = []; var permutation = function (targetColumnIndex, reverse) { var currPerm = calculateSankey(Object.assign({}, calcOptions, {offsetX: offsetX, offsetY: offsetY, width: width, height: height, loops: loops, targetColumnIndex: targetColumnIndex, reverse: reverse})); var crosses = crossesValue(currPerm.links); results.push({ crosses: crosses, reverse: reverse, targetColumnIndex: targetColumnIndex }); return crosses === 0; }; for (var index = startColumn; index <= columnsLength - 1; index++) { if (permutation(index, false) || permutation(index, true)) { break; } } var minCrosses = Math.min.apply(null, results.map(function (r) { return r.crosses; })); var bestResult = results.find(function (r) { return r.crosses === minCrosses; }); this.permutation = { targetColumnIndex: bestResult.targetColumnIndex, reverse: bestResult.reverse }; var result = calculateSankey(Object.assign({}, calcOptions, {offsetX: offsetX, offsetY: offsetY, width: width, height: height}, this.permutation)); return { sankey: result, legendBox: legendBox, titleBox: titleBox }; }; Sankey.prototype._render = function _render (options, context) { var sankeyOptions = options || this.options; var sankeyContext = context || this; var labelOptions = sankeyOptions.labels; var nodesOptions = sankeyOptions.nodes; var linkOptions = sankeyOptions.links; var nodeColors = sankeyOptions.nodeColors; var title = sankeyOptions.title; var legend = sankeyOptions.legend; var rtl = sankeyOptions.rtl; var disableKeyboardNavigation = sankeyOptions.disableKeyboardNavigation; var data = sankeyOptions.data; var ref = sankeyContext.size; var width = ref.width; var height = ref.height; var calcOptions = Object.assign({}, data, {width: width, height: height, nodesOptions: nodesOptions, title: title, legend: legend}); var ref$1 = this.calculateSankey(calcOptions, sankeyOptions); var sankey = ref$1.sankey; var titleBox = ref$1.titleBox; var legendBox = ref$1.legendBox; if (rtl) { toRtl(sankey); } var nodes = sankey.nodes; var links = sankey.links; var columns = sankey.columns; sankeyContext.columns = columns.map(function (column) { var newColumn = column.slice(); newColumn.sort(function (a, b) { return a.y0 - b.y0; }); return newColumn; }); var visual = new drawing.Group({ clip: drawing.Path.fromRect(new geometry.Rect([0, 0], [width, height])) }); if (titleBox) { var titleElement = new Title(Object.assign({}, title, {drawingRect: titleBox})); var titleVisual = titleElement.exportVisual(); visual.append(titleVisual); } if (sankey.circularLinks) { return visual; } var visualNodes = new Map(); sankeyContext.nodesVisuals = visualNodes; var models = { nodes: [], links: [], map: new Map() }; sankeyContext.models = models; var focusHighlights = []; nodes.forEach(function (node, i) { var nodeOps = resolveNodeOptions(node, nodesOptions, nodeColors, i); nodeOps.root = function () { return sankeyContext.element; }; nodeOps.navigatable = disableKeyboardNavigation !== true; var nodeInstance = new Node(nodeOps); var nodeVisual = nodeInstance.exportVisual(); nodeVisual.links = []; nodeVisual.type = NODE; node.color = nodeOps.color; node.opacity = nodeOps.opacity; nodeVisual.dataItem = Object.assign({}, data.nodes[i], {color: nodeOps.color, opacity: nodeOps.opacity, sourceLinks: node.sourceLinks.map(function (link) { return ({ sourceId: link.sourceId, targetId: link.targetId, value: link.value }); }), targetLinks: node.targetLinks.map(function (link) { return ({ sourceId: link.sourceId, targetId: link.targetId, value: link.value }); })}); visualNodes.set(node.id, nodeVisual); models.nodes.push(nodeInstance); models.map.set(node.id, nodeInstance); visual.append(nodeVisual); nodeInstance.createFocusHighlight(); if (nodeInstance._highlight) { focusHighlights.push(nodeInstance._highlight); } }); var sortedLinks = links.slice().sort(function (a, b) { return b.value - a.value; }); var linksVisuals = []; sankeyContext.linksVisuals = linksVisuals; sortedLinks.forEach(function (link) { var source = link.source; var target = link.target; var sourceNode = visualNodes.get(source.id); var targetNode = visualNodes.get(target.id); var resolvedOptions = resolveLinkOptions(link, linkOptions, sourceNode, targetNode); resolvedOptions.root = function () { return sankeyContext.element; }; resolvedOptions.navigatable = disableKeyboardNavigation !== true; resolvedOptions.rtl = rtl; var linkInstance = new Link(resolvedOptions); var linkVisual = linkInstance.exportVisual(); linkVisual.type = LINK; linkVisual.dataItem = { source: Object.assign({}, sourceNode.dataItem), target: Object.assign({}, targetNode.dataItem), value: link.value }; linkVisual.linkOptions = resolvedOptions; linksVisuals.push(linkVisual); sourceNode.links.push(linkVisual); targetNode.links.push(linkVisual); models.links.push(linkInstance); models.map.set(((source.id) + "-" + (target.id)), linkInstance); linkInstance.createFocusHighlight(); if (linkInstance._highlight) { focusHighlights.push(linkInstance._highlight); } visual.append(linkVisual); }); var diagramMinX = nodes.reduce(function (acc, node) { return Math.min(acc, node.x0); }, Infinity); var diagramMaxX = nodes.reduce(function (acc, node) { return Math.max(acc, node.x1); }, 0); nodes.forEach(function (node) { var textOps = resolveLabelOptions(node, labelOptions, rtl, diagramMinX, diagramMaxX); var labelInstance = new Label(textOps); var labelVisual = labelInstance.exportVisual(); if (labelVisual) { visual.append(labelVisual); } }); if (legendBox) { var legendElement = new Legend(Object.assign({}, legend, {rtl: rtl, drawingRect: legendBox, nodes: nodes})); var legendVisual = legendElement.exportVisual(); visual.append(legendVisual); } if (focusHighlights.length !== 0) { var focusHighlight = new drawing.Group(); focusHighlight.append.apply(focusHighlight, focusHighlights); visual.append(focusHighlight); } return visual; }; Sankey.prototype.exportVisual = function exportVisual (exportOptions) { var options = (exportOptions && exportOptions.options) ? deepExtend({}, this.options, exportOptions.options) : this.options; var context = { size: { width: defined(exportOptions && exportOptions.width) ? exportOptions.width : this.size.width, height: defined(exportOptions && exportOptions.height) ? exportOptions.height : this.size.height } }; return this._render(options, context); }; Sankey.prototype._setOptions = function _setOptions (options) { this.options = deepExtend({}, this.options, options); }; return Sankey; }(Observable)); var highlightOptions = { opacity: 1, width: 2, color: BLACK }; setDefaultOptions(Sankey, { title: { position: TOP, // 'top', 'bottom' }, labels: { visible: true, margin: { left: 8, right: 8 }, padding: 0, border: { width: 0 }, paintOrder: 'stroke', stroke: { lineJoin: "round", width: 1 }, offset: { left: 0, top: 0 } }, nodes: { width: 24, padding: 16, opacity: 1, align: 'stretch', // 'left', 'right', 'stretch' offset: { left: 0, top: 0 }, focusHighlight: { border: Object.assign({}, highlightOptions) }, labels: { ariaTemplate: function (ref) { var node = ref.node; return node.label.text; } } }, links: { colorType: 'static', // 'source', 'target', 'static' opacity: 0.4, highlight: { opacity: 0.8, inactiveOpacity: 0.2 }, focusHighlight: { border: Object.assign({}, highlightOptions) }, labels: { ariaTemplate: function (ref) { var link = ref.link; return ((link.source.label.text) + " to " + (link.target.label.text)); } } }, tooltip: { followPointer: false, delay: 200 } });