From 0ed01d90495c39695a8d81fea875e6431c0b8590 Mon Sep 17 00:00:00 2001 From: hxl187 <2713697048@qq.com> Date: Thu, 26 Dec 2024 19:44:17 +0800 Subject: [PATCH 1/3] feat(tooltip): add support for rich text styles in tooltip.textStyle --- src/component/tooltip/TooltipRichContent.ts | 403 ++++++++++---------- 1 file changed, 200 insertions(+), 203 deletions(-) diff --git a/src/component/tooltip/TooltipRichContent.ts b/src/component/tooltip/TooltipRichContent.ts index d8f038dcbf..e996a7842a 100644 --- a/src/component/tooltip/TooltipRichContent.ts +++ b/src/component/tooltip/TooltipRichContent.ts @@ -26,218 +26,215 @@ import Model from '../../model/Model'; import ZRText, { TextStyleProps } from 'zrender/src/graphic/Text'; import { TooltipMarkupStyleCreator, getPaddingFromTooltipModel } from './tooltipMarkup'; import { throwError } from '../../util/log'; +import { createTextStyle } from '../../label/labelStyle'; class TooltipRichContent { - private _zr: ZRenderType; - - private _show = false; - - private _styleCoord: [number, number, number, number] = [0, 0, 0, 0]; - - private _hideTimeout: number; - - private _alwaysShowContent: boolean = false; - - private _enterable = true; - - private _inContent: boolean; - - private _hideDelay: number; - - el: ZRText; - - constructor(api: ExtensionAPI) { - this._zr = api.getZr(); - makeStyleCoord(this._styleCoord, this._zr, api.getWidth() / 2, api.getHeight() / 2); - } - - /** - * Update when tooltip is rendered - */ - update(tooltipModel: Model) { - const alwaysShowContent = tooltipModel.get('alwaysShowContent'); - alwaysShowContent && this._moveIfResized(); - - // update alwaysShowContent - this._alwaysShowContent = alwaysShowContent; - } - - show() { - if (this._hideTimeout) { - clearTimeout(this._hideTimeout); - } - - this.el.show(); - this._show = true; - } - - /** - * Set tooltip content - */ - setContent( - content: string | HTMLElement | HTMLElement[], - markupStyleCreator: TooltipMarkupStyleCreator, - tooltipModel: Model, - borderColor: ZRColor, - arrowPosition: TooltipOption['position'] - ) { - if (zrUtil.isObject(content)) { - throwError(__DEV__ ? 'Passing DOM nodes as content is not supported in richText tooltip!' : ''); - } - if (this.el) { - this._zr.remove(this.el); - } - - const textStyleModel = tooltipModel.getModel('textStyle'); - - this.el = new ZRText({ - style: { - rich: markupStyleCreator.richTextStyles, - text: content as string, - lineHeight: 22, - borderWidth: 1, - borderColor: borderColor as string, - textShadowColor: textStyleModel.get('textShadowColor'), - fill: tooltipModel.get(['textStyle', 'color']), - padding: getPaddingFromTooltipModel(tooltipModel, 'richText'), - verticalAlign: 'top', - align: 'left' - }, - z: tooltipModel.get('z') - }); - zrUtil.each([ - 'backgroundColor', 'borderRadius', 'shadowColor', 'shadowBlur', 'shadowOffsetX', 'shadowOffsetY' - ] as const, propName => { - (this.el.style as any)[propName] = tooltipModel.get(propName); - }); - zrUtil.each([ - 'textShadowBlur', 'textShadowOffsetX', 'textShadowOffsetY' - ] as const, propName => { - this.el.style[propName] = textStyleModel.get(propName) || 0; - }); - - this._zr.add(this.el); - - const self = this; - this.el.on('mouseover', function () { - // clear the timeout in hideLater and keep showing tooltip - if (self._enterable) { - clearTimeout(self._hideTimeout); - self._show = true; - } - self._inContent = true; - }); - this.el.on('mouseout', function () { - if (self._enterable) { - if (self._show) { - self.hideLater(self._hideDelay); - } - } - self._inContent = false; - }); - } - - setEnterable(enterable?: boolean) { - this._enterable = enterable; - } - - getSize() { - const el = this.el; - const bounding = this.el.getBoundingRect(); - // bounding rect does not include shadow. For renderMode richText, - // if overflow, it will be cut. So calculate them accurately. - const shadowOuterSize = calcShadowOuterSize(el.style); - return [ - bounding.width + shadowOuterSize.left + shadowOuterSize.right, - bounding.height + shadowOuterSize.top + shadowOuterSize.bottom - ]; - } - - moveTo(x: number, y: number) { - const el = this.el; - if (el) { - const styleCoord = this._styleCoord; - makeStyleCoord(styleCoord, this._zr, x, y); - x = styleCoord[0]; - y = styleCoord[1]; - const style = el.style; - const borderWidth = mathMaxWith0(style.borderWidth || 0); - const shadowOuterSize = calcShadowOuterSize(style); - // rich text x, y do not include border. - el.x = x + borderWidth + shadowOuterSize.left; - el.y = y + borderWidth + shadowOuterSize.top; - el.markRedraw(); - } - } - - - /** - * when `alwaysShowContent` is true, - * move the tooltip after chart resized - */ - _moveIfResized() { - // The ratio of left to width - const ratioX = this._styleCoord[2]; - // The ratio of top to height - const ratioY = this._styleCoord[3]; - this.moveTo( - ratioX * this._zr.getWidth(), - ratioY * this._zr.getHeight() - ); - } - - hide() { - if (this.el) { - this.el.hide(); - } - this._show = false; - } - - hideLater(time?: number) { - if (this._show && !(this._inContent && this._enterable) && !this._alwaysShowContent) { - if (time) { - this._hideDelay = time; - // Set show false to avoid invoke hideLater multiple times - this._show = false; - this._hideTimeout = setTimeout(zrUtil.bind(this.hide, this), time) as any; - } - else { - this.hide(); - } - } - } - - isShow() { - return this._show; - } - - dispose() { - this._zr.remove(this.el); - } + private _zr : ZRenderType; + + private _show = false; + + private _styleCoord : [number, number, number, number] = [0, 0, 0, 0]; + + private _hideTimeout : number; + + private _alwaysShowContent : boolean = false; + + private _enterable = true; + + private _inContent : boolean; + + private _hideDelay : number; + + el : ZRText; + + constructor(api : ExtensionAPI) { + this._zr = api.getZr(); + makeStyleCoord(this._styleCoord, this._zr, api.getWidth() / 2, api.getHeight() / 2); + } + + /** + * Update when tooltip is rendered + */ + update(tooltipModel : Model) { + const alwaysShowContent = tooltipModel.get('alwaysShowContent'); + alwaysShowContent && this._moveIfResized(); + + // update alwaysShowContent + this._alwaysShowContent = alwaysShowContent; + } + + show() { + if (this._hideTimeout) { + clearTimeout(this._hideTimeout); + } + + this.el.show(); + this._show = true; + } + + /** + * Set tooltip content + */ + setContent( + content : string | HTMLElement | HTMLElement[], + markupStyleCreator : TooltipMarkupStyleCreator, + tooltipModel : Model, + borderColor : ZRColor, + arrowPosition : TooltipOption['position'] + ) { + if (zrUtil.isObject(content)) { + throwError(__DEV__ ? 'Passing DOM nodes as content is not supported in richText tooltip!' : ''); + } + if (this.el) { + this._zr.remove(this.el); + } + + const textStyleModel = tooltipModel.getModel('textStyle'); + + this.el = new ZRText({ + style: createTextStyle(textStyleModel, { + text: content as string, + textShadowColor: textStyleModel.get('textShadowColor'), + fill: tooltipModel.get(['textStyle', 'color']), + padding: getPaddingFromTooltipModel(tooltipModel, 'richText'), + verticalAlign: 'top', + align: 'left' + }), + z: tooltipModel.get('z') + }); + zrUtil.each([ + 'backgroundColor', 'borderRadius', 'shadowColor', 'shadowBlur', 'shadowOffsetX', 'shadowOffsetY' + ] as const, propName => { + (this.el.style as any)[propName] = tooltipModel.get(propName); + }); + zrUtil.each([ + 'textShadowBlur', 'textShadowOffsetX', 'textShadowOffsetY' + ] as const, propName => { + this.el.style[propName] = textStyleModel.get(propName) || 0; + }); + + this._zr.add(this.el); + + const self = this; + this.el.on('mouseover', function () { + // clear the timeout in hideLater and keep showing tooltip + if (self._enterable) { + clearTimeout(self._hideTimeout); + self._show = true; + } + self._inContent = true; + }); + this.el.on('mouseout', function () { + if (self._enterable) { + if (self._show) { + self.hideLater(self._hideDelay); + } + } + self._inContent = false; + }); + } + + setEnterable(enterable ?: boolean) { + this._enterable = enterable; + } + + getSize() { + const el = this.el; + const bounding = this.el.getBoundingRect(); + // bounding rect does not include shadow. For renderMode richText, + // if overflow, it will be cut. So calculate them accurately. + const shadowOuterSize = calcShadowOuterSize(el.style); + return [ + bounding.width + shadowOuterSize.left + shadowOuterSize.right, + bounding.height + shadowOuterSize.top + shadowOuterSize.bottom + ]; + } + + moveTo(x : number, y : number) { + const el = this.el; + if (el) { + const styleCoord = this._styleCoord; + makeStyleCoord(styleCoord, this._zr, x, y); + x = styleCoord[0]; + y = styleCoord[1]; + const style = el.style; + const borderWidth = mathMaxWith0(style.borderWidth || 0); + const shadowOuterSize = calcShadowOuterSize(style); + // rich text x, y do not include border. + el.x = x + borderWidth + shadowOuterSize.left; + el.y = y + borderWidth + shadowOuterSize.top; + el.markRedraw(); + } + } + + + /** + * when `alwaysShowContent` is true, + * move the tooltip after chart resized + */ + _moveIfResized() { + // The ratio of left to width + const ratioX = this._styleCoord[2]; + // The ratio of top to height + const ratioY = this._styleCoord[3]; + this.moveTo( + ratioX * this._zr.getWidth(), + ratioY * this._zr.getHeight() + ); + } + + hide() { + if (this.el) { + this.el.hide(); + } + this._show = false; + } + + hideLater(time ?: number) { + if (this._show && !(this._inContent && this._enterable) && !this._alwaysShowContent) { + if (time) { + this._hideDelay = time; + // Set show false to avoid invoke hideLater multiple times + this._show = false; + this._hideTimeout = setTimeout(zrUtil.bind(this.hide, this), time) as any; + } + else { + this.hide(); + } + } + } + + isShow() { + return this._show; + } + + dispose() { + this._zr.remove(this.el); + } } -function mathMaxWith0(val: number): number { - return Math.max(0, val); +function mathMaxWith0(val : number) : number { + return Math.max(0, val); } -function calcShadowOuterSize(style: TextStyleProps) { - const shadowBlur = mathMaxWith0(style.shadowBlur || 0); - const shadowOffsetX = mathMaxWith0(style.shadowOffsetX || 0); - const shadowOffsetY = mathMaxWith0(style.shadowOffsetY || 0); - return { - left: mathMaxWith0(shadowBlur - shadowOffsetX), - right: mathMaxWith0(shadowBlur + shadowOffsetX), - top: mathMaxWith0(shadowBlur - shadowOffsetY), - bottom: mathMaxWith0(shadowBlur + shadowOffsetY) - }; +function calcShadowOuterSize(style : TextStyleProps) { + const shadowBlur = mathMaxWith0(style.shadowBlur || 0); + const shadowOffsetX = mathMaxWith0(style.shadowOffsetX || 0); + const shadowOffsetY = mathMaxWith0(style.shadowOffsetY || 0); + return { + left: mathMaxWith0(shadowBlur - shadowOffsetX), + right: mathMaxWith0(shadowBlur + shadowOffsetX), + top: mathMaxWith0(shadowBlur - shadowOffsetY), + bottom: mathMaxWith0(shadowBlur + shadowOffsetY) + }; } -function makeStyleCoord(out: number[], zr: ZRenderType, zrX: number, zrY: number) { - out[0] = zrX; - out[1] = zrY; - out[2] = out[0] / zr.getWidth(); - out[3] = out[1] / zr.getHeight(); +function makeStyleCoord(out : number[], zr : ZRenderType, zrX : number, zrY : number) { + out[0] = zrX; + out[1] = zrY; + out[2] = out[0] / zr.getWidth(); + out[3] = out[1] / zr.getHeight(); } export default TooltipRichContent; From 83c815c14fed12e7f0ff9ec7e21185388b80ea5e Mon Sep 17 00:00:00 2001 From: hxl187 <2713697048@qq.com> Date: Thu, 26 Dec 2024 19:45:14 +0800 Subject: [PATCH 2/3] test(tooltip): add support for rich text styles in tooltip.textStyle --- test/tooltip-rich.html | 41 +++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/test/tooltip-rich.html b/test/tooltip-rich.html index 6f04ca5067..3b1dae4bf3 100644 --- a/test/tooltip-rich.html +++ b/test/tooltip-rich.html @@ -1,4 +1,3 @@ -