From: Akihiko Kusanagi Date: Fri, 14 May 2021 21:07:26 +0000 (+0800) Subject: Support monotone cubic interpolation for vertical line charts (#9084) X-Git-Tag: v3.3.0~17 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=0ba5c70618863dd0c415f08da80889dc18460677;p=thirdparty%2FChart.js.git Support monotone cubic interpolation for vertical line charts (#9084) * Support monotone cubic interpolation for vertical line charts * Use more more intuitive veriable names --- diff --git a/src/controllers/controller.line.js b/src/controllers/controller.line.js index 0346da426..3fca6a738 100644 --- a/src/controllers/controller.line.js +++ b/src/controllers/controller.line.js @@ -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(); } } diff --git a/src/elements/element.line.js b/src/elements/element.line.js index 6cb13703e..d2aff52c8 100644 --- a/src/elements/element.line.js +++ b/src/elements/element.line.js @@ -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; } } diff --git a/src/helpers/helpers.curve.js b/src/helpers/helpers.curve.js index 238a33c13..9c42c1dc6 100644 --- a/src/helpers/helpers.curve.js +++ b/src/helpers/helpers.curve.js @@ -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 @@ -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) { @@ -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]; } } } @@ -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); @@ -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] @@ -145,7 +150,7 @@ export function splineCurveMonotone(points) { monotoneAdjust(points, deltaK, mK); - monotoneCompute(points, mK); + monotoneCompute(points, mK, indexAxis); } function capControlPoint(pt, min, max) { @@ -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 @@ -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) { diff --git a/src/plugins/plugin.filler.js b/src/plugins/plugin.filler.js index 36467b33f..b8d0fed4b 100644 --- a/src/plugins/plugin.filler.js +++ b/src/plugins/plugin.filler.js @@ -575,7 +575,7 @@ export default { continue; } - source.line.updateControlPoints(area); + source.line.updateControlPoints(area, source.axis); if (draw) { drawfill(chart.ctx, source, area); } diff --git a/test/fixtures/element.line/cubicInterpolationMode/monotone.js b/test/fixtures/element.line/cubicInterpolationMode/monotone-horizontal.js similarity index 100% rename from test/fixtures/element.line/cubicInterpolationMode/monotone.js rename to test/fixtures/element.line/cubicInterpolationMode/monotone-horizontal.js diff --git a/test/fixtures/element.line/cubicInterpolationMode/monotone.png b/test/fixtures/element.line/cubicInterpolationMode/monotone-horizontal.png similarity index 100% rename from test/fixtures/element.line/cubicInterpolationMode/monotone.png rename to test/fixtures/element.line/cubicInterpolationMode/monotone-horizontal.png diff --git a/test/fixtures/element.line/cubicInterpolationMode/monotone-vertical.js b/test/fixtures/element.line/cubicInterpolationMode/monotone-vertical.js new file mode 100644 index 000000000..2debc627b --- /dev/null +++ b/test/fixtures/element.line/cubicInterpolationMode/monotone-vertical.js @@ -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 + } + } +}; diff --git a/test/fixtures/element.line/cubicInterpolationMode/monotone-vertical.png b/test/fixtures/element.line/cubicInterpolationMode/monotone-vertical.png new file mode 100644 index 000000000..51a80dbf0 Binary files /dev/null and b/test/fixtures/element.line/cubicInterpolationMode/monotone-vertical.png differ diff --git a/types/helpers/helpers.curve.d.ts b/types/helpers/helpers.curve.d.ts index 8182c5ab5..d845e1579 100644 --- a/types/helpers/helpers.curve.d.ts +++ b/types/helpers/helpers.curve.d.ts @@ -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; diff --git a/types/index.esm.d.ts b/types/index.esm.d.ts index e55068b5e..6f5d071cf 100644 --- a/types/index.esm.d.ts +++ b/types/index.esm.d.ts @@ -1711,7 +1711,7 @@ export interface LineHoverOptions extends CommonHoverOptions { export interface LineElement extends Element, VisualElement { - updateControlPoints(chartArea: ChartArea): void; + updateControlPoints(chartArea: ChartArea, indexAxis?: 'x' | 'y'): void; points: Point[]; readonly segments: Segment[]; first(): Point | false;