Skip to content

Commit

Permalink
Support monotone cubic interpolation for vertical line charts (#9084)
Browse files Browse the repository at this point in the history
* Support monotone cubic interpolation for vertical line charts

* Use more more intuitive veriable names
  • Loading branch information
nagix committed May 14, 2021
1 parent 927f24a commit 0ba5c70
Show file tree
Hide file tree
Showing 10 changed files with 55 additions and 21 deletions.
3 changes: 2 additions & 1 deletion src/controllers/controller.line.js
Expand Up @@ -102,7 +102,8 @@ export default class LineController extends DatasetController {
}

draw() {
this._cachedMeta.dataset.updateControlPoints(this.chart.chartArea);
const meta = this._cachedMeta;
meta.dataset.updateControlPoints(this.chart.chartArea, meta.iScale.axis);
super.draw();
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/elements/element.line.js
Expand Up @@ -257,12 +257,12 @@ export default class LineElement extends Element {
}
}

updateControlPoints(chartArea) {
updateControlPoints(chartArea, indexAxis) {
const me = this;
const options = me.options;
if ((options.tension || options.cubicInterpolationMode === 'monotone') && !options.stepped && !me._pointsUpdated) {
const loop = options.spanGaps ? me._loop : me._fullLoop;
_updateBezierControlPoints(me._points, options, chartArea, loop);
_updateBezierControlPoints(me._points, options, chartArea, loop, indexAxis);
me._pointsUpdated = true;
}
}
Expand Down
35 changes: 20 additions & 15 deletions src/helpers/helpers.curve.js
Expand Up @@ -3,6 +3,7 @@ import {_isPointInArea} from './helpers.canvas';

const EPSILON = Number.EPSILON || 1e-14;
const getPoint = (points, i) => i < points.length && !points[i].skip && points[i];
const getValueAxis = (indexAxis) => indexAxis === 'x' ? 'y' : 'x';

export function splineCurve(firstPoint, middlePoint, afterPoint, t) {
// Props to Rob Spencer at scaled innovation for his post on splining between points
Expand Down Expand Up @@ -71,9 +72,10 @@ function monotoneAdjust(points, deltaK, mK) {
}
}

function monotoneCompute(points, mK) {
function monotoneCompute(points, mK, indexAxis = 'x') {
const valueAxis = getValueAxis(indexAxis);
const pointsLen = points.length;
let deltaX, pointBefore, pointCurrent;
let delta, pointBefore, pointCurrent;
let pointAfter = getPoint(points, 0);

for (let i = 0; i < pointsLen; ++i) {
Expand All @@ -84,16 +86,17 @@ function monotoneCompute(points, mK) {
continue;
}

const {x, y} = pointCurrent;
const iPixel = pointCurrent[indexAxis];
const vPixel = pointCurrent[valueAxis];
if (pointBefore) {
deltaX = (x - pointBefore.x) / 3;
pointCurrent.cp1x = x - deltaX;
pointCurrent.cp1y = y - deltaX * mK[i];
delta = (iPixel - pointBefore[indexAxis]) / 3;
pointCurrent[`cp1${indexAxis}`] = iPixel - delta;
pointCurrent[`cp1${valueAxis}`] = vPixel - delta * mK[i];
}
if (pointAfter) {
deltaX = (pointAfter.x - x) / 3;
pointCurrent.cp2x = x + deltaX;
pointCurrent.cp2y = y + deltaX * mK[i];
delta = (pointAfter[indexAxis] - iPixel) / 3;
pointCurrent[`cp2${indexAxis}`] = iPixel + delta;
pointCurrent[`cp2${valueAxis}`] = vPixel + delta * mK[i];
}
}
}
Expand All @@ -113,8 +116,10 @@ function monotoneCompute(points, mK) {
* cp2x?: number,
* cp2y?: number,
* }[]} points
* @param {string} indexAxis
*/
export function splineCurveMonotone(points) {
export function splineCurveMonotone(points, indexAxis = 'x') {
const valueAxis = getValueAxis(indexAxis);
const pointsLen = points.length;
const deltaK = Array(pointsLen).fill(0);
const mK = Array(pointsLen);
Expand All @@ -132,10 +137,10 @@ export function splineCurveMonotone(points) {
}

if (pointAfter) {
const slopeDeltaX = (pointAfter.x - pointCurrent.x);
const slopeDelta = pointAfter[indexAxis] - pointCurrent[indexAxis];

// In the case of two points that appear at the same x pixel, slopeDeltaX is 0
deltaK[i] = slopeDeltaX !== 0 ? (pointAfter.y - pointCurrent.y) / slopeDeltaX : 0;
deltaK[i] = slopeDelta !== 0 ? (pointAfter[valueAxis] - pointCurrent[valueAxis]) / slopeDelta : 0;
}
mK[i] = !pointBefore ? deltaK[i]
: !pointAfter ? deltaK[i - 1]
Expand All @@ -145,7 +150,7 @@ export function splineCurveMonotone(points) {

monotoneAdjust(points, deltaK, mK);

monotoneCompute(points, mK);
monotoneCompute(points, mK, indexAxis);
}

function capControlPoint(pt, min, max) {
Expand Down Expand Up @@ -177,7 +182,7 @@ function capBezierPoints(points, area) {
/**
* @private
*/
export function _updateBezierControlPoints(points, options, area, loop) {
export function _updateBezierControlPoints(points, options, area, loop, indexAxis) {
let i, ilen, point, controlPoints;

// Only consider points that are drawn in case the spanGaps option is used
Expand All @@ -186,7 +191,7 @@ export function _updateBezierControlPoints(points, options, area, loop) {
}

if (options.cubicInterpolationMode === 'monotone') {
splineCurveMonotone(points);
splineCurveMonotone(points, indexAxis);
} else {
let prev = loop ? points[points.length - 1] : points[0];
for (i = 0, ilen = points.length; i < ilen; ++i) {
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/plugin.filler.js
Expand Up @@ -575,7 +575,7 @@ export default {
continue;
}

source.line.updateControlPoints(area);
source.line.updateControlPoints(area, source.axis);
if (draw) {
drawfill(chart.ctx, source, area);
}
Expand Down
@@ -0,0 +1,28 @@
module.exports = {
config: {
type: 'line',
data: {
datasets: [
{
data: [{x: 10, y: 1}, {x: 0, y: 5}, {x: -10, y: 15}, {x: -5, y: 19}],
borderColor: 'red',
fill: false,
cubicInterpolationMode: 'monotone'
}
]
},
options: {
indexAxis: 'y',
scales: {
x: {display: false, min: -15, max: 15},
y: {type: 'linear', display: false, min: 0, max: 20}
}
}
},
options: {
canvas: {
height: 256,
width: 512
}
}
};
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion types/helpers/helpers.curve.d.ts
Expand Up @@ -31,4 +31,4 @@ export interface MonotoneSplinePoint extends SplinePoint {
* between the dataset discrete points due to the interpolation.
* @see https://en.wikipedia.org/wiki/Monotone_cubic_interpolation
*/
export function splineCurveMonotone(points: readonly MonotoneSplinePoint[]): void;
export function splineCurveMonotone(points: readonly MonotoneSplinePoint[], indexAxis?: 'x' | 'y'): void;
2 changes: 1 addition & 1 deletion types/index.esm.d.ts
Expand Up @@ -1711,7 +1711,7 @@ export interface LineHoverOptions extends CommonHoverOptions {
export interface LineElement<T extends LineProps = LineProps, O extends LineOptions = LineOptions>
extends Element<T, O>,
VisualElement {
updateControlPoints(chartArea: ChartArea): void;
updateControlPoints(chartArea: ChartArea, indexAxis?: 'x' | 'y'): void;
points: Point[];
readonly segments: Segment[];
first(): Point | false;
Expand Down

0 comments on commit 0ba5c70

Please sign in to comment.