クチャート

<template>
  <div class="k-chart">
    <div class="legend">
      <div class="title">
        <span class="titleLegend"></span>
        {
   
   {title}}
      </div>
      <div class="MA">
        <span class="MA5Figure"></span>
        MA5: {
   
   {MA5LegendData}}
      </div>
      <div class="MA MA10">
        <span class="MA5Figure MA10Figure"></span>
        MA10: {
   
   {MA10LegendData}}
      </div>
      <div class="MA MA20">
        <span class="MA5Figure MA20Figure"></span>
        MA20: {
   
   {MA20LegendData}}
      </div>
      <div class="MA MA30">
        <span class="MA5Figure MA30Figure"></span>
        MA30: {
   
   {MA30LegendData}}
      </div>
    </div>
    <div class="chart" id="kChart"></div>
  </div>
  
</template>

<script>
  import * as echarts from 'echarts/index';
  import { round } from "../../../unit/utils";

  let myChart = null;

  const userZoom = {
    change: false,
    start: 0,
    end: 0,
  };

  let pointerInChart = false;

  function calculateMA(dayCount, data) {
    let result = [];
    for (let i = 0, len = data.length; i < len; i++) {
      if (i < dayCount) {
        result.push('-');
        continue;
      }
      let sum = 0;
      for (let j = 0; j < dayCount; j++) {
        sum += Number(data[i - j][1]);
      }
      result.push(+(sum / dayCount).toFixed(2));
    }
    return result;
  }

  export default {
    name: "KChart",
    props: {
      title: {
        type: String,
      },
      chartType: {
        type: String,
        default: () => 'L',
      },
      subjectType: {
        type: String,
      },
      isShow: {
        type: Boolean,
      },
      xData: {
        type: Array,
        default: () => [],
      },
      kData: {
        type: Array,
        default: () => [],
      },
      amountData: {
        type: Array,
        default: () => [],
      },
      rateData: {
        type: Array,
        default: () => [],
      },
      loading: {
        type: Boolean,
      },
      active: {
        type: String
      },
    },
    mounted() {
      window.addEventListener('resize', this.resize);
      myChart = echarts.init(document.getElementById('kChart'));
      
      myChart.on('datazoom', e => {
        userZoom.change = true;
        if (e.batch) {
          userZoom.start = e.batch[0].start;
          userZoom.end = e.batch[0].end;
        } else {
          userZoom.start = e.start;
          userZoom.end = e.end;
        }
      });
      //鼠标移出坐标系,图例中MA数据显示最后一条
      myChart.on('globalout', e => {
        this.MA5LegendData = this.ma5[this.ma5.length - 1];
        this.MA10LegendData = this.ma10[this.ma10.length - 1];
        this.MA20LegendData = this.ma20[this.ma20.length - 1];
        this.MA30LegendData = this.ma30[this.ma30.length - 1];
      })
    },
    beforeDestroy() {
      myChart.clear();
      window.removeEventListener('resize', this.resize);
    },
    data() {
      return {
        MA5LegendData: '',
        MA10LegendData: '',
        MA20LegendData: '',
        MA30LegendData: '',
      };
    },
    watch: {
      isShow(val) {
        if (val && myChart) {
          this.resize();
        }
      },
      loading(val) {
        if (val) {
          this.showLoading();
        } else {
          myChart.hideLoading();
        }
      },
      options() {
        myChart && myChart.setOption(this.options);
      },
      chartType() {
        userZoom.change = false;
      },
      active() {
        if(this.active === 'realtime') {
          setTimeout(()=>{
            this.resize();
          },0)
        }
      },
      ma5(val) {
        if (val.length) {
          this.MA5LegendData = val[val.length - 1];
        } else {
          return '';
        }
      },
      ma10(val) {
        if (val.length) {
          this.MA10LegendData = val[val.length - 1];
        } else {
          return '';
        }
      },
      ma20(val) {
        if (val.length) {
          this.MA20LegendData = val[val.length - 1];
        } else {
          return '';
        }
      },
      ma30(val) {
        if (val.length) {
          this.MA30LegendData = val[val.length - 1];
        } else {
          return '';
        }
      },
    },
    computed: {
      ma5() {
        return calculateMA(5, this.kData);
      },
      ma10() {
        return calculateMA(10, this.kData);
      },
      ma20() {
        return calculateMA(20, this.kData);
      },
      ma30() {
        return calculateMA(30, this.kData);
      },
      options() {
        const len = this.xData.length;
        let startValue = 0;
        let endValue = len - 1;
        const dataZoom = [
          {
            type: 'inside',
            xAxisIndex: [0, 1],
            textStyle: {
              color: 'rgba(255, 255, 255, .8)',
            }
          },
          {
            show: true,
            xAxisIndex: [0, 1],
            type: 'slider',
            top: '91%',
            textStyle: {
              color: 'rgba(255, 255, 255, .8)',
            }
          }
        ];
        if (this.chartType === 'day' || this.chartType === 'week') {
          startValue = len - 60 > 0 ? len - 60 : 0;
        }

        dataZoom.forEach(item => {
          item.startValue = startValue;
          item.endValue = endValue;
        });

        if (userZoom.change) {
          let start = userZoom.start;
          let end = userZoom.end;
          dataZoom.forEach(item => {
            item.start = start;
            item.end = end;
          });
        }
        const fontColor = 'rgba(255, 255, 255, .95)';
        const tooltipBackgroundcolor = '#334163';
        const lineColor = '#3A4560';
        const Ygreen = '#4CCEAC';
        const Yred = '#DB504A';

        return {
          animation: false,
          legend: {
            show: false,
            left: 'center',
            data: [this.title, 'MA5', 'MA10', 'MA20', 'MA30'],
            formatter: (params) => {
              switch (params) {
                case this.title:
                  return params;
                  break;
                case 'MA5':
                  //做此判断是为了避免图缩放过程中MA5出现undefined,当值为undefined,就显示-
                  if(this.MA5LegendData) {
                    return params + ': ' + this.MA5LegendData;
                  } else { return params + ': ' + '-'; };
                  break;
                case 'MA10':
                  if(this.MA10LegendData) {
                    return params + ': ' + this.MA10LegendData;
                  } else { return params + ': ' + '-'; }
                  break;
                case 'MA20':
                  if(this.MA20LegendData) {
                    return params + ': ' + this.MA20LegendData;
                  } else { return params + ': ' + '-'; }
                  break;
                case 'MA30':
                  if(this.MA30LegendData) {
                    return params + ': ' + this.MA30LegendData;
                  } else { return params + ': ' + '-'; }
                  break;
                default: 
                  return '';
              }
            }
          },
          tooltip: {
            trigger: 'axis',
            axisPointer: {
              type: 'cross'
            },
            borderWidth: 1,
            backgroundColor: tooltipBackgroundcolor,
            borderColor: lineColor,
            padding: 10,
            textStyle: {
              color: fontColor
            },
            position: function (pos, params, el, elRect, size) {
              let obj = {top: 25};
              obj[['left', 'right'][+(pos[0] < size.viewSize[0] / 2)]] = size.viewSize[0] * 0.1;
              return obj;
            },
            formatter: param => {
              let ret = [];
              ret.push(param[0].name + '<br />');
              const amountParam = param.find(item => item.seriesName === '成交量');
              if (!amountParam) { return; }
              let amount = amountParam.data[1];
              amount = this.setZero((Number(amount).toLocaleString('en-US')));
              const kParam = param.find(item => item.seriesType === 'candlestick');
              if (!kParam) { return; }
              // const MA5Param = param.find(item => item.seriesName === 'MA5');
              // const MA10Param = param.find(item => item.seriesName === 'MA10');
              // const MA20Param = param.find(item => item.seriesName === 'MA20');
              // const MA30Param = param.find(item => item.seriesName === 'MA30');
              const color = Number(kParam.data.value[2]) > Number(kParam.data.value[1]) ? '#ec0000' : '#00da3c';
              const rate = kParam.data.rate.split(',');
              const colorRate = Number(rate[0]) > 0 ? '#ec0000' : '#00da3c';
              let rateFront = this.setZero((Number(round(rate[0], 2)).toLocaleString('en-US')));
              let rateBack = this.setZero((Number(round(rate[1], 2)).toLocaleString('en-US')));
              let openData = this.setZero((Number(kParam.data.value[1]).toLocaleString('en-US')));
              let highestData = this.setZero((Number(kParam.data.value[4]).toLocaleString('en-US')));
              let lowestData = this.setZero((Number(kParam.data.value[3]).toLocaleString('en-US')));
              let closeData = this.setZero((Number(kParam.data.value[2]).toLocaleString('en-US')));
              ret.push(`<div style="display: flex; justify-content: space-between; margin-top: 8px;">
                            <div style="display: flex; align-items: center; margin-right: 20px;">
                              <div style="width: 10px; height: 10px; border-radius: 50%; background: ${color}; margin-right: 5px;"></div>
                              <span>开盘</span>
                            </div>
                            <span style="font-weight: 600;">${openData}</span>
                          </div>`);
              ret.push(`<div style="display: flex; justify-content: space-between; margin-top: 8px;">
                            <div style="display: flex; align-items: center; margin-right: 20px;">
                              <div style="width: 10px; height: 10px; border-radius: 50%; background: ${color}; margin-right: 5px;"></div>
                              <span>最高</span>
                            </div>
                            <span style="font-weight: 600;">${highestData}</span>
                          </div>`);
              ret.push(`<div style="display: flex; justify-content: space-between; margin-top: 8px;">
                            <div style="display: flex; align-items: center; margin-right: 20px;">
                              <div style="width: 10px; height: 10px; border-radius: 50%; background: ${color}; margin-right: 5px;"></div>
                              <span>最低</span>
                            </div>
                            <span style="font-weight: 600;">${lowestData}</span>
                          </div>`);
              ret.push(`<div style="display: flex; justify-content: space-between; margin-top: 8px;">
                            <div style="display: flex; align-items: center; margin-right: 20px;">
                              <div style="width: 10px; height: 10px; border-radius: 50%; background: ${color}; margin-right: 5px;"></div>
                              <span>收盘</span>
                            </div>
                            <span style="font-weight: 600;">${closeData}</span>
                          </div>`);
              ret.push(`<div style="display: flex; justify-content: space-between; margin-top: 8px;">
                            <div style="display: flex; align-items: center; margin-right: 20px;">
                              <div style="width: 10px; height: 10px; border-radius: 50%; background: ${colorRate}; margin-right: 5px;"></div>
                              <span>涨跌</span>
                            </div>
                            <span style="font-weight: 600;">${rateFront}(${rateBack}%)</span>
                          </div>`);
              ret.push(`<div style="display: flex; justify-content: space-between; margin-top: 8px;">
                            <div style="display: flex; align-items: center; margin-right: 20px;">
                              <div style="width: 10px; height: 10px; border-radius: 50%; background: ${color}; margin-right: 5px;"></div>
                              <span>成交量</span>
                            </div>
                            <span style="font-weight: 600;">${amount}${this.subjectType === 'market' ? '万手' : '亿手'}</span>
                          </div>`);
              // ret.push(`<div style="display: flex; justify-content: space-between; margin-top: 8px;">
              //               <div style="display: flex; align-items: center; margin-right: 20px;">
              //                 <div style="width: 10px; height: 10px; border-radius: 50%; background: ${MA5Param.color}; margin-right: 5px;"></div>
              //                 <span>${MA5Param.seriesName}</span>
              //               </div>
              //               <span style="font-weight: 600;">${MA5Param.data}</span>
              //             </div>`);
              // ret.push(`<div style="display: flex; justify-content: space-between; margin-top: 8px;">
              //               <div style="display: flex; align-items: center; margin-right: 20px;">
              //                 <div style="width: 10px; height: 10px; border-radius: 50%; background: ${MA10Param.color}; margin-right: 5px;"></div>
              //                 <span>${MA10Param.seriesName}</span>
              //               </div>
              //               <span style="font-weight: 600;">${MA10Param.data}</span>
              //             </div>`);
              // ret.push(`<div style="display: flex; justify-content: space-between; margin-top: 8px;">
              //               <div style="display: flex; align-items: center; margin-right: 20px;">
              //                 <div style="width: 10px; height: 10px; border-radius: 50%; background: ${MA20Param.color}; margin-right: 5px;"></div>
              //                 <span>${MA20Param.seriesName}</span>
              //               </div>
              //               <span style="font-weight: 600;">${MA20Param.data}</span>
              //             </div>`);
              // ret.push(`<div style="display: flex; justify-content: space-between; margin-top: 8px;">
              //               <div style="display: flex; align-items: center; margin-right: 20px;">
              //                 <div style="width: 10px; height: 10px; border-radius: 50%; background: ${MA30Param.color}; margin-right: 5px;"></div>
              //                 <span>${MA30Param.seriesName}</span>
              //               </div>
              //               <span style="font-weight: 600;">${MA30Param.data}</span>
              //             </div>`);
              return ret.join('');
            },
          },
          axisPointer: {
            link: {xAxisIndex: 'all'},
            label: {
              backgroundColor: '#777',
              formatter: param => {
                if (param.axisDimension === 'x' && param.axisIndex === 0) {
                  const MA5Param = param.seriesData.find(item => item.seriesName === 'MA5');
                  const MA10Param = param.seriesData.find(item => item.seriesName === 'MA10');
                  const MA20Param = param.seriesData.find(item => item.seriesName === 'MA20');
                  const MA30Param = param.seriesData.find(item => item.seriesName === 'MA30');

                  this.MA5LegendData = MA5Param.data;
                  this.MA10LegendData = MA10Param.data;
                  this.MA20LegendData = MA20Param.data;
                  this.MA30LegendData = MA30Param.data;
                }
                
                if (param.axisDimension === 'y') {
                  return round(param.value, 2);
                } else {
                  return param.value;
                }
              
              }
            }
          },
          visualMap: {
            show: false,
            seriesIndex: 5,
            dimension: 2,
            pieces: [{
              value: 1,
              color: Yred
            }, {
              value: -1,
              color: Ygreen
            }]
          },
          grid: [
            {
              left: '10%',
              right: '8%',
              height: '62%'
            },
            {
              left: '10%',
              right: '8%',
              top: '80%',
              height: '10%'
            }
          ],
          xAxis: [
            {
              type: 'category',
              data: this.xData,
              scale: true,
              boundaryGap: true,
              axisLine: {onZero: false},
              splitLine: {show: false},
              splitNumber: 20,
              min: 'dataMin',
              max: 'dataMax',
              axisPointer: {
                z: 100
              },
              axisTick: {
                alignWithLabel: true,
              },
              axisLabel: {
                color: fontColor,
              },
            },
            {
              type: 'category',
              gridIndex: 1,
              data: this.xData,
              scale: true,
              boundaryGap: true,
              axisLine: {onZero: false},
              axisTick: {show: false},
              splitLine: {show: false},
              axisLabel: {show: false},
              splitNumber: 20,
              min: 'dataMin',
              max: 'dataMax'
            }
          ],
          yAxis: [
            {
              scale: true,
              boundaryGap: ['10%', '20%'],
              axisLabel: {
                color: fontColor,
              },
              splitLine: {
                lineStyle: {
                  type: 'dashed',
                  color: lineColor
                }
              }
            },
            {
              scale: true,
              gridIndex: 1,
              splitNumber: 2,
              axisLabel: {show: false},
              axisLine: {show: false},
              axisTick: {show: false},
              splitLine: {show: false}
            }
          ],
          dataZoom,
          series: [
            {
              name: this.title,
              type: 'candlestick',
              barMaxWidth: 50,
              data: this.kData.map((item, index) => ({value: item, rate: this.rateData[index]})),
              itemStyle: {
                color: Yred,
                color0: Ygreen,
                borderColor: null,
                borderColor0: null
              },
            },
            //MA5
            {
              name: 'MA5',
              type: 'line',
              data: this.ma5,
              smooth: true,
              showSymbol: false,
              lineStyle: {
                opacity: 0.5,
                width: 1,
                color: '#4594F2',
              }
            },
            //MA10
            {
              name: 'MA10',
              type: 'line',
              data: this.ma10,
              smooth: true,
              showSymbol: false,
              lineStyle: {
                opacity: 0.5,
                width: 1,
                color: '#EB81C8',
              }
            },
            //MA20
            {
              name: 'MA20',
              type: 'line',
              data: this.ma20,
              smooth: true,
              showSymbol: false,
              lineStyle: {
                opacity: 0.5,
                width: 1,
                color: '#E89341',
              }
            },
            //MA30
            {
              name: 'MA30',
              type: 'line',
              data: this.ma30,
              smooth: true,
              showSymbol: false,
              lineStyle: {
                opacity: 0.5,
                width: 1,
                color: '#ad66a7'
              }
            },
            {
              name: '成交量',
              type: 'bar',
              barMaxWidth: 50,
              xAxisIndex: 1,
              yAxisIndex: 1,
              data: this.amountData,
            }
          ]
        };
      },
    },
    methods: {
      resize() {
        myChart.resize();
      },
      showLoading() {
        myChart.showLoading({
          text: '加载中...',
          color: '#000',
          textColor: '#000',
          //遮罩颜色
          maskColor: 'rgba(255, 255, 255, 0.7)',
          // 字体大小。从 `v4.8.0` 开始支持。
          fontSize: 14,
        });
      },
      setZero(value) {
        let num = value.toString();
        if(num.indexOf('.') === -1) {
          num = num + '.00';
        } else if(num.slice(num.indexOf('.') + 1).length === 1) {
          num = num + '0';
        }
        return num;
      }
    },
  }
</script>

<style lang="less" scoped>
  @import url("../../../assets/css/variables");
  .k-chart {
    position: relative;
    width: 100%;
    height: 100%;
    .legend {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      display: flex;
      justify-content: center;
      padding-top: 8px;
      z-index: 9999;
      .title {
        color: @font-color;
        font-size: 13px;
        font-weight: 500;
        margin-right: 10px;
        .titleLegend {
          display: inline-block;
          width: 20px;
          height: 10px;
          background-color: @profit-loss-red;
          border-radius: 3px;
        }
      }
      .MA {
        display: inline-block;
        color: @font-color;
        font-size: 13px;
        font-weight: 500;
        color: #4594F2;
        margin-right: 8px;
      }
      .MA5Figure {
        display: inline-block;
        width: 10px;
        height: 10px;
        background-color: #4594F2;
        border-radius: 5px;
      }
      .MA10 {
        color: #EB81C8;
        .MA10Figure {
          background-color: #EB81C8;
        }
      }
      .MA20 {
        color: #E89341;
        .MA20Figure {
          background-color: #E89341;
        }
      }
      .MA30 {
        color: #ad66a7;
        .MA30Figure {
          background-color: #ad66a7;
        }
      }
    }
    .chart {
      width: 100%;
      height: 100%;
    }
  }
</style>

おすすめ

転載: blog.csdn.net/honeymoon_/article/details/130504606
おすすめ