深度解析HiCharts联动与交互:手把手实现数据与图表的“对话”

前言

通过前面的Hicharts入门,本文将继续大家探索HiCharts的功能,通过实现三个功能区域缩放、数据联动、图表点击,学习HiCharts的其他常用功能,看看他们之间可以碰撞出什么火花呢?

功能示例

  • 区域缩放:框选一定范围内曲线,单独显示
  • 数据联动:框选曲线,展示框选的曲线数据
  • 图表点击:点击某个曲线点,产生交互

往期文章

HiCharts??与Echarts一样的图表库??

一、区域缩放

HiCharts中实现区域缩放也是及其简单,只需要加上如下配置项即可

yaml 复制代码
chart: {
  zoomType: "x",
  panning: true,
  panKey: "shift",
}
配置 说明
zoomType 缩放类型
panning 允许在图表中平移。最好与panKey配合使用 ,以结合缩放和平移功能
panKey 允许设置一个按键来在缩放和平移之间切换,

二、数据联动

接下来我们需要实现,框选一定范围内的曲线,然后数据表格跟着联动更新,只展示图表中框选的数据;具体实现的实现步骤如下

  • 监听框选事件
  • 取出框选范围
  • 筛选框选的数据并更新表格

主要难点在于,如何取出框选的范围?那就需要用到selection函数了

selection: 是 Highcharts 中的一个 图表事件回调函数 ,专门用于处理用户在图表上通过鼠标拖动进行 框选(范围选择) 的操作。

为了方便演示功能,稍稍改造前面的示例代码,加上table表格展示,代码如下

css 复制代码
<script setup lang="ts">
import Highcharts from 'highcharts'
import {onMounted, ref} from "vue";

interface PhoneSalesData {
  index: number;
  month: string;
  xiaomi: number;
  huawei: number;
}

const chartRef = ref<HTMLDivElement>()
const tableRef = ref()
const selectedRows = ref<PhoneSalesData[]>([])

const allPhoneSalesData: PhoneSalesData[] = [
  {index: 0, month: '1月', xiaomi: 4.2, huawei: 8.5},
  {index: 1, month: '2月', xiaomi: 3.8, huawei: 7.8},
  {index: 2, month: '3月', xiaomi: 4.5, huawei: 9.1},
  {index: 3, month: '4月', xiaomi: 5.1, huawei: 6.2},
  {index: 4, month: '5月', xiaomi: 5.7, huawei: 5.4},
  {index: 5, month: '6月', xiaomi: 6.3, huawei: 10.9},
  {index: 6, month: '7月', xiaomi: 6.8, huawei: 12.2},
  {index: 7, month: '8月', xiaomi: 7.2, huawei: 11.8},
  {index: 8, month: '9月', xiaomi: 7.5, huawei: 10.5},
  {index: 9, month: '10月', xiaomi: 8.1, huawei: 8.3},
  {index: 10, month: '11月', xiaomi: 8.6, huawei: 9.2},
  {index: 11, month: '12月', xiaomi: 9.2, huawei: 11.8},
]

const tableData = ref<PhoneSalesData[]>([...allPhoneSalesData])

const handleSelectionChange = (val: PhoneSalesData[]) => {
  selectedRows.value = val
}

const initCharts = () => {
  // 初始化图表配置
  const options = {
    chart: {
      zoomType: "x",
      panning: true,
      panKey: "shift",
      events: {
        selection: function (e) {
          console.log(e);
          return true;
        }
      }
    },
    title: {
      text: '手机品牌月度销量',
      align: 'left'
    },
    subtitle: {
      text: '市场调研',
      align: 'left'
    },
    yAxis: {
      title: {
        text: '销量 (百万台)'
      }
    },
    xAxis: {
      categories: tableData.value.map(item => item.month)
    },
    legend: {
      enabled: true, // 显示图例
      align: 'right',
      verticalAlign: 'top'
    },
    tooltip: {
      valueSuffix: ' 百万台'
    },
    plotOptions: {
      series: {
        marker: {
          enabled: true // 显示数据点
        }
      }
    },
    series: [
      {
        name: '小米',
        data: tableData.value.map(item => item.xiaomi)
      },
      {
        name: '华为',
        data: tableData.value.map(item => item.huawei)
      }
    ]
  }
  // 初始化图表
  Highcharts.chart(chartRef.value, options);
}

onMounted(() => {
  initCharts();
})
</script>

<template>
  <div>
    <div ref="chartRef" class="chart"></div>

    <div class="data-table">
      <h3>销售数据表</h3>
      <el-table
          ref="tableRef"
          :data="tableData"
          style="width: 100%"
          @selection-change="handleSelectionChange"
          border
          stripe
          height="350px"
      >
        <el-table-column type="selection" width="55"/>
        <el-table-column prop="month" label="月份" width="120"/>
        <el-table-column prop="xiaomi" label="小米 (百万台)"/>
        <el-table-column prop="huawei" label="华为 (百万台)"/>
      </el-table>
    </div>
  </div>
</template>

<style scoped>
.chart {
  width: 100%;
  height: 350px;
  margin-top: 20px;
  border: 1px solid #eee;
  border-radius: 4px;
  padding: 10px;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}

.data-table {
  margin-top: 30px;
  padding: 20px;
  border: 1px solid #eee;
  border-radius: 4px;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}

.data-table h3 {
  margin-top: 0;
  margin-bottom: 15px;
  color: #333;
}
</style>

其中selection已经定义好了,只要图表发生框选事件,我们就可以拿到框选的信息,控制台打印如下

参数 说明
xAxis X 轴范围数组,每个对象对应一个 X 轴
yAxis Y 轴范围数组
type 事件类型,值为字符串

展开 xAxis,我们可以看到共有两个对象属性,每个对象对应一个X轴,框选的范围我们便可以从这个对象数组取出,min代表最小范围,max代表最大范围,等下我们可以基于这两个参数,筛选出图表中在这个范围内的点

arduino 复制代码
selection: function (e) {
  console.log(e);

  // 取出x轴的最小值和最大值
  const minX = e.xAxis[0].min;
  const maxX = e.xAxis[0].max;

  // 遍历曲线点,判断是否在框选范围内
  for (const series of this.series) {
    // 过滤符合条件的点
    const points = series.points.filter(
        point => point.x >= minX && point.x <= maxX
    );

    if (points.length === 0) continue; // 如果没有选中点,跳过后续处理
    console.log(points)
  }

  return true;
}

通过上述代码,我们实现了取出每条曲线被框选到曲线点数据,打印结果如下图

展开某个曲线点,里面记录着曲线点的一些基本信息,比如X轴位置、Y轴位置、颜色等信息,当然也有我们所定义的数据信息,比如 index,通过index去匹配表格数据,然后更新表格的数据,就可以达到图表与表格的数据联动效果,通过 keySet 去筛选数据,然后表格数据

在筛选框选范围的曲线点的同时,记录下曲线点记录下的数据标识,根据这个标识去筛选数据,然后更新我们数据表格,便可以达到曲线和表格联动效果

arduino 复制代码
selection: function (e) {
  console.log(e);

  // 取出x轴的最小值和最大值
  const minX = e.xAxis[0].min;
  const maxX = e.xAxis[0].max;

  const keySet = new Set();
  // 遍历曲线点,判断是否在框选范围内
  for (const series of this.series) {
    // 过滤符合条件的点
    const points = series.points.filter(
        point => point.x >= minX && point.x <= maxX
    );

    if (points.length === 0) continue; // 如果没有选中点,跳过后续处理
    console.log(points)
    // 取出选中点的 index
    points.forEach((point) => keySet.add(point.index))
  }
  console.log(keySet)

  // 筛选数据
  tableData.value = allPhoneSalesData.filter(item => keySet.has(item.index))

  return true;
}

此时效果虽然实现了,但是还有点问题,每次发生框选行为后,右上角会出现一个重置缩放的按钮,此时点击会没有效果,控制台会报错,

这是因为 框选 和 重置缩放 都会触发selection函数,其中 重置缩放 所得到的值与框选的值有所不同,重置缩放是没有 xAxis和yAxis对象的,但是多了resetSelection属性,所以我们需要加上一个判断,如果是重置缩放,我们需要放行,不进行筛选操作,同时恢复表格数据(因为用户前面可能框选了数据,那此时表格数据不是完整的,那么重置缩放之后,我们也需要进行恢复)

kotlin 复制代码
// 缩放重置时直接返回,不处理任何数据
if (e.resetSelection) {
  // 恢复数据
  tableData.value = [...allPhoneSalesData]
  return true;
}

效果如下图

三、曲线点击

不仅有监听框选数据的函数,还有监听曲线点击的函数,其配置在plotOptions -> series -> events -> click下,示例代码

css 复制代码
plotOptions: {
  series: {
    events: {
      click: async function (e) {
        console.log(e);
      }
    }
  }
}

通过点击返回的数据,我们可以去匹配到该条表格数据,从而实现一些特殊操作,比如将对应的表格数据移动到顶部

ini 复制代码
click: async function (e) {
  console.log(e);
  // 取出 index 值
  const index = e.point.index;
  console.log("index", index);
  // 匹配对应的表格数据
  const findIndex = allPhoneSalesData.findIndex(item => item.index == index);
  if (findIndex == -1) return;
  const matchData = allPhoneSalesData[findIndex];
  // 将该条数据移到顶部
  tableData.value = [matchData, ...tableData.value.filter(item => item.index !== matchData.index)]
}

四、结语

HiCharts在数据交互这块,整体感觉还不错,使用者可以通过各种API去扩展操作,例如前面实现的图表与表格的联动,也正是因为这一点,所以去专门去学习研究了一下,发现效果还是不错的。如果有错误之处,欢迎讨论指正!!!

相关推荐
菠萝+冰1 小时前
在 React 中,父子组件之间的通信(传参和传方法)
前端·javascript·react.js
庚云1 小时前
一套代码如何同时适配移动端和pc端
前端
Jinuss1 小时前
Vue3源码reactivity响应式篇Reflect和Proxy详解
前端·vue3
海天胜景1 小时前
vue3 el-select 默认选中第一个
前端·javascript·vue.js
小小怪下士_---_1 小时前
uniapp开发微信小程序自定义导航栏
前端·vue.js·微信小程序·小程序·uni-app
前端W1 小时前
腾讯地图组件使用说明文档
前端
页面魔术1 小时前
无虚拟dom怎么又流行起来了?
前端·javascript·vue.js
胡gh1 小时前
如何聊懒加载,只说个懒可不行
前端·react.js·面试
Double__King2 小时前
巧用 CSS 伪元素,让背景图自适应保持比例
前端