From ab71f3fa1a236dfbcd64afe926295ae707fa21b1 Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Thu, 24 Jun 2021 17:43:34 +0800 Subject: [PATCH 01/49] chore: changed ./github/build.yml runs on macos --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 069bd87d9..158c51ca6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,7 @@ on: [ push, pull_request ] jobs: build: - runs-on: ubuntu-latest + runs-on: macos-latest steps: - name: Checkout From 1def142115059748eaa98dde824aef8a458889ba Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Thu, 24 Jun 2021 21:03:37 +0800 Subject: [PATCH 02/49] =?UTF-8?q?chore:=20=E4=BF=AE=E5=A4=8D=E4=BA=86scrip?= =?UTF-8?q?ts=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index ae857496b..a3e976114 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "fix": "eslint ./src/**/*.ts ./__tests__/**/*.ts --fix && prettier ./src ./__tests__ --write ", "test": "jest", "test-live": "DEBUG_MODE=1 jest --watch ./__tests__", - "build": "father-build & limit-size", + "build": "father-build && limit-size", "ci": "run-s lint test build", "prepublishOnly": "npm run ci", "prepare": "husky install" @@ -41,9 +41,6 @@ "@antv/gatsby-theme-antv": "^1.1.5", "@antv/util": "^2.0.13", "csstype": "^3.0.8", - "react": "^16.11.0", - "react-dom": "^16.11.0", - "react-i18next": "^11.7.0", "svg-path-parser": "^1.1.0" }, "devDependencies": { @@ -72,7 +69,10 @@ "rimraf": "^3.0.2", "ts-jest": "^26.5.1", "tslib": "^2.2.0", - "typescript": "^4.1.5" + "typescript": "^4.1.5", + "react": "^16.11.0", + "react-dom": "^16.11.0", + "react-i18next": "^11.7.0" }, "jest": { "runner": "jest-electron/runner", @@ -81,7 +81,6 @@ "preset": "ts-jest", "collectCoverage": true, "testRegex": "(/__tests__/.*\\.(test|spec))\\.ts$", - "setupFilesAfterEnv": [ "./jest.setup.js" ], From 0383cba89b627e4114a3fc27d50826a088335df8 Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Mon, 28 Jun 2021 11:00:27 +0800 Subject: [PATCH 03/49] =?UTF-8?q?feat(slider):=20=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E4=BA=86=E6=B2=A1=E6=9C=89minimap=E7=9A=84slider?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/slider/index.ts | 491 ++++++++++++++++++++++++++++++++++++++++- src/ui/slider/types.ts | 102 ++++++++- 2 files changed, 590 insertions(+), 3 deletions(-) diff --git a/src/ui/slider/index.ts b/src/ui/slider/index.ts index 95f00afba..f74345d38 100644 --- a/src/ui/slider/index.ts +++ b/src/ui/slider/index.ts @@ -1,5 +1,492 @@ -import { SliderOptions } from './types'; +import { Rect, Text } from '@antv/g'; +import { deepMix } from '@antv/util'; +// import { SliderOptions, HandleCfg, MiniMap, Pair } from './types'; +import { SliderOptions, HandleCfg, Pair } from './types'; +import { Marker } from '../marker'; +import { CustomElement, DisplayObject, ShapeAttrs } from '../../types'; +// import { /* applyAttrs */ measureTextWidth } from '../../util'; +const applyAttrs = (target: DisplayObject, attrs: ShapeAttrs) => { + Object.entries(attrs).forEach(([attrName, attrValue]) => { + target.attr(attrName, attrValue); + }); +}; export { SliderOptions }; -export class Slider {} +type HandleType = 'start' | 'end'; + +export class Slider extends CustomElement { + public static tag = 'slider'; + + /** + * 容器 + */ + private containerShape: DisplayObject; + + private backgroundShape: DisplayObject; + + private miniMapShape: DisplayObject; + + private foregroundShape: DisplayObject; + + private startHandle: DisplayObject; + + private endHandle: DisplayObject; + + private prevPos: number; + + /** + * drag事件当前选中的对象 + */ + private currTarget: DisplayObject; + + constructor(options: SliderOptions) { + super(deepMix({}, Slider.defaultOptions, options)); + this.init(); + } + + private static defaultOptions = { + type: Slider.tag, + attrs: { + orient: 'horizontal', + values: [0, 1], + names: ['', ''], + min: 0, + max: 1, + width: 200, + height: 20, + padding: { + left: 20, + right: 0, + top: 0, + bottom: 0, + }, + backgroundStyle: { + fill: '#fff', + stroke: '#e4eaf5', + lineWidth: 1, + }, + foregroundStyle: { + fill: '#afc9fb', + opacity: 0.5, + stroke: '#afc9fb', + lineWidth: 1, + }, + brushStyle: { + fill: '#eef3ff', + }, + handle: { + show: true, + size: 20, + formatter: (val: string) => val, + spacing: 10, + handleIcon: 'circle', + textStyle: { + fill: '#63656e', + textAlign: 'center', + textBaseline: 'middle', + }, + handleStyle: { + stroke: '#c5c5c5', + fill: '#9bc2ff', + lineWidth: 1, + }, + }, + miniMap: {}, + }, + }; + + attributeChangedCallback(name: string, value: any) { + value; + if (name in ['names', 'values']) { + this.setHandle(); + } + } + + public getValues() { + return this.getAttribute('values'); + } + + public setValues(values: SliderOptions['values']) { + this.setAttribute('values', values); + } + + public getNames() { + return this.getAttribute('names'); + } + + public setNames(names: SliderOptions['names']) { + this.setAttribute('names', names); + } + + private init() { + this.createBackground(); + this.createForeground(); + // this.createContainer(); + this.createHandles(); + this.bindEvents(); + console.log(this.getElementsByName('handleIcon')); + } + + /** + * 获得安全的Values + * @param adjust true: 超出范围时移动至合法区间; false: 超出范围时取min、max; + */ + private getSafetyValues(values?: Pair, adjust: boolean = false): Pair { + const { min, max } = this.attributes; + const [sV, eV] = values || this.getValues(); + let startVal = sV; + let endVal = eV; + // 交换startVal endVal + if (startVal > endVal) { + [startVal, endVal] = [endVal, startVal]; + } + // 超出范围就全选 + if (endVal - startVal > max - min) { + return [min, max]; + } + const lOffset = min - startVal; + // 无论是否调整都整体右移 + if (endVal < min) { + return [min, endVal + lOffset]; + } + if (startVal < min) { + return adjust ? [min, endVal + lOffset] : [min, endVal]; + } + const rOffset = endVal - max; + if (startVal > max) { + return [startVal - rOffset, max]; + } + if (endVal > max) { + return adjust ? [startVal - rOffset, max] : [startVal, max]; + } + return [startVal, endVal]; + } + + private getAvailableSpace() { + const { padding, width, height } = this.attributes; + return { + x: padding.left, + y: padding.top, + width: width - (padding.left + padding.right), + height: height - (padding.top + padding.bottom), + }; + } + + private getStyle(name: string, isActive?: boolean) { + const style = this.getAttribute(name); + if (isActive) { + return style.active || {}; + } + return style.default || style; + } + + private createContainer() { + // 待子组件创建完成后,计算包围盒并设置padding + const { padding } = this.attributes; + const innerWidth = 0; + const innerHeight = 0; + this.containerShape = new Rect({ + name: 'container', + attrs: { + x: 0, + y: 0, + width: padding.left + padding.right + innerWidth, + height: padding.top + padding.bottom + innerHeight, + opacity: 0, + }, + }); + this.appendChild(this.containerShape); + this.containerShape.toBack(); + } + + private createBackground() { + this.backgroundShape = new Rect({ + name: 'background', + attrs: { + ...this.getAvailableSpace(), + ...this.getStyle('backgroundStyle'), + }, + }); + this.appendChild(this.backgroundShape); + } + + private createMiniMap() {} + + /** + * 根据orient和padding计算前景的x y width height + */ + private calcForegroundPosition() { + const [start, end] = this.getSafetyValues(); + const { x, y, width, height } = this.getAvailableSpace(); + return this.getOrientVal([ + { + y, + height, + x: start * width + x, + width: (end - start) * width, + }, + { + x, + width, + y: start * height + y, + height: (end - start) * height, + }, + ]); + } + + private createForeground() { + this.foregroundShape = new Rect({ + name: 'foreground', + attrs: { + ...this.calcForegroundPosition(), + ...this.getStyle('foregroundStyle'), + }, + }); + this.appendChild(this.foregroundShape); + } + + /** + * 计算手柄的x y + */ + private calcHandlePosition(handleType: HandleType) { + const { width, height } = this.getAvailableSpace(); + const values = this.getSafetyValues(); + const L = handleType === 'start' ? 0 : (values[1] - values[0]) * this.getOrientVal([width, height]); + return { + x: this.getOrientVal([L, width / 2]), + y: this.getOrientVal([height / 2, L]), + }; + } + + /** + * 设置选区 + * 1. 设置前景大小及位置 + * 2. 设置手柄位置 + * 3. 更新文本位置 + */ + private setHandle() { + applyAttrs(this.foregroundShape, { + ...this.calcForegroundPosition(), + }); + applyAttrs(this.startHandle, { + ...this.calcHandlePosition('start'), + }); + applyAttrs(this.endHandle, { + ...this.calcHandlePosition('end'), + }); + this.setHandleText(); + } + + /** + * 计算手柄应当处于的位置 + * @param name 手柄文字 + * @param handleType start手柄还是end手柄 + * @returns + */ + private calcHandleText(handleType: HandleType) { + const { orient, names } = this.attributes; + const { size, spacing, formatter, textStyle } = this.getHandleCfg(handleType); + // 相对于获取两端可用空间 + const { width: iW, height: iH } = this.getAvailableSpace(); + const { x: fX, y: fY, width: fW, height: fH } = this.calcForegroundPosition(); + let x = 0; + let y = 0; + + const formattedText = formatter(handleType === 'start' ? names[0] : names[1]); + + const _ = new Text({ + attrs: { + text: formattedText, + ...textStyle, + }, + }); + + // 文字的包围盒 + const tBox = _.getBounds(); + _.destroy(); + if (orient === 'horizontal') { + // 文本宽度 + const textWidth = tBox.getMax()[0] - tBox.getMin()[0]; + const ss = spacing + size / 2; + const _ = ss + textWidth / 2; + if (handleType === 'start') { + // 左边可用空间 + const aLeft = fX - ss; + x = aLeft > textWidth ? -_ : _; + } else { + // 右边可用空间 + const aRight = iW - fX - fW - ss; + x = aRight > textWidth ? _ : -_; + } + } else { + const _ = spacing + size / 2; + // 文本高度 + const textHeight = tBox.getMax()[1] - tBox.getMin()[1]; + if (handleType === 'start') { + // 上方可用空间 + const aAbove = fY - size / 2; + y = aAbove > textHeight ? -_ : _; + } else { + // 下方可用空间 + const aBelow = iH - fY - fH - size / 2; + y = aBelow > textHeight ? _ : -_; + } + } + return { x, y, text: formattedText }; + } + + private setHandleText() { + applyAttrs(this.startHandle.getElementsByName('handleText')[0], { + ...this.calcHandleText('start'), + }); + applyAttrs(this.endHandle.getElementsByName('handleText')[0], { + ...this.calcHandleText('end'), + }); + } + + private createHandle(options: HandleCfg, handleType: HandleType) { + const { show, size, textStyle, handleIcon: icon, handleStyle } = options; + const handleIcon = new Marker({ + name: 'handleIcon', + attrs: { + r: size / 2, + ...(show + ? { + symbol: icon, + ...handleStyle, + } + : { + // 如果不显示的话,就创建透明的rect + symbol: 'square', + markerStyle: { + opacity: 0, + }, + }), + cursor: 'ew-resize', + }, + }); + + const handleText = new Text({ + name: 'handleText', + attrs: { + // TODO 之后考虑添加文字超长省略,可以在calcHandleTextPosition中实现 + ...textStyle, + ...this.calcHandleText(handleType), + }, + }); + + // 用 Group 创建对象会提示没有attrs属性 + const handle = new DisplayObject({ + name: `${handleType}Handle`, + attrs: { + ...this.calcHandlePosition(handleType), + }, + }); + handle.appendChild(handleIcon); + handle.appendChild(handleText); + return handle; + } + + private getHandleCfg(handleType: HandleType) { + const { handle } = this.attributes; + if (handleType in handle) { + return handle[handleType]; + } + return handle; + } + + private createHandles() { + this.startHandle = this.createHandle(this.getHandleCfg('start'), 'start'); + this.foregroundShape.appendChild(this.startHandle); + this.endHandle = this.createHandle(this.getHandleCfg('end'), 'end'); + this.foregroundShape.appendChild(this.endHandle); + } + + private bindEvents() { + this.foregroundShape.addEventListener('mousedown', this.onForeDragStart); + this.startHandle + .getElementsByName('handleIcon')[0] + .addEventListener('mousedown', this.onHandleDragStart(this.startHandle)); + this.endHandle.getElementsByName('handleIcon')[0].on('mousedown', this.onHandleDragStart(this.endHandle)); + } + + private getOrientVal([x, y]: Pair) { + const { orient } = this.attributes; + return orient === 'horizontal' ? x : y; + } + + private setForeOffset(offset: number) { + const { width, height } = this.getAvailableSpace(); + const dVal = offset / this.getOrientVal([width, height]); + const [startVal, endVal] = this.getValues(); + this.setValues(this.getSafetyValues([startVal + dVal, endVal + dVal], true)); + } + + private onHandleDragStart = (target: DisplayObject) => (e) => { + e.stopPropagation(); + this.currTarget = target; + this.prevPos = this.getOrientVal([e.x, e.y]); + this.addEventListener('mousemove', this.onHandleDragging); + document.addEventListener('mouseup', this.onHandleDragEnd); + }; + + private onHandleDragging = (e) => { + e.stopPropagation(); + const currPos = this.getOrientVal([e.x, e.y]); + const _ = currPos - this.prevPos; + + if (!_) { + return; + } + const { width, height } = this.getAvailableSpace(); + const dVal = _ / this.getOrientVal([width, height]); + + const [startVal, endVal] = this.getValues(); + let newValues = [startVal + dVal, endVal]; + if (this.currTarget.getConfig().name === 'endHandle') { + newValues = [startVal, endVal + dVal]; + } + this.prevPos = currPos; + this.setValues(this.getSafetyValues(newValues as Pair)); + }; + + private onHandleDragEnd = () => { + this.removeEventListener('mousemove', this.onHandleDragging); + document.removeEventListener('mouseup', this.onHandleDragEnd); + }; + + private onForeDragStart = (e) => { + e.stopPropagation(); + this.prevPos = this.getOrientVal([e.x, e.y]); + this.addEventListener('mousemove', this.onForeDragging); + document.addEventListener('mouseup', this.onForeDragEnd); + }; + + private onForeDragging = (e) => { + e.stopPropagation(); + const currPos = this.getOrientVal([e.x, e.y]); + const _ = currPos - this.prevPos; + this.setForeOffset(_); + this.prevPos = currPos; + }; + + private onForeDragEnd = () => { + this.removeEventListener('mousemove', this.onForeDragging); + document.removeEventListener('mouseup', this.onForeDragEnd); + }; + + private onBrushStart = () => { + // 记录当前坐标 + }; + + private onBrushing = () => { + // 更新rect大小 + // 如果鼠标右移、下移,更新宽、高即可 + // 否则同时更新x、y和宽、高 + }; + + private onBrushEnd = () => { + // 使用this.setValues()方法重新绘制 + }; +} diff --git a/src/ui/slider/types.ts b/src/ui/slider/types.ts index 7a0b7297e..4b9c0dcfd 100644 --- a/src/ui/slider/types.ts +++ b/src/ui/slider/types.ts @@ -1 +1,101 @@ -export type SliderOptions = {}; +import { ShapeAttrs, ShapeCfg } from '../../types'; +import { MarkerOptions } from '../marker'; + +export type Pair = [T, T]; + +export type HandleCfg = { + /** + * 是否显示Handle + */ + show?: boolean; + /** + * 大小 + */ + size?: number; + /** + * 文本格式化 + */ + formatter?: (text: string) => string; + /** + * 文字样式 + */ + textStyle: ShapeAttrs; + /** + * 文字与手柄的间隔 + */ + spacing: number; + /** + * 手柄图标 + */ + handleIcon?: MarkerOptions['symbol']; + /** + * 手柄图标样式 + */ + handleStyle: ShapeAttrs; +}; + +export type MiniMap = { + /** + * number[] 单线 + * number[][] 多线 + */ + data: number[] | number[][]; + /** + * 平滑曲线 + */ + smooth: boolean; + lineStyle: ShapeAttrs; + areaStyle: ShapeAttrs; +}; + +export type MixAttrs = + | ShapeAttrs + | { + default: ShapeAttrs; + active: ShapeAttrs; + }; + +export type SliderOptions = ShapeCfg & { + /** + * slider 方向 + */ + orient?: 'vertical' | 'horizontal'; + values?: Pair; + names?: Pair; + min?: number; + max?: number; + width?: number; + height?: number; + + padding?: { + left: number; + right: number; + top: number; + buttons: number; + }; + + /** + * 背景样式 + */ + backgroundStyle?: MixAttrs; + + /** + * 前景样式 + */ + foregroundStyle?: MixAttrs; + + /** + * 手柄 + */ + handle?: + | HandleCfg + | { + start: HandleCfg; + end: HandleCfg; + }; + + /** + * 缩略图数据及其配置 + */ + miniMap?: MiniMap; +}; From 26b97eb49bfb47919438b706b0589606f6062ca7 Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Tue, 29 Jun 2021 12:54:52 +0800 Subject: [PATCH 04/49] =?UTF-8?q?test(text):=20=E8=AE=BE=E7=BD=AE=E4=BA=86?= =?UTF-8?q?=E5=90=88=E9=80=82=E7=9A=84=E7=B2=BE=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/unit/util/text.spec.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/__tests__/unit/util/text.spec.ts b/__tests__/unit/util/text.spec.ts index 7bc4b07ca..52b129c34 100644 --- a/__tests__/unit/util/text.spec.ts +++ b/__tests__/unit/util/text.spec.ts @@ -6,17 +6,17 @@ const NEW_FONT_STYLE = { ...FONT_STYLE, fontSize: 20 }; describe('text', () => { test('measureTextWidth', async () => { - expect(measureTextWidth('test text', FONT_STYLE)).toBeCloseTo(32, 0); - expect(measureTextWidth('test text test text', FONT_STYLE)).toBeCloseTo(67, 0); - expect(measureTextWidth('汉字', FONT_STYLE)).toBe(20); - expect(measureTextWidth('汉字测试文本', FONT_STYLE)).toBe(60); + expect(measureTextWidth('test text', FONT_STYLE)).toBeCloseTo(32, -1); + expect(measureTextWidth('test text test text', FONT_STYLE)).toBeCloseTo(67, -1); + expect(measureTextWidth('汉字', FONT_STYLE)).toBeCloseTo(20, -1); + expect(measureTextWidth('汉字测试文本', FONT_STYLE)).toBeCloseTo(60, -1); }); test('new font measure', () => { - expect(measureTextWidth('test text', NEW_FONT_STYLE)).toBeCloseTo(64.5, 0); - expect(measureTextWidth('test text test text', NEW_FONT_STYLE)).toBeCloseTo(134, 0); - expect(measureTextWidth('汉字', NEW_FONT_STYLE)).toBe(40); - expect(measureTextWidth('汉字测试文本', NEW_FONT_STYLE)).toBe(120); + expect(measureTextWidth('test text', NEW_FONT_STYLE)).toBeCloseTo(64.5, -1); + expect(measureTextWidth('test text test text', NEW_FONT_STYLE)).toBeCloseTo(134, -1); + expect(measureTextWidth('汉字', NEW_FONT_STYLE)).toBeCloseTo(40, -1); + expect(measureTextWidth('汉字测试文本', NEW_FONT_STYLE)).toBeCloseTo(120, -1); }); test('getEllipsisText', async () => { @@ -43,7 +43,7 @@ describe('text', () => { const testText = 'test text test text'; const han = '汉字测试文本'; - expect(getEllipsisText(testText, 20, NEW_FONT_STYLE)).toBe('t...'); + expect(getEllipsisText(testText, 20, NEW_FONT_STYLE)).toBe('...'); expect(getEllipsisText(testText, 40, NEW_FONT_STYLE)).toBe('tes...'); expect(getEllipsisText(testText, 60, NEW_FONT_STYLE)).toBe('test t...'); expect(getEllipsisText(testText, 120, NEW_FONT_STYLE)).toBe('test text test t...'); From 27feadd6080949ffb9d3c65ed6f38eba1fde9af9 Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Mon, 5 Jul 2021 17:13:27 +0800 Subject: [PATCH 05/49] =?UTF-8?q?feat(slider):=20=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E4=BA=86=E5=9F=BA=E6=9C=ACslider?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/slider/index.ts | 343 ++++++++++++++++++++--------------------- src/ui/slider/types.ts | 17 +- 2 files changed, 167 insertions(+), 193 deletions(-) diff --git a/src/ui/slider/index.ts b/src/ui/slider/index.ts index f74345d38..3fa53ad9d 100644 --- a/src/ui/slider/index.ts +++ b/src/ui/slider/index.ts @@ -1,5 +1,5 @@ import { Rect, Text } from '@antv/g'; -import { deepMix } from '@antv/util'; +import { deepMix, get } from '@antv/util'; // import { SliderOptions, HandleCfg, MiniMap, Pair } from './types'; import { SliderOptions, HandleCfg, Pair } from './types'; import { Marker } from '../marker'; @@ -7,7 +7,7 @@ import { CustomElement, DisplayObject, ShapeAttrs } from '../../types'; // import { /* applyAttrs */ measureTextWidth } from '../../util'; const applyAttrs = (target: DisplayObject, attrs: ShapeAttrs) => { Object.entries(attrs).forEach(([attrName, attrValue]) => { - target.attr(attrName, attrValue); + target.setAttribute(attrName, attrValue); }); }; @@ -18,11 +18,6 @@ type HandleType = 'start' | 'end'; export class Slider extends CustomElement { public static tag = 'slider'; - /** - * 容器 - */ - private containerShape: DisplayObject; - private backgroundShape: DisplayObject; private miniMapShape: DisplayObject; @@ -33,12 +28,19 @@ export class Slider extends CustomElement { private endHandle: DisplayObject; + /** + * 选区开始的位置 + */ + private selectionStartPos: number; + + private selectionWidth: number; + private prevPos: number; /** * drag事件当前选中的对象 */ - private currTarget: DisplayObject; + private target: string; constructor(options: SliderOptions) { super(deepMix({}, Slider.defaultOptions, options)); @@ -57,9 +59,9 @@ export class Slider extends CustomElement { height: 20, padding: { left: 20, - right: 0, - top: 0, - bottom: 0, + right: 10, + top: 10, + bottom: 10, }, backgroundStyle: { fill: '#fff', @@ -71,13 +73,13 @@ export class Slider extends CustomElement { opacity: 0.5, stroke: '#afc9fb', lineWidth: 1, - }, - brushStyle: { - fill: '#eef3ff', + active: { + fill: '#ccdaf5', + }, }, handle: { show: true, - size: 20, + size: 10, formatter: (val: string) => val, spacing: 10, handleIcon: 'circle', @@ -97,7 +99,9 @@ export class Slider extends CustomElement { }; attributeChangedCallback(name: string, value: any) { - value; + if (name === 'values') { + this.emit('valuechange', value); + } if (name in ['names', 'values']) { this.setHandle(); } @@ -108,7 +112,7 @@ export class Slider extends CustomElement { } public setValues(values: SliderOptions['values']) { - this.setAttribute('values', values); + this.setAttribute('values', this.getSafetyValues(values)); } public getNames() { @@ -122,43 +126,38 @@ export class Slider extends CustomElement { private init() { this.createBackground(); this.createForeground(); - // this.createContainer(); this.createHandles(); this.bindEvents(); - console.log(this.getElementsByName('handleIcon')); } /** * 获得安全的Values - * @param adjust true: 超出范围时移动至合法区间; false: 超出范围时取min、max; */ - private getSafetyValues(values?: Pair, adjust: boolean = false): Pair { + private getSafetyValues(values?: Pair): Pair { const { min, max } = this.attributes; - const [sV, eV] = values || this.getValues(); - let startVal = sV; - let endVal = eV; + const [prevStart, prevEnd] = this.getValues(); + let [startVal, endVal] = values || [prevStart, prevEnd]; + const range = endVal - startVal; // 交换startVal endVal if (startVal > endVal) { [startVal, endVal] = [endVal, startVal]; } // 超出范围就全选 - if (endVal - startVal > max - min) { + if (range > max - min) { return [min, max]; } - const lOffset = min - startVal; - // 无论是否调整都整体右移 - if (endVal < min) { - return [min, endVal + lOffset]; - } + if (startVal < min) { - return adjust ? [min, endVal + lOffset] : [min, endVal]; - } - const rOffset = endVal - max; - if (startVal > max) { - return [startVal - rOffset, max]; + if (prevStart === min && prevEnd === endVal) { + return [min, endVal]; + } + return [min, range + min]; } if (endVal > max) { - return adjust ? [startVal - rOffset, max] : [startVal, max]; + if (prevEnd === max && prevStart === startVal) { + return [startVal, max]; + } + return [max - range, max]; } return [startVal, endVal]; } @@ -173,37 +172,25 @@ export class Slider extends CustomElement { }; } - private getStyle(name: string, isActive?: boolean) { - const style = this.getAttribute(name); + /** + * 获取style + * @param name style名 + * @param isActive 是否是active style + * @returns ShapeAttrs + */ + private getStyle(name: string | string[], isActive?: boolean, handleType?: HandleType) { + const { active, ...args } = get(handleType ? this.getHandleCfg(handleType) : this.attributes, name); if (isActive) { - return style.active || {}; + return active || {}; } - return style.default || style; - } - - private createContainer() { - // 待子组件创建完成后,计算包围盒并设置padding - const { padding } = this.attributes; - const innerWidth = 0; - const innerHeight = 0; - this.containerShape = new Rect({ - name: 'container', - attrs: { - x: 0, - y: 0, - width: padding.left + padding.right + innerWidth, - height: padding.top + padding.bottom + innerHeight, - opacity: 0, - }, - }); - this.appendChild(this.containerShape); - this.containerShape.toBack(); + return args?.default || args; } private createBackground() { this.backgroundShape = new Rect({ name: 'background', attrs: { + cursor: 'crosshair', ...this.getAvailableSpace(), ...this.getStyle('backgroundStyle'), }, @@ -214,22 +201,23 @@ export class Slider extends CustomElement { private createMiniMap() {} /** - * 根据orient和padding计算前景的x y width height + * 计算蒙板坐标和宽高 + * 默认用来计算前景位置大小 */ - private calcForegroundPosition() { - const [start, end] = this.getSafetyValues(); - const { x, y, width, height } = this.getAvailableSpace(); + private calcMask(values?: Pair) { + const [start, end] = this.getSafetyValues(values); + const { width, height } = this.getAvailableSpace(); return this.getOrientVal([ { - y, + y: 0, height, - x: start * width + x, + x: start * width, width: (end - start) * width, }, { - x, + x: 0, width, - y: start * height + y, + y: start * height, height: (end - start) * height, }, ]); @@ -239,11 +227,12 @@ export class Slider extends CustomElement { this.foregroundShape = new Rect({ name: 'foreground', attrs: { - ...this.calcForegroundPosition(), + cursor: 'move', + ...this.calcMask(), ...this.getStyle('foregroundStyle'), }, }); - this.appendChild(this.foregroundShape); + this.backgroundShape.appendChild(this.foregroundShape); } /** @@ -266,16 +255,12 @@ export class Slider extends CustomElement { * 3. 更新文本位置 */ private setHandle() { - applyAttrs(this.foregroundShape, { - ...this.calcForegroundPosition(), + applyAttrs(this.foregroundShape, this.calcMask()); + applyAttrs(this.startHandle, this.calcHandlePosition('start')); + applyAttrs(this.endHandle, this.calcHandlePosition('end')); + this.getElementsByName('handleText').forEach((handleText) => { + applyAttrs(handleText, this.calcHandleText(handleText.getConfig().identity)); }); - applyAttrs(this.startHandle, { - ...this.calcHandlePosition('start'), - }); - applyAttrs(this.endHandle, { - ...this.calcHandlePosition('end'), - }); - this.setHandleText(); } /** @@ -289,66 +274,49 @@ export class Slider extends CustomElement { const { size, spacing, formatter, textStyle } = this.getHandleCfg(handleType); // 相对于获取两端可用空间 const { width: iW, height: iH } = this.getAvailableSpace(); - const { x: fX, y: fY, width: fW, height: fH } = this.calcForegroundPosition(); - let x = 0; - let y = 0; + const { x: fX, y: fY, width: fW, height: fH } = this.calcMask(); const formattedText = formatter(handleType === 'start' ? names[0] : names[1]); - const _ = new Text({ attrs: { text: formattedText, ...textStyle, }, }); - // 文字的包围盒 const tBox = _.getBounds(); _.destroy(); + + let x = 0; + let y = 0; + const R = size / 2; if (orient === 'horizontal') { - // 文本宽度 const textWidth = tBox.getMax()[0] - tBox.getMin()[0]; - const ss = spacing + size / 2; - const _ = ss + textWidth / 2; + const sh = spacing + R; + const _ = sh + textWidth / 2; if (handleType === 'start') { - // 左边可用空间 - const aLeft = fX - ss; - x = aLeft > textWidth ? -_ : _; + const left = fX - sh - textWidth; + x = left > 0 ? -_ : _; } else { - // 右边可用空间 - const aRight = iW - fX - fW - ss; - x = aRight > textWidth ? _ : -_; + x = iW - fX - fW - sh > textWidth ? _ : -_; } } else { - const _ = spacing + size / 2; - // 文本高度 + const _ = spacing + R; const textHeight = tBox.getMax()[1] - tBox.getMin()[1]; if (handleType === 'start') { - // 上方可用空间 - const aAbove = fY - size / 2; - y = aAbove > textHeight ? -_ : _; + y = fY - R > textHeight ? -_ : _; } else { - // 下方可用空间 - const aBelow = iH - fY - fH - size / 2; - y = aBelow > textHeight ? _ : -_; + y = iH - fY - fH - R > textHeight ? _ : -_; } } return { x, y, text: formattedText }; } - private setHandleText() { - applyAttrs(this.startHandle.getElementsByName('handleText')[0], { - ...this.calcHandleText('start'), - }); - applyAttrs(this.endHandle.getElementsByName('handleText')[0], { - ...this.calcHandleText('end'), - }); - } - private createHandle(options: HandleCfg, handleType: HandleType) { const { show, size, textStyle, handleIcon: icon, handleStyle } = options; const handleIcon = new Marker({ name: 'handleIcon', + identity: handleType, attrs: { r: size / 2, ...(show @@ -363,12 +331,13 @@ export class Slider extends CustomElement { opacity: 0, }, }), - cursor: 'ew-resize', + cursor: this.getOrientVal(['ew-resize', 'ns-resize']), }, }); const handleText = new Text({ name: 'handleText', + identity: handleType, attrs: { // TODO 之后考虑添加文字超长省略,可以在calcHandleTextPosition中实现 ...textStyle, @@ -378,10 +347,10 @@ export class Slider extends CustomElement { // 用 Group 创建对象会提示没有attrs属性 const handle = new DisplayObject({ - name: `${handleType}Handle`, - attrs: { - ...this.calcHandlePosition(handleType), - }, + // name: `${handleType}Handle`, + name: 'handle', + identity: handleType, + attrs: this.calcHandlePosition(handleType), }); handle.appendChild(handleIcon); handle.appendChild(handleText); @@ -389,11 +358,14 @@ export class Slider extends CustomElement { } private getHandleCfg(handleType: HandleType) { - const { handle } = this.attributes; - if (handleType in handle) { - return handle[handleType]; + const { start, end, ...args } = this.getAttribute('handle'); + let _ = {}; + if (handleType === 'start') { + _ = start; + } else if (handleType === 'end') { + _ = end; } - return handle; + return deepMix({}, args, _); } private createHandles() { @@ -404,11 +376,19 @@ export class Slider extends CustomElement { } private bindEvents() { - this.foregroundShape.addEventListener('mousedown', this.onForeDragStart); - this.startHandle - .getElementsByName('handleIcon')[0] - .addEventListener('mousedown', this.onHandleDragStart(this.startHandle)); - this.endHandle.getElementsByName('handleIcon')[0].on('mousedown', this.onHandleDragStart(this.endHandle)); + // Drag and brush + this.backgroundShape.addEventListener('mousedown', this.onDragStart('background')); + this.backgroundShape.addEventListener('touchstart', this.onDragStart('background')); + + this.foregroundShape.addEventListener('mousedown', this.onDragStart('foreground')); + this.foregroundShape.addEventListener('touchstart', this.onDragStart('foreground')); + + this.getElementsByName('handleIcon').forEach((handleIcon) => { + handleIcon.addEventListener('mousedown', this.onDragStart(`${handleIcon.getConfig().identity}Handle`)); + handleIcon.addEventListener('touchstart', this.onDragStart(`${handleIcon.getConfig().identity}Handle`)); + }); + // Hover + this.bindHoverEvents(); } private getOrientVal([x, y]: Pair) { @@ -416,77 +396,86 @@ export class Slider extends CustomElement { return orient === 'horizontal' ? x : y; } - private setForeOffset(offset: number) { + private setValuesOffset(stOffset: number, endOffset: number = 0) { + const [oldStartVal, oldEndVal] = this.getValues(); + this.setValues([oldStartVal + stOffset, oldEndVal + endOffset].sort() as Pair); + } + + private getRatio(val: number) { const { width, height } = this.getAvailableSpace(); - const dVal = offset / this.getOrientVal([width, height]); - const [startVal, endVal] = this.getValues(); - this.setValues(this.getSafetyValues([startVal + dVal, endVal + dVal], true)); + return val / this.getOrientVal([width, height]); } - private onHandleDragStart = (target: DisplayObject) => (e) => { + private onDragStart = (target: string) => (e) => { e.stopPropagation(); - this.currTarget = target; + this.target = target; this.prevPos = this.getOrientVal([e.x, e.y]); - this.addEventListener('mousemove', this.onHandleDragging); - document.addEventListener('mouseup', this.onHandleDragEnd); + const { x, y } = this.getAvailableSpace(); + const { x: X, y: Y } = this.attributes; + this.selectionStartPos = this.getRatio(this.prevPos - this.getOrientVal([x, y]) - this.getOrientVal([X, Y])); + this.selectionWidth = 0; + this.addEventListener('mousemove', this.onDragging); + this.addEventListener('touchmove', this.onDragging); + document.addEventListener('mouseup', this.onDragEnd); + document.addEventListener('touchend', this.onDragEnd); }; - private onHandleDragging = (e) => { + private onDragging = (e) => { e.stopPropagation(); const currPos = this.getOrientVal([e.x, e.y]); const _ = currPos - this.prevPos; - - if (!_) { - return; - } - const { width, height } = this.getAvailableSpace(); - const dVal = _ / this.getOrientVal([width, height]); - - const [startVal, endVal] = this.getValues(); - let newValues = [startVal + dVal, endVal]; - if (this.currTarget.getConfig().name === 'endHandle') { - newValues = [startVal, endVal + dVal]; + if (!_) return; + const dVal = this.getRatio(_); // _ / this.getOrientVal([width, height]); + + switch (this.target) { + case 'startHandle': + this.setValuesOffset(dVal); + break; + case 'endHandle': + this.setValuesOffset(0, dVal); + break; + case 'foreground': + this.setValuesOffset(dVal, dVal); + break; + case 'background': + // 绘制蒙板 + this.selectionWidth += dVal; + this.setValues([this.selectionStartPos, this.selectionStartPos + this.selectionWidth].sort() as Pair); + break; + default: + break; } - this.prevPos = currPos; - this.setValues(this.getSafetyValues(newValues as Pair)); - }; - private onHandleDragEnd = () => { - this.removeEventListener('mousemove', this.onHandleDragging); - document.removeEventListener('mouseup', this.onHandleDragEnd); - }; - - private onForeDragStart = (e) => { - e.stopPropagation(); - this.prevPos = this.getOrientVal([e.x, e.y]); - this.addEventListener('mousemove', this.onForeDragging); - document.addEventListener('mouseup', this.onForeDragEnd); - }; - - private onForeDragging = (e) => { - e.stopPropagation(); - const currPos = this.getOrientVal([e.x, e.y]); - const _ = currPos - this.prevPos; - this.setForeOffset(_); this.prevPos = currPos; }; - private onForeDragEnd = () => { - this.removeEventListener('mousemove', this.onForeDragging); - document.removeEventListener('mouseup', this.onForeDragEnd); - }; - - private onBrushStart = () => { - // 记录当前坐标 + private onDragEnd = () => { + this.removeEventListener('mousemove', this.onDragging); + this.removeEventListener('mousemove', this.onDragging); + document.removeEventListener('mouseup', this.onDragEnd); + document.removeEventListener('touchend', this.onDragEnd); }; - private onBrushing = () => { - // 更新rect大小 - // 如果鼠标右移、下移,更新宽、高即可 - // 否则同时更新x、y和宽、高 - }; + private bindHoverEvents = () => { + this.foregroundShape.addEventListener('mouseenter', () => { + applyAttrs(this.foregroundShape, this.getStyle('foregroundStyle', true)); + }); + this.foregroundShape.addEventListener('mouseleave', () => { + applyAttrs(this.foregroundShape, this.getStyle('foregroundStyle')); + }); - private onBrushEnd = () => { - // 使用this.setValues()方法重新绘制 + this.getElementsByName('handle').forEach((handle) => { + const icon = handle.getElementsByName('handleIcon')[0]; + const text = handle.getElementsByName('handleText')[0]; + console.log(icon); + handle.addEventListener('mouseenter', () => { + applyAttrs(icon, this.getStyle('handleStyle', true, icon.getConfig().identity)); + applyAttrs(text, this.getStyle('textStyle', true, text.getConfig().identity)); + }); + handle.addEventListener('mouseleave', () => { + applyAttrs(icon, this.getStyle('handleStyle', false, icon.getConfig().identity)); + applyAttrs(text, this.getStyle('textStyle', false, text.getConfig().identity)); + }); + }); }; } diff --git a/src/ui/slider/types.ts b/src/ui/slider/types.ts index 4b9c0dcfd..195167f47 100644 --- a/src/ui/slider/types.ts +++ b/src/ui/slider/types.ts @@ -56,9 +56,6 @@ export type MixAttrs = }; export type SliderOptions = ShapeCfg & { - /** - * slider 方向 - */ orient?: 'vertical' | 'horizontal'; values?: Pair; names?: Pair; @@ -66,27 +63,15 @@ export type SliderOptions = ShapeCfg & { max?: number; width?: number; height?: number; - padding?: { left: number; right: number; top: number; buttons: number; }; - - /** - * 背景样式 - */ backgroundStyle?: MixAttrs; - - /** - * 前景样式 - */ + selectionStyle?: MixAttrs; foregroundStyle?: MixAttrs; - - /** - * 手柄 - */ handle?: | HandleCfg | { From 4f91f73424d8b1d0fbd0193a1fb28cbc286f108d Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Mon, 5 Jul 2021 17:14:28 +0800 Subject: [PATCH 06/49] =?UTF-8?q?test(slider):=20slider=E7=9A=84=E5=9F=BA?= =?UTF-8?q?=E6=9C=AC=E5=8D=95=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/unit/ui/slider/index.spec.ts | 37 ++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 __tests__/unit/ui/slider/index.spec.ts diff --git a/__tests__/unit/ui/slider/index.spec.ts b/__tests__/unit/ui/slider/index.spec.ts new file mode 100644 index 000000000..a69b5de73 --- /dev/null +++ b/__tests__/unit/ui/slider/index.spec.ts @@ -0,0 +1,37 @@ +import { Canvas } from '@antv/g'; +import { Renderer as CanvasRenderer } from '@antv/g-canvas'; +import { Slider } from '../../../../src'; +import { createDiv } from '../../../utils'; + +const renderer = new CanvasRenderer({ + enableDirtyRectangleRenderingDebug: false, + enableAutoRendering: true, + enableDirtyRectangleRendering: true, +}); + +describe('marker', () => { + test('basic', async () => { + const div = createDiv(); + + // @ts-ignore + const canvas = new Canvas({ + container: div, + width: 300, + height: 300, + renderer, + }); + + const slider = new Slider({ + attrs: { + x: 50, + y: 50, + width: 200, + height: 40, + values: [0.4, 0.7], + names: ['A', 'V'], + }, + }); + + canvas.appendChild(slider); + }); +}); From 67b3513ea91db4d4bddd156f44a7349ebd0c57c3 Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Mon, 5 Jul 2021 19:24:57 +0800 Subject: [PATCH 07/49] =?UTF-8?q?chore:=20=E8=AE=BE=E7=BD=AE=E7=BC=96?= =?UTF-8?q?=E8=AF=91=E7=9B=AE=E6=A0=87=E4=B8=BAES2019=E4=BB=A5=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=8F=AF=E9=80=89=E9=93=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index ae857496b..8a4a8b304 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "lint": "eslint ./src/**/*.ts ./__tests__/**/*.ts && prettier ./src ./__tests__ --check ", "fix": "eslint ./src/**/*.ts ./__tests__/**/*.ts --fix && prettier ./src ./__tests__ --write ", "test": "jest", - "test-live": "DEBUG_MODE=1 jest --watch ./__tests__", + "test-live": "DEBUG_MODE=1 jest --watch ./__tests__/unit/ui/slider", "build": "father-build & limit-size", "ci": "run-s lint test build", "prepublishOnly": "npm run ci", @@ -81,13 +81,19 @@ "preset": "ts-jest", "collectCoverage": true, "testRegex": "(/__tests__/.*\\.(test|spec))\\.ts$", - "setupFilesAfterEnv": [ "./jest.setup.js" ], "collectCoverageFrom": [ "src/**/*.ts" - ] + ], + "globals": { + "ts-jest": { + "tsConfig": { + "target": "ES2019" + } + } + } }, "lint-staged": { "*.{ts,tsx}": [ From dde36972d0a7833b2a55f0fc95fd361dc98f9a69 Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Mon, 5 Jul 2021 23:26:05 +0800 Subject: [PATCH 08/49] =?UTF-8?q?fix(sparkline):=20=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E4=BA=86sparkline=E7=9A=84baseline=E8=AE=A1=E7=AE=97=E6=96=B9?= =?UTF-8?q?=E6=B3=95=EF=BC=8C=E4=BF=AE=E6=AD=A3=E4=BA=86sparkline=E7=9A=84?= =?UTF-8?q?=E5=AE=9A=E4=BD=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/sparkline/index.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/ui/sparkline/index.ts b/src/ui/sparkline/index.ts index 48dc02a47..c05067239 100644 --- a/src/ui/sparkline/index.ts +++ b/src/ui/sparkline/index.ts @@ -52,11 +52,9 @@ export class Sparkline extends CustomElement { } private init() { - const { x, y, type, width, height } = this.attributes; + const { type, width, height } = this.attributes; this.sparkShapes = new Rect({ attrs: { - x, - y, width, height, }, @@ -93,6 +91,7 @@ export class Sparkline extends CustomElement { */ private createScales(data: number[][]) { const { type, width, height, isGroup, barPadding } = this.attributes; + const [minVal, maxVal] = [min(minBy(data, (arr) => min(arr))), max(maxBy(data, (arr) => max(arr)))]; return { type, x: @@ -107,7 +106,7 @@ export class Sparkline extends CustomElement { paddingInner: isGroup ? barPadding : 0, }), y: new Linear({ - domain: [min(minBy(data, (arr) => min(arr))), max(maxBy(data, (arr) => max(arr)))], + domain: [minVal >= 0 ? 0 : minVal, maxVal], range: [height, 0], }), }; From a179a7b4209d0225daad9b59de670498be23dec3 Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Tue, 6 Jul 2021 10:22:36 +0800 Subject: [PATCH 09/49] =?UTF-8?q?feat(slider):=20=E5=8A=A0=E5=85=A5?= =?UTF-8?q?=E4=BA=86sparkline?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/slider/index.ts | 30 +++++++++++++++++++++++++++--- src/ui/slider/types.ts | 17 ++--------------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/ui/slider/index.ts b/src/ui/slider/index.ts index 3fa53ad9d..c9092bb5b 100644 --- a/src/ui/slider/index.ts +++ b/src/ui/slider/index.ts @@ -3,6 +3,7 @@ import { deepMix, get } from '@antv/util'; // import { SliderOptions, HandleCfg, MiniMap, Pair } from './types'; import { SliderOptions, HandleCfg, Pair } from './types'; import { Marker } from '../marker'; +import { Sparkline } from '../sparkline'; import { CustomElement, DisplayObject, ShapeAttrs } from '../../types'; // import { /* applyAttrs */ measureTextWidth } from '../../util'; const applyAttrs = (target: DisplayObject, attrs: ShapeAttrs) => { @@ -20,7 +21,7 @@ export class Slider extends CustomElement { private backgroundShape: DisplayObject; - private miniMapShape: DisplayObject; + private sparklineShape: DisplayObject; private foregroundShape: DisplayObject; @@ -125,6 +126,7 @@ export class Slider extends CustomElement { private init() { this.createBackground(); + this.createSparkline(); this.createForeground(); this.createHandles(); this.bindEvents(); @@ -198,7 +200,29 @@ export class Slider extends CustomElement { this.appendChild(this.backgroundShape); } - private createMiniMap() {} + /** + * 生成sparkline + */ + private createSparkline() { + const { orient, sparklineCfg } = this.attributes; + // 暂时只在水平模式下绘制 + if (orient !== 'horizontal') { + return; + } + const { width, height } = this.getAvailableSpace(); + const { lineWidth: bkgLW } = this.getStyle('backgroundStyle'); + this.sparklineShape = new Sparkline({ + attrs: { + x: bkgLW / 2, + y: bkgLW / 2, + width: width - bkgLW, + height: height - bkgLW, + ...sparklineCfg, + }, + }); + this.backgroundShape.appendChild(this.sparklineShape); + this.sparklineShape.toBack(); + } /** * 计算蒙板坐标和宽高 @@ -207,6 +231,7 @@ export class Slider extends CustomElement { private calcMask(values?: Pair) { const [start, end] = this.getSafetyValues(values); const { width, height } = this.getAvailableSpace(); + return this.getOrientVal([ { y: 0, @@ -467,7 +492,6 @@ export class Slider extends CustomElement { this.getElementsByName('handle').forEach((handle) => { const icon = handle.getElementsByName('handleIcon')[0]; const text = handle.getElementsByName('handleText')[0]; - console.log(icon); handle.addEventListener('mouseenter', () => { applyAttrs(icon, this.getStyle('handleStyle', true, icon.getConfig().identity)); applyAttrs(text, this.getStyle('textStyle', true, text.getConfig().identity)); diff --git a/src/ui/slider/types.ts b/src/ui/slider/types.ts index 195167f47..1edc8f5aa 100644 --- a/src/ui/slider/types.ts +++ b/src/ui/slider/types.ts @@ -1,5 +1,6 @@ import { ShapeAttrs, ShapeCfg } from '../../types'; import { MarkerOptions } from '../marker'; +import { SparklineOptions } from '../sparkline'; export type Pair = [T, T]; @@ -34,20 +35,6 @@ export type HandleCfg = { handleStyle: ShapeAttrs; }; -export type MiniMap = { - /** - * number[] 单线 - * number[][] 多线 - */ - data: number[] | number[][]; - /** - * 平滑曲线 - */ - smooth: boolean; - lineStyle: ShapeAttrs; - areaStyle: ShapeAttrs; -}; - export type MixAttrs = | ShapeAttrs | { @@ -82,5 +69,5 @@ export type SliderOptions = ShapeCfg & { /** * 缩略图数据及其配置 */ - miniMap?: MiniMap; + sparklineCfg?: SparklineOptions; }; From 57c51c50fa55ab32d6b12d0120928ba65d4c0d24 Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Tue, 6 Jul 2021 11:39:11 +0800 Subject: [PATCH 10/49] =?UTF-8?q?refactor(slider):=20=E6=9A=82=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/unit/ui/slider/index.spec.ts | 24 ++++++++++++---- src/ui/slider/index.ts | 40 ++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 8 deletions(-) diff --git a/__tests__/unit/ui/slider/index.spec.ts b/__tests__/unit/ui/slider/index.spec.ts index a69b5de73..de45ee329 100644 --- a/__tests__/unit/ui/slider/index.spec.ts +++ b/__tests__/unit/ui/slider/index.spec.ts @@ -9,14 +9,14 @@ const renderer = new CanvasRenderer({ enableDirtyRectangleRendering: true, }); -describe('marker', () => { +describe('slider', () => { test('basic', async () => { const div = createDiv(); // @ts-ignore const canvas = new Canvas({ container: div, - width: 300, + width: 800, height: 300, renderer, }); @@ -25,10 +25,24 @@ describe('marker', () => { attrs: { x: 50, y: 50, - width: 200, - height: 40, + width: 600, + height: 80, values: [0.4, 0.7], - names: ['A', 'V'], + names: ['Abcas', 'Vxczxz'], + backgroundStyle: { + lineWidth: 2, + }, + sparklineCfg: { + // type: 'column', + // isStack: true, + data: [ + [1, 3, 2, -4], + [5, 1, 5, -8], + ], + areaStyle: { + opacity: 0.5, + }, + }, }, }); diff --git a/src/ui/slider/index.ts b/src/ui/slider/index.ts index c9092bb5b..64c85ce07 100644 --- a/src/ui/slider/index.ts +++ b/src/ui/slider/index.ts @@ -1,8 +1,8 @@ import { Rect, Text } from '@antv/g'; -import { deepMix, get } from '@antv/util'; +import { deepMix, get, isFunction, isString, isObject } from '@antv/util'; // import { SliderOptions, HandleCfg, MiniMap, Pair } from './types'; import { SliderOptions, HandleCfg, Pair } from './types'; -import { Marker } from '../marker'; +import { Marker, MarkerOptions } from '../marker'; import { Sparkline } from '../sparkline'; import { CustomElement, DisplayObject, ShapeAttrs } from '../../types'; // import { /* applyAttrs */ measureTextWidth } from '../../util'; @@ -69,6 +69,7 @@ export class Slider extends CustomElement { stroke: '#e4eaf5', lineWidth: 1, }, + sparklineCfg: {}, foregroundStyle: { fill: '#afc9fb', opacity: 0.5, @@ -95,7 +96,6 @@ export class Slider extends CustomElement { lineWidth: 1, }, }, - miniMap: {}, }, }; @@ -337,8 +337,42 @@ export class Slider extends CustomElement { return { x, y, text: formattedText }; } + /** + * 解析icon类型 + */ + private parseIcon(icon: MarkerOptions['symbol'] | string) { + // MarkerOptions['symbol']: string | FunctionalSymbol + let type = 'unknown'; + if (isObject(icon) && icon instanceof Image) type = 'Image'; + else if (isFunction(icon)) type = 'symbol'; + else if (isString(icon)) { + const dataURLsPattern = new RegExp('data:(image|text)'); + if (icon.match(dataURLsPattern)) { + type = 'base64'; + } else if (/^(https?:\/\/(([a-zA-Z0-9]+-?)+[a-zA-Z0-9]+\.)+[a-zA-Z]+)(:\d+)?(\/.*)?(\?.*)?(#.*)?$/.test(icon)) { + type = 'url'; + } + } + return type; + } + private createHandle(options: HandleCfg, handleType: HandleType) { const { show, size, textStyle, handleIcon: icon, handleStyle } = options; + const iconType = this.parseIcon(icon); + + switch (iconType) { + case 'Image': + break; + case 'symbol': + break; + case 'base64': + break; + case 'url': + break; + default: + break; + } + const handleIcon = new Marker({ name: 'handleIcon', identity: handleType, From c7751f88adb4e4a4dd7542647909f28dc9d2f3f4 Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Tue, 6 Jul 2021 20:40:15 +0800 Subject: [PATCH 11/49] =?UTF-8?q?docs(slider):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E4=BA=86slider=E7=9A=84example?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/slider/API.en.md | 1 + examples/slider/API.zh.md | 1 + examples/slider/demo/basic-slider.ts | 30 +++++++++++++ .../slider/demo/custom-handleIcon-slider.ts | 43 +++++++++++++++++++ examples/slider/demo/meta.json | 40 +++++++++++++++++ examples/slider/demo/sparkline-slider.ts | 37 ++++++++++++++++ examples/slider/demo/vertical-slider.ts | 31 +++++++++++++ examples/slider/index.en.md | 4 ++ examples/slider/index.zh.md | 4 ++ 9 files changed, 191 insertions(+) create mode 100644 examples/slider/API.en.md create mode 100644 examples/slider/API.zh.md create mode 100644 examples/slider/demo/basic-slider.ts create mode 100644 examples/slider/demo/custom-handleIcon-slider.ts create mode 100644 examples/slider/demo/meta.json create mode 100644 examples/slider/demo/sparkline-slider.ts create mode 100644 examples/slider/demo/vertical-slider.ts create mode 100644 examples/slider/index.en.md create mode 100644 examples/slider/index.zh.md diff --git a/examples/slider/API.en.md b/examples/slider/API.en.md new file mode 100644 index 000000000..ca3286b49 --- /dev/null +++ b/examples/slider/API.en.md @@ -0,0 +1 @@ +`markdown:docs/api/ui/slider.en.md` diff --git a/examples/slider/API.zh.md b/examples/slider/API.zh.md new file mode 100644 index 000000000..30cd414b5 --- /dev/null +++ b/examples/slider/API.zh.md @@ -0,0 +1 @@ +`markdown:docs/api/ui/slider.zh.md` diff --git a/examples/slider/demo/basic-slider.ts b/examples/slider/demo/basic-slider.ts new file mode 100644 index 000000000..a1e4c9b99 --- /dev/null +++ b/examples/slider/demo/basic-slider.ts @@ -0,0 +1,30 @@ +import { Canvas } from '@antv/g'; +import { Renderer as CanvasRenderer } from '@antv/g-canvas'; +import { Slider } from '@antv/gui'; + +const renderer = new CanvasRenderer({ + enableDirtyRectangleRenderingDebug: false, + enableAutoRendering: true, + enableDirtyRectangleRendering: true, +}); + +// @ts-ignore +const canvas = new Canvas({ + container: 'container', + width: 600, + height: 300, + renderer, +}); + +const slider = new Slider({ + attrs: { + x: 50, + y: 50, + width: 400, + height: 40, + values: [0.3, 0.7], + names: ['leftVal', 'rightVal'], + }, +}); + +canvas.appendChild(slider); diff --git a/examples/slider/demo/custom-handleIcon-slider.ts b/examples/slider/demo/custom-handleIcon-slider.ts new file mode 100644 index 000000000..2f38bca32 --- /dev/null +++ b/examples/slider/demo/custom-handleIcon-slider.ts @@ -0,0 +1,43 @@ +import { Canvas } from '@antv/g'; +import { Renderer as CanvasRenderer } from '@antv/g-canvas'; +import { Slider } from '@antv/gui'; + +const renderer = new CanvasRenderer({ + enableDirtyRectangleRenderingDebug: false, + enableAutoRendering: true, + enableDirtyRectangleRendering: true, +}); + +// @ts-ignore +const canvas = new Canvas({ + container: 'container', + width: 600, + height: 300, + renderer, +}); + +const slider = new Slider({ + attrs: { + x: 50, + y: 50, + width: 400, + height: 40, + values: [0.3, 0.7], + names: ['leftVal', 'rightVal'], + handle: { + start: { + size: 15, + formatter: (name, value) => { + return `${name}: ${value * 100}%`; + }, + handleIcon: 'https://gw.alipayobjects.com/mdn/rms_6ae20b/afts/img/A*N4ZMS7gHsUIAAAAAAAAAAABkARQnAQ', + }, + end: { + spacing: 20, + handleIcon: 'diamond', + }, + }, + }, +}); + +canvas.appendChild(slider); diff --git a/examples/slider/demo/meta.json b/examples/slider/demo/meta.json new file mode 100644 index 000000000..5d10970f0 --- /dev/null +++ b/examples/slider/demo/meta.json @@ -0,0 +1,40 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "basic-slider.ts", + "title": { + "zh": "基础缩略轴", + "en": "Basic Slider" + }, + "screenshot": "" + }, + { + "filename": "vertical-slider.ts", + "title": { + "zh": "垂直缩略轴", + "en": "Vertical Slider" + }, + "screenshot": "" + }, + { + "filename": "custom-handleIcon-slider.ts", + "title": { + "zh": "自定义手柄缩略轴", + "en": "Custom Handle Icon Slider" + }, + "screenshot": "" + }, + { + "filename": "sparkline-slider.ts", + "title": { + "zh": "具有缩略图的缩略轴", + "en": "Slider with Sparkline" + }, + "screenshot": "" + } + ] +} diff --git a/examples/slider/demo/sparkline-slider.ts b/examples/slider/demo/sparkline-slider.ts new file mode 100644 index 000000000..a33e2d8b8 --- /dev/null +++ b/examples/slider/demo/sparkline-slider.ts @@ -0,0 +1,37 @@ +import { Canvas } from '@antv/g'; +import { Renderer as CanvasRenderer } from '@antv/g-canvas'; +import { Slider } from '@antv/gui'; + +const renderer = new CanvasRenderer({ + enableDirtyRectangleRenderingDebug: false, + enableAutoRendering: true, + enableDirtyRectangleRendering: true, +}); + +// @ts-ignore +const canvas = new Canvas({ + container: 'container', + width: 600, + height: 300, + renderer, +}); + +const slider = new Slider({ + attrs: { + x: 50, + y: 50, + width: 400, + height: 40, + values: [0.3, 0.7], + names: ['leftVal', 'rightVal'], + sparklineCfg: { + // type: 'column', + data: [ + [1, 3, 2, -4, 1, 3, 2, -4], + [5, 1, 5, -8, 5, 1, 5, -8], + ], + }, + }, +}); + +canvas.appendChild(slider); diff --git a/examples/slider/demo/vertical-slider.ts b/examples/slider/demo/vertical-slider.ts new file mode 100644 index 000000000..50a652238 --- /dev/null +++ b/examples/slider/demo/vertical-slider.ts @@ -0,0 +1,31 @@ +import { Canvas } from '@antv/g'; +import { Renderer as CanvasRenderer } from '@antv/g-canvas'; +import { Slider } from '@antv/gui'; + +const renderer = new CanvasRenderer({ + enableDirtyRectangleRenderingDebug: false, + enableAutoRendering: true, + enableDirtyRectangleRendering: true, +}); + +// @ts-ignore +const canvas = new Canvas({ + container: 'container', + width: 300, + height: 600, + renderer, +}); + +const slider = new Slider({ + attrs: { + x: 50, + y: 50, + width: 40, + height: 400, + orient: 'vertical', + values: [0.3, 0.7], + names: ['aboveVal', 'belowVal'], + }, +}); + +canvas.appendChild(slider); diff --git a/examples/slider/index.en.md b/examples/slider/index.en.md new file mode 100644 index 000000000..289681515 --- /dev/null +++ b/examples/slider/index.en.md @@ -0,0 +1,4 @@ +--- +title: Slider +order: 6 +--- diff --git a/examples/slider/index.zh.md b/examples/slider/index.zh.md new file mode 100644 index 000000000..289681515 --- /dev/null +++ b/examples/slider/index.zh.md @@ -0,0 +1,4 @@ +--- +title: Slider +order: 6 +--- From 0a21507b82660cdeb56400ab8c72ab447f91ed20 Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Tue, 6 Jul 2021 20:40:56 +0800 Subject: [PATCH 12/49] =?UTF-8?q?docs(slider):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E4=BA=86slider=E7=9A=84=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/api/ui/slider.en.md | 50 +++++++++++++++++++++++++++++ docs/api/ui/slider.zh.md | 47 +++++++++++++++++++++++++++ docs/api/ui/sparkline.en.md | 14 +------- docs/api/ui/sparkline.zh.md | 14 +------- docs/common/shape-attrs.zh.md | 40 +++++++++++++++++++++++ docs/common/sparkline-options.en.md | 13 ++++++++ docs/common/sparkline-options.zh.md | 13 ++++++++ 7 files changed, 165 insertions(+), 26 deletions(-) create mode 100644 docs/api/ui/slider.en.md create mode 100644 docs/api/ui/slider.zh.md create mode 100644 docs/common/shape-attrs.zh.md create mode 100644 docs/common/sparkline-options.en.md create mode 100644 docs/common/sparkline-options.zh.md diff --git a/docs/api/ui/slider.en.md b/docs/api/ui/slider.en.md new file mode 100644 index 000000000..0f33346ea --- /dev/null +++ b/docs/api/ui/slider.en.md @@ -0,0 +1,50 @@ +--- +title: Slider +order: 5 +--- + +# 缩略轴 + +> 缩略轴 + +## 引入 + +```ts +import { Slider } from '@antv/gui'; +``` + +## 配置项 + +| **Property** | **Description** | **Type** | **Default** | +| --------------- | --------------- | --------------------------------------------------------- | ------------ | +| orient | Slider 朝向 | horizontal | vertical | `horizontal` | +| width | 宽度 | number | `200` | +| height | 高度 | number | `20` | +| values | 缩略轴范围 | [number, number] | `[0, 1]` | +| names | 手柄文本 | [string, string] | `['', '']` | +| min | 最小可滚动范围 | number | `0` | +| max | 最大可滚动范围 | number | `1` | +| sparkline | 缩略图配置 | SparklineOptions | `[]` | +| backgroundStyle | 自定义背景样式 | ShapeAttrs & {active: ShapeAttrs} | `[]` | +| foregroundStyle | 自定义前景样式 | ShapeAttrs & {active: ShapeAttrs} | `[]` | +| handle | 手柄配置 | handleCfg \| {start: handleCfg;end:handleCfg} | `[]` | + +### SparklineOptions + +`markdown:docs/common/sparkline-options.zh.md` + +## handleCfg + +| **Property** | **Description** | **Type** | **Default** | +| ------------ | ----------------------------------------------- | -------------------------------------------------------------------------------- | ----------- | +| show | boolean | 是否显示手柄 | `true` | +| size | number | 手柄图标大小 | `10` | +| formatter | (name, value)=>string | 文本格式化 | `[]` | +| textStyle | ShapeAttrs | 文字样式 | `[]` | +| spacing | number | 文字与手柄的间隔 | `10` | +| handleIcon | (x,y,r)=>PathCommand \| string | 手柄图标,支持**image URL**、**data URL**、**Symbol Name**、 **Symbol Function** | `[]` | +| handleStyle | ShapeAttrs & {active?: ShapeAttrs} | 手柄图标样式 | `[]` | + +### ShapeAttrs + +`markdown:docs/common/shape-attrs.zh.md` diff --git a/docs/api/ui/slider.zh.md b/docs/api/ui/slider.zh.md new file mode 100644 index 000000000..312e59895 --- /dev/null +++ b/docs/api/ui/slider.zh.md @@ -0,0 +1,47 @@ +--- +title: Slider +order: 5 +--- + +# 缩略轴 + +> 缩略轴 + +## 引入 + +```ts +import { Slider } from '@antv/gui'; +``` + +## 配置项 + +| **属性** | **描述** | **类型** | **默认值** | +| --------------- | -------------- | ----------------------------------------------------------- | ------------ | +| orient | Slider 朝向 | horizontal | vertical | `horizontal` | +| width | 宽度 | number | `200` | +| height | 高度 | number | `20` | +| values | 缩略轴范围 | [number, number] | `[0, 1]` | +| names | 手柄文本 | [string, string] | `['', '']` | +| min | 最小可滚动范围 | number | `0` | +| max | 最大可滚动范围 | number | `1` | +| sparkline | 缩略图配置 | SparklineOptions | `[]` | +| backgroundStyle | 自定义背景样式 | ShapeAttrs & {active?: ShapeAttrs} | `[]` | +| foregroundStyle | 自定义前景样式 | ShapeAttrs & {active?: ShapeAttrs} | `[]` | +| handle | 手柄配置 | handleCfg \| {start: handleCfg; end: handleCfg} | `[]` | + +## SparklineOptions +`markdown:docs/common/sparkline-options.zh.md` + +## handleCfg +| **属性** | **类型** | **描述** | **默认值** | +| ----------- | ----------------------------------------------- | -------------------------------------------------------------------------------- | ---------- | +| show | boolean | 是否显示手柄 | `true` | +| size | number | 手柄图标大小 | `10` | +| formatter | (name, value)=>string | 文本格式化 | `[]` | +| textStyle | ShapeAttrs | 文字样式 | `[]` | +| spacing | number | 文字与手柄的间隔 | `10` | +| handleIcon | (x,y,r)=>PathCommand \| string | 手柄图标,支持**image URL**、**data URL**、**Symbol Name**、 **Symbol Function** | `[]` | +| handleStyle | ShapeAttrs & {active?: ShapeAttrs} | 手柄图标样式 | `[]` | + +## ShapeAttrs +`markdown:docs/common/shape-attrs.zh.md` diff --git a/docs/api/ui/sparkline.en.md b/docs/api/ui/sparkline.en.md index 82efce8b9..2d9192e2f 100644 --- a/docs/api/ui/sparkline.en.md +++ b/docs/api/ui/sparkline.en.md @@ -15,16 +15,4 @@ import { Sparkline } from '@antv/gui'; ## Options -| **Property** | **Description** | **Type** | **Default** | -| ------------ | ------------------------ | -------------------------------------------------------- | -------------------------------------------------------- | -| type | type of sparkline | line | bar | `default` | -| width | width | number | `200` | -| height | height | number | `20` | -| data | data of sparkline | number[] | number[][] | `[]` | -| isStack | whether to stack | boolean | `false` | -| color | color of visual elements | color | color[] | (index) => color | `'#83daad', '#edbf45', '#d2cef9', '#e290b3', '#6f63f4']` | -| smooth | use smooth curves | boolean | `true` | -| lineStyle | custom line styles | StyleAttr | `[]` | -| areaStyle | custom area styles | StyleAttr | `[]` | -| isGroup | whether to group series | boolean | `false` | -| columnStyle | custom column styles | ShapeAttrs | `[]` | +`markdown:docs/common/sparkline-options.en.md` diff --git a/docs/api/ui/sparkline.zh.md b/docs/api/ui/sparkline.zh.md index bc5b498a1..b09eef14a 100644 --- a/docs/api/ui/sparkline.zh.md +++ b/docs/api/ui/sparkline.zh.md @@ -15,16 +15,4 @@ import { Sparkline } from '@antv/gui'; ## 配置项 -| **属性** | **描述** | **类型** | **默认值** | -| ----------- | ------------------ | -------------------------------------------------------- | -------------------------------------------------------- | -| type | sparkline 类型 | line | bar | `default` | -| width | 宽度 | number | `200` | -| height | 高度 | number | `20` | -| data | 数据 | number[] | number[][] | `[]` | -| isStack | 是否堆积 | boolean | `false` | -| color | 颜色 | color | color[] | (index) => color | `'#83daad', '#edbf45', '#d2cef9', '#e290b3', '#6f63f4']` | -| smooth | 平滑曲线 | boolean | `true` | -| lineStyle | 自定义线条样式 | StyleAttr | `[]` | -| areaStyle | 自定义线条填充样式 | StyleAttr | `[]` | -| isGroup | 是否分组 | boolean | `false` | -| columnStyle | 柱体样式 | ShapeAttrs | `[]` | +`markdown:docs/common/sparkline-options.zh.md` diff --git a/docs/common/shape-attrs.zh.md b/docs/common/shape-attrs.zh.md new file mode 100644 index 000000000..84cf00948 --- /dev/null +++ b/docs/common/shape-attrs.zh.md @@ -0,0 +1,40 @@ +### 基本属性 + +| **属性名** | **类型** | **描述** | +| ------------- | ------------------- | ---------------------------------------- | +| x | number | x 坐标 | +| y | number | y 坐标 | +| r | number | 半径 | +| width | number | 宽度 | +| height | number | 高度 | +| stroke | color | 描边颜色,可以是 rgba 值、颜色名(下同) | +| strokeOpacity | number | 描边透明度 | +| fill | color | 填充颜色 | +| fillOpacity | number | 填充透明度 | +| Opacity | number | 整体透明度 | +| shadowBlur | number | 模糊效果程度 | +| shadowColor | color | 阴影颜色 | +| shadowOffsetX | number | 阴影水平偏移距离 | +| shadowOffsetY | number | 阴影垂直偏移距离 | + +### 线条属性 + +| **属性名** | **类型** | **描述** | +| ---------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | +| lineWidth | number | 线条或图形边框宽度 | +| lineCap | 'butt' \| 'round' \| 'square' | 线段末端样式 | +| lineJoin | 'bevel' \| 'round' \| 'miter' | 设置 2 个长度不为 0 的相连部分(线段,圆弧,曲线)如何连接在一起的属性(长度为 0 的变形部分,其指定的末端和控制点在同一位置,会被忽略) | +| lineDash | number[] \| null | 线条或图形边框的虚线样式 | + +### 文本属性 + +| **属性名** | **类型** | **描述** | +| ------------ | ---------------------------------------------------------------------------------------- | ----------------------------------- | +| textAlign | 'start' \| 'center' \| 'end' \| 'left' \| 'right' | 设置文本内容的当前对齐方式 | +| textBaseline | 'top' \| 'hanging' \| 'middle' \| 'alphabetic' \| 'ideographic' \| 'bottom' | 设置在绘制文本时使用的当前文本基线, | +| fontStyle | 'normal' \| 'italic' \| 'oblique' | 设置字体样式 | +| fontSize | number | 设置字号 | +| fontFamily | string | 设置字体系列 | +| fontWeight | 'normal' \| 'bold' \| 'bolder' \| 'lighter' \| number | 设置字体的粗细 | +| fontVariant | 'normal' \| 'small-caps' \| string | 设置字体变体 | +| lineHeight | number | 设置行高 | diff --git a/docs/common/sparkline-options.en.md b/docs/common/sparkline-options.en.md new file mode 100644 index 000000000..5d2e546e0 --- /dev/null +++ b/docs/common/sparkline-options.en.md @@ -0,0 +1,13 @@ +| **Property** | **Description** | **Type** | **Default** | +| ------------ | ------------------------ | -------------------------------------------------------- | -------------------------------------------------------- | +| type | type of sparkline | line | bar | `default` | +| width | width | number | `200` | +| height | height | number | `20` | +| data | data of sparkline | number[] | number[][] | `[]` | +| isStack | whether to stack | boolean | `false` | +| color | color of visual elements | color | color[] | (index) => color | `'#83daad', '#edbf45', '#d2cef9', '#e290b3', '#6f63f4']` | +| smooth | use smooth curves | boolean | `true` | +| lineStyle | custom line styles | ShapeAttr | `[]` | +| areaStyle | custom area styles | ShapeAttr | `[]` | +| isGroup | whether to group series | boolean | `false` | +| columnStyle | custom column styles | ShapeAttrs | `[]` | diff --git a/docs/common/sparkline-options.zh.md b/docs/common/sparkline-options.zh.md new file mode 100644 index 000000000..7f3be79d0 --- /dev/null +++ b/docs/common/sparkline-options.zh.md @@ -0,0 +1,13 @@ +| **属性** | **描述** | **类型** | **默认值** | +| ----------- | ------------------ | --------------------------------------------------------- | -------------------------------------------------------- | +| type | sparkline 类型 | line | bar | `default` | +| width | 宽度 | number | `200` | +| height | 高度 | number | `20` | +| data | 数据 | number[] | number[][] | `[]` | +| isStack | 是否堆积 | boolean | `false` | +| color | 颜色 | color | color[] | (index) => color | `'#83daad', '#edbf45', '#d2cef9', '#e290b3', '#6f63f4']` | +| smooth | 平滑曲线 | boolean | `true` | +| lineStyle | 自定义线条样式 | ShapeAttr | `[]` | +| areaStyle | 自定义线条填充样式 | ShapeAttr | `[]` | +| isGroup | 是否分组 | boolean | `false` | +| columnStyle | 柱体样式 | ShapeAttrs | `[]` | From 7395146988962c0783c28a4a699b762fe636980b Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Tue, 6 Jul 2021 20:41:48 +0800 Subject: [PATCH 13/49] =?UTF-8?q?refactor(slider):=20slider=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E7=BC=A9=E7=95=A5=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/slider/index.ts | 167 +++++++++++++++++++++++++++++------------ 1 file changed, 119 insertions(+), 48 deletions(-) diff --git a/src/ui/slider/index.ts b/src/ui/slider/index.ts index 64c85ce07..56cdc44e1 100644 --- a/src/ui/slider/index.ts +++ b/src/ui/slider/index.ts @@ -1,6 +1,5 @@ -import { Rect, Text } from '@antv/g'; +import { Rect, Text, Image, Line } from '@antv/g'; import { deepMix, get, isFunction, isString, isObject } from '@antv/util'; -// import { SliderOptions, HandleCfg, MiniMap, Pair } from './types'; import { SliderOptions, HandleCfg, Pair } from './types'; import { Marker, MarkerOptions } from '../marker'; import { Sparkline } from '../sparkline'; @@ -59,17 +58,17 @@ export class Slider extends CustomElement { width: 200, height: 20, padding: { - left: 20, - right: 10, - top: 10, - bottom: 10, + left: 0, + right: 0, + top: 0, + bottom: 0, }, backgroundStyle: { fill: '#fff', stroke: '#e4eaf5', lineWidth: 1, }, - sparklineCfg: {}, + // sparklineCfg: {}, foregroundStyle: { fill: '#afc9fb', opacity: 0.5, @@ -84,7 +83,6 @@ export class Slider extends CustomElement { size: 10, formatter: (val: string) => val, spacing: 10, - handleIcon: 'circle', textStyle: { fill: '#63656e', textAlign: 'center', @@ -92,7 +90,7 @@ export class Slider extends CustomElement { }, handleStyle: { stroke: '#c5c5c5', - fill: '#9bc2ff', + fill: '#fff', lineWidth: 1, }, }, @@ -135,7 +133,7 @@ export class Slider extends CustomElement { /** * 获得安全的Values */ - private getSafetyValues(values?: Pair): Pair { + private getSafetyValues(values = this.getValues(), precision = 4): Pair { const { min, max } = this.attributes; const [prevStart, prevEnd] = this.getValues(); let [startVal, endVal] = values || [prevStart, prevEnd]; @@ -161,7 +159,13 @@ export class Slider extends CustomElement { } return [max - range, max]; } - return [startVal, endVal]; + const _ = (num: number) => { + const temp = 10 ** precision; + return Number(Math.round(num * temp).toFixed(0)) / temp; + }; + + // 保留小数 + return [_(startVal), _(endVal)]; } private getAvailableSpace() { @@ -206,7 +210,7 @@ export class Slider extends CustomElement { private createSparkline() { const { orient, sparklineCfg } = this.attributes; // 暂时只在水平模式下绘制 - if (orient !== 'horizontal') { + if (orient !== 'horizontal' || !sparklineCfg) { return; } const { width, height } = this.getAvailableSpace(); @@ -297,11 +301,13 @@ export class Slider extends CustomElement { private calcHandleText(handleType: HandleType) { const { orient, names } = this.attributes; const { size, spacing, formatter, textStyle } = this.getHandleCfg(handleType); + const values = this.getSafetyValues(); + // 相对于获取两端可用空间 const { width: iW, height: iH } = this.getAvailableSpace(); const { x: fX, y: fY, width: fW, height: fH } = this.calcMask(); - const formattedText = formatter(handleType === 'start' ? names[0] : names[1]); + const formattedText = formatter(...(handleType === 'start' ? [names[0], values[0]] : [names[1], values[1]])); const _ = new Text({ attrs: { text: formattedText, @@ -341,7 +347,6 @@ export class Slider extends CustomElement { * 解析icon类型 */ private parseIcon(icon: MarkerOptions['symbol'] | string) { - // MarkerOptions['symbol']: string | FunctionalSymbol let type = 'unknown'; if (isObject(icon) && icon instanceof Image) type = 'Image'; else if (isFunction(icon)) type = 'symbol'; @@ -351,48 +356,115 @@ export class Slider extends CustomElement { type = 'base64'; } else if (/^(https?:\/\/(([a-zA-Z0-9]+-?)+[a-zA-Z0-9]+\.)+[a-zA-Z]+)(:\d+)?(\/.*)?(\?.*)?(#.*)?$/.test(icon)) { type = 'url'; + } else { + // 不然就当作symbol string 处理 + type = 'symbol'; } } return type; } + /** + * 创建手柄 + */ private createHandle(options: HandleCfg, handleType: HandleType) { const { show, size, textStyle, handleIcon: icon, handleStyle } = options; const iconType = this.parseIcon(icon); - - switch (iconType) { - case 'Image': - break; - case 'symbol': - break; - case 'base64': - break; - case 'url': - break; - default: - break; - } - - const handleIcon = new Marker({ + const baseCfg = { name: 'handleIcon', identity: handleType, - attrs: { - r: size / 2, - ...(show - ? { - symbol: icon, - ...handleStyle, - } - : { - // 如果不显示的话,就创建透明的rect - symbol: 'square', - markerStyle: { - opacity: 0, - }, - }), - cursor: this.getOrientVal(['ew-resize', 'ns-resize']), - }, - }); + }; + const cursor = this.getOrientVal(['ew-resize', 'ns-resize']); + + const handleIcon = (() => { + if (!show) { + // 如果不显示的话,就创建透明的rect + return new Marker({ + ...baseCfg, + attrs: { + r: size / 2, + symbol: 'square', + markerStyle: { + opacity: 0, + }, + cursor, + }, + }); + } + + if (['base64', 'url', 'Image'].includes(iconType)) { + // TODO G那边似乎还是有点问题,暂不考虑Image + return new Image({ + ...baseCfg, + attrs: { + x: -size / 2, + y: -size / 2, + width: size, + height: size, + img: icon, + cursor, + }, + }); + } + if (iconType === 'symbol') { + return new Marker({ + ...baseCfg, + attrs: { + r: size / 2, + symbol: icon, + cursor, + ...handleStyle, + }, + }); + } + + const width = size; + const height = size * 2.4; + + // 创建默认图形 + const handleBody = new Rect({ + ...baseCfg, + attrs: { + cursor, + width, + height, + x: -width / 2, + y: -height / 2, + radius: size / 4, + ...handleStyle, + }, + }); + const { stroke, lineWidth } = handleStyle; + const X1 = (1 / 3) * width; + const X2 = (2 / 3) * width; + const Y1 = (1 / 4) * height; + const Y2 = (3 / 4) * height; + + const createLine = (x1: number, y1: number, x2: number, y2: number) => { + return new Line({ + name: 'line', + attrs: { + x1, + y1, + x2, + y2, + cursor, + stroke, + lineWidth, + }, + }); + }; + + handleBody.appendChild(createLine(X1, Y1, X1, Y2)); + handleBody.appendChild(createLine(X2, Y1, X2, Y2)); + + // 根据orient进行rotate + // 设置旋转中心 + handleBody.setOrigin(width / 2, height / 2); + handleBody.rotate(this.getOrientVal([0, 90])); + + return handleBody; + })(); const handleText = new Text({ name: 'handleText', @@ -406,7 +478,6 @@ export class Slider extends CustomElement { // 用 Group 创建对象会提示没有attrs属性 const handle = new DisplayObject({ - // name: `${handleType}Handle`, name: 'handle', identity: handleType, attrs: this.calcHandlePosition(handleType), @@ -484,7 +555,7 @@ export class Slider extends CustomElement { const currPos = this.getOrientVal([e.x, e.y]); const _ = currPos - this.prevPos; if (!_) return; - const dVal = this.getRatio(_); // _ / this.getOrientVal([width, height]); + const dVal = this.getRatio(_); switch (this.target) { case 'startHandle': From 96a908f5637caeb400c71340b6a5a3b10b3d6506 Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Tue, 6 Jul 2021 20:42:12 +0800 Subject: [PATCH 14/49] =?UTF-8?q?refactor(slider):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E4=BA=86=E5=8F=82=E6=95=B0=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/slider/types.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/ui/slider/types.ts b/src/ui/slider/types.ts index 1edc8f5aa..866991127 100644 --- a/src/ui/slider/types.ts +++ b/src/ui/slider/types.ts @@ -4,6 +4,10 @@ import { SparklineOptions } from '../sparkline'; export type Pair = [T, T]; +export type MixAttrs = ShapeAttrs & { + active?: ShapeAttrs; +}; + export type HandleCfg = { /** * 是否显示Handle @@ -28,20 +32,13 @@ export type HandleCfg = { /** * 手柄图标 */ - handleIcon?: MarkerOptions['symbol']; + handleIcon?: MarkerOptions['symbol'] | string; /** * 手柄图标样式 */ - handleStyle: ShapeAttrs; + handleStyle: MixAttrs; }; -export type MixAttrs = - | ShapeAttrs - | { - default: ShapeAttrs; - active: ShapeAttrs; - }; - export type SliderOptions = ShapeCfg & { orient?: 'vertical' | 'horizontal'; values?: Pair; From 9a7c2bdd62e364f3a79249b56ebe9fd2f24ca0dd Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Tue, 6 Jul 2021 20:51:10 +0800 Subject: [PATCH 15/49] =?UTF-8?q?test(slider):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E4=BA=86slider=20=E5=8D=95=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/unit/ui/slider/index.spec.ts | 156 +++++++++++++++++++++++-- 1 file changed, 144 insertions(+), 12 deletions(-) diff --git a/__tests__/unit/ui/slider/index.spec.ts b/__tests__/unit/ui/slider/index.spec.ts index de45ee329..bc44aeb6f 100644 --- a/__tests__/unit/ui/slider/index.spec.ts +++ b/__tests__/unit/ui/slider/index.spec.ts @@ -25,27 +25,159 @@ describe('slider', () => { attrs: { x: 50, y: 50, - width: 600, - height: 80, - values: [0.4, 0.7], - names: ['Abcas', 'Vxczxz'], - backgroundStyle: { - lineWidth: 2, + width: 400, + height: 40, + values: [0.3, 0.7], + names: ['leftVal', 'rightVal'], + }, + }); + + expect(slider.getValues()).toStrictEqual([0.3, 0.7]); + expect(slider.getNames()).toStrictEqual(['leftVal', 'rightVal']); + + slider.setValues([0, 1]); + expect(slider.getValues()).toStrictEqual([0, 1]); + + slider.setValues([-0.5, 1]); + expect(slider.getValues()).toStrictEqual([0, 1]); + + slider.setValues([-0.5, 1.5]); + expect(slider.getValues()).toStrictEqual([0, 1]); + + slider.setValues([-0.5, 0]); + expect(slider.getValues()).toStrictEqual([0, 0.5]); + + slider.setValues([-2, -1]); + expect(slider.getValues()).toStrictEqual([0, 1]); + + canvas.appendChild(slider); + slider.destroy(); + }); + + test('vertical', async () => { + const div = createDiv(); + + // @ts-ignore + const canvas = new Canvas({ + container: div, + width: 800, + height: 300, + renderer, + }); + + const slider = new Slider({ + attrs: { + x: 50, + y: 50, + width: 40, + height: 400, + orient: 'vertical', + values: [0.3, 0.7], + names: ['aboveVal', 'belowVal'], + }, + }); + + canvas.appendChild(slider); + slider.destroy(); + }); + + test('custom icon', async () => { + const div = createDiv(); + + // @ts-ignore + const canvas = new Canvas({ + container: div, + width: 800, + height: 300, + renderer, + }); + + const slider = new Slider({ + attrs: { + x: 50, + y: 50, + width: 400, + height: 40, + values: [0.3, 0.7], + names: ['leftVal', 'rightVal'], + handle: { + start: { + size: 15, + formatter: (name, value) => { + return `${name}: ${value * 100}%`; + }, + handleIcon: 'https://gw.alipayobjects.com/mdn/rms_6ae20b/afts/img/A*N4ZMS7gHsUIAAAAAAAAAAABkARQnAQ', + }, + end: { + spacing: 20, + handleIcon: 'diamond', + }, }, + }, + }); + + canvas.appendChild(slider); + slider.destroy(); + }); + + test('vertical', async () => { + const div = createDiv(); + + // @ts-ignore + const canvas = new Canvas({ + container: div, + width: 800, + height: 300, + renderer, + }); + + const slider = new Slider({ + attrs: { + x: 50, + y: 50, + width: 40, + height: 400, + orient: 'vertical', + values: [0.3, 0.7], + names: ['aboveVal', 'belowVal'], + }, + }); + + canvas.appendChild(slider); + slider.destroy(); + }); + + test('slider with sparkline', async () => { + const div = createDiv(); + + // @ts-ignore + const canvas = new Canvas({ + container: div, + width: 800, + height: 300, + renderer, + }); + + const slider = new Slider({ + attrs: { + x: 50, + y: 50, + width: 400, + height: 40, + values: [0.3, 0.7], + names: ['leftVal', 'rightVal'], sparklineCfg: { // type: 'column', - // isStack: true, data: [ - [1, 3, 2, -4], - [5, 1, 5, -8], + [1, 3, 2, -4, 1, 3, 2, -4], + [5, 1, 5, -8, 5, 1, 5, -8], ], - areaStyle: { - opacity: 0.5, - }, }, }, }); canvas.appendChild(slider); + slider.destroy(); + canvas.destroy(); }); }); From 3b03d162f662f38123edab0f3d981c746350678b Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Wed, 7 Jul 2021 15:15:27 +0800 Subject: [PATCH 16/49] =?UTF-8?q?fix(sparkline):=20=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E4=BA=86sparkline=20data=E5=8F=82=E6=95=B0=E4=B8=BA=E7=A9=BA?= =?UTF-8?q?=E6=97=B6=E4=B8=8D=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/sparkline/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ui/sparkline/index.ts b/src/ui/sparkline/index.ts index 8e966e50b..84d9cd433 100644 --- a/src/ui/sparkline/index.ts +++ b/src/ui/sparkline/index.ts @@ -25,7 +25,7 @@ export class Sparkline extends CustomElement { type: 'line', width: 200, height: 20, - data: [], + // data: [], isStack: false, color: ['#83daad', '#edbf45', '#d2cef9', '#e290b3', '#6f63f4'], smooth: true, @@ -53,7 +53,7 @@ export class Sparkline extends CustomElement { } private init() { - const { type, width, height } = this.attributes; + const { data, type, width, height } = this.attributes; this.sparkShapes = new Rect({ attrs: { width, @@ -61,6 +61,7 @@ export class Sparkline extends CustomElement { }, }); this.appendChild(this.sparkShapes); + if (!data) return; switch (type) { case 'line': this.createLine(); From 2a61d57ff6b40af1ebb847b64783bc27650f10c5 Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Wed, 7 Jul 2021 15:32:28 +0800 Subject: [PATCH 17/49] =?UTF-8?q?refactor(util):=20=E6=8A=BD=E5=8F=96?= =?UTF-8?q?=E4=BA=86=E5=8F=96=E5=BE=97=E6=95=B0=E5=AD=97=E7=B2=BE=E5=BA=A6?= =?UTF-8?q?=E7=9A=84=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/index.ts | 2 +- src/util/utils.ts | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/util/index.ts b/src/util/index.ts index 20ed23d82..403afaac4 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -1,3 +1,3 @@ export { svg2marker } from './svg2marker'; export { measureTextWidth, getEllipsisText } from './text'; -export { applyAttrs, isPC } from './utils'; +export { applyAttrs, isPC, toPrecision } from './utils'; diff --git a/src/util/utils.ts b/src/util/utils.ts index 3113117f1..01c5c2384 100644 --- a/src/util/utils.ts +++ b/src/util/utils.ts @@ -26,3 +26,11 @@ export function isPC(userAgent = undefined) { }); return flag; } + +/** + * 保留x位小数 + */ +export function toPrecision(num: number, precision: number) { + const _ = 10 ** precision; + return Number(Math.round(num * _).toFixed(0)) / _; +} From de743164b6139c6d50658f397a7a8d860ebb47b4 Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Wed, 7 Jul 2021 15:33:47 +0800 Subject: [PATCH 18/49] =?UTF-8?q?refactor(slider):=20=E6=89=8B=E6=9F=84?= =?UTF-8?q?=E5=B0=BA=E5=AF=B8=E8=87=AA=E9=80=82=E5=BA=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/slider/index.ts | 87 +++++++++++++++++++++++++++++++----------- 1 file changed, 65 insertions(+), 22 deletions(-) diff --git a/src/ui/slider/index.ts b/src/ui/slider/index.ts index 56cdc44e1..3e057fab1 100644 --- a/src/ui/slider/index.ts +++ b/src/ui/slider/index.ts @@ -3,13 +3,8 @@ import { deepMix, get, isFunction, isString, isObject } from '@antv/util'; import { SliderOptions, HandleCfg, Pair } from './types'; import { Marker, MarkerOptions } from '../marker'; import { Sparkline } from '../sparkline'; -import { CustomElement, DisplayObject, ShapeAttrs } from '../../types'; -// import { /* applyAttrs */ measureTextWidth } from '../../util'; -const applyAttrs = (target: DisplayObject, attrs: ShapeAttrs) => { - Object.entries(attrs).forEach(([attrName, attrValue]) => { - target.setAttribute(attrName, attrValue); - }); -}; +import { CustomElement, DisplayObject } from '../../types'; +import { applyAttrs, toPrecision } from '../../util'; export { SliderOptions }; @@ -18,14 +13,38 @@ type HandleType = 'start' | 'end'; export class Slider extends CustomElement { public static tag = 'slider'; + /** + * 层级关系 + * backgroundShape + * |- sparklineShape + * |- foregroundShape + * |- startHandle + * |- endHandle + */ + + /** + * 背景 + */ private backgroundShape: DisplayObject; + /** + * 缩略图 + */ private sparklineShape: DisplayObject; + /** + * 前景,即选区 + */ private foregroundShape: DisplayObject; + /** + * 起始手柄 + */ private startHandle: DisplayObject; + /** + * 终点手柄 + */ private endHandle: DisplayObject; /** @@ -33,8 +52,14 @@ export class Slider extends CustomElement { */ private selectionStartPos: number; + /** + * 选区宽度 + */ private selectionWidth: number; + /** + * 记录上一次鼠标事件所在坐标 + */ private prevPos: number; /** @@ -57,6 +82,14 @@ export class Slider extends CustomElement { max: 1, width: 200, height: 20, + sparklineCfg: { + padding: { + left: 1, + right: 1, + top: 1, + bottom: 1, + }, + }, padding: { left: 0, right: 0, @@ -68,7 +101,6 @@ export class Slider extends CustomElement { stroke: '#e4eaf5', lineWidth: 1, }, - // sparklineCfg: {}, foregroundStyle: { fill: '#afc9fb', opacity: 0.5, @@ -80,7 +112,6 @@ export class Slider extends CustomElement { }, handle: { show: true, - size: 10, formatter: (val: string) => val, spacing: 10, textStyle: { @@ -159,13 +190,9 @@ export class Slider extends CustomElement { } return [max - range, max]; } - const _ = (num: number) => { - const temp = 10 ** precision; - return Number(Math.round(num * temp).toFixed(0)) / temp; - }; // 保留小数 - return [_(startVal), _(endVal)]; + return [toPrecision(startVal, precision), toPrecision(endVal, precision)]; } private getAvailableSpace() { @@ -209,19 +236,23 @@ export class Slider extends CustomElement { */ private createSparkline() { const { orient, sparklineCfg } = this.attributes; + console.log(sparklineCfg); + // 暂时只在水平模式下绘制 - if (orient !== 'horizontal' || !sparklineCfg) { + if (orient !== 'horizontal') { return; } + const { padding, ...args } = sparklineCfg; + const { width, height } = this.getAvailableSpace(); const { lineWidth: bkgLW } = this.getStyle('backgroundStyle'); this.sparklineShape = new Sparkline({ attrs: { - x: bkgLW / 2, - y: bkgLW / 2, - width: width - bkgLW, - height: height - bkgLW, - ...sparklineCfg, + x: bkgLW / 2 + padding.left, + y: bkgLW / 2 + padding.top, + width: width - bkgLW - padding.left - padding.right, + height: height - bkgLW - padding.top - padding.bottom, + ...args, }, }); this.backgroundShape.appendChild(this.sparklineShape); @@ -300,7 +331,8 @@ export class Slider extends CustomElement { */ private calcHandleText(handleType: HandleType) { const { orient, names } = this.attributes; - const { size, spacing, formatter, textStyle } = this.getHandleCfg(handleType); + const { spacing, formatter, textStyle } = this.getHandleCfg(handleType); + const size = this.getHandleSize(handleType); const values = this.getSafetyValues(); // 相对于获取两端可用空间 @@ -368,7 +400,8 @@ export class Slider extends CustomElement { * 创建手柄 */ private createHandle(options: HandleCfg, handleType: HandleType) { - const { show, size, textStyle, handleIcon: icon, handleStyle } = options; + const { show, textStyle, handleIcon: icon, handleStyle } = options; + const size = this.getHandleSize(handleType); const iconType = this.parseIcon(icon); const baseCfg = { name: 'handleIcon', @@ -498,6 +531,16 @@ export class Slider extends CustomElement { return deepMix({}, args, _); } + private getHandleSize(handleType: HandleType) { + const handleCfg = this.getHandleCfg(handleType); + const { size } = handleCfg; + if (size) return size; + + // 没设置size的话,高度就取height的80%高度,手柄宽度是高度的1/2.4 + const { height } = this.attributes; + return (height * 0.8) / 2.4; + } + private createHandles() { this.startHandle = this.createHandle(this.getHandleCfg('start'), 'start'); this.foregroundShape.appendChild(this.startHandle); From 1d7cd927c08cb45595fdddce8187816b91876ea0 Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Wed, 7 Jul 2021 15:34:13 +0800 Subject: [PATCH 19/49] =?UTF-8?q?docs(slider):=20=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E4=BA=86=E6=A1=88=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/slider/demo/custom-handleIcon-slider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/slider/demo/custom-handleIcon-slider.ts b/examples/slider/demo/custom-handleIcon-slider.ts index 2f38bca32..16f9fade4 100644 --- a/examples/slider/demo/custom-handleIcon-slider.ts +++ b/examples/slider/demo/custom-handleIcon-slider.ts @@ -28,7 +28,7 @@ const slider = new Slider({ start: { size: 15, formatter: (name, value) => { - return `${name}: ${value * 100}%`; + return `${name}: ${(value * 100).toFixed(2)}%`; }, handleIcon: 'https://gw.alipayobjects.com/mdn/rms_6ae20b/afts/img/A*N4ZMS7gHsUIAAAAAAAAAAABkARQnAQ', }, From eb7deeb9277cbbab4543c4a62fc1d15a8a1be8e9 Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Wed, 7 Jul 2021 16:42:01 +0800 Subject: [PATCH 20/49] =?UTF-8?q?fix(slider):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E4=BA=86=E7=BA=B5=E5=90=91=E5=B8=83=E5=B1=80=E4=B8=8B=E7=9A=84?= =?UTF-8?q?slider=20=E6=89=8B=E6=9F=84=E5=B0=BA=E5=AF=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/slider/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ui/slider/index.ts b/src/ui/slider/index.ts index 3e057fab1..a0a1418e1 100644 --- a/src/ui/slider/index.ts +++ b/src/ui/slider/index.ts @@ -537,8 +537,8 @@ export class Slider extends CustomElement { if (size) return size; // 没设置size的话,高度就取height的80%高度,手柄宽度是高度的1/2.4 - const { height } = this.attributes; - return (height * 0.8) / 2.4; + const { width, height } = this.attributes; + return (this.getOrientVal([height, width]) * 0.8) / 2.4; } private createHandles() { From 98d564a758c69cb4105e93f42210c90fd75f9c35 Mon Sep 17 00:00:00 2001 From: Aarebecca Date: Wed, 7 Jul 2021 20:57:29 +0800 Subject: [PATCH 21/49] Update index.ts refactor(slider): rename type Image to image --- src/ui/slider/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ui/slider/index.ts b/src/ui/slider/index.ts index a0a1418e1..978e85dd1 100644 --- a/src/ui/slider/index.ts +++ b/src/ui/slider/index.ts @@ -380,7 +380,7 @@ export class Slider extends CustomElement { */ private parseIcon(icon: MarkerOptions['symbol'] | string) { let type = 'unknown'; - if (isObject(icon) && icon instanceof Image) type = 'Image'; + if (isObject(icon) && icon instanceof Image) type = 'image'; else if (isFunction(icon)) type = 'symbol'; else if (isString(icon)) { const dataURLsPattern = new RegExp('data:(image|text)'); @@ -425,7 +425,7 @@ export class Slider extends CustomElement { }); } - if (['base64', 'url', 'Image'].includes(iconType)) { + if (['base64', 'url', 'image'].includes(iconType)) { // TODO G那边似乎还是有点问题,暂不考虑Image return new Image({ ...baseCfg, From 7e41cdf3323b204f08253611510b081fb6d058ac Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Thu, 8 Jul 2021 10:21:18 +0800 Subject: [PATCH 22/49] =?UTF-8?q?refactor:=20=E7=A7=BB=E9=99=A4=E4=BA=86ap?= =?UTF-8?q?plyAttrs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/unit/util/utils.spec.ts | 42 +------------------------------ src/ui/button/index.ts | 19 ++++---------- src/ui/scrollbar/index.ts | 10 ++++---- src/ui/slider/index.ts | 28 +++++++++++++-------- src/util/index.ts | 2 +- src/util/utils.ts | 14 ----------- 6 files changed, 29 insertions(+), 86 deletions(-) diff --git a/__tests__/unit/util/utils.spec.ts b/__tests__/unit/util/utils.spec.ts index 733a029de..c8d32d3f1 100644 --- a/__tests__/unit/util/utils.spec.ts +++ b/__tests__/unit/util/utils.spec.ts @@ -1,5 +1,4 @@ -import { DisplayObject } from '@antv/g'; -import { applyAttrs, isPC } from '../../../src/util'; +import { isPC } from '../../../src/util'; describe('platform', () => { const USER_AGENTS_PC = { @@ -38,42 +37,3 @@ describe('platform', () => { }); }); }); - -describe('applyAttrs', () => { - test('init', () => { - const DO = new DisplayObject({ - attrs: { - x: 1, - y: 2, - width: 10, - height: 100, - }, - }); - const { x, y, width, height } = DO.attributes; - expect(x).toBe(1); - expect(y).toBe(2); - expect(width).toBe(10); - expect(height).toBe(100); - }); - - test('changeAttrs', () => { - const DO = new DisplayObject({ - attrs: { - x: 1, - y: 2, - width: 10, - height: 100, - }, - }); - - applyAttrs(DO, { - x: 100, - y: 20, - }); - const { x, y, width, height } = DO.attributes; - expect(x).toBe(100); - expect(y).toBe(20); - expect(width).toBe(10); - expect(height).toBe(100); - }); -}); diff --git a/src/ui/button/index.ts b/src/ui/button/index.ts index 44ec035a0..cb5fe668f 100644 --- a/src/ui/button/index.ts +++ b/src/ui/button/index.ts @@ -1,7 +1,7 @@ import { Rect, Text } from '@antv/g'; import { deepMix, pick } from '@antv/util'; import { ButtonOptions } from './types'; -import { CustomElement, ShapeAttrs, DisplayObject } from '../../types'; +import { CustomElement, DisplayObject } from '../../types'; import { getEllipsisText } from '../../util'; import { SIZE_STYLE, TYPE_STYLE, DISABLED_STYLE } from './constant'; @@ -165,15 +165,6 @@ export class Button extends CustomElement { this.bindEvents(onClick); } - /** - * 应用多个属性 - */ - private applyAttrs(shape: 'textShape' | 'background', attrs: ShapeAttrs) { - Object.entries(attrs).forEach((attr) => { - this[shape].attr(attr[0], attr[1]); - }); - } - private bindEvents(onClick: Function): void { const { disabled } = this.attributes; @@ -188,8 +179,8 @@ export class Button extends CustomElement { if (!disabled) { // 鼠标悬浮事件 const hoverStyle = this.getMixinStyle('hoverStyle'); - this.applyAttrs('textShape', hoverStyle.textStyle); - this.applyAttrs('background', hoverStyle.buttonStyle); + this.textShape.attr(hoverStyle.textStyle); + this.background.attr(hoverStyle.buttonStyle); this.attr('cursor', 'pointer'); } else { // 设置指针icon @@ -199,8 +190,8 @@ export class Button extends CustomElement { this.on('mouseleave', () => { // 恢复默认状态 - this.applyAttrs('textShape', this.getMixinStyle('textStyle')); - this.applyAttrs('background', this.getMixinStyle('buttonStyle')); + this.textShape.attr(this.getMixinStyle('textStyle')); + this.background.attr(this.getMixinStyle('buttonStyle')); }); } } diff --git a/src/ui/scrollbar/index.ts b/src/ui/scrollbar/index.ts index 32a98c695..c1ebeaaa4 100644 --- a/src/ui/scrollbar/index.ts +++ b/src/ui/scrollbar/index.ts @@ -1,7 +1,7 @@ import { Rect } from '@antv/g'; import { clamp, deepMix } from '@antv/util'; import { ScrollbarOptions } from './types'; -import { applyAttrs, isPC } from '../../util'; +import { isPC } from '../../util'; import { CustomElement, DisplayObject } from '../../types'; export { ScrollbarOptions }; @@ -271,18 +271,18 @@ export class Scrollbar extends CustomElement { const { thumbStyle, trackStyle } = this.attributes; // 滑块hover this.thumbShape.addEventListener('mouseenter', () => { - applyAttrs(this.thumbShape, thumbStyle.active); + this.thumbShape.attr(thumbStyle.active); }); this.thumbShape.addEventListener('mouseleave', () => { - applyAttrs(this.thumbShape, thumbStyle.default); + this.thumbShape.attr(thumbStyle.default); }); // 滑轨hover this.trackShape.addEventListener('mouseenter', () => { - applyAttrs(this.trackShape, trackStyle.active); + this.trackShape.attr(trackStyle.active); }); this.trackShape.addEventListener('mouseleave', () => { - applyAttrs(this.trackShape, trackStyle.default); + this.trackShape.attr(trackStyle.default); }); } diff --git a/src/ui/slider/index.ts b/src/ui/slider/index.ts index a0a1418e1..7f8ab01ca 100644 --- a/src/ui/slider/index.ts +++ b/src/ui/slider/index.ts @@ -4,7 +4,7 @@ import { SliderOptions, HandleCfg, Pair } from './types'; import { Marker, MarkerOptions } from '../marker'; import { Sparkline } from '../sparkline'; import { CustomElement, DisplayObject } from '../../types'; -import { applyAttrs, toPrecision } from '../../util'; +import { toPrecision } from '../../util'; export { SliderOptions }; @@ -153,6 +153,12 @@ export class Slider extends CustomElement { this.setAttribute('names', names); } + public update(cfg: SliderOptions) { + const { values, names } = cfg; + this.setValues(values); + this.setNames(names); + } + private init() { this.createBackground(); this.createSparkline(); @@ -315,11 +321,11 @@ export class Slider extends CustomElement { * 3. 更新文本位置 */ private setHandle() { - applyAttrs(this.foregroundShape, this.calcMask()); - applyAttrs(this.startHandle, this.calcHandlePosition('start')); - applyAttrs(this.endHandle, this.calcHandlePosition('end')); + this.foregroundShape.attr(this.calcMask()); + this.startHandle.attr(this.calcHandlePosition('start')); + this.endHandle.attr(this.calcHandlePosition('end')); this.getElementsByName('handleText').forEach((handleText) => { - applyAttrs(handleText, this.calcHandleText(handleText.getConfig().identity)); + handleText.attr(this.calcHandleText(handleText.getConfig().identity)); }); } @@ -631,22 +637,22 @@ export class Slider extends CustomElement { private bindHoverEvents = () => { this.foregroundShape.addEventListener('mouseenter', () => { - applyAttrs(this.foregroundShape, this.getStyle('foregroundStyle', true)); + this.foregroundShape.attr(this.getStyle('foregroundStyle')); }); this.foregroundShape.addEventListener('mouseleave', () => { - applyAttrs(this.foregroundShape, this.getStyle('foregroundStyle')); + this.foregroundShape.attr(this.getStyle('foregroundStyle')); }); this.getElementsByName('handle').forEach((handle) => { const icon = handle.getElementsByName('handleIcon')[0]; const text = handle.getElementsByName('handleText')[0]; handle.addEventListener('mouseenter', () => { - applyAttrs(icon, this.getStyle('handleStyle', true, icon.getConfig().identity)); - applyAttrs(text, this.getStyle('textStyle', true, text.getConfig().identity)); + icon.attr(this.getStyle('handleStyle', true, icon.getConfig().identity)); + text.attr(this.getStyle('textStyle', true, text.getConfig().identity)); }); handle.addEventListener('mouseleave', () => { - applyAttrs(icon, this.getStyle('handleStyle', false, icon.getConfig().identity)); - applyAttrs(text, this.getStyle('textStyle', false, text.getConfig().identity)); + icon.attr(this.getStyle('handleStyle', false, icon.getConfig().identity)); + text.attr(this.getStyle('textStyle', false, text.getConfig().identity)); }); }); }; diff --git a/src/util/index.ts b/src/util/index.ts index 403afaac4..abec5ec8d 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -1,3 +1,3 @@ export { svg2marker } from './svg2marker'; export { measureTextWidth, getEllipsisText } from './text'; -export { applyAttrs, isPC, toPrecision } from './utils'; +export { isPC, toPrecision } from './utils'; diff --git a/src/util/utils.ts b/src/util/utils.ts index 01c5c2384..3d8e13376 100644 --- a/src/util/utils.ts +++ b/src/util/utils.ts @@ -1,17 +1,3 @@ -import { DisplayObject, ShapeAttrs } from '../types'; - -/** - * 对 Group 中名为 shape 的 DisplayObject 对象应用 attrs 中的属性 - * @param group - * @param shape - * @param attrs - */ -export function applyAttrs(target: DisplayObject, attrs: ShapeAttrs) { - Object.entries(attrs).forEach(([attrName, attrValue]) => { - target.setAttribute(attrName, attrValue); - }); -} - /** * 平台判断 */ From 67d17e33da5aaeab1b9f0a135982c96ff7fedf08 Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Thu, 8 Jul 2021 10:37:26 +0800 Subject: [PATCH 23/49] =?UTF-8?q?refactor(sparkline):=20=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E4=BA=86=E7=B1=BB=E5=9E=8B=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/sparkline/path.ts | 18 +----------------- src/ui/sparkline/types.ts | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/ui/sparkline/path.ts b/src/ui/sparkline/path.ts index 36f522413..dd5ab039e 100644 --- a/src/ui/sparkline/path.ts +++ b/src/ui/sparkline/path.ts @@ -1,23 +1,7 @@ import { clone, isEqual } from '@antv/util'; -import { Linear, Band } from '@antv/scale'; import { catmullRom2Bezier } from '@antv/path-util'; import { PathCommand } from '@antv/g-base'; - -export type Point = [number, number]; -export type Line = Point[]; -export type Data = number[][]; -export type Scales = { - y: Linear; -} & ( - | { - type: 'line'; - x: Linear; - } - | { - type: 'column'; - x: Band; - } -); +import { Data, Line, Point, Scales } from './types'; /** * 根据数据获得每条线各点x,y值 diff --git a/src/ui/sparkline/types.ts b/src/ui/sparkline/types.ts index 616720bd4..cff78920f 100644 --- a/src/ui/sparkline/types.ts +++ b/src/ui/sparkline/types.ts @@ -1,5 +1,22 @@ +import { Linear, Band } from '@antv/scale'; import { ShapeAttrs, ShapeCfg } from '../../types'; +export type Point = [number, number]; +export type Line = Point[]; +export type Data = number[][]; +export type Scales = { + y: Linear; +} & ( + | { + type: 'line'; + x: Linear; + } + | { + type: 'column'; + x: Band; + } +); + export type SparklineOptions = ShapeCfg & { data?: number[] | number[][]; width?: number; From 35eacdd163d8c06b7a9d499c6782a2c573b8e82e Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Thu, 8 Jul 2021 11:00:05 +0800 Subject: [PATCH 24/49] =?UTF-8?q?refactor(sparkline):=20sparkline=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E4=BA=86update=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/unit/ui/sparkline/index.spec.ts | 513 ++++++++-------------- src/ui/slider/index.ts | 2 - src/ui/sparkline/index.ts | 117 ++--- 3 files changed, 244 insertions(+), 388 deletions(-) diff --git a/__tests__/unit/ui/sparkline/index.spec.ts b/__tests__/unit/ui/sparkline/index.spec.ts index d622577ae..7723d69b1 100644 --- a/__tests__/unit/ui/sparkline/index.spec.ts +++ b/__tests__/unit/ui/sparkline/index.spec.ts @@ -9,33 +9,34 @@ const renderer = new CanvasRenderer({ enableDirtyRectangleRendering: true, }); -describe('sparkline', () => { - test('basic line', async () => { - const div = createDiv(); - - // @ts-ignore - const canvas = new Canvas({ - container: div, - width: 300, - height: 300, - renderer, - }); +const div = createDiv(); + +// @ts-ignore +const canvas = new Canvas({ + container: div, + width: 300, + height: 300, + renderer, +}); - const sparkline = new Sparkline({ - attrs: { - x: 10, - y: 10, - width: 300, - height: 50, - smooth: false, - data: [ - [10, 2, 3, 4, 15, 10, 5, 0, 3, 1], - [5, 7, 10, 3, 10, 6, 10, 1, 5, 0], - [-10, 3, 4, 10, 15, 13, 3, 3, 10, 12], - ], - }, - }); +const sparkline = new Sparkline({ + attrs: { + x: 10, + y: 10, + width: 300, + height: 50, + smooth: false, + data: [ + [10, 2, 3, 4, 15, 10, 5, 0, 3, 1], + [5, 7, 10, 3, 10, 6, 10, 1, 5, 0], + [-10, 3, 4, 10, 15, 13, 3, 3, 10, 12], + ], + }, +}); +canvas.appendChild(sparkline); +describe('sparkline', () => { + test('basic line', async () => { const path0 = sparkline.getElementById('line-path-0').getAttribute('path'); const y = (val) => { return (1 - (val + 10) / 25) * 50; @@ -48,373 +49,213 @@ describe('sparkline', () => { expect(path0[3][2]).toBe(y(4)); expect(path0[4][2]).toBe(y(15)); expect(path0[5][2]).toBe(y(10)); - - canvas.appendChild(sparkline); - sparkline.destroy(); }); test('stack line', () => { - const div = createDiv(); - - // @ts-ignore - const canvas = new Canvas({ - container: div, + sparkline.update({ + x: 10, + y: 10, width: 300, - height: 300, - renderer, - }); - - const sparkline = new Sparkline({ - attrs: { - x: 10, - y: 10, - width: 300, - height: 50, - smooth: false, - isStack: true, - data: [ - [10, 2, 3, 4, 15, 10, 5, 0, 3, 1], - [5, 7, 10, 3, 10, 6, 10, 1, 5, 0], - [1, 3, 4, 10, 15, 13, 3, 3, 10, 12], - ], - }, + height: 50, + smooth: false, + isStack: true, + data: [ + [10, 2, 3, 4, 15, 10, 5, 0, 3, 1], + [5, 7, 10, 3, 10, 6, 10, 1, 5, 0], + [1, 3, 4, 10, 15, 13, 3, 3, 10, 12], + ], }); - - canvas.appendChild(sparkline); - sparkline.destroy(); }); test('stack curve', () => { - const div = createDiv(); - - // @ts-ignore - const canvas = new Canvas({ - container: div, + sparkline.update({ + x: 10, + y: 10, width: 300, - height: 300, - renderer, + height: 50, + smooth: true, + isStack: true, + data: [ + [10, 2, 3, 4, 15, 10, 5, 0, 3, 1], + [5, 7, 10, 3, 10, 6, 10, 1, 5, 0], + [1, 3, 4, 10, 15, 13, 3, 3, 10, 12], + ], }); - - const sparkline = new Sparkline({ - attrs: { - x: 10, - y: 10, - width: 300, - height: 50, - smooth: true, - isStack: true, - data: [ - [10, 2, 3, 4, 15, 10, 5, 0, 3, 1], - [5, 7, 10, 3, 10, 6, 10, 1, 5, 0], - [1, 3, 4, 10, 15, 13, 3, 3, 10, 12], - ], - }, - }); - - canvas.appendChild(sparkline); - sparkline.destroy(); }); test('area line', () => { - const div = createDiv(); - - // @ts-ignore - const canvas = new Canvas({ - container: div, + sparkline.update({ + x: 10, + y: 10, width: 300, - height: 300, - renderer, - }); - - const sparkline = new Sparkline({ - attrs: { - x: 10, - y: 10, - width: 300, - height: 50, - smooth: false, - areaStyle: { - lineWidth: 0, - opacity: 0.5, - }, - data: [ - [10, 2, 3, 4, 15, 10, 5, 0, 3, 1], - [5, 7, 10, 3, 10, 6, 10, 1, 5, 0], - [1, 3, 4, 10, 15, 13, 3, 3, 10, 12], - ], + height: 50, + smooth: false, + areaStyle: { + lineWidth: 0, + opacity: 0.5, }, + data: [ + [10, 2, 3, 4, 15, 10, 5, 0, 3, 1], + [5, 7, 10, 3, 10, 6, 10, 1, 5, 0], + [1, 3, 4, 10, 15, 13, 3, 3, 10, 12], + ], }); - - canvas.appendChild(sparkline); - sparkline.destroy(); }); test('area curve', () => { - const div = createDiv(); - - // @ts-ignore - const canvas = new Canvas({ - container: div, + sparkline.update({ + x: 10, + y: 10, width: 300, - height: 300, - renderer, - }); - - const sparkline = new Sparkline({ - attrs: { - x: 10, - y: 10, - width: 300, - height: 50, - smooth: true, - areaStyle: { - lineWidth: 0, - opacity: 0.5, - }, - data: [ - [10, 2, 3, 4, 15, 10, 5, 0, 3, 1], - [5, 7, 10, 3, 10, 6, 10, 1, 5, 0], - [1, 3, 4, 10, 15, 13, 3, 3, 10, 12], - ], + height: 50, + smooth: true, + areaStyle: { + lineWidth: 0, + opacity: 0.5, }, + data: [ + [10, 2, 3, 4, 15, 10, 5, 0, 3, 1], + [5, 7, 10, 3, 10, 6, 10, 1, 5, 0], + [1, 3, 4, 10, 15, 13, 3, 3, 10, 12], + ], }); - - canvas.appendChild(sparkline); - sparkline.destroy(); }); test('area stack line', () => { - const div = createDiv(); - - // @ts-ignore - const canvas = new Canvas({ - container: div, + sparkline.update({ + x: 10, + y: 10, width: 300, - height: 300, - renderer, - }); - - const sparkline = new Sparkline({ - attrs: { - x: 10, - y: 10, - width: 300, - height: 50, - smooth: false, - isStack: true, - areaStyle: { - lineWidth: 0, - opacity: 0.5, - }, - data: [ - [10, 2, 3, 4, 15, 10, 5, 0, 3, 1], - [5, 7, 10, 3, 10, 6, 10, 1, 5, 0], - [1, 3, 4, 10, 15, 13, 3, 3, 10, 12], - ], + height: 50, + smooth: false, + isStack: true, + areaStyle: { + lineWidth: 0, + opacity: 0.5, }, + data: [ + [10, 2, 3, 4, 15, 10, 5, 0, 3, 1], + [5, 7, 10, 3, 10, 6, 10, 1, 5, 0], + [1, 3, 4, 10, 15, 13, 3, 3, 10, 12], + ], }); - - canvas.appendChild(sparkline); - sparkline.destroy(); }); - test('area stack curve', () => { - const div = createDiv(); - // @ts-ignore - const canvas = new Canvas({ - container: div, + test('area stack curve', () => { + sparkline.update({ + x: 10, + y: 10, width: 300, - height: 300, - renderer, - }); - - const sparkline = new Sparkline({ - attrs: { - x: 10, - y: 10, - width: 300, - height: 50, - smooth: true, - isStack: true, - areaStyle: { - lineWidth: 0, - opacity: 0.5, - }, - data: [ - [10, 2, 3, 4, 15, 10, 5, 0, 3, 1], - [5, 7, 10, 3, 10, 6, 10, 1, 5, 0], - [1, 3, 4, 10, 15, 13, 3, 3, 10, 12], - ], + height: 50, + smooth: true, + isStack: true, + areaStyle: { + lineWidth: 0, + opacity: 0.5, }, + data: [ + [10, 2, 3, 4, 15, 10, 5, 0, 3, 1], + [5, 7, 10, 3, 10, 6, 10, 1, 5, 0], + [1, 3, 4, 10, 15, 13, 3, 3, 10, 12], + ], }); - - canvas.appendChild(sparkline); - sparkline.destroy(); }); test('basic bar', () => { - const div = createDiv(); - - // @ts-ignore - const canvas = new Canvas({ - container: div, + sparkline.update({ + x: 10, + y: 10, + type: 'column', width: 300, - height: 300, - renderer, - }); - - const sparkline = new Sparkline({ - attrs: { - x: 10, - y: 10, - type: 'column', - width: 300, - height: 50, - data: [ - [10, 2, 3, 4, 15, 10, 5, 0, 3, 1], - [5, 7, 10, 3, 10, 6, 10, 1, 5, 0], - [1, 3, 4, 10, 15, 13, 3, 3, 10, 12], - ], - }, + height: 50, + data: [ + [10, 2, 3, 4, 15, 10, 5, 0, 3, 1], + [5, 7, 10, 3, 10, 6, 10, 1, 5, 0], + [1, 3, 4, 10, 15, 13, 3, 3, 10, 12], + ], }); - - canvas.appendChild(sparkline); - sparkline.destroy(); }); test('stack bar', () => { - const div = createDiv(); - - // @ts-ignore - const canvas = new Canvas({ - container: div, + sparkline.update({ + x: 10, + y: 10, + type: 'column', width: 300, - height: 300, - renderer, - }); - - const sparkline = new Sparkline({ - attrs: { - x: 10, - y: 10, - type: 'column', - width: 300, - height: 50, - isStack: true, - data: [ - [10, 2, 3, 4, 15, 10, 5, 0, 3, 1], - [5, 7, 10, 3, 10, 6, 10, 1, 5, 0], - [1, 3, 4, 10, 15, 13, 3, 3, 10, 12], - ], - }, + height: 50, + isStack: true, + data: [ + [10, 2, 3, 4, 15, 10, 5, 0, 3, 1], + [5, 7, 10, 3, -10, 6, 10, 1, 5, 0], + [1, 3, 4, 10, -15, 13, 3, 3, -10, 12], + ], }); - - canvas.appendChild(sparkline); - sparkline.destroy(); }); - test('group bar', () => { - const div = createDiv(); - // @ts-ignore - const canvas = new Canvas({ - container: div, + test('group bar', () => { + sparkline.update({ + x: 10, + y: 10, + type: 'column', width: 300, - height: 300, - renderer, + height: 50, + isStack: false, + isGroup: true, + data: [ + [10, 2, 3, 4, 15, 10, 5, 0, 3, 1], + [5, 7, 10, 3, 10, 6, 10, 1, 5, 0], + [1, 3, 4, 10, 15, 13, 3, 3, 10, 12], + ], }); - - const sparkline = new Sparkline({ - attrs: { - x: 10, - y: 10, - type: 'column', - width: 300, - height: 50, - isGroup: true, - data: [ - [10, 2, 3, 4, 15, 10, 5, 0, 3, 1], - [5, 7, 10, 3, 10, 6, 10, 1, 5, 0], - [1, 3, 4, 10, 15, 13, 3, 3, 10, 12], - ], - }, - }); - - canvas.appendChild(sparkline); - sparkline.destroy(); }); - test('stack group bar', () => { - const div = createDiv(); - // @ts-ignore - const canvas = new Canvas({ - container: div, + test('stack group bar', () => { + sparkline.update({ + x: 10, + y: 10, + type: 'column', width: 300, - height: 300, - renderer, + height: 50, + isStack: true, + isGroup: true, + data: [ + [10, 2, 3, 4, 15, 10, 5, 0, 3, 1], + [5, 7, 10, 3, 10, 6, 10, 1, 5, 0], + [1, 3, 4, 10, 15, 13, 3, 3, 10, 12], + ], }); - - const sparkline = new Sparkline({ - attrs: { - x: 10, - y: 10, - type: 'column', - width: 300, - height: 50, - isStack: true, - isGroup: true, - data: [ - [10, 2, 3, 4, 15, 10, 5, 0, 3, 1], - [5, 7, 10, 3, 10, 6, 10, 1, 5, 0], - [1, 3, 4, 10, 15, 13, 3, 3, 10, 12], - ], - }, - }); - - canvas.appendChild(sparkline); - sparkline.destroy(); }); test('color', () => { - const div = createDiv(); - - // @ts-ignore - const canvas = new Canvas({ - container: div, - width: 350, - height: 300, - renderer, - }); - - const sparkline = new Sparkline({ - attrs: { - x: 10, - y: 10, - type: 'column', - width: 300, - height: 50, - isStack: true, - isGroup: true, - color: [ - '#678ef2', - '#7dd5a9', - '#616f8f', - '#edbf45', - '#6c5ff0', - '#83c6e8', - '#8c61b4', - '#f19d56', - '#479292', - '#f19ec2', - ], - data: [ - [10, 2, 3, 4, 15, 10, 5, 0, 3, 1], - [5, 7, 10, 3, 10, 6, 10, 1, 5, 0], - [1, 3, 4, 10, 15, -13, 3, 3, 10, 12], - ], - }, + sparkline.update({ + x: 10, + y: 10, + type: 'column', + width: 300, + height: 50, + isStack: true, + isGroup: true, + color: [ + '#678ef2', + '#7dd5a9', + '#616f8f', + '#edbf45', + '#6c5ff0', + '#83c6e8', + '#8c61b4', + '#f19d56', + '#479292', + '#f19ec2', + ], + data: [ + [10, 2, 3, 4, 15, 10, 5, 0, 3, 1], + [5, 7, 10, 3, 10, 6, 10, 1, 5, 0], + [1, 3, 4, 10, 15, -13, 3, 3, 10, 12], + ], }); + }); - canvas.appendChild(sparkline); + test('destroy', () => { sparkline.destroy(); canvas.destroy(); }); diff --git a/src/ui/slider/index.ts b/src/ui/slider/index.ts index 7f8ab01ca..d96fac1ad 100644 --- a/src/ui/slider/index.ts +++ b/src/ui/slider/index.ts @@ -242,8 +242,6 @@ export class Slider extends CustomElement { */ private createSparkline() { const { orient, sparklineCfg } = this.attributes; - console.log(sparklineCfg); - // 暂时只在水平模式下绘制 if (orient !== 'horizontal') { return; diff --git a/src/ui/sparkline/index.ts b/src/ui/sparkline/index.ts index 84d9cd433..3e630d297 100644 --- a/src/ui/sparkline/index.ts +++ b/src/ui/sparkline/index.ts @@ -2,9 +2,8 @@ import { Path, Rect } from '@antv/g'; import { clone, deepMix, isNumber, isArray, isFunction } from '@antv/util'; import { Linear, Band } from '@antv/scale'; import { PathCommand } from '@antv/g-base'; -import { SparklineOptions } from './types'; +import { Data, SparklineOptions } from './types'; import { - Data, dataToLines, lineToLinePath, lineToCurvePath, @@ -49,18 +48,21 @@ export class Sparkline extends CustomElement { } attributeChangedCallback(name: string, value: any) { + // 如果type变了,需要清空this.sparkShapes子元素 + if (name === 'type') { + this.sparkShapes.removeChildren(); + } console.log(name, value); } + public update(attrs: SparklineOptions['attrs']) { + this.attr(attrs); + this.init(); + } + private init() { - const { data, type, width, height } = this.attributes; - this.sparkShapes = new Rect({ - attrs: { - width, - height, - }, - }); - this.appendChild(this.sparkShapes); + const { data, type } = this.attributes; + this.createContainer(); if (!data) return; switch (type) { case 'line': @@ -128,6 +130,17 @@ export class Sparkline extends CustomElement { return data; } + private createContainer() { + const { width, height } = this.attributes; + if (!this.sparkShapes) { + this.sparkShapes = new Rect({ + attrs: {}, + }); + this.appendChild(this.sparkShapes); + } + this.sparkShapes.attr({ width, height }); + } + /** * 创建迷你折线图 */ @@ -144,17 +157,17 @@ export class Sparkline extends CustomElement { }); // 绘制线条 linesPaths.forEach((path, idx) => { - this.sparkShapes.appendChild( - new Path({ + const id = `line-path-${idx}`; + let el = this.getElementById(id); + if (!el) { + el = new Path({ + id, name: 'line', - id: `line-path-${idx}`, - attrs: { - path, - stroke: this.getColor(idx), - ...lineStyle, - }, - }) - ); + attrs: {}, + }); + this.sparkShapes.appendChild(el); + } + el.attr({ path, stroke: this.getColor(idx), ...lineStyle }); }); // 生成area图形 @@ -172,17 +185,17 @@ export class Sparkline extends CustomElement { } areaPaths.forEach((path, idx) => { - this.sparkShapes.appendChild( - new Path({ + const id = `line-area-${idx}`; + let el = this.getElementById(id); + if (!el) { + el = new Path({ name: 'area', id: `line-area-${idx}`, - attrs: { - path, - fill: this.getColor(idx), - ...areaStyle, - }, - }) - ); + attrs: {}, + }); + this.sparkShapes.appendChild(el); + } + el.attr({ path, fill: this.getColor(idx), ...areaStyle }); }); } } @@ -209,30 +222,34 @@ export class Sparkline extends CustomElement { data.forEach((column, i) => { column.forEach((val, j) => { + const id = `column-${i}-${j}`; + let el = this.getElementById(id); const barWidth = bandWidth / data.length; - this.sparkShapes.appendChild( - new Rect({ + if (!el) { + el = new Rect({ name: 'column', id: `column-${i}-${j}`, - attrs: { - fill: this.getColor(i), - ...columnStyle, - ...(isStack - ? { - x: x.map(j), - y: y.map(val), - width: bandWidth, - height: heightScale.map(rawData[i][j]), - } - : { - x: x.map(j) + barWidth * i, - y: val >= 0 ? y.map(val) : y.map(0), - width: barWidth, - height: heightScale.map(Math.abs(val)), - }), - }, - }) - ); + attrs: {}, + }); + this.sparkShapes.appendChild(el); + } + el.attr({ + fill: this.getColor(i), + ...columnStyle, + ...(isStack + ? { + x: x.map(j), + y: y.map(val), + width: bandWidth, + height: heightScale.map(rawData[i][j]), + } + : { + x: x.map(j) + barWidth * i, + y: val >= 0 ? y.map(val) : y.map(0), + width: barWidth, + height: heightScale.map(Math.abs(val)), + }), + }); }); }); } From 7c8f959426cc6d3070356d032eb2e6594b08013f Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Sun, 11 Jul 2021 20:04:43 +0800 Subject: [PATCH 25/49] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E4=BA=86sl?= =?UTF-8?q?ider=20=E5=92=8C=20sparkline=E4=BB=A3=E7=A0=81=EF=BC=8C?= =?UTF-8?q?=E5=85=B7=E6=9C=89update=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/abstract/component.ts | 74 ++++++++ src/ui/slider/handle.ts | 105 +++++++++++ src/ui/slider/index.ts | 350 +++++++++++++----------------------- src/ui/slider/types.ts | 54 +++--- src/ui/sparkline/columns.ts | 34 ++++ src/ui/sparkline/index.ts | 217 ++++++++++------------ src/ui/sparkline/lines.ts | 41 +++++ src/ui/sparkline/types.ts | 14 +- 8 files changed, 517 insertions(+), 372 deletions(-) create mode 100644 src/abstract/component.ts create mode 100644 src/ui/slider/handle.ts create mode 100644 src/ui/sparkline/columns.ts create mode 100644 src/ui/sparkline/lines.ts diff --git a/src/abstract/component.ts b/src/abstract/component.ts new file mode 100644 index 000000000..64b61d318 --- /dev/null +++ b/src/abstract/component.ts @@ -0,0 +1,74 @@ +import { CustomElement, DisplayObject, ShapeCfg } from '../types'; + +export type AttrsType = { [key: string]: any }; +type AttrsCallback = (isUpdated?: boolean, oldAttrs?: AttrsType) => AttrsType; +type Constructable = { + new (...args: any[]): D; +}; + +export abstract class Component extends CustomElement { + protected subComponentPool = new Map(); + + protected static defaultOptions: { [key: string]: any }; + + constructor(options: T) { + super(options); + } + + public update(attrs: AttrsType): void { + // 将cfg.attr合并到this.attributes + this.attr(attrs); + this.updateSubComponent(); + } + + protected abstract init(): void; + + public reRender(): void { + this.removeChildren(); + this.init(); + } + + protected updateSubComponent(): void { + this.subComponentPool.forEach((attrsCallback, name) => { + const target = this.getSubComponent(name); + const oldAttrs = target.attributes; + target.attr(attrsCallback(true, oldAttrs)); + }); + } + + /** + * 向当前组件添加子组件 + * @param name 子组件名 + * @param Shape 子组件类型 + * @param attrsCallback 参数获取回调方法 + * @param config 配置项 + * @param parent 插入节点 + * @returns + */ + protected appendSubComponent( + name: string, + Shape: Constructable, + attrsCallback: AttrsCallback, + config: {} = {}, + parent: DisplayObject = this + ) { + const shape = new Shape({ + ...config, + attrs: attrsCallback(), + }); + this.subComponentPool.set(name, attrsCallback); + parent.appendChild(shape); + this[name] = shape; + return shape; + } + + protected getSubComponent(name: string) { + return this[name]; + } + + protected removeSubComponent(name: string): void { + const shape = this.getSubComponent(name); + this.removeChild(shape); + this.subComponentPool.delete(name); + } +} diff --git a/src/ui/slider/handle.ts b/src/ui/slider/handle.ts new file mode 100644 index 000000000..3536ee244 --- /dev/null +++ b/src/ui/slider/handle.ts @@ -0,0 +1,105 @@ +import { Rect, Image, Line } from '@antv/g'; +import { Marker, MarkerOptions } from '../marker'; +import { AttrsType } from '../../abstract/component'; +import { CustomElement, ShapeCfg } from '../../types'; + +type HandleCfg = { + type: string; + show: boolean; + orient: string; + handleAttrs: AttrsType; +}; + +export class Handle extends CustomElement { + constructor({ attrs, ...rest }: ShapeCfg) { + super({ type: 'handle', attrs, ...rest }); + const { show, type, orient, handleAttrs } = attrs; + this.render({ show, type, orient, handleAttrs }); + } + + public render(handleCfg: HandleCfg) { + this.removeChildren(true); + const { type, show, orient, handleAttrs: attrs } = handleCfg; + + if (!show) { + this.appendChild( + new Rect({ + attrs, + name: 'handleIcon', + }) + ); + } else if (['base64', 'url', 'image'].includes(type)) { + this.appendChild( + new Image({ + attrs, + name: 'handleIcon', + }) + ); + } else if (type === 'symbol') { + attrs as MarkerOptions; + this.appendChild( + new Marker({ + // @ts-ignore + attrs, + name: 'handleIcon', + }) + ); + } else { + const { size, ...rest } = attrs; + const width = size; + const height = size * 2.4; + + // 创建默认图形 + const defaultHandle = new Rect({ + name: 'handleIcon', + attrs: { + width, + height, + x: -width / 2, + y: -height / 2, + radius: size / 4, + ...rest, + }, + }); + const { stroke, lineWidth } = rest; + const X1 = (1 / 3) * width; + const X2 = (2 / 3) * width; + const Y1 = (1 / 4) * height; + const Y2 = (3 / 4) * height; + + const createLine = (x1: number, y1: number, x2: number, y2: number) => { + return new Line({ + name: 'line', + attrs: { + x1, + y1, + x2, + y2, + stroke, + lineWidth, + }, + }); + }; + defaultHandle.appendChild(createLine(X1, Y1, X1, Y2)); + defaultHandle.appendChild(createLine(X2, Y1, X2, Y2)); + // 根据orient进行rotate + if (orient === 'vertical') { + // 设置旋转中心 + defaultHandle.setOrigin(width / 2, height / 2); + + defaultHandle.rotate(90); + } + this.appendChild(defaultHandle); + } + } + + attributeChangedCallback(name: string, value: any) { + console.log(value); + + if (name === 'handleCfg') { + console.log(value); + + this.render(value); + } + } +} diff --git a/src/ui/slider/index.ts b/src/ui/slider/index.ts index d96fac1ad..b420120e4 100644 --- a/src/ui/slider/index.ts +++ b/src/ui/slider/index.ts @@ -1,16 +1,18 @@ -import { Rect, Text, Image, Line } from '@antv/g'; +import { Rect, Text, Image } from '@antv/g'; import { deepMix, get, isFunction, isString, isObject } from '@antv/util'; import { SliderOptions, HandleCfg, Pair } from './types'; -import { Marker, MarkerOptions } from '../marker'; +import { Handle } from './handle'; +import { MarkerOptions } from '../marker'; import { Sparkline } from '../sparkline'; -import { CustomElement, DisplayObject } from '../../types'; +import { DisplayObject } from '../../types'; import { toPrecision } from '../../util'; +import { Component } from '../../abstract/component'; export { SliderOptions }; type HandleType = 'start' | 'end'; -export class Slider extends CustomElement { +export class Slider extends Component { public static tag = 'slider'; /** @@ -22,31 +24,6 @@ export class Slider extends CustomElement { * |- endHandle */ - /** - * 背景 - */ - private backgroundShape: DisplayObject; - - /** - * 缩略图 - */ - private sparklineShape: DisplayObject; - - /** - * 前景,即选区 - */ - private foregroundShape: DisplayObject; - - /** - * 起始手柄 - */ - private startHandle: DisplayObject; - - /** - * 终点手柄 - */ - private endHandle: DisplayObject; - /** * 选区开始的位置 */ @@ -72,7 +49,7 @@ export class Slider extends CustomElement { this.init(); } - private static defaultOptions = { + protected static defaultOptions = { type: Slider.tag, attrs: { orient: 'horizontal', @@ -153,13 +130,7 @@ export class Slider extends CustomElement { this.setAttribute('names', names); } - public update(cfg: SliderOptions) { - const { values, names } = cfg; - this.setValues(values); - this.setNames(names); - } - - private init() { + protected init() { this.createBackground(); this.createSparkline(); this.createForeground(); @@ -226,41 +197,38 @@ export class Slider extends CustomElement { } private createBackground() { - this.backgroundShape = new Rect({ - name: 'background', - attrs: { + const attrsCallback = () => { + return { cursor: 'crosshair', ...this.getAvailableSpace(), ...this.getStyle('backgroundStyle'), - }, - }); - this.appendChild(this.backgroundShape); + }; + }; + this.appendSubComponent('backgroundShape', Rect, attrsCallback, { name: 'background' }); } /** * 生成sparkline */ private createSparkline() { - const { orient, sparklineCfg } = this.attributes; - // 暂时只在水平模式下绘制 - if (orient !== 'horizontal') { - return; - } - const { padding, ...args } = sparklineCfg; - - const { width, height } = this.getAvailableSpace(); - const { lineWidth: bkgLW } = this.getStyle('backgroundStyle'); - this.sparklineShape = new Sparkline({ - attrs: { + const attrsCallback = () => { + const { orient, sparklineCfg } = this.attributes; + // 暂时只在水平模式下绘制 + if (orient !== 'horizontal') { + return {}; + } + const { padding, ...args } = sparklineCfg; + const { width, height } = this.getAvailableSpace(); + const { lineWidth: bkgLW } = this.getStyle('backgroundStyle'); + return { x: bkgLW / 2 + padding.left, y: bkgLW / 2 + padding.top, width: width - bkgLW - padding.left - padding.right, height: height - bkgLW - padding.top - padding.bottom, ...args, - }, - }); - this.backgroundShape.appendChild(this.sparklineShape); - this.sparklineShape.toBack(); + }; + }; + this.appendSubComponent('sparklineShape', Sparkline, attrsCallback, this.getSubComponent('backgroundShape')); } /** @@ -288,15 +256,11 @@ export class Slider extends CustomElement { } private createForeground() { - this.foregroundShape = new Rect({ - name: 'foreground', - attrs: { - cursor: 'move', - ...this.calcMask(), - ...this.getStyle('foregroundStyle'), - }, - }); - this.backgroundShape.appendChild(this.foregroundShape); + const attrsCallback = () => { + return { cursor: 'move', ...this.calcMask(), ...this.getStyle('foregroundStyle') }; + }; + + this.appendSubComponent('foregroundShape', Rect, attrsCallback, { name: 'foreground' }); } /** @@ -319,11 +283,12 @@ export class Slider extends CustomElement { * 3. 更新文本位置 */ private setHandle() { - this.foregroundShape.attr(this.calcMask()); - this.startHandle.attr(this.calcHandlePosition('start')); - this.endHandle.attr(this.calcHandlePosition('end')); - this.getElementsByName('handleText').forEach((handleText) => { - handleText.attr(this.calcHandleText(handleText.getConfig().identity)); + this.getSubComponent('foregroundShape').attr(this.calcMask()); + (['start', 'end'] as HandleType[]).forEach((handleType) => { + const handle = `${handleType}Handle`; + this.getSubComponent(handle).attr(this.calcHandlePosition(handleType)); + const handleText = `${handleType}HandleText`; + this.getSubComponent(handleText).attr(this.calcHandleText(handleType)); }); } @@ -336,6 +301,7 @@ export class Slider extends CustomElement { private calcHandleText(handleType: HandleType) { const { orient, names } = this.attributes; const { spacing, formatter, textStyle } = this.getHandleCfg(handleType); + const size = this.getHandleSize(handleType); const values = this.getSafetyValues(); @@ -344,6 +310,7 @@ export class Slider extends CustomElement { const { x: fX, y: fY, width: fW, height: fH } = this.calcMask(); const formattedText = formatter(...(handleType === 'start' ? [names[0], values[0]] : [names[1], values[1]])); + const _ = new Text({ attrs: { text: formattedText, @@ -376,6 +343,7 @@ export class Slider extends CustomElement { y = iH - fY - fH - R > textHeight ? _ : -_; } } + return { x, y, text: formattedText }; } @@ -383,8 +351,8 @@ export class Slider extends CustomElement { * 解析icon类型 */ private parseIcon(icon: MarkerOptions['symbol'] | string) { - let type = 'unknown'; - if (isObject(icon) && icon instanceof Image) type = 'Image'; + let type = 'default'; + if (isObject(icon) && icon instanceof Image) type = 'image'; else if (isFunction(icon)) type = 'symbol'; else if (isString(icon)) { const dataURLsPattern = new RegExp('data:(image|text)'); @@ -404,135 +372,75 @@ export class Slider extends CustomElement { * 创建手柄 */ private createHandle(options: HandleCfg, handleType: HandleType) { - const { show, textStyle, handleIcon: icon, handleStyle } = options; - const size = this.getHandleSize(handleType); - const iconType = this.parseIcon(icon); - const baseCfg = { - name: 'handleIcon', - identity: handleType, + // 手柄容器 + const handleEl = `${handleType}Handle`; + const handleAttrsCallback = () => { + return this.calcHandlePosition(handleType); }; - const cursor = this.getOrientVal(['ew-resize', 'ns-resize']); - - const handleIcon = (() => { - if (!show) { - // 如果不显示的话,就创建透明的rect - return new Marker({ - ...baseCfg, - attrs: { - r: size / 2, - symbol: 'square', - markerStyle: { - opacity: 0, - }, - cursor, - }, - }); - } - - if (['base64', 'url', 'Image'].includes(iconType)) { - // TODO G那边似乎还是有点问题,暂不考虑Image - return new Image({ - ...baseCfg, - attrs: { - x: -size / 2, - y: -size / 2, - width: size, - height: size, - img: icon, - cursor, - }, - }); - } - if (iconType === 'symbol') { - return new Marker({ - ...baseCfg, - attrs: { - r: size / 2, - symbol: icon, - cursor, - ...handleStyle, - }, - }); - } - - const width = size; - const height = size * 2.4; - - // 创建默认图形 - const handleBody = new Rect({ - ...baseCfg, - attrs: { + // 将手柄容器挂载到foregroundShape下 + const handle = this.appendSubComponent( + handleEl, + DisplayObject, + handleAttrsCallback, + { name: 'handle' }, + this.getSubComponent('foregroundShape') + ); + /* ---------------------------------------------------------------- */ + // 手柄文本 + const handleTextAttrsCallback = () => { + const { textStyle } = options; + return { + ...textStyle, + ...this.calcHandleText(handleType), + }; + }; + // 手柄文本挂载到handle容器下 + this.appendSubComponent(`${handleType}HandleText`, Text, handleTextAttrsCallback, { name: 'handleText' }, handle); + /* ---------------------------------------------------------------- */ + // 手柄icon + const handleIconAttrsCallback = () => { + const { height: H, orient } = this.attributes; + const { show, handleIcon, handleStyle } = options; + const cursor = this.getOrientVal(['ew-resize', 'ns-resize']); + const size = this.getHandleSize(handleType); + const iconType = this.parseIcon(handleIcon); + let attrs = {}; + if (!show) + attrs = { cursor, - width, - height, - x: -width / 2, - y: -height / 2, - radius: size / 4, - ...handleStyle, + x: -size / 2, + y: -H / 2, + height: H, + width: size, + opacity: 0, + fill: 'red', + }; + else if (['base64', 'url', 'image'].includes(iconType)) + attrs = { cursor, x: -size / 2, y: -size / 2, width: size, height: size, img: handleIcon }; + else if (iconType === 'symbol') attrs = { cursor, r: size / 2, symbol: handleIcon, ...handleStyle }; + else attrs = { cursor, size, ...handleStyle }; + return { + handleCfg: { + show, + orient, + type: iconType, + handleAttrs: attrs, }, - }); - const { stroke, lineWidth } = handleStyle; - const X1 = (1 / 3) * width; - const X2 = (2 / 3) * width; - const Y1 = (1 / 4) * height; - const Y2 = (3 / 4) * height; - - const createLine = (x1: number, y1: number, x2: number, y2: number) => { - return new Line({ - name: 'line', - attrs: { - x1, - y1, - x2, - y2, - cursor, - stroke, - lineWidth, - }, - }); }; - - handleBody.appendChild(createLine(X1, Y1, X1, Y2)); - handleBody.appendChild(createLine(X2, Y1, X2, Y2)); - - // 根据orient进行rotate - // 设置旋转中心 - handleBody.setOrigin(width / 2, height / 2); - handleBody.rotate(this.getOrientVal([0, 90])); - - return handleBody; - })(); - - const handleText = new Text({ - name: 'handleText', - identity: handleType, - attrs: { - // TODO 之后考虑添加文字超长省略,可以在calcHandleTextPosition中实现 - ...textStyle, - ...this.calcHandleText(handleType), - }, - }); - - // 用 Group 创建对象会提示没有attrs属性 - const handle = new DisplayObject({ - name: 'handle', - identity: handleType, - attrs: this.calcHandlePosition(handleType), - }); - handle.appendChild(handleIcon); - handle.appendChild(handleText); - return handle; + }; + // 手柄icon也挂载到handle容器 + this.appendSubComponent(`${handleType}HandleIcon`, Handle, handleIconAttrsCallback, { name: 'handleIcon' }, handle); } private getHandleCfg(handleType: HandleType) { const { start, end, ...args } = this.getAttribute('handle'); - let _ = {}; + let handleCfg = {}; if (handleType === 'start') { - _ = start; + handleCfg = start; } else if (handleType === 'end') { - _ = end; + handleCfg = end; } - return deepMix({}, args, _); + return deepMix({}, args, handleCfg); } private getHandleSize(handleType: HandleType) { @@ -546,25 +454,26 @@ export class Slider extends CustomElement { } private createHandles() { - this.startHandle = this.createHandle(this.getHandleCfg('start'), 'start'); - this.foregroundShape.appendChild(this.startHandle); - this.endHandle = this.createHandle(this.getHandleCfg('end'), 'end'); - this.foregroundShape.appendChild(this.endHandle); + this.createHandle(this.getHandleCfg('start'), 'start'); + this.createHandle(this.getHandleCfg('end'), 'end'); } private bindEvents() { // Drag and brush - this.backgroundShape.addEventListener('mousedown', this.onDragStart('background')); - this.backgroundShape.addEventListener('touchstart', this.onDragStart('background')); - - this.foregroundShape.addEventListener('mousedown', this.onDragStart('foreground')); - this.foregroundShape.addEventListener('touchstart', this.onDragStart('foreground')); - - this.getElementsByName('handleIcon').forEach((handleIcon) => { - handleIcon.addEventListener('mousedown', this.onDragStart(`${handleIcon.getConfig().identity}Handle`)); - handleIcon.addEventListener('touchstart', this.onDragStart(`${handleIcon.getConfig().identity}Handle`)); + const background = this.getSubComponent('backgroundShape'); + background.addEventListener('mousedown', this.onDragStart('background')); + background.addEventListener('touchstart', this.onDragStart('background')); + + const foreground = this.getSubComponent('foregroundShape'); + foreground.addEventListener('mousedown', this.onDragStart('foreground')); + foreground.addEventListener('touchstart', this.onDragStart('foreground')); + + ['start', 'end'].forEach((handleType) => { + const HandleIcon = `${handleType}HandleIcon`; + this.getSubComponent(HandleIcon).addEventListener('mousedown', this.onDragStart(handleType)); + this.getSubComponent(HandleIcon).addEventListener('touchstart', this.onDragStart(handleType)); }); - // Hover + this.bindHoverEvents(); } @@ -605,10 +514,10 @@ export class Slider extends CustomElement { const dVal = this.getRatio(_); switch (this.target) { - case 'startHandle': + case 'start': this.setValuesOffset(dVal); break; - case 'endHandle': + case 'end': this.setValuesOffset(0, dVal); break; case 'foreground': @@ -634,23 +543,22 @@ export class Slider extends CustomElement { }; private bindHoverEvents = () => { - this.foregroundShape.addEventListener('mouseenter', () => { - this.foregroundShape.attr(this.getStyle('foregroundStyle')); + const foreground = this.getSubComponent('foregroundShape'); + foreground.addEventListener('mouseenter', () => { + foreground.attr(this.getStyle('foregroundStyle')); }); - this.foregroundShape.addEventListener('mouseleave', () => { - this.foregroundShape.attr(this.getStyle('foregroundStyle')); + foreground.addEventListener('mouseleave', () => { + foreground.attr(this.getStyle('foregroundStyle')); }); - this.getElementsByName('handle').forEach((handle) => { - const icon = handle.getElementsByName('handleIcon')[0]; - const text = handle.getElementsByName('handleText')[0]; - handle.addEventListener('mouseenter', () => { - icon.attr(this.getStyle('handleStyle', true, icon.getConfig().identity)); - text.attr(this.getStyle('textStyle', true, text.getConfig().identity)); + (['start', 'end'] as HandleType[]).forEach((handleType) => { + const _ = `${handleType}HandleIcon`; + const target = this.getSubComponent(_); + target.addEventListener('mouseenter', () => { + target.attr(this.getStyle('handleStyle', true, handleType)); }); - handle.addEventListener('mouseleave', () => { - icon.attr(this.getStyle('handleStyle', false, icon.getConfig().identity)); - text.attr(this.getStyle('textStyle', false, text.getConfig().identity)); + target.addEventListener('mouseleave', () => { + target.attr(this.getStyle('handleStyle', false, handleType)); }); }); }; diff --git a/src/ui/slider/types.ts b/src/ui/slider/types.ts index 866991127..426747a8b 100644 --- a/src/ui/slider/types.ts +++ b/src/ui/slider/types.ts @@ -40,31 +40,33 @@ export type HandleCfg = { }; export type SliderOptions = ShapeCfg & { - orient?: 'vertical' | 'horizontal'; - values?: Pair; - names?: Pair; - min?: number; - max?: number; - width?: number; - height?: number; - padding?: { - left: number; - right: number; - top: number; - buttons: number; - }; - backgroundStyle?: MixAttrs; - selectionStyle?: MixAttrs; - foregroundStyle?: MixAttrs; - handle?: - | HandleCfg - | { - start: HandleCfg; - end: HandleCfg; - }; + attrs: { + orient?: 'vertical' | 'horizontal'; + values?: Pair; + names?: Pair; + min?: number; + max?: number; + width?: number; + height?: number; + padding?: { + left: number; + right: number; + top: number; + buttons: number; + }; + backgroundStyle?: MixAttrs; + selectionStyle?: MixAttrs; + foregroundStyle?: MixAttrs; + handle?: + | HandleCfg + | { + start: HandleCfg; + end: HandleCfg; + }; - /** - * 缩略图数据及其配置 - */ - sparklineCfg?: SparklineOptions; + /** + * 缩略图数据及其配置 + */ + sparklineCfg?: SparklineOptions; + }; }; diff --git a/src/ui/sparkline/columns.ts b/src/ui/sparkline/columns.ts new file mode 100644 index 000000000..4c78ece55 --- /dev/null +++ b/src/ui/sparkline/columns.ts @@ -0,0 +1,34 @@ +import { Rect } from '@antv/g'; +import { AttrsType } from '../../abstract/component'; +import { CustomElement, ShapeCfg } from '../../types'; + +type ColumnsCfg = AttrsType[][]; + +export class Columns extends CustomElement { + constructor({ attrs, ...rest }: ShapeCfg) { + super({ type: 'column', attrs, ...rest }); + this.render(attrs.columnsCfg); + } + + public render(columnsCfg: ColumnsCfg): void { + this.removeChildren(); + columnsCfg.forEach((column) => { + column.forEach((cfg) => { + this.appendChild( + new Rect({ + name: 'column', + attrs: { + ...cfg, + }, + }) + ); + }); + }); + } + + attributeChangedCallback(name: string, value: any) { + if (name === 'columnsCfg') { + this.render(value); + } + } +} diff --git a/src/ui/sparkline/index.ts b/src/ui/sparkline/index.ts index 3e630d297..dfe3fb4ee 100644 --- a/src/ui/sparkline/index.ts +++ b/src/ui/sparkline/index.ts @@ -1,8 +1,10 @@ -import { Path, Rect } from '@antv/g'; +import { Rect } from '@antv/g'; import { clone, deepMix, isNumber, isArray, isFunction } from '@antv/util'; import { Linear, Band } from '@antv/scale'; import { PathCommand } from '@antv/g-base'; import { Data, SparklineOptions } from './types'; +import { Lines } from './lines'; +import { Columns } from './columns'; import { dataToLines, lineToLinePath, @@ -12,14 +14,15 @@ import { linesToStackCurveAreaPaths, } from './path'; import { getRange, getStackedData } from './utils'; -import { CustomElement, DisplayObject } from '../../types'; +import { Component } from '../../abstract/component'; +import { DisplayObject } from '../../types'; export { SparklineOptions }; -export class Sparkline extends CustomElement { +export class Sparkline extends Component { public static tag = 'Sparkline'; - private static defaultOptions = { + protected static defaultOptions = { attrs: { type: 'line', width: 200, @@ -52,24 +55,20 @@ export class Sparkline extends CustomElement { if (name === 'type') { this.sparkShapes.removeChildren(); } - console.log(name, value); + console.log(value); } - public update(attrs: SparklineOptions['attrs']) { - this.attr(attrs); - this.init(); - } - - private init() { + protected init() { const { data, type } = this.attributes; this.createContainer(); if (!data) return; + switch (type) { case 'line': this.createLine(); break; case 'column': - this.createBar(); + this.createColumn(); break; default: break; @@ -131,126 +130,106 @@ export class Sparkline extends CustomElement { } private createContainer() { - const { width, height } = this.attributes; - if (!this.sparkShapes) { - this.sparkShapes = new Rect({ - attrs: {}, - }); - this.appendChild(this.sparkShapes); - } - this.sparkShapes.attr({ width, height }); + const attrsCallback = () => { + const { width, height } = this.attributes; + return { width, height }; + }; + this.appendSubComponent('container', Rect, attrsCallback, { name: 'container' }); } /** * 创建迷你折线图 */ private createLine() { - const { isStack, lineStyle, smooth, areaStyle, width } = this.attributes; - let data = this.getData(); - if (isStack) data = getStackedData(data); - const { x, y } = this.createScales(data) as { x: Linear; y: Linear }; - const lines = dataToLines(data, { type: 'line', x, y }); - const linesPaths: PathCommand[][] = []; - // 线条path - lines.forEach((line) => { - linesPaths.push(smooth ? lineToCurvePath(line) : lineToLinePath(line)); - }); - // 绘制线条 - linesPaths.forEach((path, idx) => { - const id = `line-path-${idx}`; - let el = this.getElementById(id); - if (!el) { - el = new Path({ - id, - name: 'line', - attrs: {}, - }); - this.sparkShapes.appendChild(el); - } - el.attr({ path, stroke: this.getColor(idx), ...lineStyle }); - }); - - // 生成area图形 - if (areaStyle) { - const range = getRange(data); - const baseline = y.map(range[0] < 0 ? 0 : range[0]); - // 折线、堆叠折线和普通曲线直接 - let areaPaths: PathCommand[][]; - if (isStack) { - areaPaths = smooth - ? linesToStackCurveAreaPaths(lines, width, baseline) - : linesToStackAreaPaths(lines, width, baseline); - } else { - areaPaths = linesToAreaPaths(lines, smooth, width, baseline); + const linesParamsCallback = () => { + const { areaStyle, isStack, lineStyle, smooth, width } = this.attributes; + let data = this.getData(); + if (isStack) data = getStackedData(data); + const { x, y } = this.createScales(data) as { x: Linear; y: Linear }; + // 线条Path + const lines = dataToLines(data, { type: 'line', x, y }); + + // 生成区域path + let areas: PathCommand[][] = []; + if (areaStyle) { + const range = getRange(data); + const baseline = y.map(range[0] < 0 ? 0 : range[0]); + if (isStack) { + areas = smooth + ? linesToStackCurveAreaPaths(lines, width, baseline) + : linesToStackAreaPaths(lines, width, baseline); + } else { + areas = linesToAreaPaths(lines, smooth, width, baseline); + } } + return { + linesCfg: { + linesAttrs: lines.map((line, idx) => { + return { + stroke: this.getColor(idx), + path: smooth ? lineToCurvePath(line) : lineToLinePath(line), + ...lineStyle, + }; + }), + areasAttrs: areas.map((path, idx) => { + return { + path, + fill: this.getColor(idx), + ...areaStyle, + }; + }), + }, + }; + }; - areaPaths.forEach((path, idx) => { - const id = `line-area-${idx}`; - let el = this.getElementById(id); - if (!el) { - el = new Path({ - name: 'area', - id: `line-area-${idx}`, - attrs: {}, - }); - this.sparkShapes.appendChild(el); - } - el.attr({ path, fill: this.getColor(idx), ...areaStyle }); - }); - } + this.appendSubComponent('lines', Lines, linesParamsCallback); } /** * 创建mini柱状图 */ - private createBar() { - const { isStack, height, columnStyle } = this.attributes; - let data = this.getData(); - if (isStack) data = getStackedData(data); - const { x, y } = this.createScales(data) as { - x: Band; - y: Linear; - }; - const [minVal, maxVal] = getRange(data); - const heightScale = new Linear({ - domain: [0, maxVal - (minVal > 0 ? 0 : minVal)], - range: [0, height], - }); - - const bandWidth = x.getBandWidth(); - const rawData = this.getData(); - - data.forEach((column, i) => { - column.forEach((val, j) => { - const id = `column-${i}-${j}`; - let el = this.getElementById(id); - const barWidth = bandWidth / data.length; - if (!el) { - el = new Rect({ - name: 'column', - id: `column-${i}-${j}`, - attrs: {}, - }); - this.sparkShapes.appendChild(el); - } - el.attr({ - fill: this.getColor(i), - ...columnStyle, - ...(isStack - ? { - x: x.map(j), - y: y.map(val), - width: bandWidth, - height: heightScale.map(rawData[i][j]), - } - : { - x: x.map(j) + barWidth * i, - y: val >= 0 ? y.map(val) : y.map(0), - width: barWidth, - height: heightScale.map(Math.abs(val)), - }), - }); + private createColumn() { + const columnsParamsCallback = () => { + const { isStack, height, columnStyle } = this.attributes; + let data = this.getData(); + if (isStack) data = getStackedData(data); + const { x, y } = this.createScales(data) as { + x: Band; + y: Linear; + }; + const [minVal, maxVal] = getRange(data); + const heightScale = new Linear({ + domain: [0, maxVal - (minVal > 0 ? 0 : minVal)], + range: [0, height], }); - }); + const bandWidth = x.getBandWidth(); + const rawData = this.getData(); + + return { + columnsCfg: data.map((column, i) => { + return column.map((val, j) => { + const barWidth = bandWidth / data.length; + return { + fill: this.getColor(i), + ...columnStyle, + ...(isStack + ? { + x: x.map(j), + y: y.map(val), + width: bandWidth, + height: heightScale.map(rawData[i][j]), + } + : { + x: x.map(j) + barWidth * i, + y: val >= 0 ? y.map(val) : y.map(0), + width: barWidth, + height: heightScale.map(Math.abs(val)), + }), + }; + }); + }), + }; + }; + this.appendSubComponent('columns', Columns, columnsParamsCallback); } } diff --git a/src/ui/sparkline/lines.ts b/src/ui/sparkline/lines.ts new file mode 100644 index 000000000..6dd4b3bee --- /dev/null +++ b/src/ui/sparkline/lines.ts @@ -0,0 +1,41 @@ +import { Path } from '@antv/g'; +import { AttrsType } from '../../abstract/component'; +import { CustomElement, ShapeCfg } from '../../types'; + +type LinesCfg = { linesAttrs: AttrsType[]; areasAttrs?: AttrsType[] }; + +export class Lines extends CustomElement { + constructor({ attrs, ...rest }: ShapeCfg) { + super({ type: 'lines', attrs, ...rest }); + this.render(attrs.linesCfg); + } + + public render(linesCfg: LinesCfg): void { + this.removeChildren(true); + const { linesAttrs, areasAttrs } = linesCfg; + linesAttrs.forEach((cfg) => { + this.appendChild( + new Path({ + name: 'line', + attrs: cfg, + }) + ); + }); + areasAttrs?.forEach((cfg, idx: number) => { + const id = `line-area-${idx}`; + this.appendChild( + new Path({ + id, + name: 'area', + attrs: cfg, + }) + ); + }); + } + + attributeChangedCallback(name: string, value: any) { + if (name === 'linesCfg') { + this.render(value); + } + } +} diff --git a/src/ui/sparkline/types.ts b/src/ui/sparkline/types.ts index cff78920f..a79884a7e 100644 --- a/src/ui/sparkline/types.ts +++ b/src/ui/sparkline/types.ts @@ -18,12 +18,13 @@ export type Scales = { ); export type SparklineOptions = ShapeCfg & { - data?: number[] | number[][]; - width?: number; - height?: number; - isStack?: boolean; - color?: string | string[] | ((idx: number) => string); -} & ( + attrs: { + data?: number[] | number[][]; + width?: number; + height?: number; + isStack?: boolean; + color?: string | string[] | ((idx: number) => string); + } & ( | { type?: 'line'; smooth?: boolean; @@ -36,3 +37,4 @@ export type SparklineOptions = ShapeCfg & { columnStyle?: ShapeAttrs | ((idx: number) => ShapeAttrs); } ); +}; From c8793a631ac44392cbb8e4035fb76fa702f2e4cd Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Sun, 11 Jul 2021 20:09:07 +0800 Subject: [PATCH 26/49] =?UTF-8?q?refactor(sparkline):=20=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E4=BA=86=E4=BB=A3=E7=A0=81=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/slider/handle.ts | 6 ------ src/ui/sparkline/index.ts | 5 +---- src/ui/sparkline/path.ts | 14 +++++++------- src/ui/sparkline/utils.ts | 2 +- 4 files changed, 9 insertions(+), 18 deletions(-) diff --git a/src/ui/slider/handle.ts b/src/ui/slider/handle.ts index 3536ee244..6e5edfc84 100644 --- a/src/ui/slider/handle.ts +++ b/src/ui/slider/handle.ts @@ -84,9 +84,7 @@ export class Handle extends CustomElement { defaultHandle.appendChild(createLine(X2, Y1, X2, Y2)); // 根据orient进行rotate if (orient === 'vertical') { - // 设置旋转中心 defaultHandle.setOrigin(width / 2, height / 2); - defaultHandle.rotate(90); } this.appendChild(defaultHandle); @@ -94,11 +92,7 @@ export class Handle extends CustomElement { } attributeChangedCallback(name: string, value: any) { - console.log(value); - if (name === 'handleCfg') { - console.log(value); - this.render(value); } } diff --git a/src/ui/sparkline/index.ts b/src/ui/sparkline/index.ts index dfe3fb4ee..23501b395 100644 --- a/src/ui/sparkline/index.ts +++ b/src/ui/sparkline/index.ts @@ -15,7 +15,6 @@ import { } from './path'; import { getRange, getStackedData } from './utils'; import { Component } from '../../abstract/component'; -import { DisplayObject } from '../../types'; export { SparklineOptions }; @@ -43,8 +42,6 @@ export class Sparkline extends Component { }, }; - private sparkShapes: DisplayObject; - constructor(options: SparklineOptions) { super(deepMix({}, Sparkline.defaultOptions, options)); this.init(); @@ -53,7 +50,7 @@ export class Sparkline extends Component { attributeChangedCallback(name: string, value: any) { // 如果type变了,需要清空this.sparkShapes子元素 if (name === 'type') { - this.sparkShapes.removeChildren(); + this.getSubComponent('sparkShapes').removeChildren(); } console.log(value); } diff --git a/src/ui/sparkline/path.ts b/src/ui/sparkline/path.ts index dd5ab039e..4621dbae6 100644 --- a/src/ui/sparkline/path.ts +++ b/src/ui/sparkline/path.ts @@ -9,10 +9,10 @@ import { Data, Line, Point, Scales } from './types'; export function dataToLines(data: Data, scales: Scales): Line[] { const { x, y } = scales; return data.map((points) => { - const _ = points.map((val: number, idx: number) => { + const lines = points.map((val: number, idx: number) => { return [x.map(idx), y.map(val)] as Point; }); - return _; + return lines; }); } @@ -21,8 +21,8 @@ export function dataToLines(data: Data, scales: Scales): Line[] { */ export function lineToLinePath(line: Line, reverse = false) { const M = reverse ? line.length - 1 : 0; - const _ = line.map((point: Point, idx: number) => [idx === M ? 'M' : 'L', ...point]) as PathCommand[]; - return reverse ? _.reverse() : _; + const linePath = line.map((point: Point, idx: number) => [idx === M ? 'M' : 'L', ...point]) as PathCommand[]; + return reverse ? linePath.reverse() : linePath; } /** @@ -55,9 +55,9 @@ export function lineToCurvePath(line: Line, reverse = false) { * 根据baseline将path闭合 */ export function closePathByBaseLine(path: PathCommand[], width: number, baseline: number) { - const _ = clone(path); - _.push(['L', width, baseline], ['L', 0, baseline], ['Z']); - return _; + const closedPath = clone(path); + closedPath.push(['L', width, baseline], ['L', 0, baseline], ['Z']); + return closedPath; } /** diff --git a/src/ui/sparkline/utils.ts b/src/ui/sparkline/utils.ts index c9d56a182..57680c6ff 100644 --- a/src/ui/sparkline/utils.ts +++ b/src/ui/sparkline/utils.ts @@ -1,5 +1,5 @@ import { clone, min, minBy, max, maxBy } from '@antv/util'; -import { Data } from './path'; +import { Data } from './types'; /** * 获得数据的最值 From fa7de400355dfedf92c879695e51fd40b4c2d6fa Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Sun, 11 Jul 2021 20:49:19 +0800 Subject: [PATCH 27/49] =?UTF-8?q?refactor(slider):=20=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E4=BA=86padding[]=E4=BB=A3=E6=9B=BF{}?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/unit/ui/scrollbar/index.spec.ts | 26 +++++++++-------- src/ui/slider/index.ts | 34 +++++++++-------------- 2 files changed, 27 insertions(+), 33 deletions(-) diff --git a/__tests__/unit/ui/scrollbar/index.spec.ts b/__tests__/unit/ui/scrollbar/index.spec.ts index b7f344da7..8a5941473 100644 --- a/__tests__/unit/ui/scrollbar/index.spec.ts +++ b/__tests__/unit/ui/scrollbar/index.spec.ts @@ -46,13 +46,14 @@ describe('scrollbar', () => { expect(scrollbar.getValue()).toBe(0.5); const { padding } = scrollbar.attributes; - const verticalPadding = padding.top + padding.bottom; + const [top, , bottom] = padding; + const verticalPadding = top + bottom; let value = 0.2; scrollbar.setValue(value); expect(scrollbar.getValue()).toBe(value); expect(scrollbar.lastChild.attributes.y).toBeCloseTo( - padding.top + (height - verticalPadding - thumbLen) * clamp(value, 0, 1), + top + (height - verticalPadding - thumbLen) * clamp(value, 0, 1), 1 ); @@ -60,7 +61,7 @@ describe('scrollbar', () => { scrollbar.setValue(value); expect(scrollbar.getValue()).toBe(value); expect(scrollbar.lastChild.attributes.y).toBeCloseTo( - padding.top + (height - verticalPadding - thumbLen) * clamp(value, 0, 1), + top + (height - verticalPadding - thumbLen) * clamp(value, 0, 1), 1 ); @@ -68,7 +69,7 @@ describe('scrollbar', () => { scrollbar.setValue(value); expect(scrollbar.getValue()).toBe(value); expect(scrollbar.lastChild.attributes.y).toBeCloseTo( - padding.top + (height - verticalPadding - thumbLen) * clamp(value, 0, 1), + top + (height - verticalPadding - thumbLen) * clamp(value, 0, 1), 1 ); @@ -76,7 +77,7 @@ describe('scrollbar', () => { scrollbar.setValue(value); expect(scrollbar.getValue()).toBe(value); expect(scrollbar.lastChild.attributes.y).toBeCloseTo( - padding.top + (height - verticalPadding - thumbLen) * clamp(value, 0, 1), + top + (height - verticalPadding - thumbLen) * clamp(value, 0, 1), 1 ); @@ -84,7 +85,7 @@ describe('scrollbar', () => { scrollbar.setValue(value); expect(scrollbar.getValue()).toBe(value); expect(scrollbar.lastChild.attributes.y).toBeCloseTo( - padding.top + (height - verticalPadding - thumbLen) * clamp(value, 0, 1), + top + (height - verticalPadding - thumbLen) * clamp(value, 0, 1), 1 ); @@ -123,13 +124,14 @@ describe('scrollbar', () => { expect(scrollbar.getValue()).toBe(0.5); const { padding } = scrollbar.attributes; - const horizonPadding = padding.left + padding.right; + const [, right, , left] = padding; + const horizonPadding = left + right; let value = 0.2; scrollbar.setValue(value); expect(scrollbar.getValue()).toBe(value); expect(scrollbar.lastChild.attributes.x).toBeCloseTo( - padding.left + (width - horizonPadding - thumbLen) * clamp(value, 0, 1), + left + (width - horizonPadding - thumbLen) * clamp(value, 0, 1), 1 ); @@ -137,7 +139,7 @@ describe('scrollbar', () => { scrollbar.setValue(value); expect(scrollbar.getValue()).toBe(value); expect(scrollbar.lastChild.attributes.x).toBeCloseTo( - padding.left + (width - horizonPadding - thumbLen) * clamp(value, 0, 1), + left + (width - horizonPadding - thumbLen) * clamp(value, 0, 1), 1 ); @@ -145,7 +147,7 @@ describe('scrollbar', () => { scrollbar.setValue(value); expect(scrollbar.getValue()).toBe(value); expect(scrollbar.lastChild.attributes.x).toBeCloseTo( - padding.left + (width - horizonPadding - thumbLen) * clamp(value, 0, 1), + left + (width - horizonPadding - thumbLen) * clamp(value, 0, 1), 1 ); @@ -153,7 +155,7 @@ describe('scrollbar', () => { scrollbar.setValue(value); expect(scrollbar.getValue()).toBe(value); expect(scrollbar.lastChild.attributes.x).toBeCloseTo( - padding.left + (width - horizonPadding - thumbLen) * clamp(value, 0, 1), + left + (width - horizonPadding - thumbLen) * clamp(value, 0, 1), 1 ); @@ -161,7 +163,7 @@ describe('scrollbar', () => { scrollbar.setValue(value); expect(scrollbar.getValue()).toBe(value); expect(scrollbar.lastChild.attributes.x).toBeCloseTo( - padding.left + (width - horizonPadding - thumbLen) * clamp(value, 0, 1), + left + (width - horizonPadding - thumbLen) * clamp(value, 0, 1), 1 ); diff --git a/src/ui/slider/index.ts b/src/ui/slider/index.ts index b420120e4..6156d5e2d 100644 --- a/src/ui/slider/index.ts +++ b/src/ui/slider/index.ts @@ -60,19 +60,9 @@ export class Slider extends Component { width: 200, height: 20, sparklineCfg: { - padding: { - left: 1, - right: 1, - top: 1, - bottom: 1, - }, - }, - padding: { - left: 0, - right: 0, - top: 0, - bottom: 0, + padding: [1, 1, 1, 1], }, + padding: [0, 0, 0, 0], backgroundStyle: { fill: '#fff', stroke: '#e4eaf5', @@ -174,11 +164,12 @@ export class Slider extends Component { private getAvailableSpace() { const { padding, width, height } = this.attributes; + const [top, right, bottom, left] = padding; return { - x: padding.left, - y: padding.top, - width: width - (padding.left + padding.right), - height: height - (padding.top + padding.bottom), + x: left, + y: top, + width: width - (left + right), + height: height - (top + bottom), }; } @@ -188,7 +179,7 @@ export class Slider extends Component { * @param isActive 是否是active style * @returns ShapeAttrs */ - private getStyle(name: string | string[], isActive?: boolean, handleType?: HandleType) { + protected getStyle(name: string | string[], isActive?: boolean, handleType?: HandleType) { const { active, ...args } = get(handleType ? this.getHandleCfg(handleType) : this.attributes, name); if (isActive) { return active || {}; @@ -218,13 +209,14 @@ export class Slider extends Component { return {}; } const { padding, ...args } = sparklineCfg; + const [top, right, bottom, left] = padding; const { width, height } = this.getAvailableSpace(); const { lineWidth: bkgLW } = this.getStyle('backgroundStyle'); return { - x: bkgLW / 2 + padding.left, - y: bkgLW / 2 + padding.top, - width: width - bkgLW - padding.left - padding.right, - height: height - bkgLW - padding.top - padding.bottom, + x: bkgLW / 2 + left, + y: bkgLW / 2 + top, + width: width - bkgLW - left - right, + height: height - bkgLW - top - bottom, ...args, }; }; From a6473357ae1f5b225150ab9b71f2fae3684f3d40 Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Sun, 11 Jul 2021 20:49:50 +0800 Subject: [PATCH 28/49] =?UTF-8?q?refactor:=20=E4=BB=8Eindex=E5=AF=BC?= =?UTF-8?q?=E5=87=BAcomponent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/abstract/index.ts | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/abstract/index.ts diff --git a/src/abstract/index.ts b/src/abstract/index.ts new file mode 100644 index 000000000..0cd0b8950 --- /dev/null +++ b/src/abstract/index.ts @@ -0,0 +1 @@ +export { Component } from './component'; From a458a2f8ff1e28c7fb78351607f36ba60649a049 Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Sun, 11 Jul 2021 20:50:17 +0800 Subject: [PATCH 29/49] =?UTF-8?q?refactor(component):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E4=BA=86getStyle=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/abstract/component.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/abstract/component.ts b/src/abstract/component.ts index 64b61d318..36edcc92b 100644 --- a/src/abstract/component.ts +++ b/src/abstract/component.ts @@ -1,3 +1,4 @@ +import { get } from '@antv/util'; import { CustomElement, DisplayObject, ShapeCfg } from '../types'; export type AttrsType = { [key: string]: any }; @@ -28,6 +29,19 @@ export abstract class Component extends CustomElement { this.init(); } + /** + * 获取样式属性 + * @param name style的key值 + * @param isActive 是否激活状态的样式 + */ + protected getStyle(name: string | string[], isActive?: boolean) { + const { active, ...args } = get(this.attributes, name); + if (isActive) { + return active || {}; + } + return args?.default || args; + } + protected updateSubComponent(): void { this.subComponentPool.forEach((attrsCallback, name) => { const target = this.getSubComponent(name); From d6ed3b36cc372862ac9b770c59239bc68e935b0d Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Sun, 11 Jul 2021 20:50:50 +0800 Subject: [PATCH 30/49] =?UTF-8?q?refactor(scrollbar):=20=E6=94=B9=E5=86=99?= =?UTF-8?q?=E4=BA=86scrollbar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/scrollbar/index.ts | 143 ++++++++++++++++---------------------- 1 file changed, 59 insertions(+), 84 deletions(-) diff --git a/src/ui/scrollbar/index.ts b/src/ui/scrollbar/index.ts index c1ebeaaa4..0e783fff7 100644 --- a/src/ui/scrollbar/index.ts +++ b/src/ui/scrollbar/index.ts @@ -1,33 +1,22 @@ import { Rect } from '@antv/g'; import { clamp, deepMix } from '@antv/util'; import { ScrollbarOptions } from './types'; -import { isPC } from '../../util'; -import { CustomElement, DisplayObject } from '../../types'; +import { Component } from '../../abstract'; export { ScrollbarOptions }; -export class Scrollbar extends CustomElement { +export class Scrollbar extends Component { /** * tag */ public static tag = 'scrollbar'; - /** - * 轨道 - */ - private trackShape: DisplayObject; - - /** - * 滑块 - */ - private thumbShape: DisplayObject; - /** * 拖动开始位置 */ private prevPos: number; - private static defaultOptions = { + protected static defaultOptions = { type: Scrollbar.tag, attrs: { // 滑条朝向 @@ -48,12 +37,7 @@ export class Scrollbar extends CustomElement { thumbLen: 20, // 滑块内边距 - padding: { - top: 2, - right: 2, - bottom: 2, - left: 2, - }, + padding: [2, 2, 2, 2], trackStyle: { default: { @@ -85,7 +69,8 @@ export class Scrollbar extends CustomElement { if (name === 'value') { const { padding } = this.attributes; const thumbOffset = this.valueOffset(value); - this.setThumbOffset(thumbOffset + this.getOrientVal([padding.left, padding.top])); + const [top, , , left] = padding; + this.setThumbOffset(thumbOffset + this.getOrientVal([left, top])); } } @@ -117,7 +102,7 @@ export class Scrollbar extends CustomElement { this.setValue(this.valueOffset(deltaOffset, true) + value); } - private init() { + protected init() { this.createTrack(); this.createThumb(); @@ -161,11 +146,12 @@ export class Scrollbar extends CustomElement { */ private getAvailableSpace() { const { width, height, padding } = this.attributes; + const [top, right, bottom, left] = padding; return { - x: padding.left, - y: padding.top, - width: width - (padding.left + padding.right), - height: height - (padding.top + padding.bottom), + x: left, + y: top, + width: width - (left + right), + height: height - (top + bottom), }; } @@ -182,65 +168,58 @@ export class Scrollbar extends CustomElement { * @param thumbOffset 滑块位置偏移量 */ private setThumbOffset(thumbOffset: number) { - this.thumbShape.setAttribute(this.getOrientVal(['x', 'y']), thumbOffset); + this.getSubComponent('thumb').setAttribute(this.getOrientVal(['x', 'y']), thumbOffset); } /** * 生成轨道属性 */ private createTrack() { - const { width, height, trackStyle } = this.attributes; - this.trackShape = new Rect({ - attrs: { - x: 0, - y: 0, - ...trackStyle.default, - width, - height, - }, - }); - this.appendChild(this.trackShape); + const trackAttrsCallback = () => { + const { width, height } = this.attributes; + return { width, height, x: 0, y: 0, ...this.getStyle('trackStyle') }; + }; + this.appendSubComponent('track', Rect, trackAttrsCallback, { name: 'track' }); } /** * 生成滑块属性 */ private createThumb() { - const { orient, value, isRound, thumbLen, thumbStyle } = this.attributes; - const trackInner = this.getAvailableSpace(); - const { x, y, width, height } = trackInner; - const baseAttrs = { - ...trackInner, - ...thumbStyle.default, - }; - - this.thumbShape = new Rect({ - attrs: (() => { - let half = width / 2; - if (orient === 'vertical') { - return { - ...baseAttrs, - y: y + this.valueOffset(value), - height: thumbLen, - radius: isRound ? half : 0, - }; - } - half = height / 2; + const thumbAttrsCallback = () => { + const { orient, value, isRound, thumbLen } = this.attributes; + const trackInner = this.getAvailableSpace(); + const { x, y, width, height } = trackInner; + const baseAttrs = { + ...trackInner, + ...this.getStyle('thumbStyle'), + }; + let half = width / 2; + if (orient === 'vertical') { return { ...baseAttrs, - x: x + this.valueOffset(value), - width: thumbLen, + y: y + this.valueOffset(value), + height: thumbLen, radius: isRound ? half : 0, }; - })(), - }); - this.appendChild(this.thumbShape); + } + half = height / 2; + return { + ...baseAttrs, + x: x + this.valueOffset(value), + width: thumbLen, + radius: isRound ? half : 0, + }; + }; + this.appendSubComponent('thumb', Rect, thumbAttrsCallback, { name: 'thumb' }); } private bindEvents() { - this.trackShape.addEventListener('click', this.onTrackClick); - this.thumbShape.addEventListener('mousedown', this.onDragStart); - this.thumbShape.addEventListener('touchstart', this.onDragStart); + const trackShape = this.getSubComponent('track'); + trackShape.addEventListener('click', this.onTrackClick); + const thumbShape = this.getSubComponent('thumb'); + thumbShape.addEventListener('mousedown', this.onDragStart); + thumbShape.addEventListener('touchstart', this.onDragStart); this.onHover(); } @@ -258,7 +237,8 @@ export class Scrollbar extends CustomElement { */ private onTrackClick = (e) => { const { x, y, padding, thumbLen } = this.attributes; - const basePos = this.getOrientVal([x + padding.left, y + padding.top]); + const [top, , , left] = padding; + const basePos = this.getOrientVal([x + left, y + top]); const clickPos = this.getOrientVal([e.x, e.y]) - thumbLen / 2; const value = this.valueOffset(clickPos - basePos, true); this.setValue(value); @@ -268,21 +248,14 @@ export class Scrollbar extends CustomElement { * 悬浮事件 */ private onHover() { - const { thumbStyle, trackStyle } = this.attributes; - // 滑块hover - this.thumbShape.addEventListener('mouseenter', () => { - this.thumbShape.attr(thumbStyle.active); - }); - this.thumbShape.addEventListener('mouseleave', () => { - this.thumbShape.attr(thumbStyle.default); - }); - - // 滑轨hover - this.trackShape.addEventListener('mouseenter', () => { - this.trackShape.attr(trackStyle.active); - }); - this.trackShape.addEventListener('mouseleave', () => { - this.trackShape.attr(trackStyle.default); + ['thumb', 'track'].forEach((name) => { + const target = this.getSubComponent(name); + target.addEventListener('mouseenter', () => { + target.attr(this.getStyle(`${name}Style`, true)); + }); + target.addEventListener('mouseleave', () => { + target.attr(this.getStyle(`${name}Style`)); + }); }); } @@ -297,8 +270,10 @@ export class Scrollbar extends CustomElement { private onDragging = (e: MouseEvent | TouchEvent) => { e.stopPropagation(); - // @ts-ignore - const currPos = this.getOrientVal(isPC() ? [e.offsetX, e.offsetY] : [e.touches[0].clientX, e.touches[0].clientY]); + const currPos = this.getOrientVal( + // @ts-ignore + e?.offsetX ? [e.offsetX, e.offsetY] : [e.touches[0].clientX, e.touches[0].clientY] + ); const diff = currPos - this.prevPos; this.setOffset(diff); this.prevPos = currPos; From f94b4dbe35fe485466a7a91cb43e7f290cb6d146 Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Sun, 11 Jul 2021 20:54:02 +0800 Subject: [PATCH 31/49] =?UTF-8?q?refactor(marker):=20=E6=94=B9=E5=86=99?= =?UTF-8?q?=E4=BA=86marker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/marker/index.ts | 34 ++++++++++++---------------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/src/ui/marker/index.ts b/src/ui/marker/index.ts index b56b3def4..c88d5cfab 100644 --- a/src/ui/marker/index.ts +++ b/src/ui/marker/index.ts @@ -1,22 +1,20 @@ import { Path } from '@antv/g'; import { deepMix, isFunction } from '@antv/util'; -import { CustomElement, DisplayObject } from '../../types'; import { MarkerOptions, FunctionalSymbol } from './types'; import { circle, square, diamond, triangleDown, triangle } from './symbol'; +import { Component } from '../../abstract'; export { MarkerOptions, FunctionalSymbol }; /** * Marker */ -export class Marker extends CustomElement { +export class Marker extends Component { /** * 标签类型 */ public static tag = 'marker'; - private pathShape: DisplayObject; - private static MARKER_SYMBOL_MAP = new Map(); /** @@ -31,7 +29,7 @@ export class Marker extends CustomElement { /** * 默认参数 */ - private static defaultOptions = { + protected static defaultOptions = { type: Marker.tag, attrs: { x: 0, @@ -42,7 +40,6 @@ export class Marker extends CustomElement { constructor(options: MarkerOptions) { super(deepMix({}, Marker.defaultOptions, options)); - this.init(); } @@ -50,29 +47,22 @@ export class Marker extends CustomElement { console.log('attributeChangedCallback', name, value); } - public getPathShape() { - return this.pathShape; - } - /** * 根据 type 获取 maker shape */ - private init(): void { - const { x, y, r, symbol, ...args } = this.attributes; - - const symbolFn = isFunction(symbol) ? symbol : Marker.MARKER_SYMBOL_MAP.get(symbol); - const path = symbolFn(x, y, r); - - this.pathShape = new Path({ - attrs: { - // 左上角锚点 + protected init(): void { + const pathAttrsCallback = () => { + const { x, y, r, symbol, ...args } = this.attributes; + const symbolFn = isFunction(symbol) ? symbol : Marker.MARKER_SYMBOL_MAP.get(symbol); + const path = symbolFn(x, y, r); + return { x, y, path, ...args, - }, - }); - this.appendChild(this.pathShape); + }; + }; + this.appendSubComponent('pathShape', Path, pathAttrsCallback); } } From 753208e1d40fda03b63256c8eae96e0d4af69627 Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Sun, 11 Jul 2021 21:01:50 +0800 Subject: [PATCH 32/49] =?UTF-8?q?refactor(icon):=20=E6=94=B9=E5=86=99?= =?UTF-8?q?=E4=BA=86icon?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/icon/index.ts | 97 ++++++++++++++------------------------------ 1 file changed, 31 insertions(+), 66 deletions(-) diff --git a/src/ui/icon/index.ts b/src/ui/icon/index.ts index 95621b345..225383722 100644 --- a/src/ui/icon/index.ts +++ b/src/ui/icon/index.ts @@ -1,35 +1,20 @@ import { Rect, Text } from '@antv/g'; import { deepMix } from '@antv/util'; -import { CustomElement, DisplayObject } from '../../types'; import { Marker } from '../marker'; import { IconOptions } from './types'; +import { Component } from '../../abstract'; export { IconOptions }; /** * 带文本的 图标组件,支持 iconfont 组件 */ -export class Icon extends CustomElement { +export class Icon extends Component { /** * 标签类型 */ public static tag = 'icon'; - /** - * 图标 - */ - private iconShape: DisplayObject; - - /** - * 文本 - */ - private textShape: DisplayObject; - - /** - * 背景,用于在交互中显示 - */ - private background: DisplayObject; - /** * 默认参数 */ @@ -53,7 +38,6 @@ export class Icon extends CustomElement { constructor(options: IconOptions) { super(deepMix({}, Icon.defaultOptions, options)); - this.init(); } @@ -61,71 +45,52 @@ export class Icon extends CustomElement { console.log('attributeChangedCallback', name, value); } - /** - * 获取 icon 图标 - */ - public getIconShape() { - return this.iconShape; - } - - /** - * 获取 text 文本 - */ - public getTextShape() { - return this.textShape; - } - /** * 根据 type 获取 maker shape */ - private init(): void { - const { x, y, symbol, size, fill, spacing, text, textStyle, markerStyle } = this.attributes; - - // 图标 - this.iconShape = new Marker({ - attrs: { - // 左上角锚点 - x: 0, - y: 0, + protected init(): void { + // icon + const iconAttrsCallback = () => { + const { symbol, size, fill, markerStyle } = this.attributes; + return { symbol, ...markerStyle, // 优先级 fill, r: size / 2, - }, - }); - this.appendChild(this.iconShape); - - // 文字 - this.textShape = new Text({ - attrs: { - // 居中,和 icon 间距 4px + }; + }; + this.appendSubComponent('iconShape', Marker, iconAttrsCallback); + + // text + const textAttrsCallback = () => { + const { size, spacing, text, textStyle } = this.attributes; + return { x: size / 2 + spacing, y: 0, ...textStyle, text, - }, - }); - this.appendChild(this.textShape); - - // 背景 - const bbox = this.getBounds(); - this.background = new Rect({ - attrs: { - // 加一个 边距 + }; + }; + this.appendSubComponent('textShape', Text, textAttrsCallback); + + // background + const backgroundAttrsCallback = () => { + const { size } = this.attributes; + const bbox = this.getBounds(); + return { x: -size / 2 - 2, y: -size / 2 - 2, width: bbox.getMax()[0] - bbox.getMin()[0], height: bbox.getMax()[1] - bbox.getMin()[1], radius: 2, fill: '#fff', - }, - }); - - this.appendChild(this.background); - // 放到背景中 - this.background.toBack(); + }; + }; + this.appendSubComponent('background', Rect, backgroundAttrsCallback); + this.getSubComponent('background').toBack(); + const { x, y, size } = this.attributes; // 3. 最后移动到对应的位置 this.translate(x + size / 2, y + size / 2); @@ -134,11 +99,11 @@ export class Icon extends CustomElement { private bindEvents() { this.on('mouseenter', () => { - this.background.attr('fill', '#F5F5F5'); + this.getSubComponent('background').attr('fill', '#F5F5F5'); }); this.on('mouseleave', () => { - this.background.attr('fill', '#fff'); + this.getSubComponent('background').attr('fill', '#fff'); }); } } From b8c716884e1650780fce5469ba90659c70a42268 Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Sun, 11 Jul 2021 22:32:44 +0800 Subject: [PATCH 33/49] =?UTF-8?q?test(sparkline):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E4=BA=86=E5=8D=95=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/unit/ui/sparkline/index.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/__tests__/unit/ui/sparkline/index.spec.ts b/__tests__/unit/ui/sparkline/index.spec.ts index 7723d69b1..f14dd92cb 100644 --- a/__tests__/unit/ui/sparkline/index.spec.ts +++ b/__tests__/unit/ui/sparkline/index.spec.ts @@ -1,3 +1,4 @@ +import { get } from '@antv/util'; import { Canvas } from '@antv/g'; import { Renderer as CanvasRenderer } from '@antv/g-canvas'; import { Sparkline } from '../../../../src'; @@ -37,7 +38,7 @@ canvas.appendChild(sparkline); describe('sparkline', () => { test('basic line', async () => { - const path0 = sparkline.getElementById('line-path-0').getAttribute('path'); + const path0 = get(sparkline, 'lines').children[0].attributes.path; const y = (val) => { return (1 - (val + 10) / 25) * 50; }; From 06aeee684835210d1d23222f817e7071c27adf0c Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Sun, 11 Jul 2021 22:33:36 +0800 Subject: [PATCH 34/49] =?UTF-8?q?refactor(icon):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E4=BA=86=E8=AE=BF=E9=97=AE=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/icon/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/icon/index.ts b/src/ui/icon/index.ts index 225383722..46aa506ee 100644 --- a/src/ui/icon/index.ts +++ b/src/ui/icon/index.ts @@ -18,7 +18,7 @@ export class Icon extends Component { /** * 默认参数 */ - private static defaultOptions = { + protected static defaultOptions = { type: Icon.tag, attrs: { size: 16, From 5ba2f1031e37d2b9df069c8f80f688de94749693 Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Sun, 11 Jul 2021 22:34:03 +0800 Subject: [PATCH 35/49] =?UTF-8?q?refactor(sparkline):=20=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E4=BA=86lines?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/sparkline/index.ts | 2 +- src/ui/sparkline/lines.ts | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/ui/sparkline/index.ts b/src/ui/sparkline/index.ts index 23501b395..a7cf57c0e 100644 --- a/src/ui/sparkline/index.ts +++ b/src/ui/sparkline/index.ts @@ -50,7 +50,7 @@ export class Sparkline extends Component { attributeChangedCallback(name: string, value: any) { // 如果type变了,需要清空this.sparkShapes子元素 if (name === 'type') { - this.getSubComponent('sparkShapes').removeChildren(); + this.getSubComponent('sparkShapes')?.removeChildren(); } console.log(value); } diff --git a/src/ui/sparkline/lines.ts b/src/ui/sparkline/lines.ts index 6dd4b3bee..6fffa2314 100644 --- a/src/ui/sparkline/lines.ts +++ b/src/ui/sparkline/lines.ts @@ -21,11 +21,9 @@ export class Lines extends CustomElement { }) ); }); - areasAttrs?.forEach((cfg, idx: number) => { - const id = `line-area-${idx}`; + areasAttrs?.forEach((cfg) => { this.appendChild( new Path({ - id, name: 'area', attrs: cfg, }) From de5fed61514e55d1d82f1cd6c75199579178e5e2 Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Sun, 11 Jul 2021 22:41:15 +0800 Subject: [PATCH 36/49] =?UTF-8?q?test(sparkline):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E4=BA=86path=E5=8D=95=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/unit/ui/sparkline/path.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__tests__/unit/ui/sparkline/path.spec.ts b/__tests__/unit/ui/sparkline/path.spec.ts index 0ac0be16d..ad8e8aff6 100644 --- a/__tests__/unit/ui/sparkline/path.spec.ts +++ b/__tests__/unit/ui/sparkline/path.spec.ts @@ -1,6 +1,6 @@ import { Linear } from '@antv/scale'; +import { Scales } from '../../../../src/ui/sparkline/types'; import { - Scales, dataToLines, lineToLinePath, lineToCurvePath, From a99edb1599b869558c87dfc2fe2462938951ff2c Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Sun, 11 Jul 2021 22:55:21 +0800 Subject: [PATCH 37/49] =?UTF-8?q?refactor(slider):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E4=BA=86slider=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/slider/types.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ui/slider/types.ts b/src/ui/slider/types.ts index 426747a8b..7356e630d 100644 --- a/src/ui/slider/types.ts +++ b/src/ui/slider/types.ts @@ -1,6 +1,6 @@ import { ShapeAttrs, ShapeCfg } from '../../types'; import { MarkerOptions } from '../marker'; -import { SparklineOptions } from '../sparkline'; +import { SparklineAttrs } from '../sparkline/types'; export type Pair = [T, T]; @@ -20,15 +20,15 @@ export type HandleCfg = { /** * 文本格式化 */ - formatter?: (text: string) => string; + formatter?: (text: string, idx: number) => string; /** * 文字样式 */ - textStyle: ShapeAttrs; + textStyle?: ShapeAttrs; /** * 文字与手柄的间隔 */ - spacing: number; + spacing?: number; /** * 手柄图标 */ @@ -36,7 +36,7 @@ export type HandleCfg = { /** * 手柄图标样式 */ - handleStyle: MixAttrs; + handleStyle?: MixAttrs; }; export type SliderOptions = ShapeCfg & { @@ -60,13 +60,13 @@ export type SliderOptions = ShapeCfg & { handle?: | HandleCfg | { - start: HandleCfg; - end: HandleCfg; + start?: HandleCfg; + end?: HandleCfg; }; /** * 缩略图数据及其配置 */ - sparklineCfg?: SparklineOptions; + sparklineCfg?: SparklineAttrs; }; }; From b91c5821e977a129dd04ff7e74003fd720ddd89d Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Sun, 11 Jul 2021 22:55:36 +0800 Subject: [PATCH 38/49] =?UTF-8?q?refactor(soparkline):=20=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E4=BA=86sparkline=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/sparkline/types.ts | 40 ++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/ui/sparkline/types.ts b/src/ui/sparkline/types.ts index a79884a7e..c6f63f1e3 100644 --- a/src/ui/sparkline/types.ts +++ b/src/ui/sparkline/types.ts @@ -17,24 +17,26 @@ export type Scales = { } ); +export type SparklineAttrs = { + data?: number[] | number[][]; + width?: number; + height?: number; + isStack?: boolean; + color?: string | string[] | ((idx: number) => string); +} & ( + | { + type?: 'line'; + smooth?: boolean; + lineStyle?: ShapeAttrs | ((idx: number) => ShapeAttrs); + areaStyle?: ShapeAttrs | ((idx: number) => ShapeAttrs); + } + | { + type: 'column'; + isGroup?: boolean; + columnStyle?: ShapeAttrs | ((idx: number) => ShapeAttrs); + } +); + export type SparklineOptions = ShapeCfg & { - attrs: { - data?: number[] | number[][]; - width?: number; - height?: number; - isStack?: boolean; - color?: string | string[] | ((idx: number) => string); - } & ( - | { - type?: 'line'; - smooth?: boolean; - lineStyle?: ShapeAttrs | ((idx: number) => ShapeAttrs); - areaStyle?: ShapeAttrs | ((idx: number) => ShapeAttrs); - } - | { - type: 'column'; - isGroup?: boolean; - columnStyle?: ShapeAttrs | ((idx: number) => ShapeAttrs); - } - ); + attrs: SparklineAttrs; }; From 6c9245ea4fb39a67e3c0ee3acc1c208e144eadc5 Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Tue, 13 Jul 2021 11:41:21 +0800 Subject: [PATCH 39/49] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9=E4=BA=86ty?= =?UTF-8?q?pe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/button/index.ts | 2 +- src/ui/icon/index.ts | 2 +- src/ui/icon/types.ts | 62 ++++++++++++++++++++++-------------------- src/ui/marker/index.ts | 3 +- src/ui/slider/index.ts | 4 +-- 5 files changed, 38 insertions(+), 35 deletions(-) diff --git a/src/ui/button/index.ts b/src/ui/button/index.ts index a3aa9a299..cd670fc1b 100644 --- a/src/ui/button/index.ts +++ b/src/ui/button/index.ts @@ -4,7 +4,7 @@ import { GUI } from '../core/gui'; import { getEllipsisText } from '../../util'; import type { ShapeAttrs, DisplayObject } from '../../types'; import { SIZE_STYLE, TYPE_STYLE, DISABLED_STYLE } from './constant'; -import type { ButtonAttrs, ButtonOptions } from './types'; +import { ButtonAttrs, ButtonOptions } from './types'; export { ButtonAttrs, ButtonOptions }; diff --git a/src/ui/icon/index.ts b/src/ui/icon/index.ts index 636a48901..39f1a3f71 100644 --- a/src/ui/icon/index.ts +++ b/src/ui/icon/index.ts @@ -3,7 +3,7 @@ import { deepMix } from '@antv/util'; import { GUI } from '../core/gui'; import { Marker } from '../marker'; import type { DisplayObject } from '../../types'; -import type { IconOptions } from './types'; +import { IconOptions } from './types'; export { IconOptions }; diff --git a/src/ui/icon/types.ts b/src/ui/icon/types.ts index bb34053b2..88c519708 100644 --- a/src/ui/icon/types.ts +++ b/src/ui/icon/types.ts @@ -1,35 +1,37 @@ import { ShapeAttrs, ShapeCfg } from '../../types'; import { MarkerOptions } from '../marker'; +export type IconAttrs = { + /** + * symbol 的图标类型,也可以自定义 + */ + symbol: MarkerOptions['symbol']; + /** + * 图标的大小,默认为 16px + */ + size?: number; + /** + * Icon 图标的颜色 + */ + fill?: string; + /** + * 图标的样式 + */ + markerStyle?: ShapeAttrs; + /** + * 间距,默认为 8px + */ + spacing?: number; + /** + * 图标附属的文本,默认不显示 + */ + text?: string; + /** + * 文本的样式 + */ + textStyle?: ShapeAttrs; +}; + export type IconOptions = ShapeCfg & { - attrs: { - /** - * symbol 的图标类型,也可以自定义 - */ - symbol: MarkerOptions['symbol']; - /** - * 图标的大小,默认为 16px - */ - size?: number; - /** - * Icon 图标的颜色 - */ - fill?: string; - /** - * 图标的样式 - */ - markerStyle?: ShapeAttrs; - /** - * 间距,默认为 8px - */ - spacing?: number; - /** - * 图标附属的文本,默认不显示 - */ - text?: string; - /** - * 文本的样式 - */ - textStyle?: ShapeAttrs; - }; + attrs: IconAttrs; }; diff --git a/src/ui/marker/index.ts b/src/ui/marker/index.ts index 8871a96d7..6c818623b 100644 --- a/src/ui/marker/index.ts +++ b/src/ui/marker/index.ts @@ -2,7 +2,7 @@ import { Path, Image } from '@antv/g'; import { deepMix, isFunction, isObject, isString } from '@antv/util'; import { GUI } from '../core/gui'; import type { DisplayObject } from '../../types'; -import type { MarkerAttrs, MarkerOptions, FunctionalSymbol } from './types'; +import { MarkerAttrs, MarkerOptions, FunctionalSymbol } from './types'; import { circle, square, diamond, triangleDown, triangle } from './symbol'; export { MarkerAttrs, MarkerOptions, FunctionalSymbol }; @@ -76,6 +76,7 @@ export class Marker extends GUI { private createMarker() { const { symbol } = this.attributes; const markerType = this.parseMarker(symbol); + console.log(markerType); if (['base64', 'url', 'image'].includes(markerType)) { this.markerShape = new Image({ diff --git a/src/ui/slider/index.ts b/src/ui/slider/index.ts index 478266de6..0019fd35c 100644 --- a/src/ui/slider/index.ts +++ b/src/ui/slider/index.ts @@ -1,10 +1,10 @@ import { Rect, Text, DisplayObject } from '@antv/g'; import { deepMix, get } from '@antv/util'; import { GUI } from '../core/gui'; +import { Handle } from './handle'; +import { SliderAttrs, SliderOptions, HandleCfg, Pair } from './types'; import { toPrecision } from '../../util'; import { Sparkline } from '../sparkline'; -import { Handle } from './handle'; -import type { SliderAttrs, SliderOptions, HandleCfg, Pair } from './types'; export { SliderAttrs, SliderOptions }; From 5f22562dea14e7e8b9c91a33e650b6685bfd39e9 Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Tue, 13 Jul 2021 15:22:48 +0800 Subject: [PATCH 40/49] =?UTF-8?q?refactor:=20=E6=9B=B4=E6=96=B0=E4=BA=86up?= =?UTF-8?q?date=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/button/index.ts | 5 +++-- src/ui/icon/index.ts | 29 ++++++++++++++--------------- src/ui/marker/index.ts | 25 +++++++++++++------------ src/ui/scrollbar/index.ts | 13 +++++++++---- src/ui/sparkline/index.ts | 11 +++++++---- 5 files changed, 46 insertions(+), 37 deletions(-) diff --git a/src/ui/button/index.ts b/src/ui/button/index.ts index cd670fc1b..1a99be397 100644 --- a/src/ui/button/index.ts +++ b/src/ui/button/index.ts @@ -56,7 +56,8 @@ export class Button extends GUI { }; attributeChangedCallback(name: string, value: any): void { - console.log('attributeChangedCallback', name, value); + name; + value; } /** @@ -156,7 +157,7 @@ export class Button extends GUI { * 组件的更新 */ public update(cfg: ButtonAttrs) { - this.attr(cfg); + this.attr(deepMix({}, this.attributes, cfg)); } /** diff --git a/src/ui/icon/index.ts b/src/ui/icon/index.ts index 39f1a3f71..0842d1558 100644 --- a/src/ui/icon/index.ts +++ b/src/ui/icon/index.ts @@ -3,20 +3,20 @@ import { deepMix } from '@antv/util'; import { GUI } from '../core/gui'; import { Marker } from '../marker'; import type { DisplayObject } from '../../types'; -import { IconOptions } from './types'; +import { IconAttrs, IconOptions } from './types'; export { IconOptions }; /** * 带文本的 图标组件,支持 iconfont 组件 */ -export class Icon extends GUI { +export class Icon extends GUI { /** * 标签类型 */ public static tag = 'icon'; - private markerShape: DisplayObject; + private markerShape: GUI; private textShape: DisplayObject; @@ -25,7 +25,7 @@ export class Icon extends GUI { /** * 默认参数 */ - protected static defaultOptions = { + private static defaultOptions = { type: Icon.tag, attrs: { size: 16, @@ -49,7 +49,8 @@ export class Icon extends GUI { } attributeChangedCallback(name: string, value: any): void { - console.log('attributeChangedCallback', name, value); + name; + value; } /** @@ -61,32 +62,31 @@ export class Icon extends GUI { name: 'tag-marker', attrs: this.getMarkerAttrs(), }); + this.appendChild(this.markerShape); // text this.textShape = new Text({ name: 'tag-text', attrs: this.getTextAttrs(), }); + this.appendChild(this.textShape); // background this.backgroundShape = new Rect({ name: 'tag-background', attrs: this.getBackgroundAttrs(), }); + this.appendChild(this.backgroundShape); this.backgroundShape.toBack(); - - // 3. 最后移动到对应的位置 - const { x, y, size } = this.attributes; - this.translate(x + size / 2, y + size / 2); - this.bindEvents(); } /** * 组件的更新 */ - public update() { - this.markerShape.attr(this.getMarkerAttrs()); + public update(cfg: IconAttrs) { + this.attr(deepMix({}, this.attributes, cfg)); + this.markerShape.update(this.getMarkerAttrs()); this.textShape.attr(this.getTextAttrs()); this.backgroundShape.attr(this.getBackgroundAttrs()); } @@ -115,7 +115,6 @@ export class Icon extends GUI { return { symbol, ...markerStyle, - // 优先级 fill, r: size / 2, }; @@ -135,8 +134,8 @@ export class Icon extends GUI { const { size } = this.attributes; const bbox = this.getBounds(); return { - x: -size / 2 - 2, - y: -size / 2 - 2, + x: -size / 2 - 1, + y: -size / 2 - 1, width: bbox.getMax()[0] - bbox.getMin()[0], height: bbox.getMax()[1] - bbox.getMin()[1], radius: 2, diff --git a/src/ui/marker/index.ts b/src/ui/marker/index.ts index 6c818623b..f76b5504a 100644 --- a/src/ui/marker/index.ts +++ b/src/ui/marker/index.ts @@ -32,7 +32,7 @@ export class Marker extends GUI { /** * 默认参数 */ - protected static defaultOptions = { + private static defaultOptions = { type: Marker.tag, attrs: { x: 0, @@ -47,7 +47,8 @@ export class Marker extends GUI { } attributeChangedCallback(name: string, value: any): void { - console.log('attributeChangedCallback', name, value); + name; + value; } /** @@ -61,7 +62,7 @@ export class Marker extends GUI { * 组件的更新 */ public update(cfg: MarkerAttrs): void { - this.attr(cfg); + this.attr(deepMix({}, this.attributes, cfg)); this.clear(); this.createMarker(); } @@ -71,24 +72,26 @@ export class Marker extends GUI { */ public clear() { this.markerShape.destroy(); + this.removeChildren(); } private createMarker() { const { symbol } = this.attributes; const markerType = this.parseMarker(symbol); - console.log(markerType); - if (['base64', 'url', 'image'].includes(markerType)) { this.markerShape = new Image({ name: 'marker-image', attrs: this.getMarkerImageAttrs(), }); + const { r } = this.attributes; + this.translate(-r, -r); } else if (markerType === 'symbol') { this.markerShape = new Path({ name: 'marker-symbol', attrs: this.getMarkerSymbolAttrs(), }); } + this.appendChild(this.markerShape); } @@ -99,8 +102,6 @@ export class Marker extends GUI { const symbolFn = isFunction(symbol) ? symbol : Marker.MARKER_SYMBOL_MAP.get(symbol); const path = symbolFn(x, y, r); return { - x, - y, path, r: halfR, ...args, @@ -110,12 +111,12 @@ export class Marker extends GUI { // image marker private getMarkerImageAttrs() { const { r, symbol, ...args } = this.attributes; - const halfR = r / 2; + const r2 = r * 2; return { - x: -halfR, - y: -halfR, - width: r, - height: r, + x: -r2, + y: -r2, + width: r2, + height: r2, img: symbol, ...args, }; diff --git a/src/ui/scrollbar/index.ts b/src/ui/scrollbar/index.ts index e84ab97d0..215458935 100644 --- a/src/ui/scrollbar/index.ts +++ b/src/ui/scrollbar/index.ts @@ -23,7 +23,7 @@ export class Scrollbar extends GUI { */ private prevPos: number; - protected static defaultOptions = { + private static defaultOptions = { type: Scrollbar.tag, attrs: { // 滑条朝向 @@ -122,15 +122,18 @@ export class Scrollbar extends GUI { * 组件的更新 */ public update(cfg: ScrollbarAttrs) { - this.attr(cfg); - throw new Error('Method not implemented.'); + this.attr(deepMix({}, this.attributes, cfg)); + this.trackShape.attr(this.getTrackAttrs()); + this.thumbShape.attr(this.getThumbAttrs()); } /** * 组件的清除 */ public clear() { - throw new Error('Method not implemented.'); + this.thumbShape.destroy(); + this.trackShape.destroy(); + this.destroy(); } /** @@ -218,6 +221,7 @@ export class Scrollbar extends GUI { name: 'track', attrs: this.getTrackAttrs(), }); + this.appendChild(this.trackShape); } private getThumbAttrs() { @@ -254,6 +258,7 @@ export class Scrollbar extends GUI { name: 'thumb', attrs: this.getThumbAttrs(), }); + this.trackShape.appendChild(this.thumbShape); } private bindEvents() { diff --git a/src/ui/sparkline/index.ts b/src/ui/sparkline/index.ts index 60b07c01d..63b675cf2 100644 --- a/src/ui/sparkline/index.ts +++ b/src/ui/sparkline/index.ts @@ -5,7 +5,7 @@ import { PathCommand } from '@antv/g-base'; import { GUI } from '../core/gui'; import type { DisplayObject } from '../../types'; import { getRange, getStackedData } from './utils'; -import type { Data, SparklineAttrs, SparklineOptions } from './types'; +import { Data, SparklineAttrs, SparklineOptions } from './types'; import { dataToLines, lineToLinePath, @@ -59,7 +59,6 @@ export class Sparkline extends GUI { if (name === 'type') { this.sparkShape?.removeChildren(); } - console.log(value); } public init() { @@ -82,8 +81,9 @@ export class Sparkline extends GUI { /** * 组件的更新 */ - public update() { - throw new Error('Method not implemented.'); + public update(cfg: SparklineAttrs) { + this.attr(deepMix({}, this.attributes, cfg)); + this.init(); } /** @@ -157,6 +157,7 @@ export class Sparkline extends GUI { name: 'container', attrs: this.getContainerAttrs(), }); + this.appendChild(this.containerShape); } private getLinesAttrs() { @@ -208,6 +209,7 @@ export class Sparkline extends GUI { name: 'sparkline', attrs: this.getLinesAttrs(), }); + this.containerShape.appendChild(this.sparkShape); } private getColumnsAttrs() { @@ -260,5 +262,6 @@ export class Sparkline extends GUI { name: 'sparkline', attrs: this.getColumnsAttrs(), }); + this.containerShape.appendChild(this.sparkShape); } } From 236dc83c5a94a584fa8bad183e8b6091c1a0bc5f Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Tue, 13 Jul 2021 15:23:25 +0800 Subject: [PATCH 41/49] =?UTF-8?q?refactor:=20=E6=9B=B4=E6=96=B0=E4=BA=86?= =?UTF-8?q?=E8=AE=BF=E9=97=AE=E6=9D=83=E9=99=90=E5=8F=8Atypes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/icon/types.ts | 2 ++ src/ui/marker/types.ts | 4 +++- src/ui/scrollbar/types.ts | 2 ++ src/ui/slider/index.ts | 30 +++++++++++++++++++++--------- src/ui/slider/types.ts | 2 ++ src/ui/sparkline/index.ts | 2 +- src/ui/sparkline/types.ts | 1 + 7 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/ui/icon/types.ts b/src/ui/icon/types.ts index 88c519708..1576e2fb4 100644 --- a/src/ui/icon/types.ts +++ b/src/ui/icon/types.ts @@ -30,6 +30,8 @@ export type IconAttrs = { * 文本的样式 */ textStyle?: ShapeAttrs; + + [key: string]: any; }; export type IconOptions = ShapeCfg & { diff --git a/src/ui/marker/types.ts b/src/ui/marker/types.ts index bb7b3deec..d8923a9af 100644 --- a/src/ui/marker/types.ts +++ b/src/ui/marker/types.ts @@ -14,11 +14,13 @@ export type MarkerAttrs = { /** * 标记的大小,默认为 16 */ - r?: number; + size?: number; /** * 标记的类型,或者 path callback */ symbol: string | FunctionalSymbol; + + [key: string]: any; }; export type MarkerOptions = ShapeCfg & { diff --git a/src/ui/scrollbar/types.ts b/src/ui/scrollbar/types.ts index 4dcc553a2..8b44af116 100644 --- a/src/ui/scrollbar/types.ts +++ b/src/ui/scrollbar/types.ts @@ -65,6 +65,8 @@ export type ScrollbarAttrs = { * 滑块样式 */ thumbStyle?: ScrollStyle; + + [key: string]: any; }; /** diff --git a/src/ui/slider/index.ts b/src/ui/slider/index.ts index 0019fd35c..fd23bf26a 100644 --- a/src/ui/slider/index.ts +++ b/src/ui/slider/index.ts @@ -30,16 +30,16 @@ export class Slider extends GUI { private backgroundShape: DisplayObject; // 迷你图 - private sparklineShape: DisplayObject; + private sparklineShape: Sparkline; // 前景、选区 private foregroundShape: DisplayObject; // 开始滑块 - private startHandle: DisplayObject; + private startHandle: Handle; // 结束滑块 - private endHandle: DisplayObject; + private endHandle: Handle; /** * 选区开始的位置 @@ -66,7 +66,7 @@ export class Slider extends GUI { this.init(); } - protected static defaultOptions = { + private static defaultOptions = { type: Slider.tag, attrs: { orient: 'horizontal', @@ -149,8 +149,12 @@ export class Slider extends GUI { * 组件的更新 */ public update(cfg: SliderAttrs) { - this.attr(cfg); - throw new Error('Method not implemented.'); + this.attr(deepMix({}, this.attributes, cfg)); + this.backgroundShape.attr(this.getBackgroundAttrs()); + this.sparklineShape.update(this.getSparklineAttrs()); + this.foregroundShape.attr(this.getForegroundAttrs()); + this.startHandle.attr(this.getHandleAttrs('start')); + this.endHandle.attr(this.getHandleAttrs('end')); } /** @@ -232,6 +236,8 @@ export class Slider extends GUI { name: 'background', attrs: this.getBackgroundAttrs(), }); + this.backgroundShape.toBack(); + this.appendChild(this.backgroundShape); } private getSparklineAttrs() { @@ -261,6 +267,7 @@ export class Slider extends GUI { name: 'sparkline', attrs: this.getSparklineAttrs(), }); + this.backgroundShape.appendChild(this.sparklineShape); } /** @@ -296,6 +303,7 @@ export class Slider extends GUI { name: 'foreground', attrs: this.getForegroundAttrs(), }); + this.backgroundShape.appendChild(this.foregroundShape); } /** @@ -322,7 +330,7 @@ export class Slider extends GUI { (['start', 'end'] as HandleType[]).forEach((handleType) => { const handle = this[`${handleType}Handle`]; handle.attr(this.calcHandlePosition(handleType)); - const handleText = this[`${handleType}HandleText`]; + const handleText = handle.getElementsByName('handleText')[0]; handleText.attr(this.calcHandleText(handleType)); }); } @@ -417,12 +425,15 @@ export class Slider extends GUI { type: 'default', orient, ...attrs, + size, }; } // 使用symbol return { type: 'symbol', ...attrs, + r: size, + symbol: handleIcon, }; } @@ -431,9 +442,8 @@ export class Slider extends GUI { */ private createHandle(options: HandleCfg, handleType: HandleType) { // 手柄容器 - let handleEl = this[`${handleType}Handle`]; - handleEl = new DisplayObject({ + const handleEl = new DisplayObject({ handleType, name: 'handle', attrs: this.getHandleAttrs(handleType), @@ -454,6 +464,8 @@ export class Slider extends GUI { attrs: this.getHandleIconAttrs(handleType), }); handleEl.appendChild(handleIcon); + + this[`${handleType}Handle`] = handleEl; } private getHandleCfg(handleType: HandleType) { diff --git a/src/ui/slider/types.ts b/src/ui/slider/types.ts index 853ba916e..d54beae76 100644 --- a/src/ui/slider/types.ts +++ b/src/ui/slider/types.ts @@ -67,6 +67,8 @@ export type SliderAttrs = { * 缩略图数据及其配置 */ sparklineCfg?: SparklineAttrs; + + [key: string]: any; }; export type SliderOptions = ShapeCfg & { diff --git a/src/ui/sparkline/index.ts b/src/ui/sparkline/index.ts index 63b675cf2..57ba5d992 100644 --- a/src/ui/sparkline/index.ts +++ b/src/ui/sparkline/index.ts @@ -28,7 +28,7 @@ export class Sparkline extends GUI { // Lines或者Columns private sparkShape: DisplayObject; - protected static defaultOptions = { + private static defaultOptions = { attrs: { type: 'line', width: 200, diff --git a/src/ui/sparkline/types.ts b/src/ui/sparkline/types.ts index c6f63f1e3..423c5807e 100644 --- a/src/ui/sparkline/types.ts +++ b/src/ui/sparkline/types.ts @@ -23,6 +23,7 @@ export type SparklineAttrs = { height?: number; isStack?: boolean; color?: string | string[] | ((idx: number) => string); + [key: string]: any; } & ( | { type?: 'line'; From 6943dc35dbff8905b8bf74fd899b2a5024ff8444 Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Tue, 13 Jul 2021 15:24:01 +0800 Subject: [PATCH 42/49] =?UTF-8?q?refactor(slider):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E4=BA=86slider=20handle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/slider/handle.ts | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/src/ui/slider/handle.ts b/src/ui/slider/handle.ts index b3fc54ad1..3a79d15b6 100644 --- a/src/ui/slider/handle.ts +++ b/src/ui/slider/handle.ts @@ -1,42 +1,32 @@ -import { Rect, Image, Line } from '@antv/g'; -import { Marker, MarkerOptions } from '../marker'; +import { Rect, Line } from '@antv/g'; +import { Marker } from '../marker'; import { CustomElement, ShapeCfg } from '../../types'; type AttrsType = { [key: string]: any }; -type HandleCfg = { +type HandleCfg = AttrsType & { type: string; - show: boolean; orient: string; - handleAttrs: AttrsType; }; export class Handle extends CustomElement { constructor({ attrs, ...rest }: ShapeCfg) { super({ type: 'handle', attrs, ...rest }); - const { show, type, orient, handleAttrs } = attrs; - this.render({ show, type, orient, handleAttrs }); + const { type, orient, ...handleAttrs } = attrs; + this.render({ type, orient, handleAttrs }); } public render(handleCfg: HandleCfg) { this.removeChildren(true); - const { type, show, orient, handleAttrs: attrs } = handleCfg; + const { type, orient, handleAttrs: attrs } = handleCfg; - if (!show) { + if (type === 'hide') { this.appendChild( new Rect({ attrs, name: 'handleIcon', }) ); - } else if (['base64', 'url', 'image'].includes(type)) { - this.appendChild( - new Image({ - attrs, - name: 'handleIcon', - }) - ); } else if (type === 'symbol') { - attrs as MarkerOptions; this.appendChild( new Marker({ // @ts-ignore From 8214e7cb6cd52dee86b91569e8c949b3f1818da8 Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Tue, 13 Jul 2021 15:24:18 +0800 Subject: [PATCH 43/49] =?UTF-8?q?test:=20=E6=9B=B4=E6=96=B0=E4=BA=86?= =?UTF-8?q?=E5=8D=95=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/unit/ui/icon/index.spec.ts | 10 ++ __tests__/unit/ui/marker/index.spec.ts | 72 ++++---- __tests__/unit/ui/scrollbar/index.spec.ts | 111 ++++++------ __tests__/unit/ui/slider/index.spec.ts | 198 ++++++++-------------- examples/icon/basic/demo/triangle.ts | 2 +- 5 files changed, 160 insertions(+), 233 deletions(-) diff --git a/__tests__/unit/ui/icon/index.spec.ts b/__tests__/unit/ui/icon/index.spec.ts index 37989bc52..0ea1c2619 100644 --- a/__tests__/unit/ui/icon/index.spec.ts +++ b/__tests__/unit/ui/icon/index.spec.ts @@ -34,5 +34,15 @@ describe('icon', () => { }); canvas.appendChild(icon); + + icon.update({ + size: 16, + spacing: 10, + symbol: + 'data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7', + }); + + icon.destroy(); + canvas.destroy(); }); }); diff --git a/__tests__/unit/ui/marker/index.spec.ts b/__tests__/unit/ui/marker/index.spec.ts index c4cbd5322..f4163aa79 100644 --- a/__tests__/unit/ui/marker/index.spec.ts +++ b/__tests__/unit/ui/marker/index.spec.ts @@ -9,31 +9,28 @@ const renderer = new CanvasRenderer({ enableDirtyRectangleRendering: true, }); -describe('marker', () => { - test('basic', async () => { - const div = createDiv(); - - // @ts-ignore - const canvas = new Canvas({ - container: div, - width: 300, - height: 300, - renderer, - }); - - const marker = new Marker({ - attrs: { - symbol: 'triangle-down', - x: 50, - y: 50, - r: 16, - fill: 'green', - }, - }); +const div = createDiv(); + +// @ts-ignore +const canvas = new Canvas({ + container: div, + width: 300, + height: 300, + renderer, +}); - canvas.appendChild(marker); - }); +const marker = new Marker({ + attrs: { + symbol: 'triangle-down', + x: 50, + y: 50, + size: 16, + fill: 'green', + }, +}); +canvas.appendChild(marker); +describe('marker', () => { test('customize marker', async () => { Marker.registerSymbol( 'star', @@ -42,26 +39,15 @@ describe('marker', () => { ) ); - const div = createDiv(); - - // @ts-ignore - const canvas = new Canvas({ - container: div, - width: 300, - height: 300, - renderer, + marker.update({ + symbol: 'star', + x: 50, + y: 50, + size: 16, + stroke: 'red', }); - - const marker = new Marker({ - attrs: { - symbol: 'star', - x: 50, - y: 50, - r: 16, - stroke: 'red', - }, - }); - - canvas.appendChild(marker); }); }); + +marker.destroy(); +canvas.destroy(); diff --git a/__tests__/unit/ui/scrollbar/index.spec.ts b/__tests__/unit/ui/scrollbar/index.spec.ts index 8a5941473..6a6574ced 100644 --- a/__tests__/unit/ui/scrollbar/index.spec.ts +++ b/__tests__/unit/ui/scrollbar/index.spec.ts @@ -19,30 +19,33 @@ const renderer = new CanvasRenderer({ enableDirtyRectangleRendering: true, }); +const div = createDiv(); + +// @ts-ignore +const canvas = new Canvas({ + container: div, + width: 300, + height: 300, + renderer, +}); + +const height = 100; +const thumbLen = 30; +const scrollbar = new Scrollbar({ + attrs: { + height, + thumbLen, + x: 100, + y: 5, + value: 0.5, + width: 20, + }, +}); + +canvas.appendChild(scrollbar); + describe('scrollbar', () => { test('basic', async () => { - const div = createDiv(); - - // @ts-ignore - const canvas = new Canvas({ - container: div, - width: 300, - height: 300, - renderer, - }); - - const height = 100; - const thumbLen = 30; - const scrollbar = new Scrollbar({ - attrs: { - height, - thumbLen, - x: 100, - y: 5, - value: 0.5, - width: 20, - }, - }); expect(scrollbar.getValue()).toBe(0.5); const { padding } = scrollbar.attributes; @@ -52,7 +55,10 @@ describe('scrollbar', () => { let value = 0.2; scrollbar.setValue(value); expect(scrollbar.getValue()).toBe(value); - expect(scrollbar.lastChild.attributes.y).toBeCloseTo( + console.log(scrollbar); + console.log(scrollbar.lastChild); + + expect(scrollbar.getElementsByName('thumb')[0].attr('y')).toBeCloseTo( top + (height - verticalPadding - thumbLen) * clamp(value, 0, 1), 1 ); @@ -60,7 +66,7 @@ describe('scrollbar', () => { value = 0.1; scrollbar.setValue(value); expect(scrollbar.getValue()).toBe(value); - expect(scrollbar.lastChild.attributes.y).toBeCloseTo( + expect(scrollbar.getElementsByName('thumb')[0].attr('y')).toBeCloseTo( top + (height - verticalPadding - thumbLen) * clamp(value, 0, 1), 1 ); @@ -68,7 +74,7 @@ describe('scrollbar', () => { value = 0.9; scrollbar.setValue(value); expect(scrollbar.getValue()).toBe(value); - expect(scrollbar.lastChild.attributes.y).toBeCloseTo( + expect(scrollbar.getElementsByName('thumb')[0].attr('y')).toBeCloseTo( top + (height - verticalPadding - thumbLen) * clamp(value, 0, 1), 1 ); @@ -76,7 +82,7 @@ describe('scrollbar', () => { value = 10; scrollbar.setValue(value); expect(scrollbar.getValue()).toBe(value); - expect(scrollbar.lastChild.attributes.y).toBeCloseTo( + expect(scrollbar.getElementsByName('thumb')[0].attr('y')).toBeCloseTo( top + (height - verticalPadding - thumbLen) * clamp(value, 0, 1), 1 ); @@ -84,41 +90,23 @@ describe('scrollbar', () => { value = -10086; scrollbar.setValue(value); expect(scrollbar.getValue()).toBe(value); - expect(scrollbar.lastChild.attributes.y).toBeCloseTo( + expect(scrollbar.getElementsByName('thumb')[0].attr('y')).toBeCloseTo( top + (height - verticalPadding - thumbLen) * clamp(value, 0, 1), 1 ); - - canvas.appendChild(scrollbar); - - // scrollbar.destroy(); - // canvas.destroy(); }); test('horizontal', async () => { - const div = createDiv(); - - // @ts-ignore - const canvas = new Canvas({ - container: div, - width: 300, - height: 300, - renderer, - }); - const width = 100; const thumbLen = 30; - - const scrollbar = new Scrollbar({ - attrs: { - width, - thumbLen, - x: 10, - y: 50, - orient: 'horizontal', - height: 20, - value: 0.5, - }, + scrollbar.update({ + width, + thumbLen, + x: 10, + y: 50, + orient: 'horizontal', + height: 20, + value: 0.5, }); expect(scrollbar.getValue()).toBe(0.5); @@ -130,7 +118,7 @@ describe('scrollbar', () => { let value = 0.2; scrollbar.setValue(value); expect(scrollbar.getValue()).toBe(value); - expect(scrollbar.lastChild.attributes.x).toBeCloseTo( + expect(scrollbar.getElementsByName('thumb')[0].attr('x')).toBeCloseTo( left + (width - horizonPadding - thumbLen) * clamp(value, 0, 1), 1 ); @@ -138,7 +126,7 @@ describe('scrollbar', () => { value = 0.1; scrollbar.setValue(value); expect(scrollbar.getValue()).toBe(value); - expect(scrollbar.lastChild.attributes.x).toBeCloseTo( + expect(scrollbar.getElementsByName('thumb')[0].attr('x')).toBeCloseTo( left + (width - horizonPadding - thumbLen) * clamp(value, 0, 1), 1 ); @@ -146,7 +134,7 @@ describe('scrollbar', () => { value = 0.9; scrollbar.setValue(value); expect(scrollbar.getValue()).toBe(value); - expect(scrollbar.lastChild.attributes.x).toBeCloseTo( + expect(scrollbar.getElementsByName('thumb')[0].attr('x')).toBeCloseTo( left + (width - horizonPadding - thumbLen) * clamp(value, 0, 1), 1 ); @@ -154,7 +142,7 @@ describe('scrollbar', () => { value = 10; scrollbar.setValue(value); expect(scrollbar.getValue()).toBe(value); - expect(scrollbar.lastChild.attributes.x).toBeCloseTo( + expect(scrollbar.getElementsByName('thumb')[0].attr('x')).toBeCloseTo( left + (width - horizonPadding - thumbLen) * clamp(value, 0, 1), 1 ); @@ -162,17 +150,18 @@ describe('scrollbar', () => { value = -10086; scrollbar.setValue(value); expect(scrollbar.getValue()).toBe(value); - expect(scrollbar.lastChild.attributes.x).toBeCloseTo( + expect(scrollbar.getElementsByName('thumb')[0].attr('x')).toBeCloseTo( left + (width - horizonPadding - thumbLen) * clamp(value, 0, 1), 1 ); scrollbar.addEventListener('valuechange', (e) => { - console.log('scroll: ', e); + e; }); canvas.appendChild(scrollbar); - // scrollbar.destroy(); - // canvas.destroy(); }); }); + +// scrollbar.destroy(); +// canvas.destroy(); diff --git a/__tests__/unit/ui/slider/index.spec.ts b/__tests__/unit/ui/slider/index.spec.ts index bc44aeb6f..c80f372c8 100644 --- a/__tests__/unit/ui/slider/index.spec.ts +++ b/__tests__/unit/ui/slider/index.spec.ts @@ -9,29 +9,29 @@ const renderer = new CanvasRenderer({ enableDirtyRectangleRendering: true, }); -describe('slider', () => { - test('basic', async () => { - const div = createDiv(); - - // @ts-ignore - const canvas = new Canvas({ - container: div, - width: 800, - height: 300, - renderer, - }); +const div = createDiv(); + +// @ts-ignore +const canvas = new Canvas({ + container: div, + width: 800, + height: 300, + renderer, +}); - const slider = new Slider({ - attrs: { - x: 50, - y: 50, - width: 400, - height: 40, - values: [0.3, 0.7], - names: ['leftVal', 'rightVal'], - }, - }); +const slider = new Slider({ + attrs: { + x: 50, + y: 50, + width: 400, + height: 40, + values: [0.3, 0.7], + names: ['leftVal', 'rightVal'], + }, +}); +describe('slider', () => { + test('basic', async () => { expect(slider.getValues()).toStrictEqual([0.3, 0.7]); expect(slider.getNames()).toStrictEqual(['leftVal', 'rightVal']); @@ -55,129 +55,71 @@ describe('slider', () => { }); test('vertical', async () => { - const div = createDiv(); - - // @ts-ignore - const canvas = new Canvas({ - container: div, - width: 800, - height: 300, - renderer, + slider.update({ + x: 50, + y: 50, + width: 40, + height: 400, + orient: 'vertical', + values: [0.3, 0.7], + names: ['aboveVal', 'belowVal'], }); - - const slider = new Slider({ - attrs: { - x: 50, - y: 50, - width: 40, - height: 400, - orient: 'vertical', - values: [0.3, 0.7], - names: ['aboveVal', 'belowVal'], - }, - }); - - canvas.appendChild(slider); - slider.destroy(); }); test('custom icon', async () => { - const div = createDiv(); - - // @ts-ignore - const canvas = new Canvas({ - container: div, - width: 800, - height: 300, - renderer, - }); - - const slider = new Slider({ - attrs: { - x: 50, - y: 50, - width: 400, - height: 40, - values: [0.3, 0.7], - names: ['leftVal', 'rightVal'], - handle: { - start: { - size: 15, - formatter: (name, value) => { - return `${name}: ${value * 100}%`; - }, - handleIcon: 'https://gw.alipayobjects.com/mdn/rms_6ae20b/afts/img/A*N4ZMS7gHsUIAAAAAAAAAAABkARQnAQ', - }, - end: { - spacing: 20, - handleIcon: 'diamond', + slider.update({ + x: 50, + y: 50, + width: 400, + height: 40, + values: [0.3, 0.7], + names: ['leftVal', 'rightVal'], + handle: { + start: { + size: 15, + formatter: (name, value) => { + return `${name}: ${value * 100}%`; }, + handleIcon: 'https://gw.alipayobjects.com/mdn/rms_6ae20b/afts/img/A*N4ZMS7gHsUIAAAAAAAAAAABkARQnAQ', + }, + end: { + spacing: 20, + handleIcon: 'diamond', }, }, }); - - canvas.appendChild(slider); - slider.destroy(); }); test('vertical', async () => { - const div = createDiv(); - - // @ts-ignore - const canvas = new Canvas({ - container: div, - width: 800, - height: 300, - renderer, + slider.update({ + x: 50, + y: 50, + width: 40, + height: 400, + orient: 'vertical', + values: [0.3, 0.7], + names: ['aboveVal', 'belowVal'], }); - - const slider = new Slider({ - attrs: { - x: 50, - y: 50, - width: 40, - height: 400, - orient: 'vertical', - values: [0.3, 0.7], - names: ['aboveVal', 'belowVal'], - }, - }); - - canvas.appendChild(slider); - slider.destroy(); }); test('slider with sparkline', async () => { - const div = createDiv(); - - // @ts-ignore - const canvas = new Canvas({ - container: div, - width: 800, - height: 300, - renderer, - }); - - const slider = new Slider({ - attrs: { - x: 50, - y: 50, - width: 400, - height: 40, - values: [0.3, 0.7], - names: ['leftVal', 'rightVal'], - sparklineCfg: { - // type: 'column', - data: [ - [1, 3, 2, -4, 1, 3, 2, -4], - [5, 1, 5, -8, 5, 1, 5, -8], - ], - }, + slider.update({ + x: 50, + y: 50, + width: 400, + height: 40, + values: [0.3, 0.7], + names: ['leftVal', 'rightVal'], + sparklineCfg: { + // type: 'column', + data: [ + [1, 3, 2, -4, 1, 3, 2, -4], + [5, 1, 5, -8, 5, 1, 5, -8], + ], }, }); - - canvas.appendChild(slider); - slider.destroy(); - canvas.destroy(); }); }); + +slider.destroy(); +canvas.destroy(); diff --git a/examples/icon/basic/demo/triangle.ts b/examples/icon/basic/demo/triangle.ts index c14f1c60e..4feec80de 100644 --- a/examples/icon/basic/demo/triangle.ts +++ b/examples/icon/basic/demo/triangle.ts @@ -21,7 +21,7 @@ const icon = new Icon({ symbol: 'triangle-down', x: 50, y: 50, - r: 16, + size: 16, fill: 'green', text: '10.24%', }, From a2608510176108c288a1a66f7025c02cdb2e59ac Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Tue, 13 Jul 2021 16:18:48 +0800 Subject: [PATCH 44/49] =?UTF-8?q?refactor(sparkline):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E4=BA=86sparkline=E7=9A=84update=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/sparkline/index.ts | 45 ++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/src/ui/sparkline/index.ts b/src/ui/sparkline/index.ts index 57ba5d992..177877356 100644 --- a/src/ui/sparkline/index.ts +++ b/src/ui/sparkline/index.ts @@ -57,25 +57,13 @@ export class Sparkline extends GUI { attributeChangedCallback(name: string, value: any) { // 如果type变了,需要清空this.sparkShapes子元素 if (name === 'type') { - this.sparkShape?.removeChildren(); + this.sparkShape?.destroy(); } } public init() { - const { data, type } = this.attributes; this.createContainer(); - if (!data) return; - - switch (type) { - case 'line': - this.createLine(); - break; - case 'column': - this.createColumn(); - break; - default: - break; - } + this.createSparkline(); } /** @@ -83,7 +71,8 @@ export class Sparkline extends GUI { */ public update(cfg: SparklineAttrs) { this.attr(deepMix({}, this.attributes, cfg)); - this.init(); + this.containerShape.removeChildren(); + this.createSparkline(); } /** @@ -93,6 +82,20 @@ export class Sparkline extends GUI { throw new Error('Method not implemented.'); } + private createSparkline() { + const { type } = this.attributes; + switch (type) { + case 'line': + this.createLine(); + break; + case 'column': + this.createColumn(); + break; + default: + break; + } + } + /** * 根据数据索引获取color */ @@ -139,6 +142,7 @@ export class Sparkline extends GUI { */ private getData(): Data { const { data: _ } = this.attributes; + if (!_ || _?.length === 0) return undefined; let data = clone(_); // number[] -> number[][] if (isNumber(data[0])) { @@ -163,6 +167,13 @@ export class Sparkline extends GUI { private getLinesAttrs() { const { areaStyle, isStack, lineStyle, smooth, width } = this.attributes; let data = this.getData(); + if (!data) + return { + linesCfg: { + linesAttrs: [], + areasAttrs: [], + }, + }; if (isStack) data = getStackedData(data); const { x, y } = this.createScales(data) as { x: Linear; y: Linear }; // 线条Path @@ -215,6 +226,10 @@ export class Sparkline extends GUI { private getColumnsAttrs() { const { isStack, height, columnStyle } = this.attributes; let data = this.getData(); + if (!data) + return { + columnsCfg: [], + }; if (isStack) data = getStackedData(data); const { x, y } = this.createScales(data) as { x: Band; From a2baf8c52d60f0804e780621d55c89193da5192b Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Tue, 13 Jul 2021 16:21:44 +0800 Subject: [PATCH 45/49] =?UTF-8?q?test(sparkline):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E4=BA=86sparkline=E5=8D=95=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/unit/ui/sparkline/index.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/__tests__/unit/ui/sparkline/index.spec.ts b/__tests__/unit/ui/sparkline/index.spec.ts index f14dd92cb..f9224acc2 100644 --- a/__tests__/unit/ui/sparkline/index.spec.ts +++ b/__tests__/unit/ui/sparkline/index.spec.ts @@ -38,7 +38,9 @@ canvas.appendChild(sparkline); describe('sparkline', () => { test('basic line', async () => { - const path0 = get(sparkline, 'lines').children[0].attributes.path; + console.log(sparkline); + + const path0 = sparkline.getElementsByName('sparkline')[0].firstChild.attr('path'); const y = (val) => { return (1 - (val + 10) / 25) * 50; }; From b6d98c5efc61b2639b9cb3513e1d25712dbe0448 Mon Sep 17 00:00:00 2001 From: Aaron Date: Wed, 14 Jul 2021 14:00:10 +0800 Subject: [PATCH 46/49] =?UTF-8?q?refactor(slider):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E4=BA=86slider=E7=9A=84update=E6=96=B9=E6=B3=95=E5=8F=82?= =?UTF-8?q?=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/slider/index.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/ui/slider/index.ts b/src/ui/slider/index.ts index fd23bf26a..d876c76aa 100644 --- a/src/ui/slider/index.ts +++ b/src/ui/slider/index.ts @@ -148,8 +148,8 @@ export class Slider extends GUI { /** * 组件的更新 */ - public update(cfg: SliderAttrs) { - this.attr(deepMix({}, this.attributes, cfg)); + public update(attrs: SliderAttrs) { + this.attr(deepMix({}, this.attributes, attrs)); this.backgroundShape.attr(this.getBackgroundAttrs()); this.sparklineShape.update(this.getSparklineAttrs()); this.foregroundShape.attr(this.getForegroundAttrs()); @@ -160,9 +160,7 @@ export class Slider extends GUI { /** * 组件的清除 */ - public clear() { - throw new Error('Method not implemented.'); - } + public clear() {} /** * 获得安全的Values From eaadfabd7c87546c47344434ceb60b43adba4db1 Mon Sep 17 00:00:00 2001 From: Aaron Date: Wed, 14 Jul 2021 14:08:58 +0800 Subject: [PATCH 47/49] =?UTF-8?q?refactor:=20=E6=9B=B4=E6=96=B0=E4=BA=86ty?= =?UTF-8?q?pes=20attrs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/icon/types.ts | 4 +--- src/ui/marker/types.ts | 6 ++---- src/ui/scrollbar/types.ts | 4 +--- src/ui/slider/types.ts | 4 +--- src/ui/sparkline/types.ts | 39 ++++++++++++++------------------------- 5 files changed, 19 insertions(+), 38 deletions(-) diff --git a/src/ui/icon/types.ts b/src/ui/icon/types.ts index 1576e2fb4..81dd582c9 100644 --- a/src/ui/icon/types.ts +++ b/src/ui/icon/types.ts @@ -1,7 +1,7 @@ import { ShapeAttrs, ShapeCfg } from '../../types'; import { MarkerOptions } from '../marker'; -export type IconAttrs = { +export type IconAttrs = ShapeAttrs & { /** * symbol 的图标类型,也可以自定义 */ @@ -30,8 +30,6 @@ export type IconAttrs = { * 文本的样式 */ textStyle?: ShapeAttrs; - - [key: string]: any; }; export type IconOptions = ShapeCfg & { diff --git a/src/ui/marker/types.ts b/src/ui/marker/types.ts index d8923a9af..f8a9c32ab 100644 --- a/src/ui/marker/types.ts +++ b/src/ui/marker/types.ts @@ -1,8 +1,8 @@ -import { ShapeCfg } from '../../types'; +import { ShapeCfg, ShapeAttrs } from '../../types'; export type FunctionalSymbol = (x: number, y: number, r: number) => any; -export type MarkerAttrs = { +export type MarkerAttrs = ShapeAttrs & { /** * 标记的位置 x,默认为 0 */ @@ -19,8 +19,6 @@ export type MarkerAttrs = { * 标记的类型,或者 path callback */ symbol: string | FunctionalSymbol; - - [key: string]: any; }; export type MarkerOptions = ShapeCfg & { diff --git a/src/ui/scrollbar/types.ts b/src/ui/scrollbar/types.ts index 8b44af116..9335ae56a 100644 --- a/src/ui/scrollbar/types.ts +++ b/src/ui/scrollbar/types.ts @@ -10,7 +10,7 @@ export type Orient = 'horizontal' | 'vertical'; /** * 滚动条组件的属性配置 */ -export type ScrollbarAttrs = { +export type ScrollbarAttrs = ShapeAttrs & { /** * 滑条朝向 */ @@ -65,8 +65,6 @@ export type ScrollbarAttrs = { * 滑块样式 */ thumbStyle?: ScrollStyle; - - [key: string]: any; }; /** diff --git a/src/ui/slider/types.ts b/src/ui/slider/types.ts index d54beae76..9c8f42e7e 100644 --- a/src/ui/slider/types.ts +++ b/src/ui/slider/types.ts @@ -39,7 +39,7 @@ export type HandleCfg = { handleStyle?: MixAttrs; }; -export type SliderAttrs = { +export type SliderAttrs = ShapeAttrs & { orient?: 'vertical' | 'horizontal'; values?: Pair; names?: Pair; @@ -67,8 +67,6 @@ export type SliderAttrs = { * 缩略图数据及其配置 */ sparklineCfg?: SparklineAttrs; - - [key: string]: any; }; export type SliderOptions = ShapeCfg & { diff --git a/src/ui/sparkline/types.ts b/src/ui/sparkline/types.ts index 423c5807e..d33d4d5e8 100644 --- a/src/ui/sparkline/types.ts +++ b/src/ui/sparkline/types.ts @@ -5,38 +5,27 @@ export type Point = [number, number]; export type Line = Point[]; export type Data = number[][]; export type Scales = { + type: 'line' | 'column'; y: Linear; -} & ( - | { - type: 'line'; - x: Linear; - } - | { - type: 'column'; - x: Band; - } -); + x: Linear | Band; +}; -export type SparklineAttrs = { +export type SparklineAttrs = ShapeAttrs & { data?: number[] | number[][]; width?: number; height?: number; isStack?: boolean; color?: string | string[] | ((idx: number) => string); - [key: string]: any; -} & ( - | { - type?: 'line'; - smooth?: boolean; - lineStyle?: ShapeAttrs | ((idx: number) => ShapeAttrs); - areaStyle?: ShapeAttrs | ((idx: number) => ShapeAttrs); - } - | { - type: 'column'; - isGroup?: boolean; - columnStyle?: ShapeAttrs | ((idx: number) => ShapeAttrs); - } -); + + type?: 'line' | 'column'; + // line + smooth?: boolean; + lineStyle?: ShapeAttrs | ((idx: number) => ShapeAttrs); + areaStyle?: ShapeAttrs | ((idx: number) => ShapeAttrs); + // column + isGroup?: boolean; + columnStyle?: ShapeAttrs | ((idx: number) => ShapeAttrs); +}; export type SparklineOptions = ShapeCfg & { attrs: SparklineAttrs; From cae61473ad2d84f89550c0f022bbdf61d60ea825 Mon Sep 17 00:00:00 2001 From: Aaron Date: Wed, 14 Jul 2021 14:11:45 +0800 Subject: [PATCH 48/49] =?UTF-8?q?test:=20=E7=A7=BB=E9=99=A4=E4=BA=86?= =?UTF-8?q?=E8=B0=83=E8=AF=95=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/unit/ui/scrollbar/index.spec.ts | 2 -- __tests__/unit/ui/sparkline/index.spec.ts | 2 -- 2 files changed, 4 deletions(-) diff --git a/__tests__/unit/ui/scrollbar/index.spec.ts b/__tests__/unit/ui/scrollbar/index.spec.ts index 6a6574ced..ab2940486 100644 --- a/__tests__/unit/ui/scrollbar/index.spec.ts +++ b/__tests__/unit/ui/scrollbar/index.spec.ts @@ -55,8 +55,6 @@ describe('scrollbar', () => { let value = 0.2; scrollbar.setValue(value); expect(scrollbar.getValue()).toBe(value); - console.log(scrollbar); - console.log(scrollbar.lastChild); expect(scrollbar.getElementsByName('thumb')[0].attr('y')).toBeCloseTo( top + (height - verticalPadding - thumbLen) * clamp(value, 0, 1), diff --git a/__tests__/unit/ui/sparkline/index.spec.ts b/__tests__/unit/ui/sparkline/index.spec.ts index f9224acc2..7ca4a687d 100644 --- a/__tests__/unit/ui/sparkline/index.spec.ts +++ b/__tests__/unit/ui/sparkline/index.spec.ts @@ -38,8 +38,6 @@ canvas.appendChild(sparkline); describe('sparkline', () => { test('basic line', async () => { - console.log(sparkline); - const path0 = sparkline.getElementsByName('sparkline')[0].firstChild.attr('path'); const y = (val) => { return (1 - (val + 10) / 25) * 50; From 5e4fc216ea2dee03745bbf192684a8cd4c090f5b Mon Sep 17 00:00:00 2001 From: Aaron Date: Wed, 14 Jul 2021 14:13:47 +0800 Subject: [PATCH 49/49] =?UTF-8?q?refactor:=20=E7=A7=BB=E9=99=A4=E4=BA=86at?= =?UTF-8?q?tributeChangedCallback=E4=B8=AD=E6=97=A0=E7=94=A8=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/button/index.ts | 5 +---- src/ui/icon/index.ts | 5 +---- src/ui/marker/index.ts | 5 +---- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/ui/button/index.ts b/src/ui/button/index.ts index 1a99be397..b1ee990e3 100644 --- a/src/ui/button/index.ts +++ b/src/ui/button/index.ts @@ -55,10 +55,7 @@ export class Button extends GUI { }, }; - attributeChangedCallback(name: string, value: any): void { - name; - value; - } + attributeChangedCallback(name: string, value: any): void {} /** * 根据size、type属性生成实际渲染的属性 diff --git a/src/ui/icon/index.ts b/src/ui/icon/index.ts index 0842d1558..dcb38da0a 100644 --- a/src/ui/icon/index.ts +++ b/src/ui/icon/index.ts @@ -48,10 +48,7 @@ export class Icon extends GUI { this.init(); } - attributeChangedCallback(name: string, value: any): void { - name; - value; - } + attributeChangedCallback(name: string, value: any): void {} /** * 根据 type 获取 maker shape diff --git a/src/ui/marker/index.ts b/src/ui/marker/index.ts index f76b5504a..0600ccc94 100644 --- a/src/ui/marker/index.ts +++ b/src/ui/marker/index.ts @@ -46,10 +46,7 @@ export class Marker extends GUI { this.init(); } - attributeChangedCallback(name: string, value: any): void { - name; - value; - } + attributeChangedCallback(name: string, value: any): void {} /** * 根据 type 获取 maker shape