js vue echarts 前端绘制 cie1931 马蹄图 色度图

js vue echarts 前端绘制 cie1931 马蹄图 色度图

  • 背景介绍
  • 思路与准备
    • 理解cie1931是什么
    • 思路解析
    • 开源工具选择
    • 准备工作
  • 实现
    • vue版代码实现
  • 总结

背景介绍

在前端开发中,遇到需要在产品中,绘制马蹄图和色坐标展现到网页上。
简单查了下资料,发现cie1931马蹄图已有很多的实现方式,包括使用py,qt,c++,但还是没有web的实现方式,故记录下解决方案,期望能帮助到他人。

思路与准备

理解cie1931是什么

简单来说,马蹄图是一种色彩对比的变现形式,基于三原色,绘制人类眼睛能看到的光谱的2维展现形式。在专业领域中,比如我所在的显示行业,马蹄图是十分重要的判定显示器色域范围的标准。
在马蹄图中可以看到比如sRGB标准等等,色域越广,显示器显示的颜色的表现力越强,在游戏和视频制作领域,对显示器的要求会比较高。
我们在平时选购显示器的时候,一般都可以借助资料查到显示器的色域,比如sRGB100%,这其实指的就是马蹄图中按sRGB标准,能100%覆盖sRGB的三角形。(当然,现在一般的屏幕都能达到sRGB100%,这个标准属于比较老的)也能看到拿2台显示器显示同一张图片的比较,高色域的明显会更亮,画面更饱满。

思路解析

最简单的实现方案:
我们知道显示器显示的基本单位就是像素点,如果我想要画一张图片,我只要获得这张图片的像素点,再阵列出来,按照一定比例,能够让人眼分辨不出显示的是点还是线就可以了。比如,我可以让100*100的div中,显示100w个点,那么我只需要控制点的颜色就好。

开源工具选择

有很多画图很方便的开源库,比如echarts,plotly。但是需要考虑到绘制10w个点,性能上有没有影响。在性能上echarts做的不错,而且示例丰富,文档全面,还是中文,所以选择的是echarts。

准备工作

1 easyRGB网站.该网站提供三刺激值转换rgb等算法,程序中需要用到。
2 马蹄图轮廓坐标,百度一下就能找到,把他转换成数组格式, 大概格式就是

// An highlighted block
export const border=[
360,0.17556,0.005294,
361,0.175483,0.005286,
362,0.1754,0.005279,
...
]

3 当我们绘制点时,还需要知道该点是否在马蹄轮廓之内,如果不在轮廓内,可以不去转换他。所以需要一个判断点是否在多边形内部的算法。

实现

vue版代码实现

实现效果:

js vue echarts 前端绘制 cie1931 马蹄图 色度图_第1张图片

完整代码:

下面展示一些 内联代码片

<template>
        <div id="colorGumutLine" style="width:200px;height:300px"></div>
</template>
<script>
  import {
    border
  } from '@/colorgamut/border'
  export default {
    props:{
      data:{

      }
    },
    data() {
      return {
        borders: border
      }
    },
    mounted(){
      this.$nextTick(()=>{
          this.createEcharts(this.data)
      })
    },
    methods: {
      createEcharts(data) {
      
          var myChart = this.$echarts.init(document.getElementById("colorGumutLine"))
          var newBorder = this.division(this.borders, 3)
          var x_array = this.getOrdi(newBorder, 1)
          var y_array = this.getOrdi(newBorder, 2)
          var gamout = [],
            Y = 0.24
          var colors = []
          for (var i = 0; i < 1; i += 0.003) {

            for (var j = 0; j < 1; j += 0.003) {
              //var inner = {}

              if (!this.isInPolygon([i, j], newBorder)) {
                continue
              }
              gamout.push([i, j])
              var XYZ = this.Yxy2XYZ(Y, i, j)
              var color = this.XYZ2RGB(XYZ.X, Y, XYZ.Z)
              colors.push(`rgb(${color.r},${color.g},${color.b})`)
            }

          }

          newBorder = this.genPolygon(newBorder)
          newBorder.push([0.17556, 0.005294], [0.73469, 0.26531])
          var option;

          option = {
            xAxis: {},
            yAxis: {},
        
            series: [{
                name: 'scatter',
                type: 'scatter',

                large:true,
                largeThreshold:1000000,
                symbolSize: 1,
                itemStyle: {

                  normal: {
                    color: function (val) {

                      return (colors[val.dataIndex])

                    }
                  }

                },
                data: gamout,
                zlevel:1
              },
              {
                data: newBorder,
                type: 'line',
                smooth: true,
                showSymbol: false,
                lineStyle:{
                  color:'rgba(0,0,0,0.5)'
                }
              },
                {
                data: data,
                type: 'line',
               
                showSymbol: false,
                lineStyle:{
                  color:'rgba(0,0,0,1)',
                  width:1
                },
                zlevel:2
           
              }
            ]
          };
          option && myChart.setOption(option);
      genPolygon(border) {
        for (var i = 0; i < border.length; i++) {
          border[i].splice(0, 1)
        }
        return border
      },
      isInPolygon(checkPoint, polygonPoints) {

        var counter = 0;
        var i;
        var xinters;
        var p1, p2;
        var pointCount = polygonPoints.length;
        p1 = polygonPoints[0];

        for (i = 1; i <= pointCount; i++) {
          p2 = polygonPoints[i % pointCount];
          if (
            checkPoint[0] > Math.min(p1[1], p2[1]) &&
            checkPoint[0] <= Math.max(p1[1], p2[1])
          ) {
            if (checkPoint[1] <= Math.max(p1[2], p2[2])) {
              if (p1[1] != p2[1]) {
                xinters =
                  ((checkPoint[0] - p1[1]) * (p2[2] - p1[2])) / (p2[1] - p1[1]) +
                  p1[2];
                if (p1[2] == p2[2] || checkPoint[1] <= xinters) {
                  counter++;
                }
              }
            }
          }
          p1 = p2;
        }
        if (counter % 2 == 0) {
          return false;
        } else {
          return true;
        }
      },
      getOrdi(array, pos) {
        var new_array = []
        for (var i = 0; i < array.length; i++) {
          new_array.push(array[i][pos])
        }
        return new_array
      },
      division(arr, length) {
        let oldArray = [...arr]
        let index = 0;
        let newArray = []
        while (index < oldArray.length) {
          newArray.push(oldArray.splice(index, length))
        }
        
        return newArray
      },
      Yxy2XYZ(Y, x, y) {
        let X = x * (Y / y)
        let Z = (1 - x - y) * (Y / y)
        return {
          X,
          Y,
          Z
        }
      },
      XYZ2RGB(x, y, z) {
        var dr, dg, db;
        dr = 0.4185 * x - 0.1587 * y - 0.0828 * z;
        dg = -0.0912 * x + 0.2524 * y + 0.0157 * z;
        db = 0.0009 * x - 0.0025 * y + 0.1786 * z;

        var max = 0;
        max = dr > dg ? dr : dg;
        max = max > db ? max : db;

        dr = dr / max * 255;
        dg = dg / max * 255;
        db = db / max * 255;

        dr = dr > 0 ? dr : 0;
        dg = dg > 0 ? dg : 0;
        db = db > 0 ? db : 0;


        if (dr > 255) {
          dr = 255;
        }
        if (dg > 255) {
          dg = 255;
        }
        if (db > 255) {
          db = 255;
        }
        let r = dr + 0.5;
        let g = dg + 0.5;
        let b = db + 0.5;
        return {
          r,
          g,
          b
        }
      }

    }
  }
</script>

整个代码还是很简单的,基本思路就是根据border(马蹄图轮廓)判断点是否在内,如果在的话,通过easyRGB的算法,XYZ2RGB,把坐标转换成RGB值就可以了。这里Y值我定义为0.24,只是亮度的变化,不会影响效果。

总结

亮点:
由于echarts有很多动态效果,生成上w个点的时候,会有渲染的过程展示出来,所以虽然绘制的慢了点,但由于能看到过程,反而在用户那边取到了很好的效果。
由于用户之前使用其他编程语言做过马蹄图,但是由于数据量大和渲染问题导致图没有画出来,或者程序崩溃。这一点上,额外控制了点的数量,测试了一般浏览器都能承受住。
缺点:
由于需要控制点的数量,所以也控制了点的大小和整个图的大小,导致绘制的图较小。如果能把马蹄图变成图片就更好了,这样需要变化的就是三角形的坐标。但是echarts貌似没有提供这种接口。

你可能感兴趣的:(web,javascript,vue.js)