echarts x轴的颜色-Echarts没能实现这个图,所以我手写一张

2023-08-21 0 1,871 百度已收录

本文适合对手动实现图表感兴趣的朋友阅读

欢迎关注后台下午茶,携手广州亮子,共同前进~

一、简介

最近有一个图表需求,无论怎么配置都配置不好,很头疼。 所以想利用这个问题实现一个手写交互体验好的图表,支持打开动画,自动适应父包长度,比echarts更齐全的配置项,更好的自定义分区线等等效果如下

2. 起源

  visualMap: {
    type'piecewise',
    showfalse,
    dimension0,
    seriesIndex0,
    pieces: [
      {
        gt1,
        lt3,
        color'rgba(0, 0, 180, 0.4)'
      },
      {
        gt5,
        lt7,
        color'rgba(0, 0, 180, 0.4)'
      }
    ]
  },

这里摘自echarts官网。 颜色更难定制。 这样的话就不好做梯度了,但是实现了分区。 手动狗头,我们再看一个例子

  series: [
    {
      type'line',
      smooth0.6,
      symbol'none',
      lineStyle: {
        color'#5470C6',
        width5
      },
      markLine: {
        symbol: ['none''none'],
        label: { showfalse },
        data: [{ xAxis1 }, { xAxis3 }, { xAxis5 }, { xAxis7 }]
      },
      areaStyle: {},
      data: [
        ['2019-10-10'200],
        ['2019-10-11'560],
        ['2019-10-12'750],
        ['2019-10-13'580],
        ['2019-10-14'250],
        ['2019-10-15'300],
        ['2019-10-16'450],
        ['2019-10-17'300],
        ['2019-10-18'100]
      ]
    }
  ]

这种情况下,分区的线段颜色是可以的,但是无法区分渐变,只能统一一个区域的渐变。

所以,在我们平时的开发中,折线图或者曲线图通常使用echarts就绰绰有余了,但是总是有很多配置让人困惑,比如分区、分区的填充颜色要渐变、线段要是渐变的,并且应该支持悬停。 修改填充颜色、标签更新等虽然有一部分可以用echarts的markArea来实现,但是看着那几个郁闷的API,既然追求完美落地,那就硬着头皮手写一下吧。

3.曲线分析思路

从一般的canvas绘制思路来看,我们首先要把它分为3层。 红色区域代表辅助图层(坐标轴、标签、辅助线、图例等),绿色区域图表层(折线、曲线等),蓝色区域标签层(标签数据显示卡等) 。 之所以分层是为了后期更容易管理视口,不然做动画和清理画布会很麻烦。

如何进行适配

这里有一个细节,canvas必须设置为width和height,而不是canvas.style.width,这样在窗口缩放场景下会出现问题。 这是最关键的一点。 其次,我们传入的axisX和axisY数据一定要知道它只是多个副本。 我们想要映射的是副本数。 例如,对于宽度为1000px的屏幕,我们取10份就是100px和500px的屏幕,取10份就是50px。 我们通常只需要考虑长度的缩放。

考虑扩展

  // 计算 Y 轴坐标比例尺 ratioY
  maxY = Math.max.apply(null, concatData);
  minY = Math.min.apply(null, concatData);
  rangeY = maxY - minY;
  // 数据和坐标范围的比值
  ratioY = (height - 2 * margin) / rangeY;
  // 计算 X 轴坐标比例尺和步长
  count = concatData.length;
  rangeX = width - 2 * margin;
  xk = 1, xkVal = xk * margin
  dataLen = data.length
  ratioX = rangeX / (count - dataLen);
  stepX = ratioX;

绘制

/**
 * 绘制坐标轴
 */

function drawAxis({
  ctx.beginPath();
  ctx.moveTo(margin, margin);
  ctx.lineTo(margin, height - margin);
  ctx.lineTo(width - margin + 2, height - margin);
  ctx.setLineDash([33])
  ctx.strokeStyle = '#aaa'
  ctx.stroke();
  ctx.setLineDash([1])
  const yLen = newOpt.axisY.data.length
  const xLen = newOpt.axisX.data.length

  // 绘制 Y 轴坐标标记和标签
  for (let i = 0; i < yLen; i++) {
    let y = (rangeY * i) / (yLen - 1) + minY;
    let yPos = height - margin - (y - minY) * ratioY;

    if (i) {
      ctx.beginPath();
      ctx.moveTo(margin, yPos);
      ctx.lineTo(width - margin, yPos);
      ctx.strokeStyle = '#ddd'
      ctx.stroke();
    }

    ctx.beginPath();
    ctx.stroke();
    newYs = []
    for (const val of options.axisY.data) {
      newYs.push(options.axisY.format(val))
    }
    ctx.fillText(newYs[i] + '', margin - 15 - options.axisY.right, yPos + 5);
    firstEnding && axisYList.push(yPos + 5)
  }

  // 绘制 X 轴坐标标签
  for (let i = 0; i < xLen; i++) {
    let x = i * stepX;
    let xPos = (margin + x);
    if (i) {
      ctx.beginPath();
      ctx.moveTo(xPos, height - margin);
      ctx.lineTo(xPos, margin);
      ctx.strokeStyle = '#ddd'
      ctx.stroke();
    }
    newXs = []
    for (const val of options.axisX.data) {
      newXs.push(options.axisX.format(val))
    }
    ctx.fillText(newXs[i], xPos - 1, height - margin + 10 + options.axisX.top);
    firstEnding && axisXList.push(xPos - 1)
  }
}

绘制曲线入口

/**
 * 绘制单组曲线
 * @param data 
 */

function drawLine(data: any{
  const { points, id, rgba, lineColor, hoverRgba } = data
  startAreaX = endAreaX
  startAreaY = endAreaY
  // 分割区
  if (firstEnding) {
    areaList.push({ x: startAreaX, y: startAreaY })
  }

  function darwColorOrLine(lineMode: boolean{
    // 绘制折线
    ctx.beginPath();
    ctx.moveTo(id ? margin + endAreaX - xkVal : margin + endAreaX, height - margin - (points[0] - minY) * ratioY);
    ctx.lineWidth = 2
    ctx.setLineDash([00])

    let x = 0, y = 0, translateX = 0
    if (id) {
      translateX -= 20
    }
    for (let i = 0; i < points.length; i++) {
      x = i * stepX + margin + endAreaX + translateX
      y = height - margin - (points[i] - minY) * ratioY;

      let x0 = (i - 1) * stepX + margin + endAreaX + translateX;
      let y0 = height - margin - (points[i - 1] - minY) * ratioY;
      let xc = x0 + stepX / 2;
      let yc = (y0 + y) / 2;
      if (i === 0) {
        prePointPosX = x
        prePointPosY = y
        ctx.lineTo(x, y);
        // 这里需要提前考虑是否是线、还是曲线
        if (!(prePointPosX === x && prePointPosY === y)) {
          pointList.push({ type'line'start: { x: prePointPosX, y: prePointPosY }, end: { x: x, y: y } })
        }
      } else {
        ctx.bezierCurveTo(xc, y0, xc, y, x, y);
        pointList.push({ type'curve'start: { x: prePointPosX, y: prePointPosY }, end: { x: x, y: y }, control1: { x: xc, y: y0 }, control2: { x: xc, y: y } })
      }
      prePointPosX = x
      prePointPosY = y
      if (i === points.length - 1) {
        endAreaX = x
        endAreaY = y

        if (firstEnding && id === newOpt.data.length - 1) {
          areaList.push({ x: x, y: y })
        }
      }
    }
    ctx.strokeStyle = lineColor
    ctx.stroke()

    lineMode && ctx.beginPath()

    // 右侧闭合点
    ctx.lineTo(endAreaX, height - margin)
    // 左侧闭合点
    ctx.lineTo(margin + startAreaX, height - margin)
    let startClosePointX = id ? startAreaX : margin + startAreaX
    // 交接闭合点
    ctx.lineTo(startClosePointX, height - margin)
    ctx.strokeStyle = 'transparent'
    lineMode && ctx.stroke();
  }
  darwColorOrLine(false)
  // 渐变
  const gradient = ctx.createLinearGradient(200110200290);

  if (isHover && areaId === id) {
    gradient.addColorStop(0`rgba(${hoverRgba[1][0]}${hoverRgba[1][1]}${hoverRgba[1][2]}, 1)`);
    gradient.addColorStop(1`rgba(${hoverRgba[0][0]}${hoverRgba[0][1]}${hoverRgba[0][2]}, 1)`);
  } else {
    gradient.addColorStop(0`rgba(${rgba[1][0]}${rgba[1][1]}${rgba[1][2]}, 1)`);
    gradient.addColorStop(1`rgba(${rgba[0][0]}${rgba[0][1]}${rgba[0][2]}, 0)`);
  }

  ctx.fillStyle = gradient;
  ctx.fill();
}
/**
 * 绘制所有组的曲线
 */

function startDrawLines({
  const { data, series } = newOpt
  for (let i = 0; i < data.length; i++) {
    drawLine({ points: data[i], id: i, rgba: series[i].rgba, hoverRgba: series[i].hoverRgba, lineColor: series[i].lineColor })
  }
  firstEnding = false  //由于是不断绘制,我们需要得到第一次渲染完的我们想要的数组,防止数据被污染
}

这里需要注意的是,我们的划分必须是线段的闭合,然后用fillStyle填充颜色。 所以你需要在终点之后对我的起点执行lineTo 3次。 addColorStop 来做渐变。

绘制贝塞尔曲线

x = i * stepX + margin + endAreaX + translateX
y = height - margin - (points[i] - minY) * ratioY;
let x0 = (i - 1) * stepX + margin + endAreaX + translateX;
let y0 = height - margin - (points[i - 1] - minY) * ratioY;
let xc = x0 + stepX / 2;
let yc = (y0 + y) / 2;
// ....
ctx.bezierCurveTo(xc, y0, xc, y, x, y);

echarts x轴的颜色-Echarts没能实现这个图,所以我手写一张

具体的api就不过多讨论了,但是我们要知道我们的曲线在一个控制点只有一个方向。 如果有两个控制点,则意味着我们的曲线最多可以有2个方向。 然而,我们的图表需要从上到下平滑过渡。 这时就必须使用两个控制点。

贝塞尔曲线原理

为了实现bezierCurveTo,其实就是对路径经过的所有点进行估计,这样更方便我们后面获取路径上的点。下面的估计会比较复杂。 其实就是应用三次贝塞尔曲线的公式。

function getBezierCurvePoints(startX: number, startY: number, cp1X: number, cp1Y: number, cp2X: number, cp2Y: number, endX: number, endY: number, steps: number{
  let points = [];

  // 使用二次贝塞尔曲线近似三次贝塞尔曲线
  let q1x = startX + (cp1X - startX) * 2 / 3;
  let q1y = startY + (cp1Y - startY) * 2 / 3;
  let q2x = endX + (cp2X - endX) * 2 / 3;
  let q2y = endY + (cp2Y - endY) * 2 / 3;

  // 采样曲线上的所有点
  for (let i = 0; i <= steps; i++) {
    let t = i / steps;
    let x = (1 - t) * (1 - t) * (1 - t) * startX +
      3 * t * (1 - t) * (1 - t) * q1x +
      3 * t * t * (1 - t) * q2x +
      t * t * t * endX;
    let y = (1 - t) * (1 - t) * (1 - t) * startY +
      3 * t * (1 - t) * (1 - t) * q1y +
      3 * t * t * (1 - t) * q2y +
      t * t * t * endY;

    points.push({ x: +x.toFixed(2), y: +y.toFixed(2) });
  }

  return points;
}

三次贝塞尔曲线的公式原理

公式的推论可以被物理学上的好大佬研究出来,但是他的估计过程还是需要知道的。 我们可以看到t从0到1的值代表了曲线的起点和终点。 t控制p1p2、p2p3、p3p4线段中Q1、Q2、Q3的比例位置,也是r1、r2对应的位置,进而得到r1、r2的相对位置。 所以它基本上是连续定义的线段中 t 的偏移位置。 t从0到1的所有点的集合就是构造曲线的集合。

echarts x轴的颜色-Echarts没能实现这个图,所以我手写一张

同时,根据这一原理echarts x轴的颜色,可以通过Ramer Douglas Peucker算法得到线段的细分,从而控制曲线是否平滑。

如何实现路径上的点行驶

之前我们可以得到曲线上的所有点,只需估计一下我的clientX是否在该点对应的路径点集合中被选中,然后在遮罩层上画一个圆和辅助线即可。

function getAllPoints(segments: PointList{
  let points = [];
  let lastPoint = null;

  // 遍历所有线段的控制点和终点,将这些点的坐标存储到数组中
  for (let i = 0; i < segments.length; i++) {
    let segment = segments[i];
    let pointsCount = 50// 点的数量
    // 如果是直线,则使用lineTo方法连接线段的终点
    if (segment.type === "line") {
      let x0 = segment.start.x;
      let y0 = segment.start.y;
      let x1 = segment.end.x;
      let y1 = segment.end.y;
      for (let j = 0; j <= pointsCount; j++) {
        let t = j / pointsCount;
        let x = x0 + (x1 - x0) * t;
        let y = y0 + (y1 - y0) * t;
        points.push({ x: +x.toFixed(2), y: +y.toFixed(2) });
      }
      // 如果是曲线,则使用贝塞尔曲线的方法绘制曲线,并将曲线上的所有点的坐标存储到数组中
    } else if (segment.type === "curve") {
      let x0 = segment.start.x;
      let y0 = segment.start.y;
      let x1 = segment.control1.x;
      let y1 = segment.control1.y;
      let x2 = segment.control2.x;
      let y2 = segment.control2.y;
      let x3 = segment.end.x;
      let y3 = segment.end.y;
      const point = getBezierCurvePoints(x0, y0, x1, y1, x2, y2, x3, y3, pointsCount)
      points.push(...point);
    }
    // 更新线段的起点
    lastPoint = segment.end;
  }
  return points
}

标签数据估计、区间估计

我们可以看到填充颜色和标签值都发生了变化。 这里我们需要检查全局坐标是否在图表内。 如果在里面,则计算pointX = clientX - dom.offsetLeft - dom.marginecharts x轴的颜色,y坐标相同。

/**
 * label显示
 * @param clientX 
 * @param clientY 
 */

function drawTouchPoint(clientX: number, clientY: number{
  cx = clientX, cy = clientY

  // 计算当前区间位置
  for (let i = 0; i < areaList.length - 1; i++) {
    const pre = areaList[i].x;
    const after = areaList[i + 1].x;

    if (cx > pre && cx < after) {
      areaId = i
    }
  }
  // 计算交叉位置,得到对应的x轴位置,从option的data中取对应的title
  for (let i = 0; i < axisXList.length - 1; i++) {
    const pre = axisXList[i];
    const after = axisXList[i + 1];
    if (cx > pre && cx < after) {
      curInfo.x = i
    }
  }
  for (let i = 0; i < axisYList.length - 1; i++) {
    const max = axisYList[i];
    const min = axisYList[i + 1];
    if (cy  min) {
      curInfo.y = i + 1
    }
  }

  let crossPoint = pathPoints.find((item: Pos) => {
    const orderNum = .5
    if (Math.abs(item.x - clientX) <= orderNum) {
      return item
    }
  }) as Pos | undefined
  if (crossPoint && canvas) {
    dotCtx.clearRect(00, canvas.width, canvas.height);

    dotCtx.beginPath()
    dotCtx.setLineDash([24]);
    dotCtx.moveTo(crossPoint.x, margin)
    dotCtx.lineTo(crossPoint.x, height - margin)
    dotCtx.strokeStyle = '#000'
    dotCtx.stroke()

    drawArc(dotCtx, crossPoint.x, crossPoint.y, 5)

    //label
    if (!isLabel) {
      labelDOM = document.createElement("div");
      labelDOM.id = 'canvasTopBox'
      labelDOM.innerHTML = ""
      container && container.appendChild(labelDOM)
      isLabel = true
    } else {
      if (labelDOM) {
        let t = crossPoint.y + labelDOM.offsetHeight > canvas.height - margin ? canvas.height - margin - labelDOM.offsetHeight : crossPoint.y - labelDOM.offsetHeight * .5
        labelDOM.style.left = crossPoint.x + 20 + 'px'
        labelDOM.style.top = t + 'px'
        labelDOM.innerHTML = `
         

           <div class='label-left' style='backGround: ${newOpt.series[areaId].lineColor}'>
           

          

            
人数:${newYs[curInfo.y]} 

            
订单数:${newXs[curInfo.x]} 

          

         

        `
      } else {
      }
    }
  }
}

面具动漫

核心原理是通过clearRect,下面的代码是从右向左描边,所以这里可以直接transfrom:rotate(-180deg)。

function drawAnimate({
  markCtx.clearRect(00, width, height);

  markCtx.fillStyle = "rgba(255, 255, 255, 1)"
  markCtx.fillRect(00, width, height);

  markCtx.clearRect(
    (width - maskWidth),
    (height - maskHeight),
    maskWidth,
    maskHeight
  );

  // 更新遮罩区域大小
  maskWidth += 20;
  maskHeight += 20;
  if (maskWidth < width) {
    animateId = requestAnimationFrame(drawAnimate);
  } else {
    cancelAnimationFrame(animateId)
    watchEvent()
  }
}

选项配置条目

export const options = {
  layout: {
    w0,
    h0,
    root'#container',
    m30
  },
  data: [[406040801050800503020], [20306040301030200304020], [20302040201010300305020]],
  axisX: {
    data: [01234567891011121314151617181920212223242526272829303132],
    format(param: string | number) {
      return param + 'w'
    },
    top4,
  },
  axisY: {
    data: [020406080],
    format(param: string | number) {
      return param + '人'
    },
    right10,
  },
  series: [
    {
      rgba: [[55162255], [11621219]],
      hoverRgba: [[55162255], [11621219]],
      lineColor'blue'
    },
    {
      rgba: [[2550135], [1350157]],
      hoverRgba: [[2550135], [1350157]],
      lineColor'purple'
    },
    {
      rgba: [[2551900], [2246276]],
      hoverRgba: [[2551900], [2246276]],
      lineColor'orange'
    }
  ]
}

推荐面试问题

echarts实现datazoom起始值(包括x轴和y轴)

更新时间:2020年7月20日13:20 点击:2042

废话就不多说了,直接看代码吧~

let option = {} //你的echarts图表的配置,这里我就不贴我的了
myChart.setOption(option);
//开始
let startValue = myChart.getModel().option.dataZoom[0].startValue;
let endValue = myChart.getModel().option.dataZoom[0].endValue;
let start = myChart.getModel().option.xAxis[0].data[startValue];//起始X轴
let end = myChart.getModel().option.xAxis[0].data[endValue];//结束X轴
 
let startNum = obj.enddate.indexOf(start);
let endNum = obj.enddate.indexOf(end);
let arr = [];
for(let i = startNum;i <= endNum;i++){
 arr.push(obj.value[i]);
}
let max = Math.max.apply(null, arr);
let min = Math.min.apply(null, arr);
 
let ystartValue = myChart.getModel().option.dataZoom[1].startValue;//y轴datazoom最小值
let yendValue = myChart.getModel().option.dataZoom[1].endValue;//y轴datazoom最大值
let de = yendValue - ystartValue;//总区间数值大小
let minbili = (min-ystartValue)/de*100;
let maxbili = (max-ystartValue)/de*100;
this.min_max.push([Math.floor(minbili),Math.ceil(maxbili)]);//得到y轴datazoom的起始值
//结束

补充知识:echarts datazoom显示的位置设置

echarts x轴的颜色-Echarts没能实现这个图,所以我手写一张

如下:

echarts x轴的颜色-Echarts没能实现这个图,所以我手写一张

echarts x轴的颜色-Echarts没能实现这个图,所以我手写一张

以上echarts实现获取datazoom初始值(包括x轴和y轴)就是小编分享的全部内容echarts柱状图x轴文字,希望能给大家一个参考echarts柱状图x轴文字,也希望大家能先支持一下猪。

[!--infotagslink--]

上一篇: 解决Echarts2滑动垂直datazoom后数据显示不全的问题

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悟空资源网 echarts echarts x轴的颜色-Echarts没能实现这个图,所以我手写一张 https://www.wkzy.net/game/129120.html

常见问题

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务