From ab71f3fa1a236dfbcd64afe926295ae707fa21b1 Mon Sep 17 00:00:00 2001 From: "yangtao.yangtao" Date: Thu, 24 Jun 2021 17:43:34 +0800 Subject: [PATCH 01/21] 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/21] =?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/21] =?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/21] =?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/21] =?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/21] =?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/21] =?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/21] =?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/21] =?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/21] =?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/21] =?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/21] =?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/21] =?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/21] =?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/21] =?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/21] =?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/21] =?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/21] =?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/21] =?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/21] =?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/21] 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,