This is an automated email from the ASF dual-hosted git repository.
wangzx pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/echarts.git
The following commit(s) were added to refs/heads/master by this push:
new 428e2ad11 feat(tooltip): add `appendTo` option to allow customizing
tooltip container (#18436)
428e2ad11 is described below
commit 428e2ad11b1f2b730ed06bcde628a0bfff28df94
Author: Han Wei <[email protected]>
AuthorDate: Sat Sep 2 17:01:12 2023 +0800
feat(tooltip): add `appendTo` option to allow customizing tooltip container
(#18436)
* feat: allow customed tooltip container
* refactor: rename variables: getAppendElement => teleport &
customContainer => container
* feat: support 3 options to locale the tooltip container
* refactor: rename customContainer to container
* fix: add model params name
* refactor: rename variables: getAppendElement => teleport &
customContainer => container
* refactor: update based on cr
* fix: add missing variable
* test: add test case for appendTo
* fix: add fallback
* fix: only update styled to position:relative when there is no custom
container
* refactor: rename element name
* fix: clearing the _container & el in the dispose function
* fix: chartContainer always exists
* test: tweak test case
* chore: tweak comment
* fix(tooltip): fix potential exception when `appendTo` is an empty string
* fix(tooltip): fix potential exception when `appendTo` is an empty string
or null
---------
Co-authored-by: 危翰 <[email protected]>
Co-authored-by: plainheart <[email protected]>
---
src/component/tooltip/TooltipHTMLContent.ts | 61 +--
src/component/tooltip/TooltipModel.ts | 12 +-
src/component/tooltip/TooltipView.ts | 4 +-
test/tooltip-appendTo.html | 550 ++++++++++++++++++++++++++++
4 files changed, 597 insertions(+), 30 deletions(-)
diff --git a/src/component/tooltip/TooltipHTMLContent.ts
b/src/component/tooltip/TooltipHTMLContent.ts
index b326acd09..da1960835 100644
--- a/src/component/tooltip/TooltipHTMLContent.ts
+++ b/src/component/tooltip/TooltipHTMLContent.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { isString, indexOf, each, bind, isArray, isDom } from
'zrender/src/core/util';
+import { isString, indexOf, each, bind, isFunction, isArray, isDom } from
'zrender/src/core/util';
import { normalizeEvent } from 'zrender/src/core/event';
import { transformLocalCoord } from 'zrender/src/core/dom';
import env from 'zrender/src/core/env';
@@ -212,14 +212,20 @@ function assembleCssText(tooltipModel:
Model<TooltipOption>, enableTransition?:
}
// If not able to make, do not modify the input `out`.
-function makeStyleCoord(out: number[], zr: ZRenderType, appendToBody: boolean,
zrX: number, zrY: number) {
+function makeStyleCoord(
+ out: number[],
+ zr: ZRenderType,
+ container: HTMLElement | null | undefined,
+ zrX: number,
+ zrY: number
+) {
const zrPainter = zr && zr.painter;
- if (appendToBody) {
+ if (container) {
const zrViewportRoot = zrPainter && zrPainter.getViewportRoot();
if (zrViewportRoot) {
// Some APPs might use scale on body, so we support CSS transform
here.
- transformLocalCoord(out, zrViewportRoot, document.body, zrX, zrY);
+ transformLocalCoord(out, zrViewportRoot, container, zrX, zrY);
}
}
else {
@@ -241,23 +247,22 @@ function makeStyleCoord(out: number[], zr: ZRenderType,
appendToBody: boolean, z
interface TooltipContentOption {
/**
- * `false`: the DOM element will be inside the container. Default value.
- * `true`: the DOM element will be appended to HTML body, which avoid
- * some overflow clip but intrude outside of the container.
+ * Specify target container of the tooltip element.
+ * Can either be an HTMLElement, CSS selector string, or a function that
returns an HTMLElement.
*/
- appendToBody: boolean
+ appendTo: ((chartContainer: HTMLElement) => HTMLElement | undefined |
null) | HTMLElement | string
}
class TooltipHTMLContent {
el: HTMLDivElement;
- private _container: HTMLElement;
+ private _api: ExtensionAPI;
+ private _container: HTMLElement | undefined | null;
private _show: boolean = false;
private _styleCoord: [number, number, number, number] = [0, 0, 0, 0];
- private _appendToBody: boolean;
private _enterable = true;
private _zr: ZRenderType;
@@ -278,7 +283,6 @@ class TooltipHTMLContent {
private _longHideTimeout: number;
constructor(
- container: HTMLElement,
api: ExtensionAPI,
opt: TooltipContentOption
) {
@@ -291,17 +295,21 @@ class TooltipHTMLContent {
(el as any).domBelongToZr = true;
this.el = el;
const zr = this._zr = api.getZr();
- const appendToBody = this._appendToBody = opt && opt.appendToBody;
- makeStyleCoord(this._styleCoord, zr, appendToBody, api.getWidth() / 2,
api.getHeight() / 2);
+ const appendTo = opt.appendTo;
+ const container: HTMLElement | null | undefined = appendTo && (
+ isString(appendTo)
+ ? document.querySelector(appendTo)
+ : isDom(appendTo)
+ ? appendTo
+ : isFunction(appendTo) && appendTo(api.getDom())
+ );
- if (appendToBody) {
- document.body.appendChild(el);
- }
- else {
- container.appendChild(el);
- }
+ makeStyleCoord(this._styleCoord, zr, container, api.getWidth() / 2,
api.getHeight() / 2);
+
+ (container || api.getDom()).appendChild(el);
+ this._api = api;
this._container = container;
// FIXME
@@ -350,11 +358,13 @@ class TooltipHTMLContent {
update(tooltipModel: Model<TooltipOption>) {
// FIXME
// Move this logic to ec main?
- const container = this._container;
- const position = getComputedStyle(container, 'position');
- const domStyle = container.style;
- if (domStyle.position !== 'absolute' && position !== 'absolute') {
- domStyle.position = 'relative';
+ if (!this._container) {
+ const container = this._api.getDom();
+ const position = getComputedStyle(container, 'position');
+ const domStyle = container.style;
+ if (domStyle.position !== 'absolute' && position !== 'absolute') {
+ domStyle.position = 'relative';
+ }
}
// move tooltip if chart resized
@@ -456,7 +466,7 @@ class TooltipHTMLContent {
moveTo(zrX: number, zrY: number) {
const styleCoord = this._styleCoord;
- makeStyleCoord(styleCoord, this._zr, this._appendToBody, zrX, zrY);
+ makeStyleCoord(styleCoord, this._zr, this._container, zrX, zrY);
if (styleCoord[0] != null && styleCoord[1] != null) {
const style = this.el.style;
@@ -511,6 +521,7 @@ class TooltipHTMLContent {
dispose() {
this.el.parentNode.removeChild(this.el);
+ this.el = this._container = null;
}
}
diff --git a/src/component/tooltip/TooltipModel.ts
b/src/component/tooltip/TooltipModel.ts
index 413b52fb4..351cc0549 100644
--- a/src/component/tooltip/TooltipModel.ts
+++ b/src/component/tooltip/TooltipModel.ts
@@ -61,13 +61,19 @@ export interface TooltipOption extends
CommonTooltipOption<TopLevelFormatterPara
renderMode?: 'auto' | TooltipRenderMode // TODO richText renamed canvas?
/**
- * If append popup dom to document.body
- * Only available when renderMode is html
+ * @deprecated
+ * use appendTo: 'body' instead
*/
appendToBody?: boolean
/**
- * specified class name of tooltip dom
+ * If append the tooltip element to another DOM element.
+ * Only available when renderMode is html
+ */
+ appendTo?: ((chartContainer: HTMLElement) => HTMLElement | undefined |
null) | string | HTMLElement
+
+ /**
+ * Specify the class name of tooltip element
* Only available when renderMode is html
*/
className?: string
diff --git a/src/component/tooltip/TooltipView.ts
b/src/component/tooltip/TooltipView.ts
index 82a222cb1..5600dc292 100644
--- a/src/component/tooltip/TooltipView.ts
+++ b/src/component/tooltip/TooltipView.ts
@@ -171,8 +171,8 @@ class TooltipView extends ComponentView {
this._tooltipContent = renderMode === 'richText'
? new TooltipRichContent(api)
- : new TooltipHTMLContent(api.getDom(), api, {
- appendToBody: tooltipModel.get('appendToBody', true)
+ : new TooltipHTMLContent(api, {
+ appendTo: tooltipModel.get('appendToBody', true) ? 'body' :
tooltipModel.get('appendTo', true)
});
}
diff --git a/test/tooltip-appendTo.html b/test/tooltip-appendTo.html
new file mode 100644
index 000000000..644697e17
--- /dev/null
+++ b/test/tooltip-appendTo.html
@@ -0,0 +1,550 @@
+<!DOCTYPE html>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <script src="lib/simpleRequire.js"></script>
+ <script src="lib/config.js"></script>
+ <script src="lib/jquery.min.js"></script>
+ <script src="lib/facePrint.js"></script>
+ <script src="lib/testHelper.js"></script>
+ <!-- <script src="ut/lib/canteen.js"></script> -->
+ <link rel="stylesheet" href="lib/reset.css" />
+ </head>
+ <body>
+ <style>
+ html {
+ perspective: 271px;
+ }
+ h1 {
+ line-height: 60px;
+ height: 60px;
+ background: #ddd;
+ text-align: center;
+ font-weight: bold;
+ font-size: 14px;
+ }
+ #position {
+ transform: scale3d(0.7, 0.6, 1) rotateY(-8deg) rotateZ(-3deg);
+ transform-style: preserve-3d;
+ position: relative;
+ }
+
+ #config {
+ width: 90%;
+ height: 600px;
+ overflow: auto;
+ position: relative;
+ }
+
+ #config > .mark {
+ background: lightblue !important;
+ }
+
+ .test-chart-block {
+ overflow: hidden !important;
+ }
+ </style>
+
+ <h1>correct position</h1>
+ <div id="position">
+ <div id="main0"></div>
+ <div id="main1"></div>
+ <div id="main2"></div>
+ <div id="main3"></div>
+ </div>
+
+ <h1>3 way to config</h1>
+ <div id="config">
+ <div id="main4"></div>
+ <div id="main5"></div>
+ <div id="main6"></div>
+ </div>
+
+ <script>
+ require(['echarts'], function (echarts) {
+ var option;
+
+ option = {
+ xAxis: {},
+ yAxis: {},
+ tooltip: {
+ appendTo: '#position',
+ formatter: [
+ '1_aaaaaaaaaaaaaa',
+ '2_aaaaaaaaaaaaaa',
+ '3_aaaaaaaaaaaaaa',
+ '4_aaaaaaaaaaaaaa',
+ '5_aaaaaaaaaaaaaa',
+ '6_aaaaaaaaaaaaaa',
+ '7_aaaaaaaaaaaaaa',
+ '8_aaaaaaaaaaaaaa',
+ '9_aaaaaaaaaaaaaa',
+ '10_aaaaaaaaaaaaaa',
+ '11_aaaaaaaaaaaaaa',
+ '12_aaaaaaaaaaaaaa'
+ ].join('<br>'),
+ extraCssText: 'transform: scaleZ(1)'
+ },
+ dataZoom: [{
+ type: 'slider'
+ }, {
+ type: 'inside'
+ }],
+ series: [{
+ type: 'line',
+ symbolSize: 30,
+ data: [[11, 22], [33, 44]]
+ }, {
+ type: 'line',
+ symbolSize: 30,
+ data: [[11, 32], [33, 44]]
+ }, {
+ type: 'line',
+ symbolSize: 30,
+ data: [[11, 42], [33, 44]]
+ }, {
+ type: 'line',
+ symbolSize: 30,
+ data: [[11, 52], [33, 44]]
+ }, {
+ type: 'line',
+ symbolSize: 30,
+ data: [[11, 62], [33, 44]]
+ }, {
+ type: 'line',
+ symbolSize: 30,
+ data: [[11, 72], [33, 44]]
+ }, {
+ type: 'line',
+ symbolSize: 30,
+ data: [[11, 82], [33, 44]]
+ }, {
+ type: 'line',
+ symbolSize: 30,
+ data: [[11, 92], [33, 44]]
+ }]
+ };
+
+ var chart = testHelper.create(echarts, 'main0', {
+ title: [
+ 'Body is CSS transfromed, tooltip should be correctly
positioned',
+ 'and not be overflow clipped.'
+ ],
+ option: option,
+ height: 300
+ });
+ });
+ </script>
+
+
+
+
+ <script>
+ require(['echarts'], function (echarts) {
+ var option;
+
+ option = {
+ xAxis: {},
+ yAxis: {},
+ tooltip: {
+ appendTo: '#position',
+ position: [120, 20],
+ formatter: [
+
'1_aa..................................................................a',
+
'2_aa..................................................................a',
+
'3_aa..................................................................a',
+
'4_aa..................................................................a',
+
'5_aa..................................................................a',
+
'6_aa..................................................................a',
+
'7_aa..................................................................a',
+
'8_aa..................................................................a',
+
'9_aa..................................................................a',
+
'10_a..................................................................aa',
+
'11_a..................................................................aa',
+
'12_a..................................................................aa',
+
'13_a..................................................................aa',
+
'14_a..................................................................aa',
+
'15_a..................................................................aa',
+
'16_a..................................................................aa',
+
'17_a..................................................................aa',
+
'18_a..................................................................aa',
+
'19_a..................................................................aa'
+ ].join('<br>'),
+ extraCssText: 'transform: scaleZ(1); background: rgba(0,
0, 0, 0.2);'
+ },
+ dataZoom: [{
+ type: 'slider'
+ }, {
+ type: 'inside'
+ }],
+ series: [{
+ type: 'line',
+ symbolSize: 50,
+ symbol: 'circle',
+ itemStyle: {
+ opacity: 0.3
+ },
+ data: [[11, 22], [33, 44]]
+ }, {
+ type: 'line',
+ symbolSize: 50,
+ symbol: 'circle',
+ itemStyle: {
+ opacity: 0.3
+ },
+ data: [[11, 92], [33, 44]]
+ }]
+ };
+
+ var chart = testHelper.create(echarts, 'main1', {
+ title: [
+ 'When mouseover tooltip the **red** stoke should keep
being generated,',
+ 'and the tooltip is able to show and hide.'
+ ],
+ option: option,
+ height: 300
+ });
+ if (chart) {
+ var zr = chart.getZr();
+ zr.on('mousemove', function (event) {
+ var circle = new echarts.graphic.Circle({
+ shape: {
+ cx: event.offsetX,
+ cy: event.offsetY,
+ r: 3
+ },
+ style: {
+ fill: 'red'
+ },
+ z: -100
+ });
+ zr.add(circle);
+ });
+ zr.on('globalout', function (event) {
+ console.log('globalout');
+ });
+ }
+ });
+ </script>
+
+
+
+
+ <script>
+ require(['echarts'], function (echarts) {
+ var option;
+
+ option = {
+ xAxis: {},
+ yAxis: {},
+ tooltip: {
+ appendTo: '#position',
+ position: 'bottom',
+ formatter: [
+ '1_aaaaaaaaaaaaaa',
+ '2_aaaaaaaaaaaaaa',
+ '3_aaaaaaaaaaaaaa',
+ '4_aaaaaaaaaaaaaa',
+ '5_aaaaaaaaaaaaaa',
+ '6_aaaaaaaaaaaaaa',
+ '7_aaaaaaaaaaaaaa',
+ '8_aaaaaaaaaaaaaa',
+ '9_aaaaaaaaaaaaaa',
+ '10_aaaaaaaaaaaaaa',
+ '11_aaaaaaaaaaaaaa',
+ '12_aaaaaaaaaaaaaa'
+ ].join('<br>'),
+ extraCssText: 'transform: scaleZ(1)'
+ },
+ dataZoom: [{
+ type: 'slider'
+ }, {
+ type: 'inside'
+ }],
+ series: [{
+ type: 'line',
+ symbolSize: 30,
+ data: [[11, 22], [33, 44]]
+ }, {
+ type: 'line',
+ symbolSize: 30,
+ data: [[11, 92], [33, 44]]
+ }]
+ };
+
+ var chart = testHelper.create(echarts, 'main2', {
+ title: [
+ 'Body is CSS transfromed, tooltip should be correctly
positioned',
+ 'and not be overflow clipped.'
+ ],
+ option: option,
+ height: 300,
+ buttons: [{
+ text: 'showTip, x: number, y: number',
+ onclick: function () {
+ var coord = chart.convertToPixel(
+ {seriesIndex: 0}, [11, 22]
+ );
+ chart.dispatchAction({
+ type: 'showTip',
+ x: coord[0],
+ y: coord[1]
+ });
+ }
+ }]
+ });
+ });
+ </script>
+
+
+
+
+
+ <script>
+ require(['echarts'], function (echarts) {
+ var option;
+
+ option = {
+ title: {
+ text: 'gloubalout not triggered yet'
+ },
+ xAxis: {},
+ yAxis: {},
+ tooltip: {
+ appendTo: '#position',
+ enterable: true,
+ formatter: [
+ '1_aaaaaaaaaaaaaa',
+ '2_aaaaaaaaaaaaaa',
+ '3_aaaaaaaaaaaaaa',
+ '4_aaaaaaaaaaaaaa',
+ '5_aaaaaaaaaaaaaa',
+ '6_aaaaaaaaaaaaaa',
+ '7_aaaaaaaaaaaaaa',
+ '8_aaaaaaaaaaaaaa',
+ '9_aaaaaaaaaaaaaa',
+ '10_aaaaaaaaaaaaaa',
+ '11_aaaaaaaaaaaaaa',
+ '12_aaaaaaaaaaaaaa'
+ ].join('<br>'),
+ extraCssText: 'transform: scaleZ(1)'
+ },
+ dataZoom: [{
+ type: 'slider'
+ }, {
+ type: 'inside'
+ }],
+ series: [{
+ type: 'line',
+ symbolSize: 30,
+ data: [[11, 22], [33, 44]]
+ }, {
+ type: 'line',
+ symbolSize: 30,
+ data: [[11, 92], [33, 44]]
+ }]
+ };
+
+ var chart = testHelper.create(echarts, 'main3', {
+ title: [
+ 'tooltip.enterable: true, enter the tooltip,',
+ 'globalout should not be triggered'
+ ],
+ option: option,
+ height: 200
+ });
+ if (chart) {
+ var zr = chart.getZr();
+ zr.on('globalout', function () {
+ chart.setOption({
+ title: {
+ text: 'gloubalout triggered at\n' + (+new Date())
+ }
+ });
+ });
+ }
+ });
+ </script>
+
+
+ <script>
+ require(['echarts'], function (echarts) {
+ var option;
+ option = {
+ xAxis: {},
+ yAxis: {},
+ tooltip: {
+ appendTo: '#config',
+ className: 'mark',
+ formatter: [
+ '1_aaaaaaaaaaaaaa',
+ '2_aaaaaaaaaaaaaa',
+ '3_aaaaaaaaaaaaaa',
+ '4_aaaaaaaaaaaaaa',
+ '5_aaaaaaaaaaaaaa',
+ '6_aaaaaaaaaaaaaa',
+ '7_aaaaaaaaaaaaaa',
+ '8_aaaaaaaaaaaaaa',
+ '9_aaaaaaaaaaaaaa',
+ '10_aaaaaaaaaaaaaa',
+ '11_aaaaaaaaaaaaaa',
+ `<span class='mark'>use_string</span>`
+ ].join('<br>'),
+ },
+ series: [{
+ type: 'line',
+ symbolSize: 30,
+ data: [[11, 22], [33, 44]]
+ }, {
+ type: 'line',
+ symbolSize: 30,
+ data: [[11, 32], [33, 44]]
+ }, {
+ type: 'line',
+ symbolSize: 30,
+ data: [[11, 82], [33, 44]]
+ }, {
+ type: 'line',
+ symbolSize: 30,
+ data: [[11, 92], [33, 44]]
+ }]
+ };
+
+ var chart = testHelper.create(echarts, 'main4', {
+ title: [
+ 'use string'
+ ],
+ option: option,
+ height: 300
+ });
+ });
+ </script>
+
+ <script>
+ require(['echarts'], function (echarts) {
+ var option;
+ option = {
+ xAxis: {},
+ yAxis: {},
+ tooltip: {
+ appendTo: () => document.querySelector('#config'),
+ className: 'mark',
+ formatter: [
+ '1_aaaaaaaaaaaaaa',
+ '2_aaaaaaaaaaaaaa',
+ '3_aaaaaaaaaaaaaa',
+ '4_aaaaaaaaaaaaaa',
+ '5_aaaaaaaaaaaaaa',
+ '6_aaaaaaaaaaaaaa',
+ '7_aaaaaaaaaaaaaa',
+ '8_aaaaaaaaaaaaaa',
+ '9_aaaaaaaaaaaaaa',
+ '10_aaaaaaaaaaaaaa',
+ '11_aaaaaaaaaaaaaa',
+ `<span class='mark'>use_function</span>`
+ ].join('<br>'),
+ },
+ series: [{
+ type: 'line',
+ symbolSize: 30,
+ data: [[11, 22], [33, 44]]
+ }, {
+ type: 'line',
+ symbolSize: 30,
+ data: [[11, 32], [33, 44]]
+ }, {
+ type: 'line',
+ symbolSize: 30,
+ data: [[11, 82], [33, 44]]
+ }, {
+ type: 'line',
+ symbolSize: 30,
+ data: [[11, 92], [33, 44]]
+ }]
+ };
+
+ var chart = testHelper.create(echarts, 'main5', {
+ title: [
+ 'use function'
+ ],
+ option: option,
+ height: 300
+ });
+ });
+ </script>
+ <script>
+ require(['echarts'], function (echarts) {
+ var option;
+ option = {
+ xAxis: {},
+ yAxis: {},
+ tooltip: {
+ appendTo: document.querySelector('#config'),
+ className: 'mark',
+ formatter: [
+ '1_aaaaaaaaaaaaaa',
+ '2_aaaaaaaaaaaaaa',
+ '3_aaaaaaaaaaaaaa',
+ '4_aaaaaaaaaaaaaa',
+ '5_aaaaaaaaaaaaaa',
+ '6_aaaaaaaaaaaaaa',
+ '7_aaaaaaaaaaaaaa',
+ '8_aaaaaaaaaaaaaa',
+ '9_aaaaaaaaaaaaaa',
+ '10_aaaaaaaaaaaaaa',
+ '11_aaaaaaaaaaaaaa',
+ ].join('<br>'),
+ },
+ series: [{
+ type: 'line',
+ symbolSize: 30,
+ data: [[11, 22], [33, 44]]
+ }, {
+ type: 'line',
+ symbolSize: 30,
+ data: [[11, 32], [33, 44]]
+ }, {
+ type: 'line',
+ symbolSize: 30,
+ data: [[11, 82], [33, 44]]
+ }, {
+ type: 'line',
+ symbolSize: 30,
+ data: [[11, 92], [33, 44]]
+ }]
+ };
+
+ var chart = testHelper.create(echarts, 'main6', {
+ title: [
+ 'use element',
+ ],
+ option: option,
+ height: 300
+ });
+ });
+ </script>
+
+ </body>
+</html>
+
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]