问题
需要展示复杂的力导向图,通过neo4j的实例代码可以知道,使用d3.js来处理相关前端渲染。这里通过集成vue3,vite来使用D3.js v7版本。
解决
Vite+Vue3
创建vue工程:
npm init vue@latest
配置:
Vue.js - The Progressive JavaScript Framework
✔ Project name: … vue-d3v7
✔ Target directory "vue-d3v7" is not empty. Remove existing files and continue? … yes
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit Testing? … No / Yes
✔ Add Cypress for both Unit and End-to-End testing? … No / Yes
✔ Add ESLint for code quality? … No / Yes
Scaffolding project in /Users/zhangyalin/Downloads/vue-d3v7...
Done. Now run:
cd vue-d3v7
npm install
npm run dev
安装依赖:
npm install
运行工程:
npm run dev
vite.config.js
import { fileURLToPath, URL } from 'url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
// 开启sourcemap配置
css:{
devSourcemap: true
},
build:{
sourcemap: true
}
})
d3 v7
npm i d3@latest
在项目中安装d3最新版本,注意这里使用的d3的v7版本。
D3v7.vue
创建自定义vue组件
<script setup>
import * as d3 from "d3";
function go() {
const width = 600, height = 1000;
const color = d3.scaleOrdinal(d3.schemeCategory10);
const types = ["CITED_BY"]
const strokeWidth = 1.5
// d3.json("/api/article/graph").then(function(data) {
// let graph = data.data;
let graph = {
"links": [
{ "source": "18470926", "target": "10097404", "type": "CITED_BY" }
],
"nodes": [
{
"label": "Article",
"id": "18470926"
},
{
"label": "Article",
"id": "10097404"
}
]
};
const simulation = d3.forceSimulation(graph.nodes)
.force("charge", d3.forceManyBody().strength(-3000))
.force("x", d3.forceX(width / 2).strength(1))
.force("y", d3.forceY(height / 2).strength(1))
.force("link", d3.forceLink(graph.links).id(function(d) {return d.id; }).distance(50).strength(1))
.on("tick", ticked);
const svg = d3.select("#graph").append("svg")
.attr("width", width).attr("height",height)
.attr("pointer-events", "all");
// Per-type markers, as they don't inherit styles.
svg.append("defs").selectAll("marker")
.data(types)
.join("marker")
.attr("id", d => `arrow-${d}`)
.attr("viewBox", "0 0 10 10")
.attr("refX", 10)
.attr("refY", 5)
.attr("markerUnits", strokeWidth)
.attr("markerWidth", 10)
.attr("markerHeight", 10)
.attr("orient", "auto")
.append("path")
.attr("fill", color)
.attr("d", 'M 0 0 L 10 5 L 0 10 z');
const link = svg.append("g")
.selectAll("g")
.data(graph.links)
.enter()
.append("line")
.attr("stroke", d => color(d.type))
.attr("stroke-width", strokeWidth)
.attr("marker-end", d => `url(${new URL(`#arrow-${d.type}`, location)})`);
const node = svg.append("g")
.selectAll("g")
.data(graph.nodes)
.enter().append("g");
node.append("circle")
.attr("r", 5)
.attr("fill", function(d) { return color(d.label); })
node.call(
d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
);
node.append("text")
.text(function(d) {
return d.id;
})
.attr('x', 6)
.attr('y', 3).style('fill', 'white');
node.append("title")
.text(function(d) { return d.label; });
function ticked() {
node.call(updateNode);
link.call(updateLink);
}
function fixna(x) {
if (isFinite(x)) return x;
return 0;
}
function updateLink(link) {
link.attr("x1", function(d) { return fixna(d.source.x); })
.attr("y1", function(d) { return fixna(d.source.y); })
.attr("x2", function(d) { return fixna(d.target.x); })
.attr("y2", function(d) { return fixna(d.target.y); });
}
function updateNode(node) {
node.attr("transform", function(d) {
return "translate(" + fixna(d.x) + "," + fixna(d.y) + ")";
});
}
function dragstarted(event, d) {
event.sourceEvent.stopPropagation();
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
// });
}
</script>
<template>
<button @click="go">Go</button>
<div id="graph">
</div>
</template>
<style scoped>
</style>
App.vue
使用自定义组件:
<script setup>
import HelloWorld from './components/HelloWorld.vue'
import D3v7 from './components/D3v7.vue'
</script>
<template>
<header>
<img alt="Vue logo" class="logo" src="./assets/logo.svg" width="125" height="125" />
<div class="wrapper">
<HelloWorld msg="You did it!" />
</div>
</header>
<main>
<D3v7 />
</main>
</template>
<style>
@import './assets/base.css';
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
font-weight: normal;
}
header {
line-height: 1.5;
}
.logo {
display: block;
margin: 0 auto 2rem;
}
a,
.green {
text-decoration: none;
color: hsla(160, 100%, 37%, 1);
transition: 0.4s;
}
@media (hover: hover) {
a:hover {
background-color: hsla(160, 100%, 37%, 0.2);
}
}
@media (min-width: 1024px) {
body {
display: flex;
place-items: center;
}
#app {
display: grid;
grid-template-columns: 1fr 1fr;
padding: 0 2rem;
}
header {
display: flex;
place-items: center;
padding-right: calc(var(--section-gap) / 2);
}
header .wrapper {
display: flex;
place-items: flex-start;
flex-wrap: wrap;
}
.logo {
margin: 0 2rem 0 0;
}
}
</style>
验证
npm run dev
效果:
源代码
https://github.com/fxtxz2/vue-d3v7
参考: