自定义tooltip,解决图表数据重叠,无法准确点击问题!

自定义tooltip,解决图表数据重叠,无法准确点击事件问题!

效果图

问题: 图表数据重叠,导致无法准确点击事件;

解决方式: 发现小程序里 ECharts 的 canvas tooltip 无法对单行做点击,所以改为自定义 tooltip 浮层,交互如下:

  1. 点击图表某一列区域(不必点中数字)→ 弹出 tooltip;
  2. 点击 tooltip 中的「内容」行 → 实现跳转页面等逻辑;
  3. 点击图表空白处 → 关闭 tooltip;
解决方式

修改文件:

  1. index.js

    • 关闭 ECharts 内置 tooltip
    • 去掉折线数字上的 triggerEvent(不再通过数字点击跳转)
    • 监听图表区域点击,按坐标换算 dataIndex 并显示自定义 tooltip
    • 行点击后触发 lineclick 事件
  2. index.wxml、index.wxss

    • 新增可点击的 tooltip 浮层,带与图例一致的颜色标记;
    • 可根据图例颜色给tooltip-marker修改颜色;这里示例就没有进一步处理了;
html 复制代码
     <view class="echartsBox">
       <block wx:if="{{ chartData && chartData.length }}">
         <ec-canvas id="{{ chartId }}" canvas-id="{{ canvasId }}" ec="{{ ec }}"></ec-canvas>
           
         <view
           wx:if="{{ tooltipPanel.visible }}"
           class="chart-tooltip-panel"
           style="left: {{ tooltipPanel.left }}px; top: {{ tooltipPanel.top }}px;"
           catchtap="noop"
         >
           <view class="tooltip-title">{{ tooltipPanel.name }}</view>
           <view class="tooltip-row tooltip-row-action" catchtap="onTooltipRowTap" data-type="4">
             <view class="tooltip-label">
               <view class="tooltip-marker tooltip-marker-bar"></view>
               <text>{{ tooltipPanel.largeLabel }}</text>
             </view>
             <text class="tooltip-value">{{ tooltipPanel.largeValue }}</text>
           </view>
         </view>
       </block>
         
       <view wx:else class="empty-tip">
         暂无数据
       </view>
     </view>
javascript 复制代码
/** 自定义 tooltip 预估尺寸(px),用于边界约束 */
const TOOLTIP_PANEL_WIDTH = 168;
const TOOLTIP_PANEL_HEIGHT = 128;

/**
 * 图表点击事件
 */
bindChartClick(chart) {
    if (!chart) return;
    const zr = chart.getZr();
    zr.off('click');
    zr.on('click', (event) => {
        if (!componentRef) return;
        const pointInPixel = [event.offsetX, event.offsetY];
        if (!chart.containPixel('grid', pointInPixel)) {
            componentRef.hideTooltipPanel();
            return;
        }
        const dataIndex = componentRef.resolveDataIndex(chart, pointInPixel);
        if (dataIndex != null) {
            componentRef.showTooltipPanel(dataIndex, pointInPixel);
        }
    });
},

/**
 * 显示 panel
 * 监听图表区域点击,按坐标换算
 */
showTooltipPanel(dataIndex, pointInPixel) {
	const dataList = this.data.dataList;
	const item = dataList[dataIndex];
	if (!item) return;

	const legendData = this.properties.chartLegend || [];

	this.createSelectorQuery()
		.select('.echartsBox')
		.boundingClientRect((rect) => {
			let left = pointInPixel[0] + 10;
			let top = pointInPixel[1] - TOOLTIP_PANEL_HEIGHT - 10;

			if (rect) {
				if (left + TOOLTIP_PANEL_WIDTH > rect.width) {
					left = rect.width - TOOLTIP_PANEL_WIDTH - 8;
				}
				if (left < 8) left = 8;
				if (top < 8) top = pointInPixel[1] + 12;
				if (top + TOOLTIP_PANEL_HEIGHT > rect.height) {
					top = rect.height - TOOLTIP_PANEL_HEIGHT - 8;
				}
			}

			this.setData({
				tooltipPanel: {
					visible: true,
					left,
					top,
					dataIndex,
					name: item.name,
                    // TEST ,可根据需求遍历legendData,赋值
					label: legendData[0],
					value: item.value,
				},
			});
		})
		.exec();
},

/**
 * 隐藏 panel
 */
hideTooltipPanel() {
	if (!this.data.tooltipPanel.visible) return;
	this.setData({
		'tooltipPanel.visible': false,
	});
},

/**
 * 点击 tool 行跳转事件
 */
onTooltipRowTap(e) {
    const type = Number(e.currentTarget.dataset.ype);
    const {
        dataIndex,
        visible
    } = this.data.tooltipPanel;
    if (!visible || dataIndex == null) return;

    const dataList = this.getSortedChartData();
    const item = dataList[dataIndex];
    if (!item) return;


    this.triggerEvent('lineclick', {
        item,
        dataIndex,
        type,
    });
    this.hideTooltipPanel();
},
css 复制代码
.echartsBox {
  position: relative;
  width: 100%;
  flex: 1;
  min-height: 0;
  height: 100%;
  padding-top: 20rpx;
  background: #fff;
  overflow: hidden;
  box-sizing: border-box;
}

.chart-tooltip-panel {
  position: absolute;
  z-index: 10;
  min-width: 280rpx;
  max-width: 420rpx;
  padding: 16rpx 20rpx;
  background: rgba(255, 255, 255, 0.98);
  border: 1px solid #e8e8e8;
  border-radius: 8rpx;
  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
  box-sizing: border-box;
  pointer-events: auto;
}

.tooltip-title {
  margin-bottom: 8rpx;
  font-size: 22rpx;
  font-weight: 500;
  color: #1e1e1e;
}

.tooltip-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20rpx;
  padding: 6rpx 0;
  font-size: 20rpx;
  color: #333;
  line-height: 1.4;
}

.tooltip-row-action {
  margin: 0 -12rpx;
  padding: 10rpx 12rpx;
  border-radius: 6rpx;
}

.tooltip-row-action:active {
  background: #f5f7fa;
}

.tooltip-label {
  display: flex;
  align-items: center;
  flex: 1;
  min-width: 0;
}

.tooltip-marker {
  flex-shrink: 0;
  width: 16rpx;
  height: 16rpx;
  margin-right: 10rpx;
}

.tooltip-marker-bar {
  border-radius: 2rpx;
  background: #96e6a1;
}

.tooltip-value {
  flex-shrink: 0;
  color: #1e1e1e;
}
相关推荐
h_65432107 天前
echarts实现柱状图加一条横线
前端·echarts
速易达网络7 天前
ECharts 图表制作完全指南:从零到可视化实战
前端·javascript·echarts
paopaokaka_luck7 天前
灾害应急物资管理系统(AI应急助手、Echarts可视化数据看板、捐赠入库联动、 物资全流程管理、库存变动追溯、灾情资讯与互动反馈、物资发放公示)
java·前端·javascript·spring boot·mysql·tomcat·echarts
paopaokaka_luck10 天前
基于springboot+vue的益暖微光公益服务平台(DeepSeek公益助手、善款信息追溯、志愿服务积分闭环、ECharts运营分析、活动状态自动流转)
java·前端·javascript·spring boot·mysql·架构·echarts
腾讯数据架构师11 天前
任务跑完怎么直接出图表?CubeStudio 任务可视化 ECharts 数据格式实操(CSV/JSON + metric.json,11 种图表一键渲染)
前端·json·echarts·数据可视化
总要学点什么12 天前
第81篇:数据可视化-PM2.5分布——让圆点的胖瘦替每座城市报出浓度
前端·信息可视化·echarts·cesium·空间分析
paopaokaka_luck12 天前
社区服务平台(加权协同过滤推荐、Jieba中文词频分析、WebSocket实时聊天、求助处理闭环、 活动积分闭环、ECharts多维分析)
java·前端·spring boot·mysql·echarts·mybatis
mmsx12 天前
Android 并发考古:newThread 与 RxJava 共存,以及 GlobalScope 的三宗罪
android·echarts·rxjava
DataGear13 天前
DataGear 6.1.1 发布,数据可视化分析平台
javascript·信息可视化·数据分析·echarts·数据可视化
paopaokaka_luck14 天前
流浪动物救助与收养系统(DeepSeek AI对话、 ECharts多维分析、救助流程衔接、领养与回访衔接、 捐赠确认与公示、志愿者协作)
java·前端·javascript·人工智能·mysql·echarts·推荐算法