From 546b43a98f4889c3b41d495a1512af153cfdbe6e Mon Sep 17 00:00:00 2001 From: zengyue Date: Fri, 22 Oct 2021 12:44:59 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=B0=83=E6=95=B4tooltip=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F=E5=92=8C=E7=BB=86=E8=8A=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../f2-next/src/components/geometry/index.tsx | 26 +- .../src/components/interval/withInterval.tsx | 3 +- .../src/components/tooltip/tooltipView.tsx | 272 +++++++++++++++--- .../src/components/tooltip/withTooltip.tsx | 17 +- packages/f2-next/src/jsx/shape/marker.ts | 6 +- packages/f2-next/src/mixins/coord.ts | 25 +- packages/f2-next/test/chart/index.test.tsx | 2 +- .../f2-next/test/components/tooltip.test.tsx | 38 --- .../test/components/tooltip/tooltip.test.tsx | 49 ++++ 9 files changed, 327 insertions(+), 111 deletions(-) delete mode 100644 packages/f2-next/test/components/tooltip.test.tsx create mode 100644 packages/f2-next/test/components/tooltip/tooltip.test.tsx diff --git a/packages/f2-next/src/components/geometry/index.tsx b/packages/f2-next/src/components/geometry/index.tsx index d35d1bf0d..fe1760a8f 100644 --- a/packages/f2-next/src/components/geometry/index.tsx +++ b/packages/f2-next/src/components/geometry/index.tsx @@ -78,7 +78,7 @@ class Geometry extends Component implements AttrMixin { const { chart } = props; const attrOptions = {}; - ATTRS.forEach(attrName => { + ATTRS.forEach((attrName) => { if (props[attrName]) { attrOptions[attrName] = this.createAttrOption(props[attrName]); } @@ -86,7 +86,7 @@ class Geometry extends Component implements AttrMixin { this.attrOptions = attrOptions; // 收集需要创建scale的字段 - each(attrOptions, option => { + each(attrOptions, (option) => { const { field, ...cfg } = option; chart.setScale(field, cfg); }); @@ -131,7 +131,7 @@ class Geometry extends Component implements AttrMixin { _getGroupScales() { const { attrs } = this; const scales = []; - each(GROUP_ATTRS, function(attrName) { + each(GROUP_ATTRS, function (attrName) { const attr = attrs[attrName]; if (attr) { const { scale } = attr; @@ -151,7 +151,7 @@ class Geometry extends Component implements AttrMixin { const appendConditions = {}; const names = []; - groupScales.forEach(scale => { + groupScales.forEach((scale) => { const field = scale.field; names.push(field); }); @@ -279,9 +279,9 @@ class Geometry extends Component implements AttrMixin { 'onPan', 'onPanStart', 'onPanEnd', - ].forEach(eventName => { + ].forEach((eventName) => { if (props[eventName]) { - canvas.on(eventName.substr(2).toLowerCase(), ev => { + canvas.on(eventName.substr(2).toLowerCase(), (ev) => { ev.geometry = this; props[eventName](ev); }); @@ -455,6 +455,7 @@ class Geometry extends Component implements AttrMixin { const { coord } = props; const invertPoint = coord.invertPoint(point); const xScale = this.getXScale(); + const yScale = this.getYScale(); // 如果不在coord坐标范围内,直接返回空 if (invertPoint.x < 0 || invertPoint.y < 0) { @@ -466,12 +467,17 @@ class Geometry extends Component implements AttrMixin { if (!value) { return rst; } - const { field: xfield } = xScale; + const { field: xField } = xScale; + const { field: yField } = yScale; for (let i = 0; i < mappedArray.length; i++) { const data = mappedArray[i]; for (let j = 0, len = data.length; j < len; j++) { - const record = data[j]; - const originValue = record[FIELD_ORIGIN][xfield]; + const record = { + ...data[j], + xField, + yField, + }; + const originValue = record[FIELD_ORIGIN][xField]; if (xScale.type === 'timeCat' && toTimeStamp(originValue) === value) { rst.push(record); } else if (originValue === value) { @@ -494,7 +500,7 @@ class Geometry extends Component implements AttrMixin { if (!this.getAttrRange('color')) { colorAttr.setRange(theme.colors); } - const items = ticks.map(tick => { + const items = ticks.map((tick) => { const { text, tickValue } = tick; const color = colorAttr.mapping(tickValue) || theme.colors[0]; return { diff --git a/packages/f2-next/src/components/interval/withInterval.tsx b/packages/f2-next/src/components/interval/withInterval.tsx index 2a14b265f..8267db16e 100644 --- a/packages/f2-next/src/components/interval/withInterval.tsx +++ b/packages/f2-next/src/components/interval/withInterval.tsx @@ -4,7 +4,7 @@ import { mix } from '@antv/util'; import Geometry from '../geometry'; import { convertRect, mappingRect } from './util'; -export default View => { +export default (View) => { return class Interval extends Geometry { startOnZero = true; @@ -55,6 +55,7 @@ export default View => { const rect = convertRect({ ...position, size, y0 }); mix(position, rect); mix(record, coord.convertRect(rect)); + mix(record, coord.convertPoint(position)); } } return mappedArray; diff --git a/packages/f2-next/src/components/tooltip/tooltipView.tsx b/packages/f2-next/src/components/tooltip/tooltipView.tsx index 602d5d5d9..9e402003d 100644 --- a/packages/f2-next/src/components/tooltip/tooltipView.tsx +++ b/packages/f2-next/src/components/tooltip/tooltipView.tsx @@ -1,60 +1,242 @@ +import Component from '../../base/component'; import { jsx } from '../../jsx'; -export default props => { - const { records, context } = props; - if (!records || !records.length) return null; - const { width } = context; - return ( - +// view 的默认配置 +const defaultStyle = { + showTitle: false, + showCrosshairs: false, + crosshairsType: 'y', + crosshairsStyle: { + stroke: 'rgba(0, 0, 0, 0.25)', + lineWidth: '2px', + }, + showTooltipMarker: true, + background: { + radius: '2px', + fill: 'rgba(0, 0, 0, 0.65)', + padding: ['6px', '10px'], + }, + titleStyle: { + fontSize: '24px', + fill: '#fff', + textAlign: 'start', + textBaseline: 'top', + }, + nameStyle: { + fontSize: '24px', + fill: 'rgba(255, 255, 255, 0.65)', + textAlign: 'start', + textBaseline: 'middle', + }, + valueStyle: { + fontSize: '24px', + fill: '#fff', + textAlign: 'start', + textBaseline: 'middle', + }, + joinString: ': ', + showItemMarker: true, + itemMarkerStyle: { + width: '12px', + radius: '6px', + symbol: 'circle', + lineWidth: '2px', + stroke: '#fff', + }, + layout: 'horizontal', + snap: false, +}; + +function directionEnabled(mode: string, dir: string) { + if (mode === undefined) { + return true; + } else if (typeof mode === 'string') { + return mode.indexOf(dir) !== -1; + } + + return false; +} + +export default class TooltipView extends Component { + rootRef: any; + itemsRef: any; + + constructor(props) { + super(props); + this.rootRef = { current: null }; + this.itemsRef = { current: null }; + } + didUpdate() { + const { props, rootRef, itemsRef } = this; + const group = rootRef.current; + if (!group) { + return; + } + const { records, coord } = props; + if (!records || !records.length) return null; + const firstRecord = records[0]; + const { x } = firstRecord; + const { left: coordLeft, width: coordWidth } = coord; + const { width } = group.get('attrs'); + const halfWidth = width / 2; + const moveX = Math.min( + Math.max(x - coordLeft - halfWidth, 0), + coordWidth - halfWidth + ); + itemsRef.current.moveTo(moveX, 0); + } + render() { + const { props } = this; + const { records, chart, coord, layout } = props; + const { top: layoutTop } = layout; + if (!records || !records.length) return null; + const { + left: coordLeft, + top: coordTop, + right: coordRight, + bottom: coordBottom, + width: coordWidth, + } = coord; + const firstRecord = records[0]; + const { x, y, xField, yField, origin: firstOrigin } = firstRecord; + const yScale = chart.getScale(yField); + const { + background, + showTitle, + titleStyle, + showItemMarker = defaultStyle.showItemMarker, + itemMarkerStyle: customItemMarkerStyle, + nameStyle, + valueStyle, + joinString = defaultStyle.joinString, + showCrosshairs = defaultStyle.showCrosshairs, + crosshairsStyle, + crosshairsType = defaultStyle.crosshairsType, + } = props; + const itemMarkerStyle = { + ...customItemMarkerStyle, + ...defaultStyle.itemMarkerStyle, + }; + + return ( - {records.map(record => { - return ( - + {showTitle ? ( + - + ) : null} + + {records.map((record) => { + const value = yScale.getText(record[yField]); + return ( + + {showItemMarker ? ( + + ) : null} + + + + ); + })} + + + {showCrosshairs ? ( + + {directionEnabled(crosshairsType, 'x') ? ( + - - - ); - })} + ) : null} + + ) : null} + {/* */} - - ); -}; + ); + } +} diff --git a/packages/f2-next/src/components/tooltip/withTooltip.tsx b/packages/f2-next/src/components/tooltip/withTooltip.tsx index e8292d773..bff0d1e79 100644 --- a/packages/f2-next/src/components/tooltip/withTooltip.tsx +++ b/packages/f2-next/src/components/tooltip/withTooltip.tsx @@ -1,7 +1,7 @@ import { jsx } from '../../jsx'; import Component from '../../base/component'; -export default View => { +export default (View) => { return class Tooltip extends Component { constructor(props) { super(props); @@ -15,16 +15,23 @@ export default View => { } _initEvent() { - const { context } = this; + const { context, props } = this; const { canvas } = context; + const { + triggerOn = 'press', + triggerOff = 'pressend', + alwaysShow = false, + } = props; - canvas.on('press', ev => { + canvas.on(triggerOn, (ev) => { const { points } = ev; this.show(points[0]); }); - canvas.on('pressend', ev => { - this.hide(); + canvas.on(triggerOff, (ev) => { + if (!alwaysShow) { + this.hide(); + } }); } diff --git a/packages/f2-next/src/jsx/shape/marker.ts b/packages/f2-next/src/jsx/shape/marker.ts index d876ceec3..9d830568c 100644 --- a/packages/f2-next/src/jsx/shape/marker.ts +++ b/packages/f2-next/src/jsx/shape/marker.ts @@ -3,7 +3,7 @@ export default (layout) => { const r = width / 2; return { x: left + r, - y: top + r, + y: top, radius: r, - } -} + }; +}; diff --git a/packages/f2-next/src/mixins/coord.ts b/packages/f2-next/src/mixins/coord.ts index 0a32d02ee..9cfb0d483 100644 --- a/packages/f2-next/src/mixins/coord.ts +++ b/packages/f2-next/src/mixins/coord.ts @@ -1,18 +1,27 @@ -import Coord, { Rect } from '../coord'; -import { isFunction } from '@antv/util'; +import Coord, { Rect, Polar } from '../coord'; +import { isString, isFunction } from '@antv/util'; -class CoordAvailable { - createCoord(cfg, layout): Coord { - if (isFunction(cfg)) { +const coordMap = { + rect: Rect, + polar: Polar, +}; + +class CoordMixin { + createCoord(cfg: any, layout): Coord { + if (isString(cfg)) { + cfg = { + type: coordMap[cfg] || Rect, + }; + } else if (isFunction(cfg)) { cfg = { - type: cfg + type: cfg, }; } const coordCfg = { // 默认直角坐标系 type: Rect, ...layout, - ...cfg + ...cfg, }; const { type, ...option } = coordCfg; const coord = new type(option); @@ -24,4 +33,4 @@ class CoordAvailable { } } -export default CoordAvailable; +export default CoordMixin; diff --git a/packages/f2-next/test/chart/index.test.tsx b/packages/f2-next/test/chart/index.test.tsx index 9d5e068d5..f08e41de0 100644 --- a/packages/f2-next/test/chart/index.test.tsx +++ b/packages/f2-next/test/chart/index.test.tsx @@ -12,7 +12,7 @@ const data = [ { type: 'b', genre: 'Strategy', sold: 10 }, { type: 'b', genre: 'Action', sold: 20 }, { type: 'b', genre: 'Shooter', sold: 20 }, - { type: 'b', genre: 'Other', sold: 40 } + { type: 'b', genre: 'Other', sold: 40 }, ]; describe('Chart', () => { diff --git a/packages/f2-next/test/components/tooltip.test.tsx b/packages/f2-next/test/components/tooltip.test.tsx deleted file mode 100644 index 06f9d20e4..000000000 --- a/packages/f2-next/test/components/tooltip.test.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { jsx } from '../../src/jsx'; -import { Line, Tooltip } from '../../src/components'; -import { createContext } from '../util'; -import { Canvas, Chart } from '../../src'; -const context = createContext(); - -const data = [ - { genre: 'Sports', sold: 275, type: 'a' }, - { genre: 'Strategy', sold: 115, type: 'a' }, - { genre: 'Action', sold: 120, type: 'a' }, - { genre: 'Shooter', sold: 350, type: 'a' }, - { genre: 'Other', sold: 150, type: 'a' }, -]; - -describe.skip('Tooltip', () => { - it('render', () => { - const { type, props } = ( - - { - // console.log(ev.records); - // }} - /> - - - ); - - // @ts-ignore - const chart = new type(props); - chart.render(); - - // const container = chart.container; - // console.log(container); - - // expect(container.get('children').length).toBe(2); - }); -}); diff --git a/packages/f2-next/test/components/tooltip/tooltip.test.tsx b/packages/f2-next/test/components/tooltip/tooltip.test.tsx new file mode 100644 index 000000000..1394d1d86 --- /dev/null +++ b/packages/f2-next/test/components/tooltip/tooltip.test.tsx @@ -0,0 +1,49 @@ +import { jsx, Canvas, Chart, Axis, Interval, Tooltip } from '../../../src'; +import { createContext } from '../../util'; +const context = createContext(); + +const data = [ + { type: 'a', genre: 'Sports', sold: 5 }, + { type: 'a', genre: 'Strategy', sold: 10 }, + { type: 'a', genre: 'Action', sold: 20 }, + { type: 'a', genre: 'Shooter', sold: 20 }, + { type: 'a', genre: 'Other', sold: 40 }, + // { type: 'a', genre: 'Sports', sold: 5 }, + // { type: 'a', genre: 'Strategy', sold: 10 }, + // { type: 'a', genre: 'Action', sold: 20 }, + // { type: 'a', genre: 'Shooter', sold: 20 }, + // { type: 'a', genre: 'Other', sold: 40 }, + // { type: 'a', genre: 'Shooter', sold: 20 }, + // { type: 'a', genre: 'Other', sold: 40 }, +]; + +describe('tooltip', () => { + it('Tooltip render', () => { + const { type, props } = ( + + + + + + + + + ); + + // @ts-ignored + const canvas = new Canvas(props); + canvas.render(); + }); +});