From 94636f7af4ce7fccd5afd960d687df0d33992082 Mon Sep 17 00:00:00 2001 From: xile611 Date: Wed, 23 Oct 2024 17:29:48 +0800 Subject: [PATCH 1/5] fix: fix scrollbar of tooltip --- .../tooltip-handler/dom/utils/common.ts | 25 +++ .../tooltip-handler/dom/utils/style.ts | 153 ++++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 packages/vchart/src/plugin/components/tooltip-handler/dom/utils/common.ts create mode 100644 packages/vchart/src/plugin/components/tooltip-handler/dom/utils/style.ts diff --git a/packages/vchart/src/plugin/components/tooltip-handler/dom/utils/common.ts b/packages/vchart/src/plugin/components/tooltip-handler/dom/utils/common.ts new file mode 100644 index 0000000000..b3dba73499 --- /dev/null +++ b/packages/vchart/src/plugin/components/tooltip-handler/dom/utils/common.ts @@ -0,0 +1,25 @@ +import { isArray, isValid } from '@visactor/vutils'; + +export const getPixelPropertyStr = (num?: number | number[], defaultStr?: string) => { + if (isValid(num)) { + if (isArray(num)) { + return num.map(n => `${n}px`).join(' '); + } + return `${num}px`; + } + return defaultStr ?? 'initial'; +}; + +export const pixelPropertyStrToNumber = (str: string): number | number[] => { + const strArr = str.split(' '); + const numArr = strArr.map(n => { + if (!Number.isNaN(n)) { + return Number.parseFloat(n); + } + return Number.parseFloat(n.substring(0, n.length - 2)); + }); + if (numArr.length === 1) { + return numArr[0]; + } + return numArr; +}; diff --git a/packages/vchart/src/plugin/components/tooltip-handler/dom/utils/style.ts b/packages/vchart/src/plugin/components/tooltip-handler/dom/utils/style.ts new file mode 100644 index 0000000000..82559563ab --- /dev/null +++ b/packages/vchart/src/plugin/components/tooltip-handler/dom/utils/style.ts @@ -0,0 +1,153 @@ +import { isValid, isValidNumber, lowerCamelCaseToMiddle, normalizePadding } from '@visactor/vutils'; +import type { ITooltipSpec, ITooltipTextTheme, ITooltipTheme } from '../../../../../component/tooltip'; +import { getPixelPropertyStr } from './common'; +import type { ITheme } from '../../../../../theme'; +import { token } from '../../../../../theme/token'; + +const DEFAULT_SHAPE_SPACING = 8; +const DEFAULT_KEY_SPACING = 26; +const DEFAULT_VALUE_SPACING = 0; + +export const getTextStyle = (style: ITooltipTextTheme = {}) => { + const textStyle: Partial = { + color: style.fill ?? style.fontColor, + fontFamily: style.fontFamily, + fontSize: getPixelPropertyStr(style.fontSize as number), + fontWeight: style.fontWeight as string, + textAlign: style.textAlign, + maxWidth: getPixelPropertyStr(style.maxWidth), + whiteSpace: style.multiLine ? 'initial' : 'nowrap', + wordBreak: style.multiLine ? style.wordBreak ?? 'break-word' : 'normal' + }; + + return textStyle; +}; + +export const getDomStyle = (spec: ITooltipSpec = {}, globalTheme: ITheme) => { + const { style = {}, enterable, transitionDuration } = spec; + const { + panel = {}, + titleLabel, + shape, + keyLabel, + valueLabel, + spaceRow: commonSpaceRow, + maxContentHeight, + align + } = style; + const panelStyle = getPanelStyle(panel); + const rowStyle: Partial = { + marginTop: '0px', + marginBottom: '0px' + }; + const contentStyle: Partial = {}; + + panelStyle.pointerEvents = enterable ? 'auto' : 'none'; + if (transitionDuration) { + panelStyle.transitionDuration = transitionDuration ? `${transitionDuration}ms` : 'initial'; + panelStyle.transitionProperty = transitionDuration ? 'transform' : 'initial'; + panelStyle.transitionTimingFunction = transitionDuration ? 'ease-out' : 'initial'; + } + panelStyle.fontFamily = (globalTheme?.fontFamily ?? token.fontFamily) as string; + + if (isValidNumber(commonSpaceRow)) { + rowStyle.marginBottom = `${commonSpaceRow}px`; + } + if (isValidNumber(maxContentHeight)) { + contentStyle.maxHeight = `${maxContentHeight}px`; + contentStyle.overflowY = 'auto'; + // todo 让内容宽度往外阔一点,给滚动条留出位置 + contentStyle.width = `calc(100% + ${panelStyle.padding ? panelStyle.padding.split(' ')[1] : '10px'})`; + } + + const shapeStyle: Partial = { + // TODO 默认值优化 + width: getPixelPropertyStr(shape?.size ?? 8) + }; + const titleStyle = getTextStyle(titleLabel); + const keyStyle = getTextStyle(keyLabel); + const valueStyle = getTextStyle(valueLabel); + const marginKey = align === 'right' ? 'marginLeft' : 'marginRight'; + + if (align === 'right') { + // rtl + panelStyle.direction = 'rtl'; + titleStyle.textAlign = 'right'; + keyStyle.textAlign = 'right'; + valueStyle.textAlign = 'left'; + } else { + titleStyle.textAlign = 'left'; + keyStyle.textAlign = 'left'; + valueStyle.textAlign = 'right'; + } + shapeStyle[marginKey] = getPixelPropertyStr(shape.spacing ?? DEFAULT_SHAPE_SPACING); + keyStyle[marginKey] = getPixelPropertyStr(keyLabel.spacing ?? DEFAULT_KEY_SPACING); + valueStyle[marginKey] = getPixelPropertyStr(valueLabel.spacing ?? DEFAULT_VALUE_SPACING); + + const lineHeight = keyStyle.lineHeight ?? valueStyle.lineHeight; + + if (isValid(lineHeight)) { + rowStyle.lineHeight = /^[0-9]*$/.test(`${lineHeight}`) ? `${lineHeight}px` : `${lineHeight}`; + } + + return { + row: rowStyle, + content: contentStyle, + panel: panelStyle, + title: titleStyle, + shape: shapeStyle, + key: keyStyle, + value: valueStyle + }; +}; + +export const getPanelStyle = (style: ITooltipTheme['panel']): Partial => { + const { backgroundColor, border, shadow, padding } = style; + const panelStyle: Partial = { + borderWidth: `${border?.width ?? 0}px` + }; + + if (border?.color) { + panelStyle.borderColor = border.color as string; + } + if (backgroundColor) { + panelStyle.backgroundColor = backgroundColor as string; + } + panelStyle.boxShadow = shadow + ? `${shadow.x}px ${shadow.y}px ${shadow.blur}px ${shadow.spread}px ${shadow.color}` + : 'initial'; + const { radius } = border ?? {}; + + if (isValid(radius)) { + panelStyle.borderRadius = isValidNumber(radius) ? `${radius}px` : `${radius}`; + } + + if (padding) { + panelStyle.padding = getPixelPropertyStr(normalizePadding(padding)); + } + + return panelStyle; +}; + +export function setStyleToDom(dom: HTMLElement, style: Partial) { + if (!dom || !dom.style || !style) { + return; + } + + Object.keys(style).forEach(key => { + (dom.style as any)[key] = (style as any)[key]; + }); +} + +export function cssToStyleString(style: Partial) { + let str = ''; + + style && + Object.keys(style).forEach(k => { + if (isValid((style as any)[k])) { + str += `${lowerCamelCaseToMiddle(k)}:${(style as any)[k]};`; + } + }); + + return str; +} From 36668f42e000b8b7aaba409a4fe7bf9a0091e14f Mon Sep 17 00:00:00 2001 From: xile611 Date: Wed, 23 Oct 2024 17:43:22 +0800 Subject: [PATCH 2/5] chore: update files of tooltip-handler --- .../tooltip-handler/dom/utils/common.ts | 25 --- .../tooltip-handler/dom/utils/style.ts | 153 ------------------ 2 files changed, 178 deletions(-) delete mode 100644 packages/vchart/src/plugin/components/tooltip-handler/dom/utils/common.ts delete mode 100644 packages/vchart/src/plugin/components/tooltip-handler/dom/utils/style.ts diff --git a/packages/vchart/src/plugin/components/tooltip-handler/dom/utils/common.ts b/packages/vchart/src/plugin/components/tooltip-handler/dom/utils/common.ts deleted file mode 100644 index b3dba73499..0000000000 --- a/packages/vchart/src/plugin/components/tooltip-handler/dom/utils/common.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { isArray, isValid } from '@visactor/vutils'; - -export const getPixelPropertyStr = (num?: number | number[], defaultStr?: string) => { - if (isValid(num)) { - if (isArray(num)) { - return num.map(n => `${n}px`).join(' '); - } - return `${num}px`; - } - return defaultStr ?? 'initial'; -}; - -export const pixelPropertyStrToNumber = (str: string): number | number[] => { - const strArr = str.split(' '); - const numArr = strArr.map(n => { - if (!Number.isNaN(n)) { - return Number.parseFloat(n); - } - return Number.parseFloat(n.substring(0, n.length - 2)); - }); - if (numArr.length === 1) { - return numArr[0]; - } - return numArr; -}; diff --git a/packages/vchart/src/plugin/components/tooltip-handler/dom/utils/style.ts b/packages/vchart/src/plugin/components/tooltip-handler/dom/utils/style.ts deleted file mode 100644 index 82559563ab..0000000000 --- a/packages/vchart/src/plugin/components/tooltip-handler/dom/utils/style.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { isValid, isValidNumber, lowerCamelCaseToMiddle, normalizePadding } from '@visactor/vutils'; -import type { ITooltipSpec, ITooltipTextTheme, ITooltipTheme } from '../../../../../component/tooltip'; -import { getPixelPropertyStr } from './common'; -import type { ITheme } from '../../../../../theme'; -import { token } from '../../../../../theme/token'; - -const DEFAULT_SHAPE_SPACING = 8; -const DEFAULT_KEY_SPACING = 26; -const DEFAULT_VALUE_SPACING = 0; - -export const getTextStyle = (style: ITooltipTextTheme = {}) => { - const textStyle: Partial = { - color: style.fill ?? style.fontColor, - fontFamily: style.fontFamily, - fontSize: getPixelPropertyStr(style.fontSize as number), - fontWeight: style.fontWeight as string, - textAlign: style.textAlign, - maxWidth: getPixelPropertyStr(style.maxWidth), - whiteSpace: style.multiLine ? 'initial' : 'nowrap', - wordBreak: style.multiLine ? style.wordBreak ?? 'break-word' : 'normal' - }; - - return textStyle; -}; - -export const getDomStyle = (spec: ITooltipSpec = {}, globalTheme: ITheme) => { - const { style = {}, enterable, transitionDuration } = spec; - const { - panel = {}, - titleLabel, - shape, - keyLabel, - valueLabel, - spaceRow: commonSpaceRow, - maxContentHeight, - align - } = style; - const panelStyle = getPanelStyle(panel); - const rowStyle: Partial = { - marginTop: '0px', - marginBottom: '0px' - }; - const contentStyle: Partial = {}; - - panelStyle.pointerEvents = enterable ? 'auto' : 'none'; - if (transitionDuration) { - panelStyle.transitionDuration = transitionDuration ? `${transitionDuration}ms` : 'initial'; - panelStyle.transitionProperty = transitionDuration ? 'transform' : 'initial'; - panelStyle.transitionTimingFunction = transitionDuration ? 'ease-out' : 'initial'; - } - panelStyle.fontFamily = (globalTheme?.fontFamily ?? token.fontFamily) as string; - - if (isValidNumber(commonSpaceRow)) { - rowStyle.marginBottom = `${commonSpaceRow}px`; - } - if (isValidNumber(maxContentHeight)) { - contentStyle.maxHeight = `${maxContentHeight}px`; - contentStyle.overflowY = 'auto'; - // todo 让内容宽度往外阔一点,给滚动条留出位置 - contentStyle.width = `calc(100% + ${panelStyle.padding ? panelStyle.padding.split(' ')[1] : '10px'})`; - } - - const shapeStyle: Partial = { - // TODO 默认值优化 - width: getPixelPropertyStr(shape?.size ?? 8) - }; - const titleStyle = getTextStyle(titleLabel); - const keyStyle = getTextStyle(keyLabel); - const valueStyle = getTextStyle(valueLabel); - const marginKey = align === 'right' ? 'marginLeft' : 'marginRight'; - - if (align === 'right') { - // rtl - panelStyle.direction = 'rtl'; - titleStyle.textAlign = 'right'; - keyStyle.textAlign = 'right'; - valueStyle.textAlign = 'left'; - } else { - titleStyle.textAlign = 'left'; - keyStyle.textAlign = 'left'; - valueStyle.textAlign = 'right'; - } - shapeStyle[marginKey] = getPixelPropertyStr(shape.spacing ?? DEFAULT_SHAPE_SPACING); - keyStyle[marginKey] = getPixelPropertyStr(keyLabel.spacing ?? DEFAULT_KEY_SPACING); - valueStyle[marginKey] = getPixelPropertyStr(valueLabel.spacing ?? DEFAULT_VALUE_SPACING); - - const lineHeight = keyStyle.lineHeight ?? valueStyle.lineHeight; - - if (isValid(lineHeight)) { - rowStyle.lineHeight = /^[0-9]*$/.test(`${lineHeight}`) ? `${lineHeight}px` : `${lineHeight}`; - } - - return { - row: rowStyle, - content: contentStyle, - panel: panelStyle, - title: titleStyle, - shape: shapeStyle, - key: keyStyle, - value: valueStyle - }; -}; - -export const getPanelStyle = (style: ITooltipTheme['panel']): Partial => { - const { backgroundColor, border, shadow, padding } = style; - const panelStyle: Partial = { - borderWidth: `${border?.width ?? 0}px` - }; - - if (border?.color) { - panelStyle.borderColor = border.color as string; - } - if (backgroundColor) { - panelStyle.backgroundColor = backgroundColor as string; - } - panelStyle.boxShadow = shadow - ? `${shadow.x}px ${shadow.y}px ${shadow.blur}px ${shadow.spread}px ${shadow.color}` - : 'initial'; - const { radius } = border ?? {}; - - if (isValid(radius)) { - panelStyle.borderRadius = isValidNumber(radius) ? `${radius}px` : `${radius}`; - } - - if (padding) { - panelStyle.padding = getPixelPropertyStr(normalizePadding(padding)); - } - - return panelStyle; -}; - -export function setStyleToDom(dom: HTMLElement, style: Partial) { - if (!dom || !dom.style || !style) { - return; - } - - Object.keys(style).forEach(key => { - (dom.style as any)[key] = (style as any)[key]; - }); -} - -export function cssToStyleString(style: Partial) { - let str = ''; - - style && - Object.keys(style).forEach(k => { - if (isValid((style as any)[k])) { - str += `${lowerCamelCaseToMiddle(k)}:${(style as any)[k]};`; - } - }); - - return str; -} From 02e245e440b71edc7b6da75452c96dd9114daa5a Mon Sep 17 00:00:00 2001 From: xile611 Date: Thu, 24 Oct 2024 16:36:52 +0800 Subject: [PATCH 3/5] feat: tooltip key/content support config by field, close #2576 --- packages/vchart/src/component/tooltip/utils/get-value.ts | 4 +++- packages/vchart/src/typings/tooltip/common.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/vchart/src/component/tooltip/utils/get-value.ts b/packages/vchart/src/component/tooltip/utils/get-value.ts index 897442fbe3..b6ed39f939 100644 --- a/packages/vchart/src/component/tooltip/utils/get-value.ts +++ b/packages/vchart/src/component/tooltip/utils/get-value.ts @@ -1,4 +1,4 @@ -import { isFunction, isNil } from '@visactor/vutils'; +import { get, isFunction, isNil, isPlainObject, isValid } from '@visactor/vutils'; import type { Datum, TooltipContentCallback, @@ -19,6 +19,8 @@ export const getTooltipContentValue = ( let value: T; if (isFunction(field)) { value = (field as TooltipContentCallback)(datum, params); + } else if (isPlainObject(field) && isValid(field.field)) { + value = get(datum, field.field) as T; } else { value = field as T; } diff --git a/packages/vchart/src/typings/tooltip/common.ts b/packages/vchart/src/typings/tooltip/common.ts index d74da284ed..607a7b105c 100644 --- a/packages/vchart/src/typings/tooltip/common.ts +++ b/packages/vchart/src/typings/tooltip/common.ts @@ -2,7 +2,7 @@ import type { Datum } from '../common'; import type { TooltipHandlerParams } from '../../component/tooltip/interface'; import type { TooltipData } from './handler'; -export type TooltipContentProperty = T | TooltipContentCallback; +export type TooltipContentProperty = T | TooltipContentCallback | { field: string }; export type TooltipContentCallback = (datum?: Datum, params?: TooltipHandlerParams) => T | undefined; export type TooltipPatternProperty = T | TooltipPatternCallback; From 69fc9c35874be7b0151353bacab4822b95b74f2f Mon Sep 17 00:00:00 2001 From: xile611 Date: Thu, 5 Dec 2024 14:28:52 +0800 Subject: [PATCH 4/5] docs: update changlog of rush --- .../feat-tooltip-content-field_2024-12-05-06-28.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/@visactor/vchart/feat-tooltip-content-field_2024-12-05-06-28.json diff --git a/common/changes/@visactor/vchart/feat-tooltip-content-field_2024-12-05-06-28.json b/common/changes/@visactor/vchart/feat-tooltip-content-field_2024-12-05-06-28.json new file mode 100644 index 0000000000..75474eb17a --- /dev/null +++ b/common/changes/@visactor/vchart/feat-tooltip-content-field_2024-12-05-06-28.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "feat: tooltip key/content support config by field, close #2576\n\n", + "type": "none", + "packageName": "@visactor/vchart" + } + ], + "packageName": "@visactor/vchart", + "email": "dingling112@gmail.com" +} \ No newline at end of file From e809d40a5d496ac32d89e52c1551a3bf0eb66f3c Mon Sep 17 00:00:00 2001 From: xile611 Date: Thu, 5 Dec 2024 14:43:20 +0800 Subject: [PATCH 5/5] docs: update options of tooltip --- .../tooltip-common/tooltip-line-pattern.md | 18 ++++++++++++++++++ .../tooltip-common/tooltip-line-pattern.md | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/docs/assets/option/en/component/tooltip-common/tooltip-line-pattern.md b/docs/assets/option/en/component/tooltip-common/tooltip-line-pattern.md index 3a10a133e7..8e45ba756e 100644 --- a/docs/assets/option/en/component/tooltip-common/tooltip-line-pattern.md +++ b/docs/assets/option/en/component/tooltip-common/tooltip-line-pattern.md @@ -21,6 +21,15 @@ It can also be configured as a function callback, with the type: Where `datum` is the default data item corresponding to the current line of the tooltip. +To read the content of a specific field in the data item, you can set `field` as follows: + +```ts +// (supported since 1.13.0) +{ + field: string; +} +``` + {{ /if }} #${prefix} keyTimeFormat(string) = '%Y%m%d' @@ -91,6 +100,15 @@ It can also be configured as a function callback, the type is: Where `datum` is the default data item corresponding to the current line of the tooltip. +To read the content of a specific field in the data item, you can set `field` as follows: + +```ts +// (supported since 1.13.0) +{ + field: string; +} +``` + {{ /if }} #${prefix} valueTimeFormat(string) = '%Y%m%d' diff --git a/docs/assets/option/zh/component/tooltip-common/tooltip-line-pattern.md b/docs/assets/option/zh/component/tooltip-common/tooltip-line-pattern.md index 97c771f35c..e8b4e1d655 100644 --- a/docs/assets/option/zh/component/tooltip-common/tooltip-line-pattern.md +++ b/docs/assets/option/zh/component/tooltip-common/tooltip-line-pattern.md @@ -21,6 +21,15 @@ tooltip 当前行 key 列的内容。如果配置为字符串,则显示为对 其中 `datum` 为 tooltip 当前行所默认对应的数据项。 +如果想要读取数据项中特定字段的内容,可以通过设置`field`实现,类型如下: + +```ts +// (自 1.13.0 支持) +{ + field: string; +} +``` + {{ /if }} #${prefix} keyTimeFormat(string) = '%Y%m%d' @@ -89,6 +98,15 @@ tooltip 当前行 value 列的内容。 (datum: Datum) => string; ``` +如果想要读取数据项中特定字段的内容,可以通过设置`field`实现,类型如下: + +```ts +// (自 1.13.0 支持) +{ + field: string; +} +``` + {{ /if }} 其中 `datum` 为 tooltip 当前行所默认对应的数据项。