Skip to content

Commit 71b4d01

Browse files
authored
Add cartesian plane sample to show a advanced use of annotations (#843)
1 parent 32e1b45 commit 71b4d01

File tree

2 files changed

+264
-0
lines changed

2 files changed

+264
-0
lines changed

docs/.vuepress/config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ module.exports = {
218218
'interaction/interaction',
219219
'interaction/dragging',
220220
'interaction/selection',
221+
'interaction/cartesianplane',
221222
],
222223
},
223224
'utils',
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
# Cartesian plane
2+
3+
```js chart-editor
4+
// <block:setup:5>
5+
let rect;
6+
const MIN = -10;
7+
const MAX = 10;
8+
// </block:setup>
9+
10+
// <block:axes:1>
11+
const yAxis = {
12+
type: 'line',
13+
value: 0,
14+
scaleID: 'x',
15+
borderColor: 'lightGray',
16+
afterDraw({chart, element}) {
17+
const scale = chart.scales.y;
18+
const labelItems = scale.getLabelItems().filter((item) => Number.isInteger(parseFloat(item.label)));
19+
drawTicks(chart, scale, {
20+
labelItems,
21+
align: 'right',
22+
xy: (translation) => ({x: element.x - scale.options.ticks.padding, y: translation[1]})
23+
});
24+
}
25+
};
26+
const xAxis = {
27+
type: 'line',
28+
value: 0,
29+
scaleID: 'y',
30+
borderColor: 'lightGray',
31+
afterDraw({chart, element}) {
32+
const scale = chart.scales.x;
33+
const labelItems = scale.getLabelItems().filter((item) => Number.isInteger(parseFloat(item.label)) && parseFloat(item.label) !== 0);
34+
drawTicks(chart, scale, {
35+
labelItems,
36+
baseline: 'top',
37+
xy: (translation) => ({x: translation[0], y: element.y2 + scale.options.ticks.padding})
38+
});
39+
}
40+
};
41+
// </block:axes>
42+
43+
// <block:rectangle:2>
44+
const box = {
45+
type: 'box',
46+
display: () => !!rect,
47+
backgroundColor: 'rgba(255, 174, 201, 0.2)',
48+
borderColor: 'red',
49+
borderDash: [4, 4],
50+
xMin: () => rect ? rect.left : undefined,
51+
xMax: () => rect ? rect.right : undefined,
52+
yMin: () => rect ? rect.bottom : undefined,
53+
yMax: () => rect ? rect.top : undefined,
54+
z: -1
55+
};
56+
const A = {
57+
type: 'label',
58+
content: 'A',
59+
position: {
60+
x: 'end',
61+
y: 'end'
62+
},
63+
xValue: () => rect ? rect.left : undefined,
64+
yValue: () => rect ? rect.top : undefined
65+
};
66+
const B = {
67+
type: 'label',
68+
content: 'B',
69+
position: {
70+
x: 'start',
71+
y: 'end'
72+
},
73+
xValue: () => rect ? rect.right : undefined,
74+
yValue: () => rect ? rect.top : undefined
75+
};
76+
const C = {
77+
type: 'label',
78+
content: 'C',
79+
position: {
80+
x: 'end',
81+
y: 'start'
82+
},
83+
xValue: () => rect ? rect.left : undefined,
84+
yValue: () => rect ? rect.bottom : undefined
85+
};
86+
const D = {
87+
type: 'label',
88+
content: 'D',
89+
position: {
90+
x: 'start',
91+
y: 'start'
92+
},
93+
xValue: () => rect ? rect.right : undefined,
94+
yValue: () => rect ? rect.bottom : undefined
95+
};
96+
// </block:rectangle>
97+
98+
// <block:summary:3>
99+
const summary = {
100+
type: 'label',
101+
display: () => !!rect,
102+
drawTime: 'afterDraw',
103+
backgroundColor: 'white',
104+
borderColor: 'silver',
105+
borderWidth: 1,
106+
borderRadius: 6,
107+
content() {
108+
if (rect) {
109+
const result = [];
110+
result.push('A: (' + rect.left.toFixed(2) + ', ' + rect.top.toFixed(2) + ')');
111+
result.push('B: (' + rect.right.toFixed(2) + ', ' + rect.top.toFixed(2) + ')');
112+
result.push('C: (' + rect.left.toFixed(2) + ', ' + rect.bottom.toFixed(2) + ')');
113+
result.push('D: (' + rect.right.toFixed(2) + ', ' + rect.bottom.toFixed(2) + ')');
114+
const AB = Math.abs(rect.right - rect.left);
115+
const AC = Math.abs(rect.bottom - rect.top);
116+
result.push('AB = CD = ' + AB.toFixed(2));
117+
result.push('AC = BD = ' + AC.toFixed(2));
118+
result.push('AD = BC = ' + Math.sqrt(Math.pow(AB, 2) + Math.pow(AC, 2)).toFixed(2));
119+
result.push('Perimeter = ' + ((AB + AC) * 2).toFixed(2));
120+
result.push('Area = ' + (AB * AC).toFixed(2));
121+
return result;
122+
}
123+
},
124+
font: {
125+
size: 12,
126+
family: 'Courier'
127+
},
128+
padding: 5,
129+
position: {
130+
x: () => rect && rect.left < 0 ? 'end' : 'start',
131+
y: () => rect && rect.top <= 0 ? 'start' : 'end'
132+
},
133+
textAlign: () => rect && rect.left < 0 ? 'right' : 'left',
134+
xValue: () => rect && rect.left < 0 ? MAX : MIN,
135+
yValue: () => rect && rect.top <= 0 ? MAX : MIN
136+
};
137+
// </block:box>
138+
139+
// <block:utils:4>
140+
function calculateBox(e, chart) {
141+
const xValue = chart.scales.x.getValueForPixel(e.x);
142+
const yValue = chart.scales.y.getValueForPixel(e.y);
143+
return {
144+
left: xValue > 0 ? 0 : xValue,
145+
right: xValue > 0 ? xValue : 0,
146+
top: yValue > 0 ? yValue : 0,
147+
bottom: yValue > 0 ? 0 : yValue
148+
};
149+
}
150+
151+
function drawTicks(chart, scale, {labelItems, align, baseline, xy}) {
152+
const ctx = chart.ctx;
153+
ctx.save();
154+
ctx.beginPath();
155+
for (const item of labelItems) {
156+
const {font, label, options} = item;
157+
const {textAlign, textBaseline, translation} = options;
158+
const point = xy(translation);
159+
ctx.beginPath();
160+
ctx.font = font.string;
161+
ctx.textAlign = align || textAlign;
162+
ctx.textBaseline = baseline || textBaseline;
163+
ctx.fillStyle = 'silver';
164+
ctx.fillText(parseFloat(label).toFixed(0), point.x, point.y);
165+
ctx.fill();
166+
}
167+
ctx.restore();
168+
}
169+
// </block:utils>
170+
171+
/* <block:config:0> */
172+
const config = {
173+
type: 'scatter',
174+
options: {
175+
layout: {
176+
padding: {
177+
top: 20,
178+
left: 20,
179+
right: 20
180+
}
181+
},
182+
onClick(e, elements, chart) {
183+
rect = calculateBox(e, chart);
184+
chart.update();
185+
},
186+
elements: {
187+
labelAnnotation: {
188+
display: () => !!rect,
189+
borderWidth: 0,
190+
padding: 0,
191+
font: {
192+
size: 20,
193+
style: 'oblique'
194+
}
195+
}
196+
},
197+
scales: {
198+
x: {
199+
min: MIN,
200+
max: MAX,
201+
grid: {
202+
drawTicks: false
203+
},
204+
ticks: {
205+
display: false,
206+
stepSize: 0.5
207+
}
208+
},
209+
y: {
210+
min: MIN,
211+
max: MAX,
212+
grid: {
213+
drawTicks: false
214+
},
215+
ticks: {
216+
display: false,
217+
stepSize: 0.5
218+
}
219+
}
220+
},
221+
plugins: {
222+
annotation: {
223+
clip: false,
224+
common: {
225+
drawTime: 'beforeDraw'
226+
},
227+
annotations: {
228+
xAxis,
229+
yAxis,
230+
box,
231+
A, B, C, D,
232+
summary
233+
}
234+
},
235+
title: {
236+
display: true,
237+
text: 'Click on the chart to set the point to build the rectangle',
238+
position: 'bottom',
239+
padding: {
240+
top: 16,
241+
bottom: 0
242+
}
243+
}
244+
}
245+
}
246+
};
247+
/* </block:config> */
248+
249+
const actions = [
250+
{
251+
name: 'Reset',
252+
handler: function(chart) {
253+
rect = undefined;
254+
chart.update();
255+
}
256+
}
257+
];
258+
259+
module.exports = {
260+
actions: actions,
261+
config: config,
262+
};
263+
```

0 commit comments

Comments
 (0)