Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 3dffb4f

Browse filesBrowse 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
Copy full SHA for 3dffb4f

File tree

Expand file treeCollapse file tree

13 files changed

+395
-42
lines changed
Filter options
Expand file treeCollapse file tree

13 files changed

+395
-42
lines changed

‎src/core/core.controller.js

Copy file name to clipboardExpand all lines: src/core/core.controller.js
+6-30Lines changed: 6 additions & 30 deletions
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

Copy file name to clipboard
+33Lines changed: 33 additions & 0 deletions
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

Copy file name to clipboardExpand all lines: src/helpers/index.ts
+1Lines changed: 1 addition & 0 deletions
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

Copy file name to clipboardExpand all lines: src/plugins/plugin.filler/filler.drawing.js
+37-12Lines changed: 37 additions & 12 deletions
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

Copy file name to clipboardExpand all lines: src/types/index.d.ts
+10Lines changed: 10 additions & 0 deletions
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<
+78Lines changed: 78 additions & 0 deletions
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
+77Lines changed: 77 additions & 0 deletions
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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.