feat(图表): 部分图表支持提示轮播

This commit is contained in:
jianneng-fit2cloud
2025-10-23 19:09:58 +08:00
parent 002773ff0e
commit 50ed53bfed
18 changed files with 759 additions and 244 deletions

View File

@@ -181,7 +181,7 @@ import { usePermissionStoreWithOut } from '@/store/modules/permission'
import { activeWatermarkCheckUser } from '@/components/watermark/watermark'
import { getCanvasStyle } from '@/utils/style'
import EmptyBackground from '../empty-background/src/EmptyBackground.vue'
import ChartCarouselTooltip from '@/views/chart/components/js/g2plot_tooltip_carousel'
import G2TooltipCarousel from '@/views/chart/components/js/G2TooltipCarousel'
const downLoading = ref(false)
const dvMainStore = dvMainStoreWithOut()
@@ -324,7 +324,6 @@ const dialogInit = (canvasStyle, view, item, opt, params = { scale: 0.5 }) => {
}
nextTick(() => {
initWatermark()
ChartCarouselTooltip.paused()
})
}
@@ -455,7 +454,7 @@ const initWatermark = () => {
activeWatermarkCheckUser('enlarge-inner-content', 'canvas-main', state.scale)
}
const handleClose = () => {
ChartCarouselTooltip.closeEnlargeDialogDestroy(viewInfo.value.id)
G2TooltipCarousel.closeEnlargeDialogDestroy(viewInfo.value.id)
}
defineExpose({
dialogInit

View File

@@ -77,6 +77,7 @@ declare interface Chart {
fontFamily?: string
dashboardHidden?: boolean
inMobile?: boolean
}
declare type CustomAttr = DeepPartial<ChartAttr> | JSONString<DeepPartial<ChartAttr>>
declare type CustomStyle = DeepPartial<ChartStyle> | JSONString<DeepPartial<ChartStyle>>

View File

@@ -0,0 +1,586 @@
import { parseJson } from '@/views/chart/components/js/util'
import { listenerTooltipShow } from '@/views/chart/components/js/panel/charts/g2/bar/barUtil'
class G2TooltipCarousel {
/**
* 存储 G2TooltipCarousel 实例的缓存,key为chart的container
* @private
*/
private static instanceCache = new Map<string, G2TooltipCarousel>()
// 支持轮播的图表类型
private static SUPPORT_CHART_TYPES = [
'bar',
'bar-stack',
'bar-group',
'bar-group-stack',
'percentage-bar-stack',
'line',
'area',
'area-stack',
'chart-mix',
'chart-mix-group',
'chart-mix-stack',
'chart-mix-dual-line',
'pie',
'pie-donut'
]
private newChart: any
private chart: any
private data: any[]
private normalInterval: number
private finalExtraWait: number
private index: number
private isPaused: boolean
private intersectionObserver: IntersectionObserver
private chartElement: HTMLElement
private timers = { interval: null, carousel: null, nextItem: null }
private isExecuting: boolean
private isViewEnlarged: boolean
private instanceId: number
private isDestroyed: boolean
private chartIsVisible: boolean
// 事件处理函数引用
private handleMouseEnter: EventListener
private handleMouseLeave: EventListener
private handleMouseWheel: EventListener
private handleVisibility: EventListener
/**
* 构造函数,初始化轮播实例
*/
constructor(newChart: any, chart: any, data: any[]) {
// 重新创建实例前销毁已有实例
G2TooltipCarousel.destroyByContainer(chart.container)
this.newChart = newChart
this.chart = chart
const { isLine, lineEncodedX, isMix, mixEncodedX } = this.specialChartTypes()
if (isLine && lineEncodedX) {
this.data = data
} else if (isMix && mixEncodedX) {
this.data = this.groupByField(data, { x: mixEncodedX })
} else {
const encode = this.newChart?.children?.[0]?.value?.encode
if (encode) {
this.data = this.groupByField(data, encode)
}
}
this.index = 0
this.isPaused = false
this.isViewEnlarged = false
this.isDestroyed = false
this.instanceId = Date.now()
// 事件处理函数绑定
this.handleMouseEnter = this.pause.bind(this)
this.handleMouseLeave = this.mouseLeave.bind(this)
this.handleMouseWheel = this.mouseWheel.bind(this)
this.handleVisibility = this.handleVisibilityChange.bind(this)
const { tooltip } = parseJson(this.chart.customAttr)
// 如果不显示tooltip,销毁实例
if (!tooltip.show) {
G2TooltipCarousel.destroyByContainer(this.chart.container)
return null
}
const carousel = chart.customAttr?.tooltip?.carousel
// 不支持轮播或类型不支持时销毁实例
if (!carousel?.enable || !G2TooltipCarousel.SUPPORT_CHART_TYPES.includes(chart.type)) {
G2TooltipCarousel.destroyByContainer(chart.container)
return null
}
// 图表渲染后显示第一个tooltip
this.newChart.on('afterrender', () => {
listenerTooltipShow(this.newChart, chart)
if (this.chart.dashboardHidden) return
this.showTooltipAtData(this.buildTooltipData(), this.data?.[0] || data?.[0] || [])
})
this.normalInterval = carousel?.stayTime * 1000
this.finalExtraWait = carousel?.intervalTime * 1000
this.chartElement = this.newChart.getContainer()
this.isViewEnlarged = this.chart.container.indexOf('viewDialog') > -1
G2TooltipCarousel.instanceCache.forEach(instance => {
// 如果有仪表盘隐藏或者当前有放大视图时,停止轮播
if (instance.chart.dashboardHidden || this.isViewEnlarged) {
G2TooltipCarousel.getInstanceByContainerId(instance.chart.container)?.stop()
}
})
this.init()
G2TooltipCarousel.instanceCache.set(chart.container, this)
}
/**
* 判断图表是否为折线图或面积图
* @private
*/
private isLineChart(): boolean {
return ['line', 'area', 'area-stack'].includes(this.chart.type)
}
/**
* 判断图表是否为混合图表
* @private
*/
private isMixChart(): boolean {
return ['chart-mix', 'chart-mix-dual-line', 'chart-mix-group', 'chart-mix-stack'].includes(
this.chart.type
)
}
/**
* 通过 encode 的 x 字段对数据进行分组,返回每组的第一个数据项
* @param data
* @param encode
* @private
*/
private groupByField<T>(data: T[], encode: T) {
const groups: Record<string, T[]> = {}
data.forEach(item => {
const key = String(item[encode?.x || encode?.color])
if (!groups[key]) groups[key] = []
groups[key].push(item)
})
return Object.keys(groups).map(key => ({
...groups[key][0]
}))
}
/**
* 通过容器 ID 获取 G2TooltipCarousel 实例
*/
static getInstanceByContainerId(containerId: string): G2TooltipCarousel | undefined {
return G2TooltipCarousel.instanceCache.get(containerId)
}
/**
* 销毁指定容器的 G2TooltipCarousel 实例
*/
static destroyByContainer(containerId?: string) {
if (containerId) {
const instance = G2TooltipCarousel.instanceCache.get(containerId)
if (instance) {
instance.destroy()
}
}
}
/**
* 初始化事件监听和可见性检测
*/
private init() {
this.addEventListeners()
}
/**
* 添加鼠标和页面可见性事件监听
* 绑定时用同一函数引用,防止内存泄漏
*/
private addEventListeners() {
this.chartElement.addEventListener('mouseenter', this.handleMouseEnter)
this.chartElement.addEventListener('mouseleave', this.handleMouseLeave)
const previewCanvasMain = document.getElementById('preview-canvas-main')
const editCanvasMain = document.getElementById('edit-canvas-main')
const mobileComList = document.getElementById('mobile-com-list')
const wheelTargetElements = [previewCanvasMain, editCanvasMain, mobileComList].filter(
el => el !== null
)
if (wheelTargetElements && wheelTargetElements.length > 0) {
wheelTargetElements[0].addEventListener('mousewheel', this.mouseWheel)
wheelTargetElements[0].addEventListener('touchmove', this.touchMove.bind(this), {
passive: true
})
}
document.addEventListener('visibilitychange', this.handleVisibility)
if (!this.intersectionObserver) {
// threshold 0.7 当可见比例跨过 0.7 时会触发回调
this.intersectionObserver = new IntersectionObserver(this.handleIntersection.bind(this), {
root: null,
threshold: [0, 0.5, 0.7, 1]
})
}
this.intersectionObserver?.observe(this.newChart.getContainer())
}
/**
* 移除事件监听
* 解绑时用同一函数引用,防止内存泄漏
*/
private removeEventListeners() {
this.chartElement.removeEventListener('mouseenter', this.handleMouseEnter)
this.chartElement.removeEventListener('mouseleave', this.handleMouseLeave)
const previewCanvasMain = document.getElementById('preview-canvas-main')
const editCanvasMain = document.getElementById('edit-canvas-main')
const mobileComList = document.getElementById('mobile-com-list')
const wheelTargetElements = [previewCanvasMain, editCanvasMain, mobileComList].filter(
el => el !== null
)
if (wheelTargetElements && wheelTargetElements.length > 0) {
wheelTargetElements[0].removeEventListener('mousewheel', this.mouseWheel)
wheelTargetElements[0].removeEventListener('touchmove', this.touchMove.bind(this), {
passive: true
})
}
document.removeEventListener('visibilitychange', this.handleVisibility)
}
/**
* 定义 mousewheel 事件处理函数(PC端)
* @private
*/
private mouseWheel = this.debounce(() => {
G2TooltipCarousel.instanceCache?.forEach(instance => {
instance.pause(true)
instance.resume(true)
})
}, 0)
/**
* 定义 touchmove 事件处理函数(移动端)
* @param event
* @private
*/
private touchMove(event: TouchEvent) {
this.mouseWheel(event)
}
/**
* 防抖
*/
private debounce(func: (...args: any[]) => void, delay: number): (...args: any[]) => void {
let timeout: number | null = null
return (...args: any[]) => {
if (timeout) clearTimeout(timeout)
timeout = window.setTimeout(() => {
func(...args)
}, delay)
}
}
/**
* IntersectionObserver回调,处理元素进入/离开视口
* 只有可见区域大于70%时才恢复轮播,否则暂停
*/
private handleIntersection(entries) {
const entry = entries[0]
if (entry.intersectionRatio < 0.7 || this.chart.dashboardHidden) {
this.hideTooltipAtData()
this.chartIsVisible = false
this.pause(true)
} else {
this.chartIsVisible = true
this.resume(true)
}
}
/**
* 页面可见性变化处理
*/
private handleVisibilityChange() {
if (document.hidden) {
this.pause(true)
return
}
// 元素仍在视口中时恢复轮播
if (this.newChart.getContainer().offsetParent !== null) {
const hasViewDialog = Array.from(G2TooltipCarousel.instanceCache.keys()).some(key =>
key?.includes('viewDialog')
)
if (hasViewDialog && !this.chart.dashboardHidden ? this.isViewEnlarged : true) {
this.resume(true)
} else {
this.pause(true)
}
}
}
/**
* 判断是否需要暂停轮播
*/
private shouldPauseCarousel(): boolean {
return (
this.isPaused ||
!this.data ||
this.data.length === 0 ||
this.hasParentWithSwitchHidden() ||
!this.checkChartElementAndCache() ||
!this.chartIsVisible
)
}
/**
* 轮播下一个tooltip
*/
private next() {
// 防止并发执行
if (this.isExecuting || this.isDestroyed) {
return
}
this.isExecuting = true
try {
// 使用 shouldPauseCarousel 方法判断是否需要暂停轮播
if (this.shouldPauseCarousel()) {
this.clearTimer()
this.isExecuting = false
return
}
this.clearTimer()
if (this.index >= this.data.length) {
this.index = 0
}
const currentItem = this.data[this.index]
if (!currentItem) {
this.clearTimer()
this.isExecuting = false
return
}
const tooltipData = this.buildTooltipData(currentItem)
// 显示当前tooltip
this.showTooltipAtData(tooltipData, currentItem)
// 定时切换到下一个tooltip
this.timers.carousel = setTimeout(() => {
if (!this.data || this.data.length === 0) {
this.clearTimer()
this.isExecuting = false
return
}
const isLastItem = this.index === this.data.length - 1
if (isLastItem) {
this.hideTooltipAtData()
// 最后一个后等待额外时间再轮播
this.timers.interval = setTimeout(() => {
this.index = 0
this.isExecuting = false
this.next()
}, this.finalExtraWait)
} else {
this.isExecuting = false
this.timers.nextItem = setTimeout(() => {
this.index += 1
this.next()
}, 20)
}
}, this.normalInterval)
} finally {
// 没有定时器活动时释放执行锁
if (!this.timers.carousel && !this.timers.interval && !this.timers.nextItem) {
this.isExecuting = false
}
}
}
/**
* 构建要显示tooltip的数据项
*/
buildTooltipData(currentItem?: any) {
const itemData = currentItem || this.data?.[0] || {}
const tooltipData = {
data: {
data: itemData
}
}
const { isLine, lineEncodedX, isMix, mixEncodedX } = this.specialChartTypes()
// 折线图和混合图表需要单独处理 encode.x 字段
if ((isLine && lineEncodedX) || (isMix && mixEncodedX)) {
tooltipData.data.data = { x: itemData[isLine ? lineEncodedX : mixEncodedX] }
}
return tooltipData
}
/**
* 特殊图表encode.x获取方式不同
* 折线图和混合图表的 encode.x 配置
*/
specialChartTypes() {
const isLine = this.isLineChart()
const lineEncodedX = isLine ? this.newChart?.encode?.()?.x : undefined
const isMix = this.isMixChart()
const mixEncodedX = isMix ? this.findEncodeX(this.newChart) : undefined
return { isLine, lineEncodedX, isMix, mixEncodedX }
}
/**
* 递归查找混合图表中的 encode.x 配置
* @param chart
*/
findEncodeX(chart: any): any {
if (chart?.encode?.x) {
return chart.encode.x
}
if (Array.isArray(chart?.children) && chart.children.length > 0) {
for (const child of chart.children) {
if (child.value?.encode?.x) {
return child.value.encode.x
}
const result = this.findEncodeX(child)
if (result !== undefined) {
return result
}
}
}
return undefined
}
/**
* 显示指定数据项的 tooltip
* @param tooltipData
* @param originalData
*/
showTooltipAtData(tooltipData, originalData?: any) {
this.newChart.emit('tooltip:show', tooltipData)
if (this.isMixChart() && originalData) {
this.newChart.emit('element:select', { data: { data: [originalData] } })
} else {
this.newChart.emit('element:select', { data: { data: [tooltipData.data.data] } })
}
}
/**
* 隐藏 tooltip
*/
hideTooltipAtData() {
this.newChart.emit('tooltip:hide')
this.newChart.emit('element:unselect', {})
}
/**
* 停止轮播
*/
stop() {
this.isPaused = true
this.isExecuting = false
this.clearTimer()
this.hideTooltipAtData()
}
/**
* 启动轮播
* 增加防重入判断,避免多次启动导致多个定时器
*/
start() {
// 防止多次启动
if (!this.isPaused && this.isExecuting) return
this.isPaused = false
this.index = 0
this.next()
}
/**
* 暂停轮播
*/
pause(force = false) {
if (force) {
this.isPaused = true
this.isExecuting = true
}
this.clearTimer()
}
/**
* 恢复轮播
*/
resume(force = false) {
if (force) {
this.isPaused = false
}
// 没有活跃定时器时才启动轮播
if (!this.isPaused) {
this.clearTimer()
this.isExecuting = false
this.next()
}
}
mouseLeave(ev) {
const el = this.chartElement
setTimeout(() => {
// 获取鼠标位置
const mouseX = ev.clientX
const mouseY = ev.clientY
// 获取div的边界信息
const rect = el.getBoundingClientRect()
// 判断鼠标位置是否在div内
const isInside =
mouseX >= rect.left + 10 &&
mouseX <= rect.right - 10 &&
mouseY >= rect.top + 10 &&
mouseY <= rect.bottom - 10
if (!isInside) {
this.pause(true)
this.resume(true)
}
}, 300)
}
/**
* 销毁实例
*/
destroy() {
this.isDestroyed = true
this.stop()
this.removeEventListeners()
this.intersectionObserver?.disconnect()
G2TooltipCarousel.instanceCache.delete(this.chart.container)
}
/**
* 清除所有定时器
*/
private clearTimer() {
if (this.timers.carousel) {
clearTimeout(this.timers.carousel)
this.timers.carousel = null
}
if (this.timers.interval) {
clearTimeout(this.timers.interval)
this.timers.interval = null
}
if (this.timers.nextItem) {
clearTimeout(this.timers.nextItem)
this.timers.nextItem = null
}
}
/**
* 检查图表元素以及缓存是否存在
* @private
*/
private checkChartElementAndCache() {
const element = document.getElementById(this.chart.container)
return element && G2TooltipCarousel.instanceCache.has(this.chart.container)
}
/**
* 判断元素是否有父元素包含 'switch-hidden' 类
*/
private hasParentWithSwitchHidden() {
let parent = this.newChart.getContainer()
while (parent) {
if (parent.classList.contains('switch-hidden')) {
return true
}
parent = parent.parentElement
}
return false
}
/**
* 关闭放大图表弹窗,销毁对应实例并恢复其他实例轮播
*/
static closeEnlargeDialogDestroy(id?: string) {
if (id) {
G2TooltipCarousel.instanceCache?.forEach(instance => {
if (
instance &&
instance.chart.id === id &&
instance.chart.container.indexOf('viewDialog') > -1
) {
instance.destroy()
}
})
}
setTimeout(() => {
// 恢复其他实例的轮播
const instances = Array.from(G2TooltipCarousel.instanceCache.values())
instances?.forEach(instance => {
instance.isExecuting = false
instance?.start()
})
}, 400)
}
}
export default G2TooltipCarousel

View File

@@ -13,6 +13,7 @@ import {
getLineDash,
handleChartDashboardHidden,
setGradientColor,
toLinearGradient,
TOOLTIP_ITEM_TPL,
TOOLTIP_TITLE_TPL
} from '@/views/chart/components/js/panel/common/common_antv'
@@ -22,13 +23,13 @@ import {
DEFAULT_YAXIS_STYLE
} from '@/views/chart/components/editor/util/chart'
import {
configTooltip,
createTooltipWrapper,
tooltipCss,
Transform,
ViewSpec
} from '@/views/chart/components/js/panel/charts/g2/bar/barUtil'
import { addExtremumText, extremumEvt } from '@/views/chart/components/js/extremumUitl'
import G2TooltipCarousel from '@/views/chart/components/js/G2TooltipCarousel'
const { t } = useI18n()
const DEFAULT_DATA: any[] = []
@@ -81,7 +82,12 @@ export class Bar extends G2ChartView<ViewSpec, G2Column> {
},
interaction: {
elementHighlight: {
background: true
background: true,
region: true
},
elementSelect: {
background: true,
single: true
}
},
tooltip: false,
@@ -110,7 +116,7 @@ export class Bar extends G2ChartView<ViewSpec, G2Column> {
handleChartDashboardHidden(chart, options)
newChart.options(options)
newChart.on('interval:click', action)
configTooltip(newChart, chart)
new G2TooltipCarousel(newChart, chart, data).start()
extremumEvt(newChart, chart, options.children[0], container, scale, this.name === 'bar')
return newChart
}
@@ -246,7 +252,7 @@ export class Bar extends G2ChartView<ViewSpec, G2Column> {
})
const itemsHtml = result
.map(item => {
const marker = item.color
const marker = toLinearGradient(item.color)
const label = item.name
const value = item.value
return TOOLTIP_ITEM_TPL.replace('{marker}', marker)

View File

@@ -58,7 +58,7 @@ export function createTooltipWrapper(chart: Chart) {
g2TooltipWrapper.id = wrapperId
g2TooltipWrapper.style.position = 'absolute'
g2TooltipWrapper.style.pointerEvents = 'none'
g2TooltipWrapper.style.zIndex = '9999'
g2TooltipWrapper.style.zIndex = '2000'
g2TooltipWrapper.style.top = '0px'
document.body.appendChild(g2TooltipWrapper)
}
@@ -88,7 +88,7 @@ export function tooltipCss(tooltipAttr: DeepPartial<ChartTooltipAttr>) {
}
}
export function configTooltip(newChart: G2Chart, chart: Chart) {
export function listenerTooltipShow(newChart: G2Chart, chart: Chart) {
newChart.on('tooltip:show', event => {
const tooltipWrapper = document.getElementById(tooltipWrapperId(chart.container))
const allTooltips = tooltipWrapper?.querySelectorAll('.g2-tooltip')
@@ -100,10 +100,14 @@ export function configTooltip(newChart: G2Chart, chart: Chart) {
}
tooltip.removeEventListener('mouseleave', tooltipMouseleave)
tooltip.addEventListener('mouseleave', tooltipMouseleave)
if (event.client.y < tooltip.getBoundingClientRect().height) {
// 调整 tooltip z-index 和位置
tooltipWrapper.style.zIndex = chart.container.indexOf('viewDialog') > -1 ? '9999' : '2000'
const clientY = event.client?.y
if (!clientY) return
if (clientY < tooltip.getBoundingClientRect().height) {
tooltip.style.top = '0px'
} else {
tooltip.style.top = `${event.client.y - tooltip.getBoundingClientRect().height}px`
tooltip.style.top = `${clientY - tooltip.getBoundingClientRect().height}px`
}
})
})

View File

@@ -4,7 +4,7 @@ import {
} from '@/views/chart/components/js/panel/charts/g2/bar/common'
import { flow, hexColorToRGBA, parseJson } from '@/views/chart/components/js/util'
import {
configTooltip,
listenerTooltipShow,
createTooltipWrapper,
tooltipCss,
ViewSpec
@@ -193,7 +193,7 @@ export class ProgressBar extends HorizontalStackBar {
handleChartDashboardHidden(chart, newOptions)
newChart.options(newOptions)
newChart.on('interval:click', action)
configTooltip(newChart, chart)
listenerTooltipShow(newChart, chart)
return newChart
}

View File

@@ -4,7 +4,7 @@ import {
} from '@/views/chart/components/js/panel/charts/g2/bar/common'
import { flow, parseJson } from '@/views/chart/components/js/util'
import {
configTooltip,
listenerTooltipShow,
createTooltipWrapper,
tooltipCss,
ViewSpec
@@ -118,7 +118,7 @@ export class RangeBar extends HorizontalBar {
handleChartDashboardHidden(chart, options)
newChart.options(options)
newChart.on('interval:click', action)
configTooltip(newChart, chart)
listenerTooltipShow(newChart, chart)
return newChart
}

View File

@@ -2,7 +2,10 @@ import { Chart as G2Column } from '@antv/g2'
import { G2DrawOptions } from '@/views/chart/components/js/panel/types/impl/g2'
import { useI18n } from '@/hooks/web/useI18n'
import { flow, hexColorToRGBA, parseJson } from '@/views/chart/components/js/util'
import { ViewSpec, configTooltip } from '@/views/chart/components/js/panel/charts/g2/bar/barUtil'
import {
ViewSpec,
listenerTooltipShow
} from '@/views/chart/components/js/panel/charts/g2/bar/barUtil'
import {
handleChartDashboardHidden,
setGradientColor
@@ -171,7 +174,7 @@ export class Waterfall extends Bar {
handleChartDashboardHidden(chart, options)
newChart.options(options)
newChart.on('interval:click', action)
configTooltip(newChart, chart)
listenerTooltipShow(newChart, chart)
return newChart
}

View File

@@ -27,6 +27,8 @@ import {
TOOLTIP_TITLE_TPL
} from '../../../common/common_antv'
import { registerSymbol, Symbols } from '@antv/g2/esm/utils/marker'
import G2TooltipCarousel from '@/views/chart/components/js/G2TooltipCarousel'
import { createTooltipWrapper, tooltipCss } from '../bar/barUtil'
const { t } = useI18n()
const DEFAULT_DATA = []
@@ -108,7 +110,7 @@ export class Area extends G2ChartView {
newChart.options(options)
newChart.on('point:click', action)
extremumEvt(newChart, chart, options, container, scale, this.name === 'area')
// configPlotTooltipEvent(chart, newChart)
new G2TooltipCarousel(newChart, chart, data).start()
return newChart
}
@@ -560,39 +562,14 @@ export class Area extends G2ChartView {
pre[next.id] = next
return pre
}, {}) as Record<string, SeriesFormatter>
let g2TooltipWrapper = document.getElementById('G2-TOOLTIP-WRAPPER')
if (!g2TooltipWrapper) {
g2TooltipWrapper = document.createElement('div')
g2TooltipWrapper.id = 'G2-TOOLTIP-WRAPPER'
g2TooltipWrapper.style.position = 'absolute'
g2TooltipWrapper.style.pointerEvents = 'none'
g2TooltipWrapper.style.zIndex = '9999'
document.body.appendChild(g2TooltipWrapper)
}
const yAxis = chart.yAxis
const tooltipOptions: G2Spec = {
tooltip: d => d,
interaction: {
tooltip: {
crosshairsLineDash: [4, 4],
mount: g2TooltipWrapper,
css: {
'.g2-tooltip': {
background: tooltipAttr.backgroundColor
},
'.g2-tooltip-title': {
color: tooltipAttr.color,
'font-size': `${tooltipAttr.fontSize}px`
},
'.g2-tooltip-list-item-name-label': {
color: tooltipAttr.color,
'font-size': `${tooltipAttr.fontSize}px`
},
'.g2-tooltip-list-item-value': {
color: tooltipAttr.color,
'font-size': `${tooltipAttr.fontSize}px`
}
},
mount: createTooltipWrapper(chart),
css: tooltipCss(tooltipAttr),
render: (e, { title, items: originalItems }) => {
const titleHtml = TOOLTIP_TITLE_TPL.replace('{title}', title)
let tooltipItems = originalItems
@@ -844,38 +821,13 @@ export class StackArea extends Area {
defaultsDeep(lineMark, { tooltip: false })
return options
}
let g2TooltipWrapper = document.getElementById('G2-TOOLTIP-WRAPPER')
if (!g2TooltipWrapper) {
g2TooltipWrapper = document.createElement('div')
g2TooltipWrapper.id = 'G2-TOOLTIP-WRAPPER'
g2TooltipWrapper.style.position = 'absolute'
g2TooltipWrapper.style.pointerEvents = 'none'
g2TooltipWrapper.style.zIndex = '9999'
document.body.appendChild(g2TooltipWrapper)
}
const tooltipOptions: G2Spec = {
tooltip: d => d,
interaction: {
tooltip: {
crosshairsLineDash: [4, 4],
mount: g2TooltipWrapper,
css: {
'.g2-tooltip': {
background: tooltipAttr.backgroundColor
},
'.g2-tooltip-title': {
color: tooltipAttr.color,
'font-size': `${tooltipAttr.fontSize}px`
},
'.g2-tooltip-list-item-name-label': {
color: tooltipAttr.color,
'font-size': `${tooltipAttr.fontSize}px`
},
'.g2-tooltip-list-item-value': {
color: tooltipAttr.color,
'font-size': `${tooltipAttr.fontSize}px`
}
},
mount: createTooltipWrapper(chart),
css: tooltipCss(tooltipAttr),
render: (e, { title, items }) => {
const titleHtml = TOOLTIP_TITLE_TPL.replace('{title}', title)
const result = []

View File

@@ -25,6 +25,8 @@ import {
} from '../../../common/common_antv'
import { extremumEvt, addExtremumText } from '@/views/chart/components/js/extremumUitl'
import { registerSymbol, Symbols } from '@antv/g2/esm/utils/marker'
import G2TooltipCarousel from '@/views/chart/components/js/G2TooltipCarousel'
import { createTooltipWrapper, tooltipCss } from '../bar/barUtil'
const { t } = useI18n()
const DEFAULT_DATA = []
@@ -94,7 +96,20 @@ export class Line extends G2ChartView {
}
},
children: [
{ type: 'line', encode: { series: 'category' } },
{
type: 'line',
encode: { series: 'category' },
interaction: {
elementHighlight: {
background: true,
region: true
},
elementSelect: {
background: true,
single: true
}
}
},
{ type: 'point', tooltip: false }
]
}
@@ -104,8 +119,8 @@ export class Line extends G2ChartView {
handleChartDashboardHidden(chart, options)
newChart.options(options)
newChart.on('point:click', action)
new G2TooltipCarousel(newChart, chart, data).start()
extremumEvt(newChart, chart, options, container, scale)
// configPlotTooltipEvent(chart, newChart)
return newChart
}
@@ -625,39 +640,16 @@ export class Line extends G2ChartView {
pre[next.id] = next
return pre
}, {}) as Record<string, SeriesFormatter>
let g2TooltipWrapper = document.getElementById('G2-TOOLTIP-WRAPPER')
if (!g2TooltipWrapper) {
g2TooltipWrapper = document.createElement('div')
g2TooltipWrapper.id = 'G2-TOOLTIP-WRAPPER'
g2TooltipWrapper.style.position = 'absolute'
g2TooltipWrapper.style.pointerEvents = 'none'
g2TooltipWrapper.style.zIndex = '9999'
document.body.appendChild(g2TooltipWrapper)
}
const yAxis = chart.yAxis
const tooltipOptions: G2Spec = {
tooltip: d => d,
interaction: {
...lineMark.interaction,
tooltip: {
crosshairsLineDash: [4, 4],
mount: g2TooltipWrapper,
css: {
'.g2-tooltip': {
background: tooltipAttr.backgroundColor
},
'.g2-tooltip-title': {
color: tooltipAttr.color,
'font-size': `${tooltipAttr.fontSize}px`
},
'.g2-tooltip-list-item-name-label': {
color: tooltipAttr.color,
'font-size': `${tooltipAttr.fontSize}px`
},
'.g2-tooltip-list-item-value': {
color: tooltipAttr.color,
'font-size': `${tooltipAttr.fontSize}px`
}
},
mount: createTooltipWrapper(chart),
css: tooltipCss(tooltipAttr),
position: 'top-right',
render: (e, { title, items: originalItems }) => {
const titleHtml = TOOLTIP_TITLE_TPL.replace('{title}', title)
let tooltipItems = originalItems

View File

@@ -17,12 +17,16 @@ import {
} from '@/views/chart/components/editor/util/chart'
import {
handleChartDashboardHidden,
setGradientColor,
TOOLTIP_ITEM_TPL,
TOOLTIP_TITLE_TPL
} from '../../../common/common_antv'
import { CHART_MIX_EDITOR_PROPERTY, CHART_MIX_EDITOR_PROPERTY_INNER } from './common'
import { registerSymbol, Symbols } from '@antv/g2/esm/utils/marker'
import G2TooltipCarousel from '@/views/chart/components/js/G2TooltipCarousel'
import {
createTooltipWrapper,
tooltipCss
} from '@/views/chart/components/js/panel/charts/g2/bar/barUtil'
const { t } = useI18n()
/**
@@ -214,7 +218,17 @@ export class GroupLineMix extends G2ChartView {
}
]
}
]
],
interaction: {
elementHighlight: {
background: true,
region: true
},
elementSelect: {
background: true,
single: true
}
}
}
const newChart = new G2Chart({ container })
const options = this.setupOptions(chart, initOptions, {
@@ -227,8 +241,7 @@ export class GroupLineMix extends G2ChartView {
newChart.on('interval:click', action)
handleChartDashboardHidden(chart, options)
newChart.options(options)
// extremumEvt(newChart, chart, options, container)
// configPlotTooltipEvent(chart, newChart)
new G2TooltipCarousel(newChart, chart, [...leftData, ...rightData]).start()
return newChart
}
@@ -576,26 +589,11 @@ export class GroupLineMix extends G2ChartView {
const tooltipOptions: G2Spec = {
tooltip: d => d,
interaction: {
...options.interaction,
tooltip: {
crosshairsLineDash: [4, 4],
mount: g2TooltipWrapper,
css: {
'.g2-tooltip': {
background: tooltip.backgroundColor
},
'.g2-tooltip-title': {
color: tooltip.color,
'font-size': `${tooltip.fontSize}px`
},
'.g2-tooltip-list-item-name-label': {
color: tooltip.color,
'font-size': `${tooltip.fontSize}px`
},
'.g2-tooltip-list-item-value': {
color: tooltip.color,
'font-size': `${tooltip.fontSize}px`
}
},
mount: createTooltipWrapper(chart),
css: tooltipCss(tooltip),
render: (_, { title, items }) => {
const titleHtml = TOOLTIP_TITLE_TPL.replace('{title}', title)
if (tooltip.seriesTooltipFormatter?.length) {

View File

@@ -24,6 +24,11 @@ import {
} from '../../../common/common_antv'
import { CHART_MIX_EDITOR_PROPERTY, CHART_MIX_EDITOR_PROPERTY_INNER } from './common'
import { registerSymbol, Symbols } from '@antv/g2/esm/utils/marker'
import G2TooltipCarousel from '@/views/chart/components/js/G2TooltipCarousel'
import {
createTooltipWrapper,
tooltipCss
} from '@/views/chart/components/js/panel/charts/g2/bar/barUtil'
const { t } = useI18n()
/**
@@ -194,7 +199,17 @@ export class GroupLineMix extends G2ChartView {
}
]
}
]
],
interaction: {
elementHighlight: {
background: true,
region: true
},
elementSelect: {
background: true,
single: true
}
}
}
const newChart = new G2Chart({ container })
const options = this.setupOptions(chart, initOptions, {
@@ -207,8 +222,7 @@ export class GroupLineMix extends G2ChartView {
newChart.on('interval:click', action)
handleChartDashboardHidden(chart, options)
newChart.options(options)
// extremumEvt(newChart, chart, options, container)
// configPlotTooltipEvent(chart, newChart)
new G2TooltipCarousel(newChart, chart, [...leftData, ...rightData]).start()
return newChart
}
@@ -551,39 +565,15 @@ export class GroupLineMix extends G2ChartView {
pre[next.id] = next
return pre
}, {}) as Record<string, SeriesFormatter>
let g2TooltipWrapper = document.getElementById('G2-TOOLTIP-WRAPPER')
if (!g2TooltipWrapper) {
g2TooltipWrapper = document.createElement('div')
g2TooltipWrapper.id = 'G2-TOOLTIP-WRAPPER'
g2TooltipWrapper.style.position = 'absolute'
g2TooltipWrapper.style.pointerEvents = 'none'
g2TooltipWrapper.style.zIndex = '9999'
document.body.appendChild(g2TooltipWrapper)
}
const yAxis = chart.yAxis
const tooltipOptions: G2Spec = {
tooltip: d => d,
interaction: {
...options.interaction,
tooltip: {
crosshairsLineDash: [4, 4],
mount: g2TooltipWrapper,
css: {
'.g2-tooltip': {
background: tooltip.backgroundColor
},
'.g2-tooltip-title': {
color: tooltip.color,
'font-size': `${tooltip.fontSize}px`
},
'.g2-tooltip-list-item-name-label': {
color: tooltip.color,
'font-size': `${tooltip.fontSize}px`
},
'.g2-tooltip-list-item-value': {
color: tooltip.color,
'font-size': `${tooltip.fontSize}px`
}
},
mount: createTooltipWrapper(chart),
css: tooltipCss(tooltip),
render: (_, { title, items }) => {
const titleHtml = TOOLTIP_TITLE_TPL.replace('{title}', title)
if (tooltip.seriesTooltipFormatter?.length) {

View File

@@ -23,6 +23,11 @@ import {
} from '../../../common/common_antv'
import { CHART_MIX_EDITOR_PROPERTY, CHART_MIX_EDITOR_PROPERTY_INNER } from './common'
import { registerSymbol, Symbols } from '@antv/g2/esm/utils/marker'
import G2TooltipCarousel from '@/views/chart/components/js/G2TooltipCarousel'
import {
createTooltipWrapper,
tooltipCss
} from '@/views/chart/components/js/panel/charts/g2/bar/barUtil'
const { t } = useI18n()
/**
@@ -192,7 +197,17 @@ export class StackLineMix extends G2ChartView {
}
]
}
]
],
interaction: {
elementHighlight: {
background: true,
region: true
},
elementSelect: {
background: true,
single: true
}
}
}
const newChart = new G2Chart({ container })
const options = this.setupOptions(chart, initOptions, {
@@ -205,8 +220,7 @@ export class StackLineMix extends G2ChartView {
newChart.on('interval:click', action)
handleChartDashboardHidden(chart, options)
newChart.options(options)
// extremumEvt(newChart, chart, options, container)
// configPlotTooltipEvent(chart, newChart)
new G2TooltipCarousel(newChart, chart, [...leftData, ...rightData]).start()
return newChart
}
@@ -549,39 +563,15 @@ export class StackLineMix extends G2ChartView {
pre[next.id] = next
return pre
}, {}) as Record<string, SeriesFormatter>
let g2TooltipWrapper = document.getElementById('G2-TOOLTIP-WRAPPER')
if (!g2TooltipWrapper) {
g2TooltipWrapper = document.createElement('div')
g2TooltipWrapper.id = 'G2-TOOLTIP-WRAPPER'
g2TooltipWrapper.style.position = 'absolute'
g2TooltipWrapper.style.pointerEvents = 'none'
g2TooltipWrapper.style.zIndex = '9999'
document.body.appendChild(g2TooltipWrapper)
}
const { yAxis } = chart
const tooltipOptions: G2Spec = {
tooltip: d => d,
interaction: {
...options.interaction,
tooltip: {
crosshairsLineDash: [4, 4],
mount: g2TooltipWrapper,
css: {
'.g2-tooltip': {
background: tooltip.backgroundColor
},
'.g2-tooltip-title': {
color: tooltip.color,
'font-size': `${tooltip.fontSize}px`
},
'.g2-tooltip-list-item-name-label': {
color: tooltip.color,
'font-size': `${tooltip.fontSize}px`
},
'.g2-tooltip-list-item-value': {
color: tooltip.color,
'font-size': `${tooltip.fontSize}px`
}
},
mount: createTooltipWrapper(chart),
css: tooltipCss(tooltip),
render: (_, { title, items }) => {
const titleHtml = TOOLTIP_TITLE_TPL.replace('{title}', title)
if (tooltip.seriesTooltipFormatter?.length) {

View File

@@ -22,6 +22,11 @@ import {
TOOLTIP_TITLE_TPL
} from '../../../common/common_antv'
import { CHART_MIX_EDITOR_PROPERTY, CHART_MIX_EDITOR_PROPERTY_INNER } from './common'
import G2TooltipCarousel from '@/views/chart/components/js/G2TooltipCarousel'
import {
createTooltipWrapper,
tooltipCss
} from '@/views/chart/components/js/panel/charts/g2/bar/barUtil'
const { t } = useI18n()
/**
@@ -171,7 +176,17 @@ export class ColumnLineMix extends G2ChartView {
},
tooltip: false
}
]
],
interaction: {
elementHighlight: {
background: true,
region: true
},
elementSelect: {
background: true,
single: true
}
}
}
const newChart = new G2Chart({ container })
const options = this.setupOptions(chart, initOptions, { chartObj: newChart })
@@ -180,8 +195,7 @@ export class ColumnLineMix extends G2ChartView {
newChart.on('interval:click', action)
handleChartDashboardHidden(chart, options)
newChart.options(options)
// extremumEvt(newChart, chart, options, container)
// configPlotTooltipEvent(chart, newChart)
new G2TooltipCarousel(newChart, chart, [...leftData, ...rightData]).start()
return newChart
}
@@ -401,39 +415,15 @@ export class ColumnLineMix extends G2ChartView {
pre[next.id] = next
return pre
}, {}) as Record<string, SeriesFormatter>
let g2TooltipWrapper = document.getElementById('G2-TOOLTIP-WRAPPER')
if (!g2TooltipWrapper) {
g2TooltipWrapper = document.createElement('div')
g2TooltipWrapper.id = 'G2-TOOLTIP-WRAPPER'
g2TooltipWrapper.style.position = 'absolute'
g2TooltipWrapper.style.pointerEvents = 'none'
g2TooltipWrapper.style.zIndex = '9999'
document.body.appendChild(g2TooltipWrapper)
}
const yAxis = chart.yAxis
const tooltipOptions: G2Spec = {
tooltip: d => d,
interaction: {
...options.interaction,
tooltip: {
crosshairsLineDash: [4, 4],
mount: g2TooltipWrapper,
css: {
'.g2-tooltip': {
background: tooltip.backgroundColor
},
'.g2-tooltip-title': {
color: tooltip.color,
'font-size': `${tooltip.fontSize}px`
},
'.g2-tooltip-list-item-name-label': {
color: tooltip.color,
'font-size': `${tooltip.fontSize}px`
},
'.g2-tooltip-list-item-value': {
color: tooltip.color,
'font-size': `${tooltip.fontSize}px`
}
},
mount: createTooltipWrapper(chart),
css: tooltipCss(tooltip),
render: (_, { title, items }) => {
const titleHtml = TOOLTIP_TITLE_TPL.replace('{title}', title)
if (tooltip.seriesTooltipFormatter?.length) {

View File

@@ -21,6 +21,8 @@ import { useI18n } from '@/hooks/web/useI18n'
import { valueFormatter } from '@/views/chart/components/js/formatter'
import { cloneDeep, defaultsDeep, isEmpty } from 'lodash-es'
import { Chart as G2Chart, G2Spec } from '@antv/g2'
import G2TooltipCarousel from '@/views/chart/components/js/G2TooltipCarousel'
import { createTooltipWrapper, tooltipCss } from '../../bar/barUtil'
const { t } = useI18n()
@@ -59,6 +61,11 @@ export class Pie extends G2ChartView {
},
coordinate: {
type: 'theta'
},
interaction: {
elementSelect: {
single: true
}
}
}
const total = data.reduce((pre, next) => pre + (next.value ?? 0), 0)
@@ -69,6 +76,7 @@ export class Pie extends G2ChartView {
newChart.on('interval:click', d => {
d.data?.data?.field !== customAttr.basicStyle.topNLabel && action(d)
})
new G2TooltipCarousel(newChart, chart, data).start()
return newChart
}
@@ -215,39 +223,13 @@ export class Pie extends G2ChartView {
pre[next.id] = next
return pre
}, {}) as Record<string, SeriesFormatter>
let g2TooltipWrapper = document.getElementById('G2-TOOLTIP-WRAPPER')
if (!g2TooltipWrapper) {
g2TooltipWrapper = document.createElement('div')
g2TooltipWrapper.id = 'G2-TOOLTIP-WRAPPER'
g2TooltipWrapper.style.position = 'absolute'
g2TooltipWrapper.style.pointerEvents = 'none'
g2TooltipWrapper.style.zIndex = '9999'
document.body.appendChild(g2TooltipWrapper)
}
const { total } = context
const tooltipOptions: G2Spec = {
tooltip: d => d,
interaction: {
tooltip: {
mount: g2TooltipWrapper,
css: {
'.g2-tooltip': {
background: tooltipAttr.backgroundColor
},
'.g2-tooltip-title': {
color: tooltipAttr.color,
'font-size': `${tooltipAttr.fontSize}px`
},
'.g2-tooltip-list-item-name-label': {
color: tooltipAttr.color,
'font-size': `${tooltipAttr.fontSize}px`
},
'.g2-tooltip-list-item-value': {
color: tooltipAttr.color,
'font-size': `${tooltipAttr.fontSize}px`
}
},
mount: createTooltipWrapper(chart),
css: tooltipCss(tooltipAttr),
render: (_, { items }) => {
let tooltipItems = items
if (tooltipAttr.seriesTooltipFormatter?.length) {

View File

@@ -2650,3 +2650,22 @@ export function handleChartDashboardHidden(chart: Chart, options) {
}
}
}
/**
* 将渐变色字符串(如 l(270) 0:rgba(255,69,0,0.3) 1:rgba(255,69,0,1))转换为 CSS 的 linear-gradient 语法
* @param str
*/
export function toLinearGradient(str: string): string {
// 匹配角度和颜色点
const match = str.match(/^l\((\d+)\)\s*(.*)$/)
if (!match) return str
const angle = match[1] === '270' ? '360' : '90'
const stops = match[2]
.split(/\s+/)
.map(s => {
const [offset, color] = s.split(':')
return `${color} ${parseFloat(offset) * 100}%`
})
.join(', ')
return `linear-gradient(${angle}deg, ${stops})`
}

View File

@@ -354,7 +354,11 @@ const renderG2 = async (chart, chartView: G2PlotChartView<any, any>) => {
// 在这里清理掉之前图表的空dom
configEmptyDataStyle([1], containerId)
myChart?.destroy()
const dashboardHidden = props.element.dashboardHidden
// 处理图表在右侧小区域时隐藏文本标识
let dashboardHidden = props.element.dashboardHidden
if (dvMainStore.mobileInPc) {
dashboardHidden = !props.element.inMobile
}
myChart = await chartView.drawChart({
chartObj: myChart,
container: containerId,

View File

@@ -88,7 +88,6 @@ const state = reactive({
]
})
const dialogInit = (dvType = 'dashboard') => {
ChartCarouselTooltip.paused()
curDvType.value = dvType
dialogShow.value = true
dvMainStore.initCurMultiplexingComponents()
@@ -110,7 +109,7 @@ const saveMultiplexing = () => {
})
}
const handleClose = () => {
ChartCarouselTooltip.closeEnlargeDialogDestroy()
G2TooltipCarousel.closeEnlargeDialogDestroy()
}
defineExpose({
dialogInit