@progress/kendo-charts
Version:
Kendo UI platform-independent Charts library
1,022 lines (837 loc) • 35.7 kB
JavaScript
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
}
});