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]
]
}