|
@@ -1,39 +1,89 @@
|
|
|
<template>
|
|
|
- <el-form label-position="left">
|
|
|
- <el-form-item label="场景名或完整url">
|
|
|
- <el-input v-model="sceneNameOrUrl" />
|
|
|
- </el-form-item>
|
|
|
- <el-button
|
|
|
- type="primary"
|
|
|
- @click="getWholeData"
|
|
|
+ <div class="formsWrapper">
|
|
|
+ <el-form
|
|
|
+ class="form1"
|
|
|
+ label-position="left"
|
|
|
>
|
|
|
- 重新获取全部数据点
|
|
|
- </el-button>
|
|
|
- </el-form>
|
|
|
-
|
|
|
- <el-form label-position="top">
|
|
|
- <el-form-item label="路径1数据(必填)(在全部节点中的index,以英文逗号分隔)(蓝色圆圈表示)">
|
|
|
- <el-input v-model="formData.path1" />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="路径2数据(选填)(在全部节点中的index,以英文逗号分隔)(绿色数字1表示)">
|
|
|
- <el-input v-model="formData.path2" />
|
|
|
- </el-form-item>
|
|
|
-
|
|
|
- <el-button
|
|
|
- type="primary"
|
|
|
- @click="renderPath"
|
|
|
+ <el-form-item label="场景名或完整url">
|
|
|
+ <el-input v-model="sceneNameOrUrl" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ @click="getWholeData"
|
|
|
+ >
|
|
|
+ 重新获取全部数据点
|
|
|
+ </el-button>
|
|
|
+ </el-form>
|
|
|
+ <el-form
|
|
|
+ class="form2"
|
|
|
+ label-position="top"
|
|
|
+ >
|
|
|
+ <el-form-item label="路径1数据(必填)(在全部节点中的index,以英文逗号分隔)(浅蓝色大圆圈表示)">
|
|
|
+ <el-input v-model="formData.path1" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="路径2数据(选填)(在全部节点中的index,以英文逗号分隔)(深蓝色小圆圈表示)">
|
|
|
+ <el-input v-model="formData.path2" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ @click="renderPath"
|
|
|
+ >
|
|
|
+ 显示路径
|
|
|
+ </el-button>
|
|
|
+ <el-button @click="onResetPath">
|
|
|
+ Reset
|
|
|
+ </el-button>
|
|
|
+ </el-form>
|
|
|
+ <el-form
|
|
|
+ class="form3"
|
|
|
+ label-position="top"
|
|
|
>
|
|
|
- 显示路径
|
|
|
- </el-button>
|
|
|
- <el-button @click="resetForm">
|
|
|
- Reset
|
|
|
- </el-button>
|
|
|
- </el-form>
|
|
|
+ <el-form-item label="起点坐标(以英文逗号分隔)(红色小圆圈表示)">
|
|
|
+ <el-input v-model="formData.startPoint" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="终点坐标(以英文逗号分隔)(绿色小圆圈表示)">
|
|
|
+ <el-input v-model="formData.endPoint" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ @click="renderStartEndPoint"
|
|
|
+ >
|
|
|
+ 显示起点终点
|
|
|
+ </el-button>
|
|
|
+ <el-button @click="onResetStartEndPoint">
|
|
|
+ Reset
|
|
|
+ </el-button>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
|
|
|
<div class="infoText">
|
|
|
{{ infoText }}
|
|
|
</div>
|
|
|
- <div class="svgWrapper" />
|
|
|
+ <div class="map">
|
|
|
+ <div class="svgWrapper" />
|
|
|
+ <div class="button-group">
|
|
|
+ <el-button
|
|
|
+ class="btn"
|
|
|
+ type="primary"
|
|
|
+ @click="zoomInToStartPoint"
|
|
|
+ >
|
|
|
+ zoom in到起点
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ class="btn"
|
|
|
+ type="primary"
|
|
|
+ @click="zoomInToEndPoint"
|
|
|
+ >
|
|
|
+ zoom in到终点
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ clas="btn"
|
|
|
+ @click="resetZoom"
|
|
|
+ >
|
|
|
+ reset zoom
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
@@ -44,13 +94,15 @@ import { ElLoading } from 'element-plus'
|
|
|
const LENGTH_PER_POINT = 0.36
|
|
|
|
|
|
// 视口尺寸
|
|
|
-let svgWidth = document.documentElement.clientWidth
|
|
|
-let svgHeight = document.documentElement.clientHeight - 330
|
|
|
+let svgWidth = document.documentElement.clientWidth - 200
|
|
|
+let svgHeight = document.documentElement.clientHeight - 280
|
|
|
let viewportRatio = svgWidth / svgHeight
|
|
|
|
|
|
let pxPerUnitLength = 0 // 原始单位长度对应的像素数
|
|
|
let rawWholeData = []
|
|
|
let wholeDataForRender = []
|
|
|
+let startPoint = null
|
|
|
+let endPoint = null
|
|
|
|
|
|
// 全部数据点的分布中心
|
|
|
let xCenter = 0
|
|
@@ -60,12 +112,15 @@ function resetGlobalVars() {
|
|
|
pxPerUnitLength = 0 // 原始单位长度对应的像素数
|
|
|
rawWholeData = []
|
|
|
wholeDataForRender = []
|
|
|
- xCenter = 0
|
|
|
- yCenter = 0
|
|
|
+ xCenter = 0
|
|
|
+ yCenter = 0
|
|
|
+ startPoint = null
|
|
|
+ endPoint = null
|
|
|
}
|
|
|
|
|
|
let svgNode = null
|
|
|
let gNode = null
|
|
|
+let zoomObj = null
|
|
|
|
|
|
export default {
|
|
|
name: 'App',
|
|
@@ -77,6 +132,8 @@ export default {
|
|
|
formData: {
|
|
|
path1: '',
|
|
|
path2: '',
|
|
|
+ startPoint: '',
|
|
|
+ endPoint: '',
|
|
|
}
|
|
|
}
|
|
|
},
|
|
@@ -90,18 +147,37 @@ export default {
|
|
|
this.getWholeData()
|
|
|
},
|
|
|
methods: {
|
|
|
- inputToArray(input) {
|
|
|
+ inputPathStringToArray(input) {
|
|
|
let temp = input.trim()
|
|
|
if(temp[0] === '[') { temp = temp.substr(1)}
|
|
|
if (temp[temp.length - 1] === ']') { temp = temp.substr(0, temp.length - 1) }
|
|
|
+ temp = temp.trim()
|
|
|
+ if (!temp) {
|
|
|
+ return []
|
|
|
+ }
|
|
|
+ let mapSuccess = true
|
|
|
temp = temp.split(',').map((item) => {
|
|
|
- return Number(item)
|
|
|
+ item = item.trim()
|
|
|
+ if (!item) {
|
|
|
+ return undefined
|
|
|
+ } else {
|
|
|
+ const num = Number(item)
|
|
|
+ if (!Number.isSafeInteger(num)) {
|
|
|
+ mapSuccess = false
|
|
|
+ return undefined
|
|
|
+ } else {
|
|
|
+ return num
|
|
|
+ }
|
|
|
+ }
|
|
|
})
|
|
|
- if (temp.includes(NaN)) {
|
|
|
+ if (!mapSuccess) {
|
|
|
window.alert(`解析输入路径失败:${input}`)
|
|
|
return -1
|
|
|
+ } else {
|
|
|
+ return temp.filter((item) => {
|
|
|
+ return item !== undefined
|
|
|
+ })
|
|
|
}
|
|
|
- return temp
|
|
|
},
|
|
|
getWholeData() {
|
|
|
if (!this.sceneNameOrUrl.trim()) {
|
|
@@ -117,7 +193,7 @@ export default {
|
|
|
resetGlobalVars()
|
|
|
gNode.selectAll('rect').remove()
|
|
|
gNode.selectAll('circle').remove()
|
|
|
- gNode.selectAll('text').remove()
|
|
|
+
|
|
|
|
|
|
const that = this
|
|
|
getWholeData(this.sceneNameOrUrl).then((res) => {
|
|
@@ -144,9 +220,9 @@ export default {
|
|
|
let areaRatio = xLength / yLength
|
|
|
|
|
|
// 各个点坐标映射到视口坐标
|
|
|
- if (viewportRatio >= areaRatio) { // 视口高度应略小于y轴方向分布幅度
|
|
|
+ if (viewportRatio >= areaRatio) { // 分布范围应略小于svg尺寸
|
|
|
pxPerUnitLength = svgHeight / yLength * 0.9
|
|
|
- } else { // 视口宽度应略小于x轴方向分布幅度
|
|
|
+ } else {
|
|
|
pxPerUnitLength = svgWidth / xLength * 0.9
|
|
|
}
|
|
|
let wholeXArrayInPx = xArray.map((eachX) => {
|
|
@@ -180,12 +256,12 @@ export default {
|
|
|
that.infoText = ''
|
|
|
})
|
|
|
|
|
|
- svgNode.call(d3.zoom().on("zoom", zoomed));
|
|
|
+ zoomObj = d3.zoom().on("zoom", zoomed)
|
|
|
+ svgNode.call(zoomObj);
|
|
|
|
|
|
function zoomed({transform}) {
|
|
|
gNode.attr("transform", transform);
|
|
|
}
|
|
|
-
|
|
|
}).finally(() => {
|
|
|
this.loadingHandler.close()
|
|
|
})
|
|
@@ -196,18 +272,18 @@ export default {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- gNode.selectAll('circle').remove()
|
|
|
- gNode.selectAll('text').remove()
|
|
|
+ gNode.selectAll('circle.path1').remove()
|
|
|
+ gNode.selectAll('circle.path2').remove()
|
|
|
+
|
|
|
|
|
|
- let rawPathDataIndex = this.inputToArray(this.formData.path1)
|
|
|
+ let rawPathDataIndex = this.inputPathStringToArray(this.formData.path1)
|
|
|
if (rawPathDataIndex === -1) {
|
|
|
return
|
|
|
}
|
|
|
- let rawPathDataIndex2 = this.inputToArray(this.formData.path2)
|
|
|
+ let rawPathDataIndex2 = this.inputPathStringToArray(this.formData.path2)
|
|
|
if (rawPathDataIndex2 === -1) {
|
|
|
return
|
|
|
}
|
|
|
-
|
|
|
// 基于path index 拿到path节点数组
|
|
|
let rawPathData = {
|
|
|
data: [
|
|
@@ -267,26 +343,126 @@ export default {
|
|
|
}
|
|
|
|
|
|
// 进行渲染
|
|
|
- gNode.selectAll('circle').data(pathDataForRender).enter().append('circle')
|
|
|
+ gNode.selectAll('circle.path1').data(pathDataForRender).enter().append('circle')
|
|
|
+ .classed('path1', true)
|
|
|
.attr('cx', (d) => d[0])
|
|
|
.attr('cy', (d) => d[1])
|
|
|
.attr('r', LENGTH_PER_POINT * pxPerUnitLength / 2)
|
|
|
- .attr('fill', 'blue')
|
|
|
+ .attr('fill', '#000088')
|
|
|
.attr('pointer-events', 'none')
|
|
|
- // todo: 不用数字,改用不同颜色的图形。
|
|
|
- gNode.selectAll('text').data(pathDataForRender2).enter().append('text')
|
|
|
- .text('1')
|
|
|
- .attr('x', (d) => d[0])
|
|
|
- .attr('y', (d) => d[1] + LENGTH_PER_POINT * pxPerUnitLength / 2)
|
|
|
- .attr('fill', 'green')
|
|
|
- .attr('text-anchor', 'middle')
|
|
|
+ gNode.selectAll('circle.path2').data(pathDataForRender2).enter().append('circle')
|
|
|
+ .classed('path2', true)
|
|
|
+ .attr('cx', (d) => d[0])
|
|
|
+ .attr('cy', (d) => d[1])
|
|
|
+ .attr('r', LENGTH_PER_POINT * pxPerUnitLength / 3)
|
|
|
+ .attr('fill', 'blue')
|
|
|
.attr('pointer-events', 'none')
|
|
|
- .attr('font-size', LENGTH_PER_POINT * pxPerUnitLength * 10)
|
|
|
},
|
|
|
- resetForm() {
|
|
|
+ onResetPath() {
|
|
|
this.formData.path1 = ''
|
|
|
this.formData.path2 = ''
|
|
|
+ gNode.selectAll('circle.path1').remove()
|
|
|
+ gNode.selectAll('circle.path2').remove()
|
|
|
+ },
|
|
|
+ renderStartEndPoint() {
|
|
|
+ gNode.selectAll('circle.start').remove()
|
|
|
+ gNode.selectAll('circle.end').remove()
|
|
|
+
|
|
|
+ startPoint = this.formData.startPoint.trim()
|
|
|
+ if(startPoint[0] === '[') { startPoint = startPoint.substr(1)}
|
|
|
+ if (startPoint[startPoint.length - 1] === ']') { startPoint = startPoint.substr(0, startPoint.length - 1) }
|
|
|
+ startPoint = startPoint.trim()
|
|
|
+ startPoint = startPoint.split(',').map((item) => {
|
|
|
+ item = item.trim()
|
|
|
+ if (!item) {
|
|
|
+ return undefined
|
|
|
+ } else {
|
|
|
+ return Number(item)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ startPoint = startPoint.filter((item) => {
|
|
|
+ return item !== undefined
|
|
|
+ })
|
|
|
+ if (startPoint.length !== 2) {
|
|
|
+ window.alert(`解析起点坐标失败`)
|
|
|
+ startPoint = null
|
|
|
+ }
|
|
|
+
|
|
|
+ endPoint = this.formData.endPoint.trim()
|
|
|
+ if(endPoint[0] === '[') { endPoint = endPoint.substr(1)}
|
|
|
+ if (endPoint[endPoint.length - 1] === ']') { endPoint = endPoint.substr(0, endPoint.length - 1) }
|
|
|
+ endPoint = endPoint.trim()
|
|
|
+ endPoint = endPoint.split(',').map((item) => {
|
|
|
+ item = item.trim()
|
|
|
+ if (!item) {
|
|
|
+ return undefined
|
|
|
+ } else {
|
|
|
+ return Number(item)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ endPoint = endPoint.filter((item) => {
|
|
|
+ return item !== undefined
|
|
|
+ })
|
|
|
+ if (endPoint.length !== 2) {
|
|
|
+ window.alert(`解析终点坐标失败`)
|
|
|
+ endPoint = null
|
|
|
+ }
|
|
|
+
|
|
|
+ if (startPoint) {
|
|
|
+ // 起点坐标映射到视口坐标
|
|
|
+ startPoint[0] = (startPoint[0] - xCenter) * pxPerUnitLength + svgWidth / 2
|
|
|
+ startPoint[1] = (startPoint[1] - yCenter) * pxPerUnitLength + svgHeight / 2
|
|
|
+ // 进行渲染
|
|
|
+ gNode.selectAll('circle.start').data([startPoint]).enter().append('circle')
|
|
|
+ .classed('start', true)
|
|
|
+ .attr('cx', (d) => d[0])
|
|
|
+ .attr('cy', (d) => d[1])
|
|
|
+ .attr('r', LENGTH_PER_POINT * pxPerUnitLength / 3.5)
|
|
|
+ .attr('fill', 'red')
|
|
|
+ .attr('pointer-events', 'none')
|
|
|
+ }
|
|
|
+
|
|
|
+ if (endPoint) {
|
|
|
+ // 终点坐标映射到视口坐标
|
|
|
+ endPoint[0] = (endPoint[0] - xCenter) * pxPerUnitLength + svgWidth / 2
|
|
|
+ endPoint[1] = (endPoint[1] - yCenter) * pxPerUnitLength + svgHeight / 2
|
|
|
+ // 进行渲染
|
|
|
+ gNode.selectAll('circle.end').data([endPoint]).enter().append('circle')
|
|
|
+ .classed('end', true)
|
|
|
+ .attr('cx', (d) => d[0])
|
|
|
+ .attr('cy', (d) => d[1])
|
|
|
+ .attr('r', LENGTH_PER_POINT * pxPerUnitLength / 3.5)
|
|
|
+ .attr('fill', 'green')
|
|
|
+ .attr('pointer-events', 'none')
|
|
|
+ }
|
|
|
},
|
|
|
+ onResetStartEndPoint() {
|
|
|
+ this.formData.startPoint = ''
|
|
|
+ this.formData.endPoint = ''
|
|
|
+ gNode.selectAll('circle.start').remove()
|
|
|
+ gNode.selectAll('circle.end').remove()
|
|
|
+ },
|
|
|
+ zoomIn(coordinate) {
|
|
|
+ if (!Array.isArray(coordinate) || coordinate.length !== 2) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ svgNode.transition().duration(1000).call(
|
|
|
+ zoomObj.transform,
|
|
|
+ d3.zoomIdentity.translate(svgWidth / 2, svgHeight / 2).scale(100 / pxPerUnitLength).translate(- coordinate[0], - coordinate[1])
|
|
|
+ );
|
|
|
+ },
|
|
|
+ zoomInToStartPoint() {
|
|
|
+ this.zoomIn(startPoint)
|
|
|
+ },
|
|
|
+ zoomInToEndPoint() {
|
|
|
+ this.zoomIn(endPoint)
|
|
|
+ },
|
|
|
+ resetZoom() {
|
|
|
+ svgNode.transition().duration(1000).call(
|
|
|
+ zoomObj.transform,
|
|
|
+ d3.zoomIdentity.scale(1)
|
|
|
+ )
|
|
|
+ }
|
|
|
},
|
|
|
}
|
|
|
</script>
|
|
@@ -296,13 +472,40 @@ export default {
|
|
|
font-family: Avenir, Helvetica, Arial, sans-serif;
|
|
|
-webkit-font-smoothing: antialiased;
|
|
|
-moz-osx-font-smoothing: grayscale;
|
|
|
- text-align: center;
|
|
|
color: #2c3e50;
|
|
|
}
|
|
|
+.formsWrapper {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+}
|
|
|
+.formsWrapper .form1 {
|
|
|
+ width: 25vw;
|
|
|
+}
|
|
|
+.formsWrapper .form2 {
|
|
|
+ width: 45vw;
|
|
|
+}
|
|
|
+.formsWrapper .form3 {
|
|
|
+ width: 25vw;
|
|
|
+}
|
|
|
.infoText {
|
|
|
min-height: 2em;
|
|
|
}
|
|
|
+.map {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+.map .button-group {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+.map .button-group .btn {
|
|
|
+ margin: 10px;
|
|
|
+}
|
|
|
.svgWrapper {
|
|
|
+ display: inline-block;
|
|
|
overflow: hidden;
|
|
|
background: #eee;
|
|
|
}
|