diff --git a/src/core/core.controller.js b/src/core/core.controller.js index 47b238da8aa..e0408ae212a 100644 --- a/src/core/core.controller.js +++ b/src/core/core.controller.js @@ -6,9 +6,8 @@ import {_detectPlatform} from '../platform/index.js'; import PluginService from './core.plugins.js'; import registry from './core.registry.js'; import Config, {determineAxis, getIndexAxis} from './core.config.js'; -import {retinaScale, _isDomSupported} from '../helpers/helpers.dom.js'; import {each, callback as callCallback, uid, valueOrDefault, _elementsEqual, isNullOrUndef, setsEqual, defined, isFunction, _isClickEvent} from '../helpers/helpers.core.js'; -import {clearCanvas, clipArea, createContext, unclipArea, _isPointInArea} from '../helpers/index.js'; +import {clearCanvas, clipArea, createContext, unclipArea, _isPointInArea, _isDomSupported, retinaScale, getDatasetClipArea} from '../helpers/index.js'; // @ts-ignore import {version} from '../../package.json'; import {debounce} from '../helpers/helpers.extras.js'; @@ -101,23 +100,6 @@ function determineLastEvent(e, lastEvent, inChartArea, isClick) { return e; } -function getSizeForArea(scale, chartArea, field) { - return scale.options.clip ? scale[field] : chartArea[field]; -} - -function getDatasetArea(meta, chartArea) { - const {xScale, yScale} = meta; - if (xScale && yScale) { - return { - left: getSizeForArea(xScale, chartArea, 'left'), - right: getSizeForArea(xScale, chartArea, 'right'), - top: getSizeForArea(yScale, chartArea, 'top'), - bottom: getSizeForArea(yScale, chartArea, 'bottom') - }; - } - return chartArea; -} - class Chart { static defaults = defaults; @@ -800,31 +782,25 @@ class Chart { */ _drawDataset(meta) { const ctx = this.ctx; - const clip = meta._clip; - const useClip = !clip.disabled; - const area = getDatasetArea(meta, this.chartArea); const args = { meta, index: meta.index, cancelable: true }; + // @ts-expect-error + const clip = getDatasetClipArea(this, meta); if (this.notifyPlugins('beforeDatasetDraw', args) === false) { return; } - if (useClip) { - clipArea(ctx, { - left: clip.left === false ? 0 : area.left - clip.left, - right: clip.right === false ? this.width : area.right + clip.right, - top: clip.top === false ? 0 : area.top - clip.top, - bottom: clip.bottom === false ? this.height : area.bottom + clip.bottom - }); + if (clip) { + clipArea(ctx, clip); } meta.controller.draw(); - if (useClip) { + if (clip) { unclipArea(ctx); } diff --git a/src/helpers/helpers.dataset.ts b/src/helpers/helpers.dataset.ts new file mode 100644 index 00000000000..000dcfe1977 --- /dev/null +++ b/src/helpers/helpers.dataset.ts @@ -0,0 +1,33 @@ +import type {Chart, ChartArea, ChartMeta, Scale, TRBL} from '../types/index.js'; + +function getSizeForArea(scale: Scale, chartArea: ChartArea, field: keyof ChartArea) { + return scale.options.clip ? scale[field] : chartArea[field]; +} + +function getDatasetArea(meta: ChartMeta, chartArea: ChartArea): TRBL { + const {xScale, yScale} = meta; + if (xScale && yScale) { + return { + left: getSizeForArea(xScale, chartArea, 'left'), + right: getSizeForArea(xScale, chartArea, 'right'), + top: getSizeForArea(yScale, chartArea, 'top'), + bottom: getSizeForArea(yScale, chartArea, 'bottom') + }; + } + return chartArea; +} + +export function getDatasetClipArea(chart: Chart, meta: ChartMeta): TRBL | false { + const clip = meta._clip; + if (clip.disabled) { + return false; + } + const area = getDatasetArea(meta, chart.chartArea); + + return { + left: clip.left === false ? 0 : area.left - (clip.left === true ? 0 : clip.left), + right: clip.right === false ? chart.width : area.right + (clip.right === true ? 0 : clip.right), + top: clip.top === false ? 0 : area.top - (clip.top === true ? 0 : clip.top), + bottom: clip.bottom === false ? chart.height : area.bottom + (clip.bottom === true ? 0 : clip.bottom) + }; +} diff --git a/src/helpers/index.ts b/src/helpers/index.ts index 1917ce740a1..9fde7b85951 100644 --- a/src/helpers/index.ts +++ b/src/helpers/index.ts @@ -13,3 +13,4 @@ export * from './helpers.options.js'; export * from './helpers.math.js'; export * from './helpers.rtl.js'; export * from './helpers.segment.js'; +export * from './helpers.dataset.js'; diff --git a/src/plugins/plugin.filler/filler.drawing.js b/src/plugins/plugin.filler/filler.drawing.js index 2e2fbd2b99e..9abb513cff0 100644 --- a/src/plugins/plugin.filler/filler.drawing.js +++ b/src/plugins/plugin.filler/filler.drawing.js @@ -1,35 +1,37 @@ -import {clipArea, unclipArea} from '../../helpers/index.js'; +import {clipArea, unclipArea, getDatasetClipArea} from '../../helpers/index.js'; import {_findSegmentEnd, _getBounds, _segments} from './filler.segment.js'; import {_getTarget} from './filler.target.js'; export function _drawfill(ctx, source, area) { const target = _getTarget(source); - const {line, scale, axis} = source; + const {chart, index, line, scale, axis} = source; const lineOpts = line.options; const fillOption = lineOpts.fill; const color = lineOpts.backgroundColor; const {above = color, below = color} = fillOption || {}; + const meta = chart.getDatasetMeta(index); + const clip = getDatasetClipArea(chart, meta); if (target && line.points.length) { clipArea(ctx, area); - doFill(ctx, {line, target, above, below, area, scale, axis}); + doFill(ctx, {line, target, above, below, area, scale, axis, clip}); unclipArea(ctx); } } function doFill(ctx, cfg) { - const {line, target, above, below, area, scale} = cfg; + const {line, target, above, below, area, scale, clip} = cfg; const property = line._loop ? 'angle' : cfg.axis; ctx.save(); if (property === 'x' && below !== above) { clipVertical(ctx, target, area.top); - fill(ctx, {line, target, color: above, scale, property}); + fill(ctx, {line, target, color: above, scale, property, clip}); ctx.restore(); ctx.save(); clipVertical(ctx, target, area.bottom); } - fill(ctx, {line, target, color: below, scale, property}); + fill(ctx, {line, target, color: below, scale, property, clip}); ctx.restore(); } @@ -65,7 +67,7 @@ function clipVertical(ctx, target, clipY) { } function fill(ctx, cfg) { - const {line, target, property, color, scale} = cfg; + const {line, target, property, color, scale, clip} = cfg; const segments = _segments(line, target, property); for (const {source: src, target: tgt, start, end} of segments) { @@ -75,7 +77,7 @@ function fill(ctx, cfg) { ctx.save(); ctx.fillStyle = backgroundColor; - clipBounds(ctx, scale, notShape && _getBounds(property, start, end)); + clipBounds(ctx, scale, clip, notShape && _getBounds(property, start, end)); ctx.beginPath(); @@ -103,12 +105,35 @@ function fill(ctx, cfg) { } } -function clipBounds(ctx, scale, bounds) { - const {top, bottom} = scale.chart.chartArea; +function clipBounds(ctx, scale, clip, bounds) { + const chartArea = scale.chart.chartArea; const {property, start, end} = bounds || {}; - if (property === 'x') { + + if (property === 'x' || property === 'y') { + let left, top, right, bottom; + + if (property === 'x') { + left = start; + top = chartArea.top; + right = end; + bottom = chartArea.bottom; + } else { + left = chartArea.left; + top = start; + right = chartArea.right; + bottom = end; + } + ctx.beginPath(); - ctx.rect(start, top, end - start, bottom - top); + + if (clip) { + left = Math.max(left, clip.left); + right = Math.min(right, clip.right); + top = Math.max(top, clip.top); + bottom = Math.min(bottom, clip.bottom); + } + + ctx.rect(left, top, right - left, bottom - top); ctx.clip(); } } diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 14461328a92..807fe820879 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -429,6 +429,15 @@ export declare const RadarController: ChartComponent & { prototype: RadarController; new (chart: Chart, datasetIndex: number): RadarController; }; + +interface ChartMetaClip { + left: number | boolean; + top: number | boolean; + right: number | boolean; + bottom: number | boolean; + disabled: boolean; +} + interface ChartMetaCommon { type: string; controller: DatasetController; @@ -462,6 +471,7 @@ interface ChartMetaCommon v - 10), + fill: '1', + borderColor: 'rgb(255, 0, 0)', + backgroundColor: 'rgba(255, 0, 0, 0.25)', + xAxisID: 'x1', + }, + { + data: values, + fill: false, + borderColor: 'rgb(255, 0, 0)', + xAxisID: 'x1', + }, + { + data: values, + fill: false, + borderColor: 'rgb(0, 0, 255)', + xAxisID: 'x2', + }, + { + data: values.map(v => v + 10), + fill: '-1', + borderColor: 'rgb(0, 0, 255)', + backgroundColor: 'rgba(0, 0, 255, 0.25)', + xAxisID: 'x2', + } + ] + }, + options: { + clip: false, + indexAxis: 'y', + animation: false, + responsive: false, + plugins: { + legend: false, + title: false, + tooltip: false + }, + elements: { + point: { + radius: 0 + }, + line: { + cubicInterpolationMode: 'monotone', + borderColor: 'transparent', + tension: 0 + } + }, + scales: { + x2: { + axis: 'x', + stack: 'stack', + max: 80, + display: false, + }, + x1: { + min: 50, + axis: 'x', + stack: 'stack', + display: false, + }, + y: { + display: false, + } + } + } + }, +}; diff --git a/test/fixtures/plugin.filler/line/dataset/clip-bounds-x-off.png b/test/fixtures/plugin.filler/line/dataset/clip-bounds-x-off.png new file mode 100644 index 00000000000..f050a4759f3 Binary files /dev/null and b/test/fixtures/plugin.filler/line/dataset/clip-bounds-x-off.png differ diff --git a/test/fixtures/plugin.filler/line/dataset/clip-bounds-x.js b/test/fixtures/plugin.filler/line/dataset/clip-bounds-x.js new file mode 100644 index 00000000000..0ba25ac3122 --- /dev/null +++ b/test/fixtures/plugin.filler/line/dataset/clip-bounds-x.js @@ -0,0 +1,77 @@ +const labels = [1, 2, 3, 4, 5, 6, 7]; +const values = [65, 59, 80, 81, 56, 55, 40]; + +module.exports = { + description: 'https://github.com/chartjs/Chart.js/issues/12052', + config: { + type: 'line', + data: { + labels, + datasets: [ + { + data: values.map(v => v - 10), + fill: '1', + borderColor: 'rgb(255, 0, 0)', + backgroundColor: 'rgba(255, 0, 0, 0.25)', + xAxisID: 'x1', + }, + { + data: values, + fill: false, + borderColor: 'rgb(255, 0, 0)', + xAxisID: 'x1', + }, + { + data: values, + fill: false, + borderColor: 'rgb(0, 0, 255)', + xAxisID: 'x2', + }, + { + data: values.map(v => v + 10), + fill: '-1', + borderColor: 'rgb(0, 0, 255)', + backgroundColor: 'rgba(0, 0, 255, 0.25)', + xAxisID: 'x2', + } + ] + }, + options: { + indexAxis: 'y', + animation: false, + responsive: false, + plugins: { + legend: false, + title: false, + tooltip: false + }, + elements: { + point: { + radius: 0 + }, + line: { + cubicInterpolationMode: 'monotone', + borderColor: 'transparent', + tension: 0 + } + }, + scales: { + x2: { + axis: 'x', + stack: 'stack', + max: 80, + display: false, + }, + x1: { + min: 50, + axis: 'x', + stack: 'stack', + display: false, + }, + y: { + display: false, + } + } + } + }, +}; diff --git a/test/fixtures/plugin.filler/line/dataset/clip-bounds-x.png b/test/fixtures/plugin.filler/line/dataset/clip-bounds-x.png new file mode 100644 index 00000000000..4f1dfdd6cab Binary files /dev/null and b/test/fixtures/plugin.filler/line/dataset/clip-bounds-x.png differ diff --git a/test/fixtures/plugin.filler/line/dataset/clip-bounds-y-off.js b/test/fixtures/plugin.filler/line/dataset/clip-bounds-y-off.js new file mode 100644 index 00000000000..16a9759bb7d --- /dev/null +++ b/test/fixtures/plugin.filler/line/dataset/clip-bounds-y-off.js @@ -0,0 +1,77 @@ +const labels = [1, 2, 3, 4, 5, 6, 7]; +const values = [65, 59, 80, 81, 56, 55, 40]; + +module.exports = { + description: 'https://github.com/chartjs/Chart.js/issues/12052', + config: { + type: 'line', + data: { + labels, + datasets: [ + { + data: values.map(v => v - 10), + fill: '1', + borderColor: 'rgb(255, 0, 0)', + backgroundColor: 'rgba(255, 0, 0, 0.25)', + yAxisID: 'y1', + }, + { + data: values, + fill: false, + borderColor: 'rgb(255, 0, 0)', + yAxisID: 'y1', + }, + { + data: values, + fill: false, + borderColor: 'rgb(0, 0, 255)', + yAxisID: 'y2', + }, + { + data: values.map(v => v + 10), + fill: '-1', + borderColor: 'rgb(0, 0, 255)', + backgroundColor: 'rgba(0, 0, 255, 0.25)', + yAxisID: 'y2', + } + ] + }, + options: { + clip: false, + animation: false, + responsive: false, + plugins: { + legend: false, + title: false, + tooltip: false + }, + elements: { + point: { + radius: 0 + }, + line: { + cubicInterpolationMode: 'monotone', + borderColor: 'transparent', + tension: 0 + } + }, + scales: { + y2: { + axis: 'y', + stack: 'stack', + max: 80, + display: false, + }, + y1: { + min: 50, + axis: 'y', + stack: 'stack', + display: false, + }, + x: { + display: false, + } + } + } + }, +}; diff --git a/test/fixtures/plugin.filler/line/dataset/clip-bounds-y-off.png b/test/fixtures/plugin.filler/line/dataset/clip-bounds-y-off.png new file mode 100644 index 00000000000..a2b8766f84d Binary files /dev/null and b/test/fixtures/plugin.filler/line/dataset/clip-bounds-y-off.png differ diff --git a/test/fixtures/plugin.filler/line/dataset/clip-bounds-y.js b/test/fixtures/plugin.filler/line/dataset/clip-bounds-y.js new file mode 100644 index 00000000000..cbfc6d40381 --- /dev/null +++ b/test/fixtures/plugin.filler/line/dataset/clip-bounds-y.js @@ -0,0 +1,76 @@ +const labels = [1, 2, 3, 4, 5, 6, 7]; +const values = [65, 59, 80, 81, 56, 55, 40]; + +module.exports = { + description: 'https://github.com/chartjs/Chart.js/issues/12052', + config: { + type: 'line', + data: { + labels, + datasets: [ + { + data: values.map(v => v - 10), + fill: '1', + borderColor: 'rgb(255, 0, 0)', + backgroundColor: 'rgba(255, 0, 0, 0.25)', + yAxisID: 'y1', + }, + { + data: values, + fill: false, + borderColor: 'rgb(255, 0, 0)', + yAxisID: 'y1', + }, + { + data: values, + fill: false, + borderColor: 'rgb(0, 0, 255)', + yAxisID: 'y2', + }, + { + data: values.map(v => v + 10), + fill: '-1', + borderColor: 'rgb(0, 0, 255)', + backgroundColor: 'rgba(0, 0, 255, 0.25)', + yAxisID: 'y2', + } + ] + }, + options: { + animation: false, + responsive: false, + plugins: { + legend: false, + title: false, + tooltip: false + }, + elements: { + point: { + radius: 0 + }, + line: { + cubicInterpolationMode: 'monotone', + borderColor: 'transparent', + tension: 0 + } + }, + scales: { + y2: { + axis: 'y', + stack: 'stack', + max: 80, + display: false, + }, + y1: { + min: 50, + axis: 'y', + stack: 'stack', + display: false, + }, + x: { + display: false, + } + } + } + }, +}; diff --git a/test/fixtures/plugin.filler/line/dataset/clip-bounds-y.png b/test/fixtures/plugin.filler/line/dataset/clip-bounds-y.png new file mode 100644 index 00000000000..137e0315bb2 Binary files /dev/null and b/test/fixtures/plugin.filler/line/dataset/clip-bounds-y.png differ