From 45ecff1b34f676569ba19d54388ece40414c021f Mon Sep 17 00:00:00 2001 From: stockiNail Date: Thu, 2 Feb 2023 15:33:03 +0100 Subject: [PATCH 1/2] Add cartesian plane sample to show a advanced use of annotations --- docs/.vuepress/config.js | 1 + docs/samples/interaction/cartesianplane.md | 263 +++++++++++++++++++++ 2 files changed, 264 insertions(+) create mode 100644 docs/samples/interaction/cartesianplane.md diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 95bf2fd74..7f9d84aa2 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -218,6 +218,7 @@ module.exports = { 'interaction/interaction', 'interaction/dragging', 'interaction/selection', + 'interaction/cartesianplane', ], }, 'utils', diff --git a/docs/samples/interaction/cartesianplane.md b/docs/samples/interaction/cartesianplane.md new file mode 100644 index 000000000..5cf4695e8 --- /dev/null +++ b/docs/samples/interaction/cartesianplane.md @@ -0,0 +1,263 @@ +# Cartesian plane + +```js chart-editor +// +let RECT; +const MIN = -10; +const MAX = 10; +// + +// +const yAxis = { + type: 'line', + value: 0, + scaleID: 'x', + borderColor: 'lightGray', + afterDraw({chart, element}) { + const scale = chart.scales.y; + const labelItems = scale.getLabelItems().filter((item) => Number.isInteger(parseFloat(item.label))); + drawTicks(chart, scale, { + labelItems, + align: 'right', + xy: (translation) => ({x: element.x - scale.options.ticks.padding, y: translation[1]}) + }); + } +}; +const xAxis = { + type: 'line', + value: 0, + scaleID: 'y', + borderColor: 'lightGray', + afterDraw({chart, element}) { + const scale = chart.scales.x; + const labelItems = scale.getLabelItems().filter((item) => Number.isInteger(parseFloat(item.label)) && parseFloat(item.label) !== 0); + drawTicks(chart, scale, { + labelItems, + baseline: 'top', + xy: (translation) => ({x: translation[0], y: element.y2 + scale.options.ticks.padding}) + }); + } +}; +// + +// +const box = { + type: 'box', + display: () => !!RECT, + backgroundColor: 'rgba(255, 174, 201, 0.2)', + borderColor: 'red', + borderDash: [4, 4], + xMin: () => RECT ? RECT.left : undefined, + xMax: () => RECT ? RECT.right : undefined, + yMin: () => RECT ? RECT.bottom : undefined, + yMax: () => RECT ? RECT.top : undefined, + z: -1 +}; +const A = { + type: 'label', + content: 'A', + position: { + x: 'end', + y: 'end' + }, + xValue: () => RECT ? RECT.left : undefined, + yValue: () => RECT ? RECT.top : undefined +}; +const B = { + type: 'label', + content: 'B', + position: { + x: 'start', + y: 'end' + }, + xValue: () => RECT ? RECT.right : undefined, + yValue: () => RECT ? RECT.top : undefined +}; +const C = { + type: 'label', + content: 'C', + position: { + x: 'end', + y: 'start' + }, + xValue: () => RECT ? RECT.left : undefined, + yValue: () => RECT ? RECT.bottom : undefined +}; +const D = { + type: 'label', + content: 'D', + position: { + x: 'start', + y: 'start' + }, + xValue: () => RECT ? RECT.right : undefined, + yValue: () => RECT ? RECT.bottom : undefined +}; +// + +// +const summary = { + type: 'label', + display: () => !!RECT, + drawTime: 'afterDraw', + backgroundColor: 'white', + borderColor: 'silver', + borderWidth: 1, + borderRadius: 6, + content() { + if (RECT) { + const result = []; + result.push('A: (' + RECT.left.toFixed(2) + ', ' + RECT.top.toFixed(2) + ')'); + result.push('B: (' + RECT.right.toFixed(2) + ', ' + RECT.top.toFixed(2) + ')'); + result.push('C: (' + RECT.left.toFixed(2) + ', ' + RECT.bottom.toFixed(2) + ')'); + result.push('D: (' + RECT.right.toFixed(2) + ', ' + RECT.bottom.toFixed(2) + ')'); + const AB = Math.abs(RECT.right - RECT.left); + const AC = Math.abs(RECT.bottom - RECT.top); + result.push('AB = CD = ' + AB.toFixed(2)); + result.push('AC = BD = ' + AC.toFixed(2)); + result.push('AD = BC = ' + Math.sqrt(Math.pow(AB, 2) + Math.pow(AC, 2)).toFixed(2)); + result.push('Perimeter = ' + ((AB + AC) * 2).toFixed(2)); + result.push('Area = ' + (AB * AC).toFixed(2)); + return result; + } + }, + font: { + size: 12, + family: 'Courier' + }, + padding: 5, + position: { + x: () => RECT && RECT.left < 0 ? 'end' : 'start', + y: () => RECT && RECT.top <= 0 ? 'start' : 'end' + }, + textAlign: () => RECT && RECT.left < 0 ? 'right' : 'left', + xValue: () => RECT && RECT.left < 0 ? MAX : MIN, + yValue: () => RECT && RECT.top <= 0 ? MAX : MIN +}; +// + +// +function calculateBox(e, chart) { + const xValue = chart.scales.x.getValueForPixel(e.x); + const yValue = chart.scales.y.getValueForPixel(e.y); + return { + left: xValue > 0 ? 0 : xValue, + right: xValue > 0 ? xValue : 0, + top: yValue > 0 ? yValue : 0, + bottom: yValue > 0 ? 0 : yValue + }; +} + +function drawTicks(chart, scale, {labelItems, align, baseline, xy}) { + const ctx = chart.ctx; + ctx.save(); + ctx.beginPath(); + for (const item of labelItems) { + const {font, label, options} = item; + const {textAlign, textBaseline, translation} = options; + const point = xy(translation); + ctx.beginPath(); + ctx.font = font.string; + ctx.textAlign = align || textAlign; + ctx.textBaseline = baseline || textBaseline; + ctx.fillStyle = 'silver'; + ctx.fillText(parseFloat(label).toFixed(0), point.x, point.y); + ctx.fill(); + } + ctx.restore(); +} +// + +/* */ +const config = { + type: 'scatter', + options: { + layout: { + padding: { + top: 20, + left: 20, + right: 20 + } + }, + onClick(e, elements, chart) { + RECT = calculateBox(e, chart); + chart.update(); + }, + elements: { + labelAnnotation: { + display: () => !!RECT, + borderWidth: 0, + padding: 0, + font: { + size: 20, + style: 'oblique' + } + } + }, + scales: { + x: { + min: MIN, + max: MAX, + grid: { + drawTicks: false + }, + ticks: { + display: false, + stepSize: 0.5 + } + }, + y: { + min: MIN, + max: MAX, + grid: { + drawTicks: false + }, + ticks: { + display: false, + stepSize: 0.5 + } + } + }, + plugins: { + annotation: { + clip: false, + common: { + drawTime: 'beforeDraw' + }, + annotations: { + xAxis, + yAxis, + box, + A, B, C, D, + summary + } + }, + title: { + display: true, + text: 'Click on the chart to set the point to build the rectangle', + position: 'bottom', + padding: { + top: 16, + bottom: 0 + } + } + } + } +}; +/* */ + +const actions = [ + { + name: 'Reset', + handler: function(chart) { + RECT = undefined; + chart.update(); + } + } +]; + +module.exports = { + actions: actions, + config: config, +}; +``` \ No newline at end of file From 704be622713bb080592531a3b17666942a102905 Mon Sep 17 00:00:00 2001 From: stockiNail Date: Thu, 2 Feb 2023 16:28:18 +0100 Subject: [PATCH 2/2] apply review --- docs/samples/interaction/cartesianplane.md | 60 +++++++++++----------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/docs/samples/interaction/cartesianplane.md b/docs/samples/interaction/cartesianplane.md index 5cf4695e8..15d0a5a65 100644 --- a/docs/samples/interaction/cartesianplane.md +++ b/docs/samples/interaction/cartesianplane.md @@ -2,7 +2,7 @@ ```js chart-editor // -let RECT; +let rect; const MIN = -10; const MAX = 10; // @@ -43,14 +43,14 @@ const xAxis = { // const box = { type: 'box', - display: () => !!RECT, + display: () => !!rect, backgroundColor: 'rgba(255, 174, 201, 0.2)', borderColor: 'red', borderDash: [4, 4], - xMin: () => RECT ? RECT.left : undefined, - xMax: () => RECT ? RECT.right : undefined, - yMin: () => RECT ? RECT.bottom : undefined, - yMax: () => RECT ? RECT.top : undefined, + xMin: () => rect ? rect.left : undefined, + xMax: () => rect ? rect.right : undefined, + yMin: () => rect ? rect.bottom : undefined, + yMax: () => rect ? rect.top : undefined, z: -1 }; const A = { @@ -60,8 +60,8 @@ const A = { x: 'end', y: 'end' }, - xValue: () => RECT ? RECT.left : undefined, - yValue: () => RECT ? RECT.top : undefined + xValue: () => rect ? rect.left : undefined, + yValue: () => rect ? rect.top : undefined }; const B = { type: 'label', @@ -70,8 +70,8 @@ const B = { x: 'start', y: 'end' }, - xValue: () => RECT ? RECT.right : undefined, - yValue: () => RECT ? RECT.top : undefined + xValue: () => rect ? rect.right : undefined, + yValue: () => rect ? rect.top : undefined }; const C = { type: 'label', @@ -80,8 +80,8 @@ const C = { x: 'end', y: 'start' }, - xValue: () => RECT ? RECT.left : undefined, - yValue: () => RECT ? RECT.bottom : undefined + xValue: () => rect ? rect.left : undefined, + yValue: () => rect ? rect.bottom : undefined }; const D = { type: 'label', @@ -90,29 +90,29 @@ const D = { x: 'start', y: 'start' }, - xValue: () => RECT ? RECT.right : undefined, - yValue: () => RECT ? RECT.bottom : undefined + xValue: () => rect ? rect.right : undefined, + yValue: () => rect ? rect.bottom : undefined }; // // const summary = { type: 'label', - display: () => !!RECT, + display: () => !!rect, drawTime: 'afterDraw', backgroundColor: 'white', borderColor: 'silver', borderWidth: 1, borderRadius: 6, content() { - if (RECT) { + if (rect) { const result = []; - result.push('A: (' + RECT.left.toFixed(2) + ', ' + RECT.top.toFixed(2) + ')'); - result.push('B: (' + RECT.right.toFixed(2) + ', ' + RECT.top.toFixed(2) + ')'); - result.push('C: (' + RECT.left.toFixed(2) + ', ' + RECT.bottom.toFixed(2) + ')'); - result.push('D: (' + RECT.right.toFixed(2) + ', ' + RECT.bottom.toFixed(2) + ')'); - const AB = Math.abs(RECT.right - RECT.left); - const AC = Math.abs(RECT.bottom - RECT.top); + result.push('A: (' + rect.left.toFixed(2) + ', ' + rect.top.toFixed(2) + ')'); + result.push('B: (' + rect.right.toFixed(2) + ', ' + rect.top.toFixed(2) + ')'); + result.push('C: (' + rect.left.toFixed(2) + ', ' + rect.bottom.toFixed(2) + ')'); + result.push('D: (' + rect.right.toFixed(2) + ', ' + rect.bottom.toFixed(2) + ')'); + const AB = Math.abs(rect.right - rect.left); + const AC = Math.abs(rect.bottom - rect.top); result.push('AB = CD = ' + AB.toFixed(2)); result.push('AC = BD = ' + AC.toFixed(2)); result.push('AD = BC = ' + Math.sqrt(Math.pow(AB, 2) + Math.pow(AC, 2)).toFixed(2)); @@ -127,12 +127,12 @@ const summary = { }, padding: 5, position: { - x: () => RECT && RECT.left < 0 ? 'end' : 'start', - y: () => RECT && RECT.top <= 0 ? 'start' : 'end' + x: () => rect && rect.left < 0 ? 'end' : 'start', + y: () => rect && rect.top <= 0 ? 'start' : 'end' }, - textAlign: () => RECT && RECT.left < 0 ? 'right' : 'left', - xValue: () => RECT && RECT.left < 0 ? MAX : MIN, - yValue: () => RECT && RECT.top <= 0 ? MAX : MIN + textAlign: () => rect && rect.left < 0 ? 'right' : 'left', + xValue: () => rect && rect.left < 0 ? MAX : MIN, + yValue: () => rect && rect.top <= 0 ? MAX : MIN }; // @@ -180,12 +180,12 @@ const config = { } }, onClick(e, elements, chart) { - RECT = calculateBox(e, chart); + rect = calculateBox(e, chart); chart.update(); }, elements: { labelAnnotation: { - display: () => !!RECT, + display: () => !!rect, borderWidth: 0, padding: 0, font: { @@ -250,7 +250,7 @@ const actions = [ { name: 'Reset', handler: function(chart) { - RECT = undefined; + rect = undefined; chart.update(); } }