From 3c72a85c1a902e7419235a640f39a6c001ebaf5b Mon Sep 17 00:00:00 2001 From: hustcc Date: Fri, 11 Dec 2020 13:24:35 +0800 Subject: [PATCH 1/4] feat(liquid): add outline, wave options for customize --- __tests__/bugs/issue-charts-367-spec.ts | 2 + __tests__/unit/plots/liquid/index-spec.ts | 57 ++++++++++- src/plots/liquid/adaptor.ts | 4 +- src/plots/liquid/index.ts | 8 ++ src/plots/liquid/shapes/liquid.ts | 113 +++++++++++++++------- src/plots/liquid/types.ts | 25 ++++- 6 files changed, 166 insertions(+), 43 deletions(-) diff --git a/__tests__/bugs/issue-charts-367-spec.ts b/__tests__/bugs/issue-charts-367-spec.ts index 52bdc26e5b..481f9775f2 100644 --- a/__tests__/bugs/issue-charts-367-spec.ts +++ b/__tests__/bugs/issue-charts-367-spec.ts @@ -59,5 +59,7 @@ describe('charts #367', () => { .getChildren() .map((v) => v.attr('text')) ).toEqual(LABELS); + + gauge.destroy(); }); }); diff --git a/__tests__/unit/plots/liquid/index-spec.ts b/__tests__/unit/plots/liquid/index-spec.ts index f032e39265..0514bac16c 100644 --- a/__tests__/unit/plots/liquid/index-spec.ts +++ b/__tests__/unit/plots/liquid/index-spec.ts @@ -7,7 +7,7 @@ describe('liquid', () => { width: 600, height: 300, autoFit: false, - percent: 0.75, + percent: 0.45, }); liquid.render(); @@ -16,16 +16,65 @@ describe('liquid', () => { expect(liquid.options.radius).toBe(0.9); // @ts-ignore - expect(liquid.chart.middleGroup.getChildren()[0].getChildren()[1].attr('r')).toBe(135); + expect(liquid.chart.middleGroup.getChildren()[0].getChildren()[0].attr('r')).toBe(135); // 宽 < 高,按照高度来设置 radius liquid.changeSize(300, 500); - // @ts-ignore - expect(liquid.chart.middleGroup.getChildren()[0].getChildren()[1].attr('r')).toBe(135); // circle + expect(liquid.chart.middleGroup.getChildren()[0].getChildren()[0].attr('r')).toBe(135); + // 宽 < 高,按照高度来设置 radius + liquid.changeSize(500, 500); + // @ts-ignore + expect(liquid.chart.middleGroup.getChildren()[0].getChildren()[0].attr('r')).toBe(225); liquid.destroy(); }); + + it('outline & wave', () => { + const liquid = new Liquid(createDiv(), { + width: 600, + height: 300, + autoFit: false, + percent: 0.3, + }); + + liquid.render(); + expect(liquid.options.outline).toEqual({ border: 2, distance: 0 }); + expect(liquid.options.wave).toEqual({ count: 3, length: 192 }); + + expect(liquid.chart.middleGroup.findAllByName('wrap')[0].attr('r')).toBe(135); + expect(liquid.chart.middleGroup.findAllByName('wrap')[0].attr('lineWidth')).toBe(2); + expect(liquid.chart.middleGroup.findAllByName('waterwave-path').length).toBe(3); + expect(liquid.chart.middleGroup.findAllByName('waterwave-path')[0].get('animations')[0].toAttrs.matrix[6]).toBe( + 192 + ); + expect(liquid.chart.middleGroup.findAllByName('waves')[0].get('clipShape').attr('r')).toBe(135); + + liquid.update({ + outline: { + border: 4, + distance: 8, + }, + wave: { + count: 5, + length: 128, + }, + }); + + expect(liquid.options.outline).toEqual({ border: 4, distance: 8 }); + expect(liquid.options.wave).toEqual({ count: 5, length: 128 }); + + expect(liquid.chart.middleGroup.findAllByName('wrap')[0].attr('r')).toBe(135); + expect(liquid.chart.middleGroup.findAllByName('wrap')[0].attr('lineWidth')).toBe(4); + expect(liquid.chart.middleGroup.findAllByName('waterwave-path').length).toBe(5); + expect(liquid.chart.middleGroup.findAllByName('waterwave-path')[0].get('animations')[0].toAttrs.matrix[6]).toBe( + 128 + ); + expect(liquid.chart.middleGroup.findAllByName('waves')[0].get('clipShape').attr('r')).toBe(127); + + liquid.destroy(); + }); + it('change data', () => { const liquid = new Liquid(createDiv(), { width: 600, diff --git a/src/plots/liquid/adaptor.ts b/src/plots/liquid/adaptor.ts index 1640084b15..f985adeaa4 100644 --- a/src/plots/liquid/adaptor.ts +++ b/src/plots/liquid/adaptor.ts @@ -14,7 +14,7 @@ const CAT_VALUE = 'liquid'; */ function geometry(params: Params): Params { const { chart, options } = params; - const { percent, color, liquidStyle, radius } = options; + const { percent, color, liquidStyle, radius, outline, wave } = options; const data = [{ percent, type: CAT_VALUE }]; @@ -47,6 +47,8 @@ function geometry(params: Params): Params { // 将 radius 传入到自定义 shape 中 geometry.customInfo({ radius, + outline, + wave, }); // 关闭组件 diff --git a/src/plots/liquid/index.ts b/src/plots/liquid/index.ts index 48ab48b5ed..fc602dbaf5 100644 --- a/src/plots/liquid/index.ts +++ b/src/plots/liquid/index.ts @@ -29,6 +29,14 @@ export class Liquid extends Plot { }, }, }, + outline: { + border: 2, + distance: 0, + }, + wave: { + count: 3, + length: 192, + }, }; } diff --git a/src/plots/liquid/shapes/liquid.ts b/src/plots/liquid/shapes/liquid.ts index 8101eeefc1..49c11b1cf0 100644 --- a/src/plots/liquid/shapes/liquid.ts +++ b/src/plots/liquid/shapes/liquid.ts @@ -1,18 +1,27 @@ import { registerShape } from '@antv/g2'; -import { IGroup } from '@antv/g-base'; +import { IGroup, IShape } from '@antv/g-base'; import { reduce, isNumber, mix } from '@antv/util'; import { transform } from '../../../utils/matrix'; -import { Point } from '../../../types'; +import { Point, ShapeStyle } from '../../../types'; +import { LiquidOptions } from '..'; -function lerp(a: number, b: number, factor: number) { - return (1 - factor) * a + factor * b; +const DURATION = 5000; + +/** + * 一个线性映射的函数 + * @param min + * @param max + * @param factor + */ +function lerp(min: number, max: number, factor: number) { + return min + (max - min) * factor; } /** * 波浪的 attrs * @param cfg */ -function getFillAttrs(cfg) { +function getFillAttrs(cfg: ShapeStyle) { const attrs = { opacity: 1, ...cfg.style }; if (cfg.color && !attrs.fill) { @@ -26,11 +35,11 @@ function getFillAttrs(cfg) { * 圆圈的 attrs * @param cfg */ -function getLineAttrs(cfg) { +function getLineAttrs(cfg: ShapeStyle) { const defaultAttrs = { fill: '#fff', fillOpacity: 0, - lineWidth: 2, + lineWidth: 4, }; const attrs = mix({}, defaultAttrs, cfg.style); @@ -113,7 +122,7 @@ function getWaterWavePath( cx: number, cy: number ) { - const curves = Math.ceil(((2 * radius) / waveLength) * 4) * 2; + const curves = Math.ceil(((2 * radius) / waveLength) * 4) * 4; const path = []; let _phase = phase; @@ -192,36 +201,57 @@ function getWaterWavePath( * @param group 图组 * @param clip 用于剪切的图形 * @param radius 绘制图形的高度 + * @param waveLength 波的长度 */ -function addWaterWave(x, y, level, waveCount, waveAttrs, group, clip, radius) { +function addWaterWave( + x: number, + y: number, + level: number, + waveCount: number, + waveAttrs: ShapeStyle, + group: IGroup, + clip: IShape, + radius: number, + waveLength: number +) { const { fill, opacity } = waveAttrs; const bbox = clip.getBBox(); const width = bbox.maxX - bbox.minX; const height = bbox.maxY - bbox.minY; - const duration = 5000; - for (let i = 0; i < waveCount; i++) { - const factor = waveCount <= 1 ? 0 : i / (waveCount - 1); + + for (let idx = 0; idx < waveCount; idx++) { + const factor = waveCount <= 1 ? 0 : idx / (waveCount - 1); const wave = group.addShape('path', { - name: `wave-path-${i}`, + name: `waterwave-path`, attrs: { - path: getWaterWavePath(radius, bbox.minY + height * level, width / 4, 0, width / lerp(56, 64, factor), x, y), + path: getWaterWavePath( + radius, + bbox.minY + height * level, + waveLength, + 0, + width / 32, // 波幅高度 + x, + y + ), fill, - opacity: lerp(0.6, 0.3, factor) * opacity, + opacity: lerp(0.2, 0.9, factor) * opacity, }, }); try { - const matrix = transform([['t', width / 2, 0]]); + const matrix = transform([['t', waveLength, 0]]); + + wave.stopAnimate(); wave.animate( { matrix }, { - duration: lerp(duration, 0.7 * duration, factor), + duration: lerp(0.5 * DURATION, DURATION, factor), repeat: true, } ); } catch (e) { // TODO off-screen canvas 中动画会找不到 canvas - console.error('off-screen group animate error!'); + console.warn('off-screen group animate error!'); } } } @@ -231,8 +261,12 @@ registerShape('interval', 'liquid-fill-gauge', { const cx = 0.5; const cy = 0.5; - // 需要更好的传参方式 - const radio = isNumber(cfg.customInfo.radius) ? cfg.customInfo.radius : 0.9; + const { customInfo } = cfg; + const { radius: radio } = customInfo; + const outline: LiquidOptions['outline'] = customInfo.outline; + const wave: LiquidOptions['wave'] = customInfo.wave; + const { border, distance } = outline; + const { count: waveCount, length: waveLength } = wave; // 获取最小 minX const minX = reduce( @@ -250,41 +284,48 @@ registerShape('interval', 'liquid-fill-gauge', { // 保证半径是 画布宽高最小值的 radius 值 const radius = Math.min(halfWidth, minXPoint.y * radio); const waveAttrs = getFillAttrs(cfg); + const circleAttrs = getLineAttrs(cfg); + // 1. 首先绘制一个外圆 + container.addShape('circle', { + name: 'wrap', + attrs: mix(circleAttrs, { + x: center.x, + y: center.y, + r: radius, + fill: 'transparent', + lineWidth: border, + }), + }); + + // 2. 绘制波的 group const waves = container.addGroup({ name: 'waves', }); - waves.setClip({ + // 3. 波对应的 clip 裁剪形状 + const clipCircle = waves.setClip({ type: 'circle', attrs: { x: center.x, y: center.y, - r: radius, + r: radius - distance, }, }); - const clipCircle = waves.get('clipShape'); + + // 4. 绘制波形 addWaterWave( center.x, center.y, - 1 - (cfg.points[1] as Point).y, // cfg.y / (2 * cp.y), - 3, + 1 - (cfg.points[1] as Point).y, + waveCount, waveAttrs, waves, clipCircle, - radius * 4 + radius * 2, + waveLength ); - container.addShape('circle', { - name: 'wrap', - attrs: mix(getLineAttrs(cfg), { - x: center.x, - y: center.y, - r: radius, - fill: 'transparent', - }), - }); - return container; }, }); diff --git a/src/plots/liquid/types.ts b/src/plots/liquid/types.ts index d6a0ec4abf..10c861e7a3 100644 --- a/src/plots/liquid/types.ts +++ b/src/plots/liquid/types.ts @@ -1,15 +1,36 @@ import { Options, StyleAttr, ColorAttr, Statistic } from '../../types'; +/** 轮廓的配置 */ +type Outline = Partial<{ + /** 外环的宽度,默认为 2px */ + readonly border: number; + /** 内外的边距,默认为 0px */ + readonly distance: number; + // /** 外边框的形状,可以有 circle,rect,默认为 circle */ + // readonly containerShape?: 'circle' | 'rect'; +}>; + +type Wave = Partial<{ + /** 波形的数量,默认为 3 */ + readonly count: number; + /** 波形的长度,默认是 192 */ + readonly length: number; +}>; + /** 配置类型定义 */ export interface LiquidOptions extends Omit { /** 指标比例 */ readonly percent: number; /** 配置水波图的颜色,使用默认色板的颜色 */ readonly color?: ColorAttr; - /** 配置水波图的样式 */ - readonly liquidStyle?: StyleAttr; /** 水波的外半径, 0 ~ 1,默认为 0.9 */ readonly radius?: number; + /** 配置水波图的样式 */ + readonly liquidStyle?: StyleAttr; /** 指标文本组件 */ readonly statistic?: Statistic; + /** 外环轮廓的配置 */ + readonly outline?: Outline; + /** 波的配置 */ + readonly wave?: Wave; } From 824fc283e86355df97a599243d9089939831a5a2 Mon Sep 17 00:00:00 2001 From: hustcc Date: Fri, 11 Dec 2020 13:34:24 +0800 Subject: [PATCH 2/4] chore: add issue, pr template --- .github/ISSUE_TEMPLATE.md | 8 ++++++++ .github/PULL_REQUEST_TEMPLATE.md | 13 +++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000..cacf7bcb97 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,8 @@ + +* **G2Plot Version**: +* **Platform**: +* **Mini Showcase(like screenshots)**: +* **CodePen Link**: + + + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..e53dc560a5 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,13 @@ + +### PR includes + + +- [ ] fixed #0 +- [ ] add / modify test cases +- [ ] documents, demos + +### Screenshot + +| Before | After | +|----|----| +| ❌ | ✅ | From 4fb62e84d19cc29800fb65e41e95820653985f63 Mon Sep 17 00:00:00 2001 From: hustcc Date: Fri, 11 Dec 2020 14:00:05 +0800 Subject: [PATCH 3/4] docs: update document & demos --- .github/ISSUE_TEMPLATE.md | 1 - .github/PULL_REQUEST_TEMPLATE.md | 1 - docs/manual/plots/liquid.en.md | 22 ++++++++++++++++++++++ docs/manual/plots/liquid.zh.md | 24 +++++++++++++++++++++++- examples/liquid/basic/demo/basic.ts | 25 +++++++------------------ 5 files changed, 52 insertions(+), 21 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index cacf7bcb97..ba0a7a1b42 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,4 +1,3 @@ - * **G2Plot Version**: * **Platform**: * **Mini Showcase(like screenshots)**: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index e53dc560a5..23d4195d7f 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,4 +1,3 @@ - ### PR includes diff --git a/docs/manual/plots/liquid.en.md b/docs/manual/plots/liquid.en.md index 1c583b3070..93fd070703 100644 --- a/docs/manual/plots/liquid.en.md +++ b/docs/manual/plots/liquid.en.md @@ -33,6 +33,28 @@ Liguid graphic style. `markdown:docs/common/color.en.md` +#### outline + +**optional** _Outline_ + +The ouline configure for liquid plot, includes: + +| Attr | Type | Desc | +| ------------ | -------------- | ----------------------------------------------------- | +| border | number | border width of ouline, default 2px | +| disatance | number | disatance between ouline and wave, default 0px | + +#### wave + +**optional** _Wave_ + +The wave configure for liquid plot, includes: + +| Attr | Type | Desc | +| ------------ | -------------- | ----------------------------------------------------- | +| count | number | wave count, default 3 | +| length | number | wave length, default is 192px | + ### Plot Components #### statistic diff --git a/docs/manual/plots/liquid.zh.md b/docs/manual/plots/liquid.zh.md index 8186777602..7fd016ded0 100644 --- a/docs/manual/plots/liquid.zh.md +++ b/docs/manual/plots/liquid.zh.md @@ -27,12 +27,34 @@ order: 12 **optional** _StyleAttr | Function_ -水波图的样式。 +水波图的配色样式。 `markdown:docs/common/shape-style.zh.md` `markdown:docs/common/color.zh.md` +#### outline + +**optional** _Outline_ + +水波图的外框容器配置。主要包含以下内容: + +| 属性名 | 类型 | 介绍 | +| ------------ | -------------- | -------------------------------------------- | +| border | number | 外框容器的 border 宽度,默认为 2 像素 | +| disatance | number | 外框容器和内部波形的间距,默认为 0 像素 | + +#### wave + +**optional** _Wave_ + +水波图的波形配置。主要包含以下内容: + +| 属性名 | 类型 | 介绍 | +| ------------ | -------------- | -------------------------------------------- | +| count | number | 水波的个数,默认为 3 个 | +| length | number | 水波的波长度,默认为 192 像素 | + ### 图表组件 #### statistic ✨ diff --git a/examples/liquid/basic/demo/basic.ts b/examples/liquid/basic/demo/basic.ts index 7cb5e81c95..3ebf8409a4 100644 --- a/examples/liquid/basic/demo/basic.ts +++ b/examples/liquid/basic/demo/basic.ts @@ -1,24 +1,13 @@ -import { Liquid, measureTextWidth } from '@antv/g2plot'; +import { Liquid } from '@antv/g2plot'; const liquidPlot = new Liquid('container', { percent: 0.25, - statistic: { - content: { - style: { - fontSize: 60, - fill: 'black', - }, - customHtml: (container, view, { percent }) => { - const { width, height } = container.getBoundingClientRect(); - const d = Math.sqrt(Math.pow(width / 2, 2) + Math.pow(height / 2, 2)); - const text = `${(percent * 100).toFixed(0)}%`; - const textWidth = measureTextWidth(text, { fontSize: 60 }); - const scale = Math.min(d / textWidth, 1); - return `
${text}
`; - }, - }, + outline: { + border: 4, + distance: 8, + }, + wave: { + length: 128, }, }); liquidPlot.render(); From 9fcb3eff32a62c2f3fe83f1380a101c19484a7ed Mon Sep 17 00:00:00 2001 From: hustcc Date: Fri, 11 Dec 2020 14:31:03 +0800 Subject: [PATCH 4/4] fix: ci pass --- __tests__/bugs/issue-1909-spec.ts | 7 +++++-- __tests__/unit/plots/liquid/liquid-style-spec.ts | 6 +++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/__tests__/bugs/issue-1909-spec.ts b/__tests__/bugs/issue-1909-spec.ts index 9eeb58ebe3..28a7ca3436 100644 --- a/__tests__/bugs/issue-1909-spec.ts +++ b/__tests__/bugs/issue-1909-spec.ts @@ -12,7 +12,7 @@ describe('#1909', () => { liquidPlot.render(); // @ts-ignore - expect(liquidPlot.chart.geometries[0].container.getChildren()[0].getChildren()[0].attr('opacity')).toBe(0.6); + expect(liquidPlot.chart.geometries[0].container.getChildren()[1].getChildren()[0].attr('opacity')).toBe(0.2); liquidPlot.update({ liquidStyle: { @@ -21,7 +21,10 @@ describe('#1909', () => { }); // @ts-ignore - expect(liquidPlot.chart.geometries[0].container.getChildren()[0].getChildren()[0].attr('opacity')).toBe(0.06); + expect(liquidPlot.chart.geometries[0].container.getChildren()[1].getChildren()[0].attr('opacity')).toBeCloseTo( + 0.02, + 5 + ); liquidPlot.destroy(); }); diff --git a/__tests__/unit/plots/liquid/liquid-style-spec.ts b/__tests__/unit/plots/liquid/liquid-style-spec.ts index 378b5fc8f8..43dad5506c 100644 --- a/__tests__/unit/plots/liquid/liquid-style-spec.ts +++ b/__tests__/unit/plots/liquid/liquid-style-spec.ts @@ -18,9 +18,9 @@ describe('liquid', () => { liquid.render(); // @ts-ignore - expect(liquid.chart.middleGroup.getChildren()[0].getChildren()[1].attr('stroke')).toBe('blue'); // circle + expect(liquid.chart.middleGroup.getChildren()[0].getChildren()[0].attr('stroke')).toBe('blue'); // circle // @ts-ignore - expect(liquid.chart.middleGroup.getChildren()[0].getChildren()[0].getChildren()[0].attr('fill')).toBe('green'); // wave path + expect(liquid.chart.middleGroup.getChildren()[0].getChildren()[1].getChildren()[0].attr('fill')).toBe('green'); // wave path // @ts-ignore liquid.chart.getController('annotation').clear(true); @@ -34,7 +34,7 @@ describe('liquid', () => { // G2 chart.clear 的时候,geometry 销毁了,但是 container 还保留的,内存泄露。 // @ts-ignore - expect(liquid.chart.middleGroup.getChildren()[0].getChildren()[0].getChildren()[0].attr('fill')).toBe('red'); // wave path + expect(liquid.chart.middleGroup.getChildren()[0].getChildren()[1].getChildren()[0].attr('fill')).toBe('red'); // wave path liquid.destroy(); });