Skip to content

Commit 3dffb4f

Browse files
authored
fix: respect dataset clipping area when filling line charts (#12057)
* fix(plugin.filler): respect dataset clipping area when filling line charts The filling area must respect the dataset's clipping area when clipping is enabled. Before this change, the line would be clipped according to the dataset's area but the fill would overlap other datasets. Closes #12052 * chore(plugin.filler): use @ts-expect-error instead of @ts-ignore
1 parent a647e0d commit 3dffb4f

13 files changed

+395
-42
lines changed

src/core/core.controller.js

+6-30
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@ import {_detectPlatform} from '../platform/index.js';
66
import PluginService from './core.plugins.js';
77
import registry from './core.registry.js';
88
import Config, {determineAxis, getIndexAxis} from './core.config.js';
9-
import {retinaScale, _isDomSupported} from '../helpers/helpers.dom.js';
109
import {each, callback as callCallback, uid, valueOrDefault, _elementsEqual, isNullOrUndef, setsEqual, defined, isFunction, _isClickEvent} from '../helpers/helpers.core.js';
11-
import {clearCanvas, clipArea, createContext, unclipArea, _isPointInArea} from '../helpers/index.js';
10+
import {clearCanvas, clipArea, createContext, unclipArea, _isPointInArea, _isDomSupported, retinaScale, getDatasetClipArea} from '../helpers/index.js';
1211
// @ts-ignore
1312
import {version} from '../../package.json';
1413
import {debounce} from '../helpers/helpers.extras.js';
@@ -101,23 +100,6 @@ function determineLastEvent(e, lastEvent, inChartArea, isClick) {
101100
return e;
102101
}
103102

104-
function getSizeForArea(scale, chartArea, field) {
105-
return scale.options.clip ? scale[field] : chartArea[field];
106-
}
107-
108-
function getDatasetArea(meta, chartArea) {
109-
const {xScale, yScale} = meta;
110-
if (xScale && yScale) {
111-
return {
112-
left: getSizeForArea(xScale, chartArea, 'left'),
113-
right: getSizeForArea(xScale, chartArea, 'right'),
114-
top: getSizeForArea(yScale, chartArea, 'top'),
115-
bottom: getSizeForArea(yScale, chartArea, 'bottom')
116-
};
117-
}
118-
return chartArea;
119-
}
120-
121103
class Chart {
122104

123105
static defaults = defaults;
@@ -800,31 +782,25 @@ class Chart {
800782
*/
801783
_drawDataset(meta) {
802784
const ctx = this.ctx;
803-
const clip = meta._clip;
804-
const useClip = !clip.disabled;
805-
const area = getDatasetArea(meta, this.chartArea);
806785
const args = {
807786
meta,
808787
index: meta.index,
809788
cancelable: true
810789
};
790+
// @ts-expect-error
791+
const clip = getDatasetClipArea(this, meta);
811792

812793
if (this.notifyPlugins('beforeDatasetDraw', args) === false) {
813794
return;
814795
}
815796

816-
if (useClip) {
817-
clipArea(ctx, {
818-
left: clip.left === false ? 0 : area.left - clip.left,
819-
right: clip.right === false ? this.width : area.right + clip.right,
820-
top: clip.top === false ? 0 : area.top - clip.top,
821-
bottom: clip.bottom === false ? this.height : area.bottom + clip.bottom
822-
});
797+
if (clip) {
798+
clipArea(ctx, clip);
823799
}
824800

825801
meta.controller.draw();
826802

827-
if (useClip) {
803+
if (clip) {
828804
unclipArea(ctx);
829805
}
830806

src/helpers/helpers.dataset.ts

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import type {Chart, ChartArea, ChartMeta, Scale, TRBL} from '../types/index.js';
2+
3+
function getSizeForArea(scale: Scale, chartArea: ChartArea, field: keyof ChartArea) {
4+
return scale.options.clip ? scale[field] : chartArea[field];
5+
}
6+
7+
function getDatasetArea(meta: ChartMeta, chartArea: ChartArea): TRBL {
8+
const {xScale, yScale} = meta;
9+
if (xScale && yScale) {
10+
return {
11+
left: getSizeForArea(xScale, chartArea, 'left'),
12+
right: getSizeForArea(xScale, chartArea, 'right'),
13+
top: getSizeForArea(yScale, chartArea, 'top'),
14+
bottom: getSizeForArea(yScale, chartArea, 'bottom')
15+
};
16+
}
17+
return chartArea;
18+
}
19+
20+
export function getDatasetClipArea(chart: Chart, meta: ChartMeta): TRBL | false {
21+
const clip = meta._clip;
22+
if (clip.disabled) {
23+
return false;
24+
}
25+
const area = getDatasetArea(meta, chart.chartArea);
26+
27+
return {
28+
left: clip.left === false ? 0 : area.left - (clip.left === true ? 0 : clip.left),
29+
right: clip.right === false ? chart.width : area.right + (clip.right === true ? 0 : clip.right),
30+
top: clip.top === false ? 0 : area.top - (clip.top === true ? 0 : clip.top),
31+
bottom: clip.bottom === false ? chart.height : area.bottom + (clip.bottom === true ? 0 : clip.bottom)
32+
};
33+
}

src/helpers/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ export * from './helpers.options.js';
1313
export * from './helpers.math.js';
1414
export * from './helpers.rtl.js';
1515
export * from './helpers.segment.js';
16+
export * from './helpers.dataset.js';

src/plugins/plugin.filler/filler.drawing.js

+37-12
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,37 @@
1-
import {clipArea, unclipArea} from '../../helpers/index.js';
1+
import {clipArea, unclipArea, getDatasetClipArea} from '../../helpers/index.js';
22
import {_findSegmentEnd, _getBounds, _segments} from './filler.segment.js';
33
import {_getTarget} from './filler.target.js';
44

55
export function _drawfill(ctx, source, area) {
66
const target = _getTarget(source);
7-
const {line, scale, axis} = source;
7+
const {chart, index, line, scale, axis} = source;
88
const lineOpts = line.options;
99
const fillOption = lineOpts.fill;
1010
const color = lineOpts.backgroundColor;
1111
const {above = color, below = color} = fillOption || {};
12+
const meta = chart.getDatasetMeta(index);
13+
const clip = getDatasetClipArea(chart, meta);
1214
if (target && line.points.length) {
1315
clipArea(ctx, area);
14-
doFill(ctx, {line, target, above, below, area, scale, axis});
16+
doFill(ctx, {line, target, above, below, area, scale, axis, clip});
1517
unclipArea(ctx);
1618
}
1719
}
1820

1921
function doFill(ctx, cfg) {
20-
const {line, target, above, below, area, scale} = cfg;
22+
const {line, target, above, below, area, scale, clip} = cfg;
2123
const property = line._loop ? 'angle' : cfg.axis;
2224

2325
ctx.save();
2426

2527
if (property === 'x' && below !== above) {
2628
clipVertical(ctx, target, area.top);
27-
fill(ctx, {line, target, color: above, scale, property});
29+
fill(ctx, {line, target, color: above, scale, property, clip});
2830
ctx.restore();
2931
ctx.save();
3032
clipVertical(ctx, target, area.bottom);
3133
}
32-
fill(ctx, {line, target, color: below, scale, property});
34+
fill(ctx, {line, target, color: below, scale, property, clip});
3335

3436
ctx.restore();
3537
}
@@ -65,7 +67,7 @@ function clipVertical(ctx, target, clipY) {
6567
}
6668

6769
function fill(ctx, cfg) {
68-
const {line, target, property, color, scale} = cfg;
70+
const {line, target, property, color, scale, clip} = cfg;
6971
const segments = _segments(line, target, property);
7072

7173
for (const {source: src, target: tgt, start, end} of segments) {
@@ -75,7 +77,7 @@ function fill(ctx, cfg) {
7577
ctx.save();
7678
ctx.fillStyle = backgroundColor;
7779

78-
clipBounds(ctx, scale, notShape && _getBounds(property, start, end));
80+
clipBounds(ctx, scale, clip, notShape && _getBounds(property, start, end));
7981

8082
ctx.beginPath();
8183

@@ -103,12 +105,35 @@ function fill(ctx, cfg) {
103105
}
104106
}
105107

106-
function clipBounds(ctx, scale, bounds) {
107-
const {top, bottom} = scale.chart.chartArea;
108+
function clipBounds(ctx, scale, clip, bounds) {
109+
const chartArea = scale.chart.chartArea;
108110
const {property, start, end} = bounds || {};
109-
if (property === 'x') {
111+
112+
if (property === 'x' || property === 'y') {
113+
let left, top, right, bottom;
114+
115+
if (property === 'x') {
116+
left = start;
117+
top = chartArea.top;
118+
right = end;
119+
bottom = chartArea.bottom;
120+
} else {
121+
left = chartArea.left;
122+
top = start;
123+
right = chartArea.right;
124+
bottom = end;
125+
}
126+
110127
ctx.beginPath();
111-
ctx.rect(start, top, end - start, bottom - top);
128+
129+
if (clip) {
130+
left = Math.max(left, clip.left);
131+
right = Math.min(right, clip.right);
132+
top = Math.max(top, clip.top);
133+
bottom = Math.min(bottom, clip.bottom);
134+
}
135+
136+
ctx.rect(left, top, right - left, bottom - top);
112137
ctx.clip();
113138
}
114139
}

src/types/index.d.ts

+10
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,15 @@ export declare const RadarController: ChartComponent & {
429429
prototype: RadarController;
430430
new (chart: Chart, datasetIndex: number): RadarController;
431431
};
432+
433+
interface ChartMetaClip {
434+
left: number | boolean;
435+
top: number | boolean;
436+
right: number | boolean;
437+
bottom: number | boolean;
438+
disabled: boolean;
439+
}
440+
432441
interface ChartMetaCommon<TElement extends Element = Element, TDatasetElement extends Element = Element> {
433442
type: string;
434443
controller: DatasetController;
@@ -462,6 +471,7 @@ interface ChartMetaCommon<TElement extends Element = Element, TDatasetElement ex
462471
_sorted: boolean;
463472
_stacked: boolean | 'single';
464473
_parsed: unknown[];
474+
_clip: ChartMetaClip;
465475
}
466476

467477
export type ChartMeta<
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
const labels = [1, 2, 3, 4, 5, 6, 7];
2+
const values = [65, 59, 80, 81, 56, 55, 40];
3+
4+
module.exports = {
5+
description: 'https://github.com./chartjs/Chart.js/issues/12052',
6+
config: {
7+
type: 'line',
8+
data: {
9+
labels,
10+
datasets: [
11+
{
12+
data: values.map(v => v - 10),
13+
fill: '1',
14+
borderColor: 'rgb(255, 0, 0)',
15+
backgroundColor: 'rgba(255, 0, 0, 0.25)',
16+
xAxisID: 'x1',
17+
},
18+
{
19+
data: values,
20+
fill: false,
21+
borderColor: 'rgb(255, 0, 0)',
22+
xAxisID: 'x1',
23+
},
24+
{
25+
data: values,
26+
fill: false,
27+
borderColor: 'rgb(0, 0, 255)',
28+
xAxisID: 'x2',
29+
},
30+
{
31+
data: values.map(v => v + 10),
32+
fill: '-1',
33+
borderColor: 'rgb(0, 0, 255)',
34+
backgroundColor: 'rgba(0, 0, 255, 0.25)',
35+
xAxisID: 'x2',
36+
}
37+
]
38+
},
39+
options: {
40+
clip: false,
41+
indexAxis: 'y',
42+
animation: false,
43+
responsive: false,
44+
plugins: {
45+
legend: false,
46+
title: false,
47+
tooltip: false
48+
},
49+
elements: {
50+
point: {
51+
radius: 0
52+
},
53+
line: {
54+
cubicInterpolationMode: 'monotone',
55+
borderColor: 'transparent',
56+
tension: 0
57+
}
58+
},
59+
scales: {
60+
x2: {
61+
axis: 'x',
62+
stack: 'stack',
63+
max: 80,
64+
display: false,
65+
},
66+
x1: {
67+
min: 50,
68+
axis: 'x',
69+
stack: 'stack',
70+
display: false,
71+
},
72+
y: {
73+
display: false,
74+
}
75+
}
76+
}
77+
},
78+
};
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
const labels = [1, 2, 3, 4, 5, 6, 7];
2+
const values = [65, 59, 80, 81, 56, 55, 40];
3+
4+
module.exports = {
5+
description: 'https://github.com./chartjs/Chart.js/issues/12052',
6+
config: {
7+
type: 'line',
8+
data: {
9+
labels,
10+
datasets: [
11+
{
12+
data: values.map(v => v - 10),
13+
fill: '1',
14+
borderColor: 'rgb(255, 0, 0)',
15+
backgroundColor: 'rgba(255, 0, 0, 0.25)',
16+
xAxisID: 'x1',
17+
},
18+
{
19+
data: values,
20+
fill: false,
21+
borderColor: 'rgb(255, 0, 0)',
22+
xAxisID: 'x1',
23+
},
24+
{
25+
data: values,
26+
fill: false,
27+
borderColor: 'rgb(0, 0, 255)',
28+
xAxisID: 'x2',
29+
},
30+
{
31+
data: values.map(v => v + 10),
32+
fill: '-1',
33+
borderColor: 'rgb(0, 0, 255)',
34+
backgroundColor: 'rgba(0, 0, 255, 0.25)',
35+
xAxisID: 'x2',
36+
}
37+
]
38+
},
39+
options: {
40+
indexAxis: 'y',
41+
animation: false,
42+
responsive: false,
43+
plugins: {
44+
legend: false,
45+
title: false,
46+
tooltip: false
47+
},
48+
elements: {
49+
point: {
50+
radius: 0
51+
},
52+
line: {
53+
cubicInterpolationMode: 'monotone',
54+
borderColor: 'transparent',
55+
tension: 0
56+
}
57+
},
58+
scales: {
59+
x2: {
60+
axis: 'x',
61+
stack: 'stack',
62+
max: 80,
63+
display: false,
64+
},
65+
x1: {
66+
min: 50,
67+
axis: 'x',
68+
stack: 'stack',
69+
display: false,
70+
},
71+
y: {
72+
display: false,
73+
}
74+
}
75+
}
76+
},
77+
};
Loading

0 commit comments

Comments
 (0)