一、Grafana ECharts 基本结构

Grafana 用的是 (echarts-panel),核心是一段 JS 函数,最后 return 一个标准 ECharts option

function(data, context) {
  // 1. 取数据:data.series / data.fields / data.timeRange ...
  // 2. 组装成 ECharts 的 option
  let option = {
    // 标准 ECharts 配置
  };
  return option;
}

//对于旧版可以直接定义 
let option = {
    // 标准 ECharts 配置
  };
return option;

在 ECharts 代码中添加打印语句

// Grafana 会自动将查询结果注入到全局变量 data 中
console.log("=== 完整 Grafana 数据结构 ===", data);
console.log("=== 数据系列列表 ===", data.series);
console.log("=== 查询状态 ===", data.state);
console.log("=== 数据系列数量 ===", data.series.length);


// 系列A
const seriesA = data.series.find(s => s.refId === "A");
console.log("\n=== 系列A(数值数据)详情 ===", seriesA);
console.log("系列A字段定义:", seriesA.fields);
const timeFieldA = seriesA.fields.find(f => f.type === "time");
const valueFieldA = seriesA.fields.find(f => f.name === "valueFloat");

// 系列B
const seriesB = data.series.find(s => s.refId === "B");
console.log("\n=== 系列B(字符串数据)详情 ===", seriesB);
console.log("系列B字段定义:", seriesB.fields);

...

示例:

软件版本:grafana v8.2.7 + InfluxDB 1.X + echart 2.2.4

Grafana中的查询SQL

通过打印数据结构:console.log("=== 完整 Grafana 数据结构 ===", data);

打印出的是Grafana v8.x 特有的 "字段值数组" 格式,每个字段独立存储自己的所有值,而不是按行存储。

// 根对象 data
{
  state: "Done",                // 查询状态
  series: [                     // 3个数据系列,对应你的3个查询A/B/C
    // 系列0:查询A - PV(过程值)
    {
      name: "PV",               // 系列名称
      refId: "A",               // 对应查询面板的查询ID
      length: 774,              // 数据点总数(774个时间点)
      meta: { executedQueryString: "..." }, // 实际执行的SQL
      fields: [                 // 2个字段:时间 + 数值
        // 字段0:时间字段
        {
          name: "Time",
          type: "time",
          values: [1716123456000, 1716123457000, ...], // 774个时间戳(毫秒)
          config: {},
          state: {}
        },
        // 字段1:数值字段
        {
          name: "Value",
          type: "number",
          values: [23.5, 24.1, 23.8, ...], // 774个PV值
          labels: undefined
        }
      ]
    },
    // 系列1:查询B - Recipe(配方)
    {
      name: "Recipe",
      refId: "B",
      length: 8,                // 只有8个数据点
      fields: [
        { name: "Time", type: "time", values: [...] }, // 8个时间戳
        { name: "Value", type: "number", values: [...] }  // 8个配方值
      ]
    },
    // 系列2:查询C - SV(设定值)
    {
      name: "SV",
      refId: "C",
      length: 8,                // 只有8个数据点
      fields: [
        { name: "Time", type: "time", values: [...] }, // 8个时间戳
        { name: "Value", type: "number", values: [...] }  // 8个设定值
      ]
    }
  ],
  timeRange: { from: T, to: T }, // 当前面板时间范围
  annotations: [],
  error: undefined,
  request: { ... },
  structureRev: 8,
  timings: { dataProcessingTime: 0 }
}

二、数据提取

console.log("=== 完整 Grafana 数据结构 ===", data);
console.log("=== 顶层 series 数组 ===", data?.series ?? []);
console.log("=== 查询状态 ===", data?.state);
console.log("=== 系列总数 ===", Array.isArray(data?.series) ? data.series.length : 0);

/**
 * 工具:安全判断有效非空数组
 * @param {*} arr
 * @returns {boolean}
 */
function isSafeArray(arr) {
  return Array.isArray(arr) && arr.length > 0;
}

/**
 * 按 refId 匹配,返回对应单条系列对象
 * @param {Object|null|undefined} data Grafana 根数据
 * @param {string} refId 查询标识 A/B/C
 * @returns {Object|null} 单个series对象
 */
function getSeriesByRefId(data, refId) {
  if (!data || !isSafeArray(data.series)) return null;
  return data.series.find(item => item.refId === refId) || null;
}

/**
 * 根据字段固定下标提取数值数组
 * 约定:fields[0] = Time,fields[1] = valueInt
 * @param {Object|null} singleSeries 单个series对象
 * @param {number} fieldIndex 0=Time / 1=valueInt
 * @returns {Array} 数据数组,无数据返回[]
 */
function getFieldByIndex(singleSeries, fieldIndex) {
  // 校验series和fields数组
  if (!singleSeries || !isSafeArray(singleSeries.fields)) return [];
  // 下标越界直接返回空
  if (fieldIndex < 0 || fieldIndex >= singleSeries.fields.length) return [];

  const targetField = singleSeries.fields[fieldIndex];
  if (!targetField?.values) return [];

  // 兼容 Grafana 两种存储格式:buffer / 直接values数组
  const valueList = targetField.values.buffer ?? targetField.values;
  return isSafeArray(valueList) ? valueList : [];
}

// ====================== 业务提取逻辑 ======================
// 1. 根据refId获取目标单系列对象
const targetSeriesItem = getSeriesByRefId(data, "A");

// 2. 按下标分别提取时间、数值
const timeArr = getFieldByIndex(targetSeriesItem, 0);    // fields[0] Time
const valueIntArr = getFieldByIndex(targetSeriesItem, 1); // fields[1] valueInt

console.log("时间数组 Time(fields[0]):", timeArr);
console.log("整型数值 valueInt(fields[1]):", valueIntArr);

三、ECharts 核心配置项

1. title 标题

title: {
  text: 'CPU 使用率',
  subtext: '近1小时',
  left: 'center', // left/right/center/百分比
  top: 10,
  textStyle: { fontSize: 16, color: '#fff' }
}

2. tooltip 提示框(重点!)

tooltip: {
  trigger: 'axis', // axis(坐标轴触发)/ item(图形触发,饼图)
  show: true,
  formatter: '{b}: {c} %', // 自定义格式
  axisPointer: { type: 'cross' } // 十字准星
}

3. legend 图例

legend: {
  data: ['CPU', '内存'], // 对应 series.name
  top: 0,
  textStyle: { color: '#ccc' }
}

4. grid 绘图网格(直角坐标系)

grid: {
  left: '3%', right: '4%', bottom: '3%', top: '15%',
  containLabel: true // 包含坐标轴标签,防止溢出
}

5. xAxis /yAxis 坐标轴

时间轴(最常用)

xAxis: {
  type: 'time', // time / category / value
  axisLabel: {
    formatter: '{HH}:{mm}', // 时间格式化
    color: '#ccc'
  }
}

类目轴(分类)

xAxis: {
  type: 'category',
  data: ['周一','周二','周三']
}

数值轴

yAxis: {
  type: 'value',
  name: '使用率(%)',
  min: 0, max: 100
}

6. series 系列(最重要,决定图表类型)

数组,每个元素对应一条曲线 / 柱 / 饼:

series: [
  {
    name: 'CPU',
    type: 'line', // line/bar/pie/gauge/radar...
    data: [/* 数组:[时间, 值] 或 [值] */],
    smooth: true, // 平滑曲线
    lineStyle: { width: 2 },
    itemStyle: { color: '#ff9800' }
  }
]

7. 其他常用

  • color: ['#ff9800', '#2196f3']:全局配色
  • backgroundColor: 'transparent':背景(Grafana 面板透明)
  • animation: true:开启动画

8.dataZoom

dataZoom = 区域缩放 + 平移,解决数据太多挤在一起看不清的问题

  • inside:纯鼠标 / 手势(滚轮缩放、拖拽平移、框选),无 UI 条
  • slider:底部 / 右侧滑块条,可拖动、缩放,有 UI
    dataZoom: [
      {
        type: 'inside',
        xAxisIndex: 0,          // 控制哪个 x 轴(时序固定 0)
        zoomOnMouseWheel: true, //是否滚轮缩放
        moveOnMouseMove: true,   //拖拽平移
        start: 0, end: 100,     // 起止百分比 0–100
        filterMode: 'empty'
      },
      {
        type: 'slider',
        xAxisIndex: 0,
        start: 0, end: 100,
        height: 14,
        bottom: 10,
        backgroundColor: 'rgba(255,255,255,0.1)',
        fillerColor: 'rgba(70,130,180,0.4)',
        handleStyle: { color: '#4682b4' }
      }
    ]

示例:

// ==================== 最终图表配置 ====================
const option = {
  tooltip: {
    trigger: 'axis',
    formatter: params => {
      const i = params?.[0]?.dataIndex;
      if (i == null || !groupAvg[i]) return '无数据';
      // 升级:鼠标悬浮显示【当前配方】
      return `
        当前配方:${groupRecipes[i]}<br/>
        直径均值:${groupAvg[i].toFixed(3)}<br/>
        中心线 CL:${cl.toFixed(3)}<br/>
        上控线 UCL:${ucl.toFixed(3)}<br/>
        下控制线 LCL:${lcl.toFixed(3)}
      `;
    }
  },
  legend: {
    show: true,
    top: 5,
    textStyle: { fontSize: 15, color: '#333' },
    itemWidth: 18,
    itemHeight: 14
  },
  dataZoom: [
    { type: 'inside' },
    { type: 'slider', show: true, bottom: 5 }
  ],
  grid: [
    { left: '3%', right: '3%', top: '8%', height: '55%' },
    { left: '3%', right: '3%', top: '70%', height: '18%' }
  ],
  xAxis: [
    {
      type: 'category',
      data: groupX || [],
      axisLabel: { show: false },
      axisLine: { show: false }
    },
    {
      type: 'category',
      data: groupX || [],
      gridIndex: 1,
      axisLabel: { show: false },
      axisLine: { show: false }
    }
  ],
  yAxis: [
    {
      type: 'value',
      axisLabel: { show: true, fontSize: 11 },
      axisTick: { show: false },
      splitLine: { show: false },
      min: yMin,
      max: yMax
    },
    {
      type: 'value',
      gridIndex: 1,
      axisLabel: { show: false },
      axisTick: { show: false },
      splitLine: { show: false }
    }
  ],
  series: [
    {
      name: '直径组均值',
      type: 'line',
      data: groupAvg || [],
      itemStyle: { color: '#FFC107' },
      lineStyle: { color: '#FFC107', width: 2 },
      symbolSize: 10,
      markLine: {
        animation: false,
        data: recipeMarkLines
      }
    },
    {
      name: '中心线 CL',
      type: 'line',
      data: groupX.map(() => cl) || [],
      lineStyle: { color: '#00B42A', width: 3 },
      symbol: 'none'
    },
    {
      name: '上控制线 UCL',
      type: 'line',
      data: groupX.map(() => ucl) || [],
      lineStyle: { color: '#1890FF', type: 'dashed', width: 2 },
      symbol: 'none'
    },
    {
      name: '下控制线 LCL',
      type: 'line',
      data: groupX.map(() => lcl) || [],
      lineStyle: { color: '#333', type: 'dashed', width: 2 },
      symbol: 'none'
    }
  ]
};

return option;

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐