From 332c7849e3a8ce62f6bb82d6e2077bb2de4874f5 Mon Sep 17 00:00:00 2001 From: hustcc Date: Thu, 17 Aug 2023 22:30:49 +0800 Subject: [PATCH] docs: add intersting demo --- site/.dumirc.ts | 8 ++ .../annotation/shape/demo/watermark.ts | 6 +- .../interesting/demo/25d-column.ts | 98 ++++++++++++++ .../interesting/interesting/demo/messi.ts | 103 ++++++++++++++ .../interesting/interesting/demo/meta.json | 32 +++++ .../interesting/interesting/demo/petal.ts | 126 ++++++++++++++++++ .../interesting/interesting/index.en.md | 4 + .../interesting/interesting/index.zh.md | 4 + src/shape/shape/shape.ts | 15 ++- 9 files changed, 386 insertions(+), 10 deletions(-) create mode 100644 site/examples/interesting/interesting/demo/25d-column.ts create mode 100644 site/examples/interesting/interesting/demo/messi.ts create mode 100644 site/examples/interesting/interesting/demo/meta.json create mode 100644 site/examples/interesting/interesting/demo/petal.ts create mode 100644 site/examples/interesting/interesting/index.en.md create mode 100644 site/examples/interesting/interesting/index.zh.md diff --git a/site/.dumirc.ts b/site/.dumirc.ts index 1ec80fa2a4..72b4046e59 100644 --- a/site/.dumirc.ts +++ b/site/.dumirc.ts @@ -329,6 +329,14 @@ export default defineConfig({ }, icon: 'other', }, + { + slug: 'interesting', + title: { + zh: '趣味可视化', + en: 'Interesting', + }, + icon: 'other', + }, { slug: 'style', title: { diff --git a/site/examples/annotation/shape/demo/watermark.ts b/site/examples/annotation/shape/demo/watermark.ts index aabe0de329..30b3d1682c 100644 --- a/site/examples/annotation/shape/demo/watermark.ts +++ b/site/examples/annotation/shape/demo/watermark.ts @@ -37,10 +37,8 @@ chart.shape().style('x', '80%').style('y', '70%').style('render', watermark); chart.render(); -function watermark({ x, y }) { - const { - canvas: { document }, - } = chart.getContext(); +function watermark({ x, y }, context) { + const { document } = context; const g = document.createElement('g', {}); const c1 = document.createElement('circle', { diff --git a/site/examples/interesting/interesting/demo/25d-column.ts b/site/examples/interesting/interesting/demo/25d-column.ts new file mode 100644 index 0000000000..faf60f6faf --- /dev/null +++ b/site/examples/interesting/interesting/demo/25d-column.ts @@ -0,0 +1,98 @@ +import { Chart, register } from '@antv/g2'; + +register('shape.interval.column25d', myColumn); + +const data = [ + { year: '1951 年', sales: 38 }, + { year: '1952 年', sales: 52 }, + { year: '1956 年', sales: 61 }, + { year: '1957 年', sales: 145 }, + { year: '1958 年', sales: 48 }, + { year: '1959 年', sales: 38 }, + { year: '1960 年', sales: 38 }, + { year: '1962 年', sales: 38 }, + { year: '1963 年', sales: 65 }, + { year: '1964 年', sales: 122 }, + { year: '1967 年', sales: 132 }, + { year: '1968 年', sales: 144 }, +]; + +const chart = new Chart({ + container: 'container', + autoFit: true, + theme: 'classic', +}); + +chart.data(data); + +chart + .interval() + .encode('x', 'year') + .encode('y', 'sales') + .style('shape', 'column25d') + .scale('x', { padding: 0.3 }); + +chart.legend('year', { + width: 10, +}); + +chart.render(); + +/** + * Draw 2.5d column shape. + */ +function myColumn({ fill, stroke }, context) { + return (points) => { + const x3 = points[1][0] - points[0][0]; + const x4 = x3 / 2 + points[0][0]; + const { document } = context; + const g = document.createElement('g', {}); + + const r = document.createElement('polygon', { + style: { + points: [ + [points[0][0], points[0][1]], + [x4, points[1][1] + 8], + [x4, points[3][1] + 8], + [points[3][0], points[3][1]], + ], + fill: 'rgba(114, 177, 207, 0.5)', + stroke: 'rgba(0,0,0,0.1)', + strokeOpacity: 0.1, + inset: 30, + }, + }); + + const p = document.createElement('polygon', { + style: { + points: [ + [x4, points[1][1] + 8], + [points[1][0], points[1][1]], + [points[2][0], points[2][1]], + [x4, points[2][1] + 8], + ], + fill: 'rgba(126, 212, 236, 0.5)', + stroke: 'rgba(0,0,0,0.3)', + strokeOpacity: 0.1, + }, + }); + + const t = document.createElement('polygon', { + style: { + points: [ + [points[0][0], points[0][1]], + [x4, points[1][1] - 8], + [points[1][0], points[1][1]], + [x4, points[1][1] + 8], + ], + fill: 'rgba(173, 240, 255, 0.65)', + }, + }); + + g.appendChild(r); + g.appendChild(p); + g.appendChild(t); + + return g; + }; +} diff --git a/site/examples/interesting/interesting/demo/messi.ts b/site/examples/interesting/interesting/demo/messi.ts new file mode 100644 index 0000000000..d4272172df --- /dev/null +++ b/site/examples/interesting/interesting/demo/messi.ts @@ -0,0 +1,103 @@ +import { Chart } from '@antv/g2'; + +const FW = 600; +const FH = 400; +const P = 50; + +const chart = new Chart({ + container: 'container', + width: FW + P * 2, + height: FH + P * 2, + padding: P, + theme: 'classic', +}); + +// Draw football field. +chart.shape().style('x', '0%').style('y', '0%').style('render', football); + +// Analysis messi's shoot data. +chart + .rect() + .data({ + type: 'fetch', + value: + 'https://mdn.alipayobjects.com/afts/file/A*FCRjT4NGENEAAAAAAAAAAAAADrd2AQ/messi.json', + }) + .transform({ + type: 'bin', + opacity: 'count', + thresholdsX: 15, + thresholdsY: 15, + }) + .encode('x', (d) => Number(d.X)) + .encode('y', (d) => Number(d.Y)) + .scale('x', { domain: [0, 1] }) + .scale('y', { domain: [0, 1] }) + .axis(false) + .legend(false); + +chart.render(); + +/** + * Draw a football field. + */ +function football(_, context) { + const { document } = context; + + const g = document.createElement('g'); + const r = document.createElement('rect', { + style: { + x: 0, + y: 0, + width: FW, + height: FH, + fill: 'green', + fillOpacity: 0.2, + stroke: 'grey', + lineWidth: 1, + }, + }); + + const r1 = document.createElement('rect', { + style: { + x: FW - FH * 0.6 * 0.45, + y: (FH - FH * 0.6) / 2, + width: FH * 0.6 * 0.45, + height: FH * 0.6, + strokeOpacity: 0.5, + stroke: 'grey', + lineWidth: 1, + }, + }); + + const r2 = document.createElement('rect', { + style: { + x: FW - FH * 0.3 * 0.45, + y: (FH - FH * 0.3) / 2, + width: FH * 0.3 * 0.45, + height: FH * 0.3, + strokeOpacity: 0.5, + stroke: 'grey', + lineWidth: 1, + }, + }); + + const l = document.createElement('line', { + style: { + x1: FW / 2, + y1: 0, + x2: FW / 2, + y2: FH, + strokeOpacity: 0.4, + stroke: 'grey', + lineWidth: 2, + }, + }); + + g.append(r); + g.append(r1); + g.append(r2); + g.append(l); + + return g; +} diff --git a/site/examples/interesting/interesting/demo/meta.json b/site/examples/interesting/interesting/demo/meta.json new file mode 100644 index 0000000000..b165c16438 --- /dev/null +++ b/site/examples/interesting/interesting/demo/meta.json @@ -0,0 +1,32 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "petal.ts", + "title": { + "zh": "花瓣图", + "en": "Petal Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*4eB8TIjkbkAAAAAAAAAAAAAADmJ7AQ/fmt.webp" + }, + { + "filename": "messi.ts", + "title": { + "zh": "梅西射门分析", + "en": "Messi's shoot" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*N6mYTas3VIgAAAAAAAAAAAAADmJ7AQ/fmt.webp" + }, + { + "filename": "25d-column.ts", + "title": { + "zh": "2.5D 柱形图", + "en": "2.5D Column" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*GThCTqSNiSkAAAAAAAAAAAAADmJ7AQ/fmt.webp" + } + ] +} diff --git a/site/examples/interesting/interesting/demo/petal.ts b/site/examples/interesting/interesting/demo/petal.ts new file mode 100644 index 0000000000..fe829c15e0 --- /dev/null +++ b/site/examples/interesting/interesting/demo/petal.ts @@ -0,0 +1,126 @@ +import { Chart, register } from '@antv/g2'; + +// 注册自定义图形,代码在下面 +register('shape.interval.petal', petal); + +const chart = new Chart({ + container: 'container', + theme: 'classic', + autoFit: true, +}); + +chart.coordinate({ type: 'theta' }); + +chart.data([ + { type: '分类一', value: 27 }, + { type: '分类二', value: 25 }, + { type: '分类三', value: 18 }, + { type: '分类四', value: 15 }, + { type: '分类五', value: 10 }, + { type: 'Other', value: 5 }, +]); + +chart + .interval() + .transform({ type: 'stackY' }) + .encode('y', 'value') + .encode('color', 'type') + .encode('shape', 'petal') + .style('offset', 0.5) // 👈🏻 在这里配置属性 + .style('ratio', 0.2) // 👈🏻 在这里配置属性 + .label({ + text: (d, i, data) => d.type + '\n' + d.value, + radius: 0.9, + style: { + fontSize: 9, + dy: 12, + }, + }) + .animate('enter', { type: 'fadeIn' }) + .legend(false); + +chart.render(); + +/** Functions for custom shape. */ + +function getPoint(p0, p1, ratio) { + return [p0[0] + (p1[0] - p0[0]) * ratio, p0[1] + (p1[1] - p0[1]) * ratio]; +} + +function sub(p1, p2) { + const [x1, y1] = p1; + const [x2, y2] = p2; + return [x1 - x2, y1 - y2]; +} + +function dist(p0, p1) { + const [x0, y0] = p0; + const [x1, y1] = p1; + return Math.sqrt((x0 - x1) ** 2 + (y0 - y1) ** 2); +} + +function getAngle(p) { + const [x, y] = p; + return Math.atan2(y, x); +} + +function getXY(angle, center, radius) { + return [ + Math.cos(angle) * radius + center[0], + Math.sin(angle) * radius + center[1], + ]; +} + +/** + * Custom shape for petal. + */ +function petal({ offset = 1, ratio = 0.5 }, context) { + const { coordinate } = context; + return (points, value, defaults) => { + // 圆形坐标 + const center = coordinate.getCenter(); + // 1° 的偏移 + const offsetAngle = (Math.PI / 180) * offset; + // eslint-disable-next-line + let [p0, p1, p2, p3] = points; + // 半径 + const radius = dist(center, p0); + const qRadius = radius * ratio; + const angleQ1 = getAngle(sub(p3, center)) + offsetAngle; + const angleQ2 = getAngle(sub(p0, center)) - offsetAngle; + + // 偏移 1° 后的 q1, q2 + const q1 = getXY(angleQ1, center, qRadius); + const q2 = getXY(angleQ2, center, qRadius); + + // 偏移 1° 后的 p3, p0 + p3 = getXY(angleQ1, center, radius); + p0 = getXY(angleQ2, center, radius); + + // mid 对应的角度为 p0 和 p3 中点的夹角 + const angle = getAngle(sub(getPoint(p0, p3, 0.5), center)); + const mid = getXY(angle, center, radius); + + const path = [ + ['M', ...p1], + ['L', ...q1], + ['Q', ...p3, ...mid], + ['Q', ...p0, ...q2], + ['L', ...p2], + ['Z'], + ]; + + const { document } = chart.getContext().canvas; + const g = document.createElement('g', {}); + const p = document.createElement('path', { + style: { + path, + inset: 1, + fill: value.color, + }, + }); + g.appendChild(p); + + return g; + }; +} diff --git a/site/examples/interesting/interesting/index.en.md b/site/examples/interesting/interesting/index.en.md new file mode 100644 index 0000000000..06b98f2176 --- /dev/null +++ b/site/examples/interesting/interesting/index.en.md @@ -0,0 +1,4 @@ +--- +title: Interesting +order: 1 +--- \ No newline at end of file diff --git a/site/examples/interesting/interesting/index.zh.md b/site/examples/interesting/interesting/index.zh.md new file mode 100644 index 0000000000..b34497452f --- /dev/null +++ b/site/examples/interesting/interesting/index.zh.md @@ -0,0 +1,4 @@ +--- +title: 趣味可视化 +order: 1 +--- \ No newline at end of file diff --git a/src/shape/shape/shape.ts b/src/shape/shape/shape.ts index d61313fd46..4e2520100a 100644 --- a/src/shape/shape/shape.ts +++ b/src/shape/shape/shape.ts @@ -6,15 +6,18 @@ export type ShapeOptions = Record; /** * Draw a custom shape. */ -export const Shape: SC = (options) => { +export const Shape: SC = (options, context) => { const { render, ...rest } = options; return (points) => { const [[x0, y0]] = points; - return render({ - ...rest, - x: x0, - y: y0, - }); + return render( + { + ...rest, + x: x0, + y: y0, + }, + context, + ); }; };