@progress/kendo-charts
Version:
Kendo UI platform-independent Charts library
263 lines (216 loc) • 8.22 kB
JavaScript
/* eslint-disable camelcase */
import { drawing } from '@progress/kendo-drawing';
import { SankeyElement } from './element';
import { deepExtend } from '../common';
import { defined } from '../drawing-utils';
import { ARIA_ACTIVE_DESCENDANT } from '../common/constants';
var bezierPoint = function (p1, p2, p3, p4, t) {
var t1 = 1 - t;
var t1t1 = t1 * t1;
var tt = t * t;
return (p1 * t1t1 * t1) + (3 * p2 * t * t1t1) + (3 * p3 * tt * t1) + (p4 * tt * t);
};
function calculatePerpendicularLine(x1, y1, x2, y2, L) {
// 1. Calculate the midpoint M
var xM = (x1 + x2) / 2;
var yM = (y1 + y2) / 2;
var dx, dy;
if (y1 === y2) {
// The line AB is horizontal
dx = 0;
dy = L / 2;
} else if (x1 === x2) {
// The line AB is vertical
dx = L / 2;
dy = 0;
} else {
// Common case when the line is not horizontal or vertical
// 2. Calculate the slope of the original line
var m = (y2 - y1) / (x2 - x1);
// 3. Calculate the slope of the perpendicular line
var mPerp = -1 / m;
// 4. Calculate dx and dy
dx = (L / 2) / Math.sqrt(1 + mPerp * mPerp);
dy = mPerp * dx;
}
// 5. Coordinates of the points of the perpendicular line
var P1 = { x: xM - dx, y: yM - dy };
var P2 = { x: xM + dx, y: yM + dy };
return { P1: P1, P2: P2 };
}
function findIntersection(a, b, L, p, q) {
// Midpoint between a and b
var midpoint = {
x: (a.x + b.x) / 2,
y: (a.y + b.y) / 2
};
// Vector of the line ab
var ab_dx = b.x - a.x;
var ab_dy = b.y - a.y;
// Vector, perpendicular to ab
var perp_dx = -ab_dy;
var perp_dy = ab_dx;
// Normalize the perpendicular vector and scale it to 2*L
var magnitude = Math.sqrt(perp_dx * perp_dx + perp_dy * perp_dy);
perp_dx = (perp_dx / magnitude) * L;
perp_dy = (perp_dy / magnitude) * L;
// The endpoints of the perpendicular, 2*L long
var c1 = {
x: midpoint.x + perp_dx,
y: midpoint.y + perp_dy
};
var c2 = {
x: midpoint.x - perp_dx,
y: midpoint.y - perp_dy
};
// Check for intersection of the lines pq and the perpendicular
var pq_dx = q.x - p.x;
var pq_dy = q.y - p.y;
var denominator = (pq_dy) * (c1.x - c2.x) - (pq_dx) * (c1.y - c2.y);
if (Math.abs(denominator) < 1e-10) {
// The lines are almost parallel, no intersection
return null;
}
var ua = (pq_dx * (c2.y - p.y) - pq_dy * (c2.x - p.x)) / denominator;
var ub = ((c1.x - c2.x) * (c2.y - p.y) - (c1.y - c2.y) * (c2.x - p.x)) / denominator;
if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) {
var intersection = {
x: c2.x + ua * (c1.x - c2.x),
// y: c2.y + ua * (c1.y - c2.y)
};
return intersection;
}
// No intersection of the segments
return null;
}
var calculateControlPointsOffsetX = function (link, rtl) {
var halfWidth = link.width / 2;
var x0 = rtl ? link.x1 : link.x0;
var x1 = rtl ? link.x0 : link.x1;
var y0 = rtl ? link.y1 : link.y0;
var y1 = rtl ? link.y0 : link.y1;
var xC = (x0 + x1) / 2;
var middlePoint = [xC, bezierPoint(y0, y0, y1, y1, 0.5)];
var tH = 0.4999;
var pointH = [
bezierPoint(x0, xC, xC, x1, tH),
bezierPoint(y0, y0, y1, y1, tH)
];
var line = calculatePerpendicularLine(middlePoint[0], middlePoint[1], pointH[0], pointH[1], link.width);
var middlePointDown = [xC, bezierPoint(y0 + halfWidth, y0 + halfWidth, y1 + halfWidth, y1 + halfWidth, 0.5)];
// const middlePointUp = [xC, bezierPoint(y0 - halfWidth, y0 - halfWidth, y1 - halfWidth, y1 - halfWidth, 0.5)];
var P = line.P1.y > line.P2.y ? line.P1 : line.P2;
var L = halfWidth;
var LDir = (y0 > y1 ? 1 : -1) * L;
var a = P;
var b = { x: middlePointDown[0], y: middlePointDown[1] };
var p = { x: middlePointDown[0], y: middlePointDown[1] };
var q = { x: Math.max(1, middlePointDown[0] + LDir), y: middlePointDown[1] };
var Pmx = findIntersection(a, b, L, p, q) || { x: (middlePointDown[0] + P.x) / 2 };
var P1 = x0;
var P4 = x1;
var P2 = (Pmx.x - (0.125 * P1) - (0.125 * P4)) / 0.75;
return xC - P2;
};
export var Link = (function (SankeyElement) {
function Link () {
SankeyElement.apply(this, arguments);
}
if ( SankeyElement ) Link.__proto__ = SankeyElement;
Link.prototype = Object.create( SankeyElement && SankeyElement.prototype );
Link.prototype.constructor = Link;
Link.prototype.getElement = function getElement () {
var link = this.options.link;
var x0 = link.x0;
var x1 = link.x1;
var y0 = link.y0;
var y1 = link.y1;
var xC = (x0 + x1) / 2;
return new drawing.Path(this.visualOptions())
.moveTo(x0, y0).curveTo([xC, y0], [xC, y1], [x1, y1]);
};
Link.prototype.getLabelText = function getLabelText (options) {
var labelTemplate = options.labels.ariaTemplate;
if (labelTemplate) {
return labelTemplate({ link: options.link });
}
};
Link.prototype.visualOptions = function visualOptions () {
var options = this.options;
var link = this.options.link;
var ariaLabel = this.getLabelText(options);
return {
stroke: {
width: options.link.width,
color: link.color || options.color,
opacity: defined(link.opacity) ? link.opacity : options.opacity
},
role: 'graphics-symbol',
ariaRoleDescription: 'Link',
ariaLabel: ariaLabel
};
};
Link.prototype.createFocusHighlight = function createFocusHighlight () {
if (!this.options.navigatable) {
return;
}
var ref = this.options;
var link = ref.link;
var x0 = link.x0;
var x1 = link.x1;
var y0 = link.y0;
var y1 = link.y1;
var xC = (x0 + x1) / 2;
var halfWidth = link.width / 2;
var offset = calculateControlPointsOffsetX(link, this.options.rtl);
this._highlight = new drawing.Path({ stroke: this.options.focusHighlight.border, visible: false })
.moveTo(x0, y0 + halfWidth)
.lineTo(x0, y0 - halfWidth)
.curveTo([xC + offset, y0 - halfWidth], [xC + offset, y1 - halfWidth], [x1, y1 - halfWidth])
.lineTo(x1, y1 + halfWidth)
.curveTo([xC - offset, y1 + halfWidth], [xC - offset, y0 + halfWidth], [x0, y0 + halfWidth]);
};
Link.prototype.focus = function focus (options) {
if (this._highlight) {
var ref = options || {};
var highlight = ref.highlight; if ( highlight === void 0 ) highlight = true;
if (highlight) {
this._highlight.options.set('visible', true);
}
var id = (this.options.link.sourceId) + "->" + (this.options.link.targetId);
this.visual.options.set('id', id);
if (this.options.root()) {
this.options.root().setAttribute(ARIA_ACTIVE_DESCENDANT, id);
}
}
};
Link.prototype.blur = function blur () {
if (this._highlight) {
this._highlight.options.set('visible', false);
this.visual.options.set('id', '');
if (this.options.root()) {
this.options.root().removeAttribute(ARIA_ACTIVE_DESCENDANT);
}
}
};
return Link;
}(SankeyElement));
export var resolveLinkOptions = function (link, options, sourceNode, targetNode) {
var linkOptions = deepExtend({},
options,
{
link: link,
opacity: link.opacity,
color: link.color,
colorType: link.colorType,
visual: link.visual,
highlight: link.highlight
}
);
if (linkOptions.colorType === 'source') {
linkOptions.color = sourceNode.options.fill.color;
} else if (linkOptions.colorType === 'target') {
linkOptions.color = targetNode.options.fill.color;
}
return linkOptions;
};