D3实现站点路线图demo分享

分享一下通过D3实现的站点路线分布图,这是一个demo。效果图如下:

在这里插入图片描述

源码如下:
<template><div class="map-test" ref="d3Chart"><div class="tooltip" id="popup-element"><span>{{ text }}</span><i id="close-element" class="el-icon-close"></i></div><div class="mark" v-show="visible"></div></div>
</template><script>
import * as d3 from "d3";export default {name: "MapTest",components: {},data() {return {text: null,svgInstance: null, // d3元素实例popupInstance: null, // 弹窗实例visible: false,allData: [], // 全部点位数据allLineData: [], // 全部连线数据gsNamePointData: [], // 高速名称点位数据};},computed: {},methods: {createChart() {const width = window.innerWidth;const height = window.innerHeight;this.svgInstance = d3.select(this.$refs.d3Chart).append("svg").attr("width", width).attr("height", height).attr("viewBox", `0 0 ${width} ${height}`).attr("preserveAspectRatio", "xMidYMid slice");this.popupInstance = d3.select("#popup-element");const closeBtn = d3.select("#close-element");closeBtn.on("click", (event) => {this.popupInstance.transition().duration(300).style("opacity", 0) // 使弹窗逐渐透明.style("transform", "scale(0)"); // 缩小弹窗});// 全部点位信息const data = [{x: 500,y: 150,label: "湖州",code: 2117,source: 2117,target: 2115,type: "station",gsName: "申苏浙皖",},{x: 700,y: 150,label: "织里",code: 2115,source: 2115,target: 2113,type: "station",gsName: "申苏浙皖",},{x: 1200,y: 150,label: "南浔",code: 2113,source: 2113,target: null,type: "station",gsName: "申苏浙皖",},{x: 700,y: 350,label: "湖州东",code: 3233,source: 3233,target: 3231,type: "station",gsName: "申嘉湖",},{x: 1200,y: 350,label: "双林",code: 3231,source: 3231,target: 3229,type: "station",gsName: "申嘉湖",},{x: 1400,y: 350,label: "南浔南",code: 3229,source: 3229,target: null,type: "station",gsName: "申嘉湖",},{x: 700,y: 550,label: "钟管",code: 4049,source: 4049,target: 4051,type: "station",gsName: "杭绕西复线",},{x: 1200,y: 550,label: "新市西",code: 4051,source: 4051,target: 3206,type: "station",gsName: "杭绕西复线",},{x: 1350,y: 550,label: "新市枢纽",code: 3206,source: 3206,target: null,type: "station",gsName: "杭绕西复线",},{x: 800,y: 700,label: "雷甸",code: 3243,source: 3243,target: 3241,type: "station",gsName: "练杭",},{x: 1300,y: 700,label: "新安",code: 3241,source: 3241,target: 3239,type: "station",gsName: "练杭",},{x: 1500,y: 700,label: "新市",code: 3239,source: 3239,target: null,type: "station",gsName: "练杭",},{x: 900,y: 150,label: "织里枢纽",code: 5101,source: 5101,target: 5111,type: "hub",gsName: "沪杭高速",},{x: 900,y: 210,label: "织里东",code: 5111,source: 5111,target: 5113,type: "hh-station",gsName: "沪杭高速",},{x: 900,y: 280,label: "南浔西",code: 5113,source: 5113,target: 5102,type: "hh-station",gsName: "沪杭高速",},{x: 900,y: 350,label: "双林枢纽",code: 5102,source: 5102,target: 5115,type: "hh-station",gsName: "沪杭高速",},{x: 900,y: 410,label: "菱湖(分中心)",code: 5115,source: 5115,target: 5117,type: "hh-station",gsName: "沪杭高速",},{x: 900,y: 480,label: "千金",code: 5117,source: 5117,target: 5103,type: "hh-station",gsName: "沪杭高速",},{x: 900,y: 550,label: "士林枢纽",code: 5103,source: 5103,target: 5119,type: "hub",gsName: "沪杭高速",},{x: 960,y: 620,label: "下舍",code: 5119,source: 5119,target: 5104,type: "hh-station",gsName: "沪杭高速",},{x: 1050,y: 700,label: "新安枢纽",code: 5104,source: 5104,target: null,type: "hub",gsName: "沪杭高速",},];this.allData = data;const colorList = ["#409EFF","#67C23A","#E6A23C","#F56C6C","#909399",];// 获取“全部点位”连线数据this.allLineData = this.getLineData(data);const gsKeyToValue = [...new Set(data.map((ele) => ele.gsName))];// 高速名称数据gsKeyToValue.forEach((ele, index) => {const line = this.allLineData.filter((item) => item.gsName === ele);if (line.length > 0) {this.drawLine(this.svgInstance,line,index,colorList[index]);}const gsPointList = this.allData.filter((item) => item.gsName === ele);const len = gsPointList.length;if (len >= 2) {this.gsNamePointData.push(this.calculateGSNamePosition(gsPointList[len - 1],gsPointList[len - 2],ele));} else if (len == 1) {this.gsNamePointData.push(this.calculateGSNamePosition(gsPointList[len - 1],gsPointList[len - 1],ele));}});// 画“全部点位”this.drawPoint(this.svgInstance, data);// 画“收费站名称”this.drawPointText(this.svgInstance,data,"label",0,-25,40,-10);// 画“收费站编码”this.drawPointText(this.svgInstance, data, "code", 0, -25, 40, 6);this.drawPointText(this.svgInstance,this.gsNamePointData,"label",0,-25,0,30,'#000',20,'bold');},/*** 通过判断type返回目标图片的地址* @param   {String}   type       图片类型* @returns {String}   url        目标图片的地址*/setImgUrl(type) {let url;switch (type) {case "gantry":url = require("../../../assets/equipmentIcon.png");break;case "station":url = require("../../../assets/dataIcon.png");break;case "hub":url = require("../../../assets/userIcon.png");break;case "hh-station":url = require("../../../assets/homeIcon.png");break;}return url;},/*** 画点* @param   {Object}   svg         d3实例* @param   {Array}    pointData   点位数据* @param   {String}   type        点位类型* @returns {void}     无返回值*/drawPoint(svg, pointData) {// 根据类型设置图标地址svg.selectAll(".point").data(pointData).enter().append("image").attr("class", (d) => {return `.point-${d.type}`;}).attr("id", (d) => {return `id-${d.code}`;}).attr("x", (d) => d.x - 20).attr("y", (d) => d.y - 20).attr("width", 40).attr("height", 40).attr("href", (d) => {return this.setImgUrl(d.type);}).attr("r", 8).on("mouseover", (event, d) => {this.visible = true;// 置灰“当前节点非相关节点”的文本svg.selectAll("text").classed("opacity-1", true);// 高亮“当前节点相关节点”的文本svg.selectAll("text").filter((n) => n.gsName == d.gsName).classed("opacity-10", true);// 置灰“当前节点非相关节点”的图标svg.selectAll("image").classed("opacity-1", true);// 高亮“当前节点相关节点”的图标svg.selectAll("image").filter((n) => n.gsName == d.gsName).classed("opacity-10", true);// 置灰“当前节点非相关节点”的连线svg.selectAll("line").classed("opacity-1", true);// 高亮“当前节点相关节点”的连线const ele = svg.selectAll("line").filter((l) => l.gsName === d.gsName);// 设置初始状态ele.classed("opacity-10", true).style("stroke-dasharray", "25, 25").style("stroke-dashoffset", 0);// 启动流水效果function startAnimation() {const length = 1000;ele.style("stroke-dasharray", "25,25") // 设置虚线的总长度.style("stroke-dashoffset", length) // 设置初始的偏移量.transition() // 创建过渡动画.duration(10000) // 设置动画时长.ease(d3.easeLinear) // 使用线性过渡.style("stroke-dashoffset", 0) // 让偏移量为 0,从而产生流水效果.on("end", startAnimation); // 动画结束时递归调用}// 启动动画startAnimation();}).on("mouseout", (event, d) => {this.visible = false;// 置灰节点的文本svg.selectAll("text").classed("opacity-1", false);svg.selectAll("text").filter((n) => n.gsName == d.gsName).classed("opacity-10", false);// 置灰节点的图标svg.selectAll("image").filter((n) => n.gsName == d.gsName).classed("opacity-10", false);svg.selectAll("image").classed("opacity-1", false);// 置灰节点间的连线,停止动画svg.selectAll("line").classed("opacity-1", false);svg.selectAll("line").filter((l) => l.gsName === d.gsName).classed("opacity-10", false).style("stroke-dasharray", "0,0").interrupt();});},/*** 画“点文字”* @param   {Object}   svg         d3实例* @param   {Array}    pointData   点位数据* @param   {String}   property    展示文字的属性* @param   {Number}   x           横向(x轴)偏移量* @param   {Number}   y           纵向(y轴)偏移量* @param   {Number}   dx          文本之间的间距* @param   {Number}   dy          文本之间的间距* @param   {String}   color       文本之间的间距* @param   {Number}   fontSize    文字大小* @param   {Number}   fontWeight  文字加粗* @returns {void}     无返回值*/drawPointText(svg,pointData,property,x = 0,y = 0,dx = 0,dy = 0,color = "#000",fontSize = 14,fontWeight = 400,) {svg.selectAll(`.text-${property}`).data(pointData).enter().append("text").attr("class", `.text-${property}`).attr("x", (d) => d.x + x + dx).attr("y", (d) => d.y + y).attr("text-anchor", "middle").attr("fill", color).attr("font-size", fontSize).attr("font-weight", fontWeight).append("tspan").attr("x", (d) => d.x + dx).attr("dy", dy).text((d) => d[property]);},/**** @param   {Object}    svg        d3实例* @param   {Array}     linkData   点位连接数据* @param   {String}    lineName   线名称* @param   {String}    lineColor   线名称* @returns {void}      无返回值*/drawLine(svg, linkData, lineName, lineColor) {svg.selectAll(`.line-${lineName}`).data(linkData).enter().append("line").attr("class", `.line-${lineName}`).attr("x1", (d) => d.source.x).attr("y1", (d) => d.source.y).attr("x2", (d) => d.target.x).attr("y2", (d) => d.target.y).style("stroke", lineColor).style("stroke-width", 10) // 设置线条宽度.style("stroke-linecap", "square") // 设置端点样式.style("stroke-linejoin", "round"); // 设置连接点样式},/*** 获取连线数据* @param   {Array}     linkData   点位连接数据* @returns {void}      无返回值*/getLineData(linkData) {const res = [];// 创建一个站点代码与站点对象的映射const stationMap = linkData.reduce((map, station) => {map[station.code] = station;return map;}, {});// 遍历原始的站点列表来构建最终的结果for (let i = 0; i < linkData.length; i++) {const currentStation = linkData[i];const targetCode = currentStation.target;// 如果目标站点存在if (targetCode && stationMap[targetCode]) {const targetStation = stationMap[targetCode];// 创建一个新的对象,将source和target配对res.push({source: currentStation,target: targetStation,gsName: currentStation.gsName,});// 标记该站点的目标站点为null,防止重复配对stationMap[targetCode] = null;}}return res;},/*** 通过倒数前两个点位计算高速名称的点位数据* @param   {Object}    lastOne    倒数第一个点位* @param   {Object}    lastTwo    倒数第二个点位* @param   {String}    label      高速名称* @param   {Number}    distance   距离* @returns {Object}    高速点位*/calculateGSNamePosition(lastOne, lastTwo, label, distance = 100) {// 计算lastOne到lastTwo的向量const vx = lastOne.x - lastTwo.x;const vy = lastOne.y - lastTwo.y;// 计算lastOne到lastTwo的距离const dist = Math.sqrt(vx * vx + vy * vy);// 计算单位向量const unitX = vx / dist;const unitY = vy / dist;// 根据单位向量计算a3的位置,a3距离lastOne的横纵坐标都为200const a3X = lastOne.x + unitX * distance;const a3Y = lastOne.y + unitY * distance;// 返回a3的位置return { x: a3X, y: a3Y, label, gsName: label };},},created() {},mounted() {this.createChart();},
};
</script><style lang="less">
.map-test {height: 100%;width: 100%;overflow: hidden;cursor: pointer;position: relative;svg {width: 100%;height: 100%;cursor: pointer;}.tooltip {position: absolute;width: 200px;height: 40px;background-color: pink;z-index: 9;transform: scale(0);font-size: 20px;display: flex;align-items: center;justify-content: center;opacity: 0;transition: opacity 0.5s ease, transform 0.5s ease;.el-icon-close {position: absolute;top: 0;right: 0;font-size: 16px;}}.opacity-10 {opacity: 1!important;}.opacity-2 {opacity: 0.2;}.opacity-1 {opacity: 0.1;}.mark {position: absolute;top: 0;left: 0;height: 100%;width: 100%;background: rgba(0,0,0,0.05);z-index: -1;}
}
</style>
D3 官网:https://d3js.org/
D3 API地址:https://d3js.org/api
功能描述:
  1. 画点、画线、画文本内容
  2. 鼠标悬浮点位,高亮关联点位、文本及其高速公路名称
  3. 鼠标悬浮展示动态流水线效果
  4. 弹窗效果已包含,未在demo中展示
数据分析:

当前数据是前端mock数据,后续应用在实际项目中需要后端给到的数据格式是:

  1. 每个点位中需要有相对位置坐标,前端根据视口范围计算,展示点位
  2. 点位需要包含类型,来判断展示对应的图表(门架、站点、枢纽等)
  3. 点位需要包含指向关系,作用是用来画连接线
  4. 点位需要包含高速名称,用于展示高速名称标识
  5. 点位必须按照顺序返回,否则连线会比较乱,视觉体验差
注意:
  • 高速公路名称是根据calculateGSNamePosition函数计算的出来的
后端返回的数据样例:
// x,y代表坐标
// label: 名称
// source:源
// target:指向目标
// type:类型
const data = {'沪杭高速': [{x: 900,y: 150,label: "织里枢纽",code: 5101,source: 5101,target: 5111,type: "hub",},{x: 900,y: 210,label: "织里东",code: 5111,source: 5111,target: 5113,type: "station",},],
};// x,y代表坐标
// label: 名称
// source:源
// target:指向目标
// type:类型
// gsName:高速名称
const data1 = [{x: 900,y: 150,label: "织里枢纽",code: 5101,source: 5101,target: 5111,type: "hub",gsName: "沪杭高速",},{x: 900,y: 210,label: "织里东",code: 5111,source: 5111,target: 5113,type: "station",gsName: "沪杭高速",},
];

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/63641.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

算法日记 42 day 图论

今天来看看广度优先搜索&#xff0c;并且写几个题。刷到这里我才想起来&#xff0c;当时第一次面试的时候问的就是这个题&#xff0c;当时大概知道一点思路&#xff0c;但不清楚是图论方面的&#xff0c;更别说写出来了。 广度优先搜索&#xff08;BFS&#xff09; 不同于深度…

【NLP 13、实践 ② 判断文本中是否有特定字符出现】

人活着就是为了救赎自己&#xff1b;为了经历世间的美好&#xff1b;为了在挫折中成长变得更坚强 —— 24.12.10 一、定义模型 1.嵌入层 nn.Embedding&#xff1a;将离散值转化为向量 # embedding层&#xff0c;vocab&#xff1a;词表&#xff0c;要多少个数据&#xff08;向…

GD32中断

1.什么是中断&#xff1a;打断现在正在做的事&#xff0c;去执行其他事。 2.ARM异常中断结构 3.中断向量编号。中断向量是 进行了映射的&#xff0c;直接映射到 flash中的地址。 4.中断执行结构。向量里面保存的是执行函数的地址。&#xff08;具体可在编译完后的map文件中查看…

三菱FX3U模拟量产品的介绍

FX3u可编程控制器模拟量产品包括&#xff1a;特殊适配器、特殊功能模块的连接 1、连接在FX3U可编程控制器的左侧。 2、连接特殊适配器时&#xff0c;需要功能扩展板。 3、最多可以连接4台模拟量特殊适配器。 4、使用高速输入输出特殊适配器时&#xff0c;请将模拟量特殊适配器连…

【PlantUML系列】流程图(四)

目录 目录 一、基础用法 1.1 开始和结束 1.2 操作步骤 1.3 条件判断 1.4 并行处理 1.5 循环 1.6 分区 1.7 泳道 一、基础用法 1.1 开始和结束 开始一般使用start关键字&#xff1b;结束一般使用stop/end关键字。基础用法包括&#xff1a; start ... stopstart ...…

idea压缩js,css

这是需要的jar包(文章顶部也可以下载) 地址:https://download.csdn.net/download/yuzheh521/90109966?spm1001.2101.3001.9500 压缩js arguments: -jar E:\swj\jar_packages\css_js_compress\yuicompressor-2.4.8.jar --type js --charset utf-8 $FilePath$ -o $FileNameWith…

ASP.NET |日常开发中连接Oracle数据库详解

ASP.NET &#xff5c;日常开发中连接Oracle数据库详解 前言一、安装和配置 Oracle 数据访问组件1.1 安装ODP.NET&#xff08;Oracle Data Provider for.NET&#xff09;&#xff1a;1.2 引用相关程序集&#xff1a; 二、配置连接字符串2.1 连接字符串的基本组成部分&#xff1a…

【linux系统】基础开发工具(yum、Vim)

1. 软件包管理器 1.1 什么是软件包 在Linux下安装软件, ⼀个通常的办法是下载到程序的源代码, 并进⾏编译, 得到可执⾏程序. 但是这样太麻烦了, 于是有些⼈把⼀些常⽤的软件提前编译好, 做成软件包(可以理解成windows上的安装程序)放在⼀个服务器上, 通过包管理器可以很⽅便的…

C语言:define定义常量和定义宏(详解)

本篇博客给大家带来的是#define定义常量和#define定义宏的方法 &#x1f41f;&#x1f41f;文章专栏&#xff1a;C语言 &#x1f680;&#x1f680;若有问题评论区下讨论&#xff0c;我会及时回答 ❤❤欢迎大家点赞、收藏、分享 你们的支持就是我创造的动力 今日思想&#xff1…

Let up bring up a linux.part2 [十一]

之前的篇幅中我们已经将 Linux 内核 bringup 起来了&#xff0c;不知道大家有没有去尝试将根文件系统运行起来&#xff0c;今天我就带领大家完成这个事情&#xff0c;可以跟着下面的步骤一步步来完成&#xff1a; 在这里我们使用 busybox 构建 rootfs&#xff1a; 下载 busyb…

使用GO--Swagger生成文档

概述 在前后端分离的项目中&#xff0c;后端配置swagger可以很好的帮助前端人员了解后端接口参数和数据传输。go-swagger 是一个功能全面且高性能的Go语言实现工具包&#xff0c;用于处理Swagger 2.0&#xff08;即OpenAPI 2.0&#xff09;规范。它提供了丰富的工具集&#x…

pushgateway HA高可用方案

未经本人同意不得转载&#xff0c;若引用请附上原文链接。 项目使用flink来处理kafka中的无界流数据&#xff0c;采用的是flink on yarn的模式部署flink任务。最近做flink任务的监控过程中&#xff0c;踩了一些坑。下面是过程&#xff0c;只想看最终方案的直接拉到最后。 先说…

01-Chromedriver下载与配置(mac)

下载地址&#xff1a; 这里我用的最后一个&#xff0c;根据自己chrome浏览器选择相应的版本号即可 ChromeDriver官网下载地址&#xff1a;https://sites.google.com/chromium.org/driver/downloads ChromeDriver官网最新版下载地址&#xff1a;https://googlechromelabs.git…

使用docker-compose安装Milvus向量数据库及Attu可视化连接工具

首先确保系统已经安装上了docker 然后去https://github.com/docker/compose/releases/下载安装docker-compose 跟随自己下系统和服务器情况下载 上传到服务器 mv docker-compose-linux-aarch64 docker-compose chmod x docker-compose2.dockr-compose命令 docker-compose …

Conda + JuiceFS :增强 AI 开发环境共享能力

Conda 是当前 AI 应用开发领域中非常流行的环境和包管理系统&#xff0c;因其能够简单便捷地创建与系统资源相隔离的虚拟环境广受欢迎。 Conda 支持在不同的操作系统上重建相同的工作环境&#xff0c;但在环境共享复用方面仍存在一些挑战。比如&#xff0c;在不同机器上复用相…

【SpringBoot】31 Session + Redis 实战

Gitee https://gitee.com/Lin_DH/system 介绍 【SpringBoot】30 Cookie、Session、Token https://blog.csdn.net/weixin_44088274/article/details/144241595 背景 Spring Session 是 Spring 的一个子项目&#xff0c;它提供了一种管理用户会话信息的方法&#xff0c;无论…

关于网站的权重和百度蜘蛛爬虫的关系

网站的权重和百度蜘蛛爬虫的关系是密切关联的。 网站权重是一个衡量网站在搜索引擎中重要性的概念&#xff0c;它反映了网站在搜索引擎算法中的相对重要程度。而百度蜘蛛爬虫则是百度搜索引擎用来抓取网页内容的工具&#xff0c;通过分析网页的URL、内容、链接等因素来评估网站…

游戏引擎学习第35天

开场介绍 今天的任务是继续改进一个虚拟的瓦片地图系统&#xff0c;使其适合处理更大的世界。我们希望这个系统能管理大范围的游戏世界&#xff0c;其中包含按需存储的小区域。昨天&#xff0c;我们介绍了“内存区域”的概念&#xff0c;用于管理持久性存储。我们计划今天继续…

Leetcode经典题5--轮转数组

题目描述 给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 k 是非负数。 输入输出示例 &#xff1a; 输入: nums [1,2,3,4,5,6,7], k 3 输出: [5,6,7,1,2,3,4] 解释: 向右轮转 1 步: [7,1,2,3,4,5,6] 向右轮转 2 步: [6,7,1,2,3,4,5] 向右…

【JS】简单CSS简单JS写的上传进度条

纯JS写的&#xff0c;简单的上传进度条&#xff0c;当上传的文件较大&#xff0c;加一个动态画面&#xff0c;就不会让人觉得出错了或网络卡了 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"v…