plotly绘制分区图

88 阅读4分钟
devDependencies --- "d3": "^7.4.4",
dependencies --- "plotly.js-dist-min": "^2.12.1",

<template>
  <div class="ycfqt">
    <div id="chart-container" style="width:100%;height:800px"></div>
    <div class="right-container">
      <div class="compass">
        <img src="~@/assets/images/rockBurst/compass.png" alt="">
      </div>
      <ul class="legend">
        <li v-for="(item,index) in colorLegend" :key="index">
          <span class="color" :style="{'background':item.color}"></span>
          <span class="index">{{ item.index }}</span>
        </li>
      </ul>
    </div>
  </div>
</template>
<script>
import * as d3 from 'd3'
import Plotly from 'plotly.js-dist-min'
export default ({
  props: {
    chartData: {
      type: Object,
      default: () => ({})
    },
    colorLegend: {
      type: Array,
      default: () => []
    }
  },
  data() {
    return {
      cLayout: {
        title: '',
        font: {
          size: 12
        },
        margin: {
          t: 30,
          b: 30,
          l: 30,
          r: 30
        },
        xaxis: {
          dtick: 2000,
          ticklen: 4,
          ticks: 'outside',
          tickfont: {
            size: 12
          },
          showticksuffix: 'none',
          exponentformat: 'none',
          minor: {
            ticklen: 2,
            dtick: 2000
          }
        },
        xaxis2: {
          dtick: 2000,
          ticklen: 4,
          ticks: 'outside',
          tickfont: {
            size: 12
          },
          exponentformat: 'none',
          matches: 'x',
          side: 'top',
          gridcolor: '#3336',
          showgrid: false,
          minor: {
            ticklen: 2,
            dtick: 2000
          }
        },
        yaxis: {
          dtick: 2000,
          ticklen: 4,
          ticks: 'outside',
          tickfont: {
            size: 12
          },
          anchor: 'x',
          scaleanchor: 'x',
          tickangle: 270,
          exponentformat: 'none',
          minor: {
            ticklen: 2,
            dtick: 2000
          }
        },
        yaxis2: {
          dtick: 2000,
          ticklen: 4,
          ticks: 'outside',
          tickangle: 270,
          tickfont: {
            size: 12
          },
          exponentformat: 'none',
          matches: 'y',
          side: 'right',
          gridcolor: '#3336',
          showgrid: false,
          minor: {
            ticklen: 2,
            dtick: 2000
          }
        },
        dragmode: 'pan'
      },
      cConfig: {
        displaylogo: false,
        displayModeBar: true,
        locale: 'zh-cn',
        scrollZoom: true,
        modeBarButtonsToRemove: [
          'autoScale2d', 'autoscale', 'editInChartStudio', 'editinchartstudio', 'hoverCompareCartesian', 'hovercompare', 'lasso', 'lasso2d', 'orbitRotation', 'orbitrotation', 'pan', 'pan2d', 'pan3d', 'reset', 'resetCameraDefault3d', 'resetCameraLastSave3d', 'resetGeo', 'resetSankeyGroup', 'resetScale2d', 'resetViewMapbox', 'resetViews', 'resetcameradefault', 'resetcameralastsave', 'resetsankeygroup', 'resetscale', 'resetview', 'resetviews', 'select', 'select2d', 'sendDataToCloud', 'senddatatocloud', 'tableRotation', 'tablerotation', 'toImage', 'toggleHover', 'toggleSpikelines', 'togglehover', 'togglespikelines', 'toimage', 'zoom', 'zoom2d', 'zoom3d', 'zoomIn2d', 'zoomInGeo', 'zoomInMapbox', 'zoomOut2d', 'zoomOutGeo', 'zoomOutMapbox', 'zoomin', 'zoomout'
        ]
      }
    }
  },
  methods: {
    setData () {
      const data = this.chartData
      const selfFuncGetXy = (arrLength3) => {
        const arr = []
        const [first, second, last] = arrLength3
        const num = (last - first) / (second - first)
        for (let i = 0; i < num + 1; i++) {
          arr.push(first + i * (second - first))
        }
        return arr
      }
      const xArr = [data['point1-1'].X, data['point1-2'].X, data['pointLast'].X]
      const yArr = [data['point1-1'].Y, data['point2-1'].Y, data['pointLast'].Y]
      const x = selfFuncGetXy(xArr)
      const y = selfFuncGetXy(yArr)
      let grid
      // const minX = data['point1-1'].X
      // const minY = data['point1-1'].Y
      // const maxX = data['pointLast'].X
      // const maxY = data['pointLast'].Y
      // data.boundary = [
      //   { x: minX, y: maxY },
      //   { x: maxX, y: maxY },
      //   { x: maxX, y: minY },
      //   { x: minX, y: minY }
      // ]
      // 暂时写死井田数据,待后台接口OK再替换
      data.boundary = [
      { x: 37384408.749, y: 4344876.337 },
      { x: 37380505.547, y: 4351148.421 },
      { x: 37370140.16, y: 4351148.421 },
      { x: 37370334.696, y: 4350004.865 },
      { x: 37370661.12, y: 4349400.53 },
      { x: 37372871.425, y: 4344290.864 },
      { x: 37377997.503, y: 4345103.528 },
      { x: 37384408.749, y: 4344876.337 }
      ]
      const polygon = data.boundary.map(el => {
        if (!grid) {
          grid = { maxX: el.x, minX: el.x, maxY: el.y, minY: el.y }
        } else {
          grid.maxX = el.x > grid.maxX ? el.x : grid.maxX
          grid.minX = el.x < grid.minX ? el.x : grid.minX
          grid.maxY = el.y > grid.maxY ? el.y : grid.maxY
          grid.minY = el.y < grid.minY ? el.y : grid.minY
        }
        return [el.x, el.y]
      })
      let zMin, zMax
      data.grid.map(el => {
        el.map(ell => {
          if (!zMin) {
            zMin = ell
            zMax = ell
          }
          if (ell < zMin) {
            zMin = ell
          }
          if (ell > zMax) {
            zMax = ell
          }
        })
      })
      const chartData = { x: x, y: y, z: data.grid, p: polygon, range: grid, zMin: zMin, zMax: zMax }
      this.draw(chartData)
    },
    draw (params) {
      const trace = {
        x: [],
        y: [],
        mode: 'markers',
        type: 'scatter'
      }
      const contours = {
        xaxis: 'x2',
        yaxis: 'y2',
        name: '',
        type: 'contour',
        x: params.x,
        y: params.y,
        z: params.z,
        showlegend: false,
        showscale: false,
        opacity: 1,
        line: {
          color: '#333',
          width: 1
        },
        contours: {
          showlabels: false,
          labelfont: {
            size: 12,
            color: '#000'
          },
          start: params.zMin,
          end: params.zMax,
          size: (params.zMax - params.zMin) / 25
        },
        colorscale: this.colorLegend.length ? this.handleColorScale() : [
          [0, '#000'],
          [0.25, '#fbcb64'],
          [0.5, '#f1eb18'],
          [0.75, '#cbdb2a'],
          [1, '#78bf40']
        ]
      }

      const crossPoints = {
        yaxis: 'y2',
        xaxis: 'x2',
        name: '',
        type: 'scatter',
        mode: 'markers+text',
        // x: [37372000, 37374000, 37376000],
        // y: [4351148.421, 4351148.421, 4351148.421],
        textposition: 'bottom center',
        textfont: {
          color: '#000',
          size: 10
        },
        marker: {
          color: '#003',
          line: { width: 3 },
          opacity: 0,
          size: 10,
          symbol: 'circle-open'
        },
        showlegend: false,
        opacity: 0.9
      }

      let path = 'M '
      params.p.map((el, index) => {
        path += el.join(' ') + ((index === params.p.length - 1) ? ' Z' : ' L ')
      })
      this.cLayout.shapes = [
        {
          type: 'path',
          fillcolor: '#transparent',
          line: {
            color: 'transparent'
          },
          layer: 'below',
          path: path
        }
      ]
      if (params.range) {
        this.cLayout.xaxis.range = [params.range.minX, params.range.maxX]
        this.cLayout.yaxis.range = [params.range.minY, params.range.maxY]
      }
      const arr = [trace, contours, crossPoints]
      Plotly.newPlot('chart-container', arr, this.cLayout, this.cConfig)
      d3.select('.nsewdrag').attr('stroke', '#333').style('stroke-width', 1)

      let t = false
      // 井田边界裁剪
      const clipBoundary = (eventdata) => {
        const pathD = d3.select('.layer-subplot').select('.shapelayer').select('path').attr('d')
        const newPath = pathD.split(' ').map((el, index) => {
          if (el.length !== 1) {
            el -= index % 3 === 1 ? this.cLayout.margin.l : this.cLayout.margin.t
          }
          return el
        })
        d3.select('#clipArea').remove()
        d3.select('.layer-subplot').append('clipPath').attr('id', 'clipArea').append('path').attr('d', newPath.join(' '))
        d3.select('#clipBorder').remove()
        d3.select('.subplot.x2y2').select('.plot').append('path').attr('id', 'clipBorder').attr('stroke', '#000').attr('stroke-width', 1).attr('fill', 'none').attr('d', newPath.join(' '))
        d3.select('.contour').attr('clip-path', 'url(#clipArea)')

        const points = d3.select('.points')
        const point = d3.selectAll('.point')
        const points2 = points.append('g')
        if (points) {
          point.nodes().forEach((e, i) => {
            const currentTransform = point.filter(function (d, j) { return i === j }).attr('transform')
            const xy = currentTransform.split('(')[1].split(')')[0].split(',')
            if (!t) {
              const iconUrl = require('@/assets/images/rockBurst/cross.png')
              points2.append('image', ':first-child')
                .attr('xlink:href', iconUrl).attr('class', 'crossIcon')
                .attr('transform', `translate(${xy[0] - 10},${xy[1] - 10})`)
                .attr('width', 20)
                .attr('height', 20)
                .raise()
            } else {
              d3.selectAll('.crossIcon').filter(function (d, j) { return i === j }).attr('transform', `translate(${xy[0] - 10},${xy[1] - 10})`)
            }
          })
          t = true
          if (eventdata) {
            const currentDtick = Math.ceil((eventdata['yaxis.range[1]'] - eventdata['yaxis.range[0]']) / 500) * 100
            if (currentDtick) {
              setTimeout(() => {
                ['xaxis', 'yaxis', 'xaxis2', 'yaxis2'].map(el => {
                  this.cLayout[el].dtick = currentDtick
                  this.cLayout[el].minor.dtick = currentDtick / 5
                })
              })
            }
          }
        }
      }
      clipBoundary()
      const polt = document.getElementById('chart-container')
      polt.on('plotly_relayout', function (eventdata) {
        clipBoundary(eventdata)
      })
    },
    handleColorScale() {
      const colors = this.colorLegend
      const step1 = 1 / colors.length
      const colorScale1 = [[0, '#000']]
      for (let i = 0; i < colors.length; i++) {
        colorScale1.push([step1 * (i + 1), colors[i].color])
      }
      // 将颜色比例尺劈得特别特别细,多个值共用同一种颜色,才能保证轮廓线之间看不出渐变效果
      const colorScale2 = []
      const step2 = 0.00001
      for (let i = 0; i < 1; i += step2) {
        const c = colorScale1.find(it => it[0] > i)[1]
        colorScale2.push([i.toFixed(5), c])
      }
      return colorScale2
    }
  }
})
</script>
<style scoped lang="less">
.ycfqt{
  display: flex;
  position: relative;
  .right-container{
    position: absolute;
    top: 30px;
    right: 30px;
    width: 100px;
    height: calc(100% - 60px);
    text-align:center;
    display: flex;
    flex-flow: column;
    justify-content: space-between;
    .compass{
      width: 57px;
      height: 57px;
      padding: 13.5px;
      border-radius: 50%;
      background: #fff;
      margin-left: auto ;
      img{
        width: 30px;
        height: 30px;
      }
    }

    .legend{
      list-style-type: none;
      padding:0;
      display: flex;
      flex-direction: column;
      justify-content: end;
      li{
        display: flex;
        line-height: 30px;
        margin-bottom:2px;
      }
      .color{
        display: inline-block;
        width: 50px;
        height: 30px;
        box-shadow: 0px 1px 4px 0px rgba(0,0,0,0.5)
      }
      .index{
        display: inline-block;
        width: 30px;
        height: 30px;
        margin-left: 6px;
        background: #fff;
        color: #000;
        box-shadow: 0px 1px 4px 0px rgba(0,0,0,0.5);
      }
    }
  }
}
</style>

分布图样例数据,需要后台插值算法

const cdata = {
    'contourName': '分布图',
    'point1-1': {
        'col': 1,
        'V': 32.625,
        'X': 16294425.000,
        'Y': 4180155.000,
        'Z': 32.625,
        'row': 1
    },
    'point1-2': {
        'col': 2,
        'V': 32.625,
        'X': 16294712.000,
        'Y': 4180155.000,
        'Z': 32.625,
        'row': 1
    },
    'point2-1': {
        'col': 1,
        'V': 32.625,
        'X': 16294425.000,
        'Y': 4180442.000,
        'Z': 32.625,
        'row': 2
    },
    'pointLast': {
        'row': 250,
        'col': 249,
        'V': 31.425,
        'X': 16365601.000,
        'Y': 4251618.000,
        'Z': 31.425
    },
    grid:[
        [val1-1,val1-2,...val1-249],
        [val2-1,val2-2,...val2-249],
        ...,
        [val250-1,val250-2,...val250-249]
    ]
 }