Commit c3e78185 authored by 郭铭瑶's avatar 郭铭瑶 🤘

init project

parents
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: ['plugin:vue/vue3-recommended', 'plugin:prettier/recommended'],
parserOptions: {
parser: 'babel-eslint',
},
rules: {
'vue/no-multiple-template-root': 'off',
'vue/max-attributes-per-line': 'off',
'vue/html-self-closing': 'off',
'vue/singleline-html-element-content-newline': 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, // 生产环境禁止debugger
'no-console': process.env.NODE_ENV === 'production' ? 2 : 0, // 生产环境禁止console
'no-alert': process.env.NODE_ENV === 'production' ? 2 : 0, // 生产环境禁止alert
'no-shadow-restricted-names': 2, // 禁用关键字及保留字等
'dot-notation': 1, // 尽可能使用 . 来访问对象属性
'no-multi-spaces': 1, // 禁止使用多个空格
'brace-style': 1, // 大括号风格 - one true brace style
'no-var': 1, // 禁用var声明
'no-new-object': 1, // 禁止new Object
'no-array-constructor': 1, // 禁止new Array
'prefer-const': 1, // 要求使用 const 声明那些声明后不再被修改的变量
'prefer-destructuring': 1, // 优先使用数组和对象解构
'no-param-reassign': 1, // 禁止在函数中对函数参数重新赋值
'no-extra-semi': 1, // 禁用不必要的分号
// "no-unused-vars": 1, // 禁止已声明但未使用的变量
// "indent": [1, 2], // 使用2个空格缩进
'no-multiple-empty-lines': [1, { max: 1 }], // 禁止连续出现2个及以上空行
'default-case': 1, // 要求switch语句必须有default分支
'key-spacing': [1, { beforeColon: false, afterColon: true }], // 冒号前不要空格,后需要空格
'comma-spacing': [1, { before: false, after: true }], // 逗号前不要空格,后需要空格
'arrow-spacing': [1, { before: true, after: true }], // 箭头函数中的箭头前后需要留空格
quotes: [1, 'single'], // 字符串使用单引号
semi: [1, 'never'], // 禁止使用分号
},
}
node_modules
.DS_Store
dist
dist-ssr
*.local
\ No newline at end of file
module.exports = {
tabWidth: 2,
useTabs: false,
singleQuote: true,
semi: false,
}
\ No newline at end of file
MIT License
Copyright (c) 2021 Max Kwok
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# my-vite-app
vite + vue 项目自用模板
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "relation-graph",
"version": "0.0.0",
"scripts": {
"dev": "vite",
"start": "node node_modules/esbuild/install.js && npm run dev",
"build": "vite build"
},
"dependencies": {
"d3": "^5.15.0",
"vue": "^3.0.5"
},
"devDependencies": {
"@vitejs/plugin-vue": "^1.1.5",
"@vue/compiler-sfc": "^3.0.5",
"babel-eslint": "^10.1.0",
"eslint": "^7.17.0",
"eslint-config-prettier": "^7.1.0",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-vue": "^7.4.1",
"prettier": "^2.2.1",
"stylus": "^0.54.8",
"vite": "^2.1.0"
},
"description": "关系图工具",
"author": "Guo"
}
<template>
<Main />
</template>
<script>
import Main from './components/main.vue'
export default {
name: 'App',
components: { Main },
}
</script>
<style lang="stylus">
html,
body
margin 0
padding 0
#app
font-family Avenir, Helvetica, Arial, sans-serif
-webkit-font-smoothing antialiased
-moz-osx-font-smoothing grayscale
color #2c3e50
</style>
<template>
<div ref="container" />
</template>
<script>
import RelationGraph from '@/util/useD3.js'
import { ref } from '@vue/reactivity'
import { nextTick, onMounted, watch } from '@vue/runtime-core'
export default {
name: 'D3',
props: {
data: {
type: Object,
required: true,
},
config: {
type: Object,
default() {
return {}
},
},
},
setup(props) {
let instance = null
const container = ref(null)
onMounted(async () => {
if (!props.data.nodes || !props.data.links) return
await nextTick()
init()
})
function init() {
if (instance) {
instance.selectAll('*').remove()
} else {
instance = new RelationGraph(container.value, props.data, props.config)
}
}
watch(() => props.data, init)
return {
container,
}
},
}
</script>
<style lang="stylus">
.relation-tooltip {
position: fixed;
padding: 0.5rem 1rem;
background-color: rgba(0,0,0,0.5);
border-radius: 0.5rem;
color: #fff;
backdrop-filter: blur(0.5rem);
-webkit-backdrop-filter: blur(0.5rem);
}
</style>
<template>
<D3
:data="data"
:config="config"
style="background: #fff; width: 100%; height: 100vh"
/>
</template>
<script>
import D3 from './d3.vue'
import mockData from '@/util/mock.js'
import { ref } from '@vue/reactivity'
export default {
name: 'Main',
components: { D3 },
setup() {
const data = ref(mockData)
const config = ref({
config: {
strokeColor: 'skyblue',
},
})
return {
data,
config,
}
},
}
</script>
<style lang="stylus" scoped></style>
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
export default {
nodes: [
{
id: 119828,
name: '上海上实物业管理有限公司',
cspAddress: '上海市浦东新区浦东南路360号8楼',
cspId: '120302184910583',
color: '#82DF12',
index: 0,
x: 477.55056997846737,
y: 258.0991179058284,
vy: 0.015468822208985674,
vx: -0.09482328126482943,
},
{
id: 2191,
name: '地杰国际城F街坊(欧香四季)',
sectAddress: '御桥路1978弄',
sectId: '120302190631416',
cspId: '120302184910583',
csmId: '1807131176932409',
color: 'pink',
index: 1,
x: 827.9439862247012,
y: 653.7522566978123,
vy: 0.017203251786801845,
vx: -0.08619690081525651,
},
{
id: 2303,
name: '浦东新区浦东世纪花园二期小区',
sectAddress: '银霄路39弄',
sectId: '120302190335787',
cspId: '120302184910583',
csmId: '120302185430024',
color: 'pink',
index: 2,
x: 203.0752240505647,
y: 47.32122257038382,
vy: 0.012977775082658611,
vx: -0.09761988808612744,
},
{
id: 3314,
name: '金桥瑞仕花园',
sectAddress: '枣庄路399弄',
sectId: '120302190622545',
cspId: '120302184910583',
csmId: '1706061138724669',
color: 'pink',
index: 3,
x: 919.0861579502003,
y: -32.63016870608719,
vy: 0.01230290299089266,
vx: -0.0906524369006897,
},
{
id: 3523,
name: '家化滨江苑',
sectAddress: '浦明路1188弄',
sectId: '120302190619661',
cspId: '120302184910583',
csmId: '1708091145667207',
color: 'pink',
index: 4,
x: 157.77147503485796,
y: 390.4097457158214,
vy: 0.01658688771341018,
vx: -0.09841039135618555,
},
{
id: 3889,
name: '广兰丽园三期(水岸新郡)',
sectAddress: '祖冲之路2201弄',
sectId: '1607011107810174',
cspId: '120302184910583',
csmId: '1905061183325316',
color: 'pink',
index: 5,
x: 675.408205350506,
y: 284.31914206433936,
vy: 0.015201436493413748,
vx: -0.09024381750749001,
},
{
id: 3919,
name: '谐趣鑫庭',
sectAddress: '上丰路1483弄1-40号;宏雅路8、18、36、58、80号',
sectId: '1607011107810186',
cspId: '120302184910583',
csmId: '1607191109712112',
color: 'pink',
index: 6,
x: 522.9912570230817,
y: -85.01255156575041,
vy: 0.012140823431825232,
vx: -0.09563352866075074,
},
{
id: 4527,
name: '张江南二编制单元B1-8地块项目',
sectAddress: '盛荣路188弄1-5号',
sectId: '1508211072661230',
cspId: '120302184910583',
csmId: '1806021175624149',
color: 'pink',
index: 7,
x: 1071.5259681818584,
y: 336.8417250333232,
vy: 0.01379040172932755,
vx: -0.08541520191694248,
},
{
id: 4553,
name: '桥语别墅',
sectAddress: '唐安路199弄1号',
sectId: '120302190632761',
cspId: '120302184910583',
csmId: '1609271116614035',
color: 'pink',
index: 8,
x: 553.7307232409793,
y: 442.81524506685486,
vy: 0.016475630803987725,
vx: -0.09183114597573705,
},
{
id: 4949,
name: '地杰国际城E街坊',
sectAddress: '御桥路2066弄',
sectId: '120302190631463',
cspId: '120302184910583',
csmId: '1807131176932409',
color: 'pink',
index: 9,
x: 431.81572076725575,
y: 601.318610972062,
vy: 0.01765586606701204,
vx: -0.0907190506979843,
},
{
id: 5148,
name: '中国航海博物馆',
sectAddress: '申港大道197号',
sectId: '1805311175270742',
cspId: '120302184910583',
csmId: '1805311175270744',
color: 'pink',
index: 10,
x: 644.8914951910173,
y: -243.39241045904376,
vy: 0.00958389946806233,
vx: -0.09654360926771219,
},
{
id: 5197,
name: '环球金融中心',
sectAddress: '上海市浦东新区世纪大道100号',
sectId: '1805311175264805',
cspId: '120302184910583',
csmId: '1805311175264810',
color: 'pink',
index: 11,
x: 949.7218594771383,
y: 495.2828188824847,
vy: 0.015636065196823264,
vx: -0.08567094494686826,
},
{
id: 5434,
name: '张江南二编制单元B1-9地块',
sectAddress: '浦东新区盛荣路288弄',
sectId: '1806021175624147',
cspId: '120302184910583',
csmId: '1806021175624149',
color: 'pink',
index: 12,
x: 995.1654592670088,
y: 152.11335706142492,
vy: 0.012937877561956943,
vx: -0.08636631426342771,
},
{
id: 5458,
name: '上海市浦东新区三林体育中心',
sectAddress: '上海市浦东新区齐河路201号',
sectId: '1805311175301523',
cspId: '120302184910583',
csmId: '1805311175301525',
color: 'pink',
index: 13,
x: 324.912326359955,
y: -111.18783967152294,
vy: 0.011869007305472043,
vx: -0.09775385096064017,
},
{
id: 119743,
name: '顾忠华',
phone: '13761084612',
csId: '1807131176932409',
cspId: '120302184910583',
color: 'gold',
index: 14,
x: 629.8754055337664,
y: 627.6690677386077,
vy: 0.015991671430421007,
vx: -0.0883891354628461,
},
{
id: 118977,
name: '李婷婷',
phone: '13641626091',
csId: '120302185430024',
cspId: '120302184910583',
color: 'gold',
index: 15,
x: 279.5140254385836,
y: 231.8946235686577,
vy: 0.015531548709303952,
vx: -0.09750722181663286,
},
{
id: 119737,
name: '王健军',
phone: '13818060472',
csId: '1706061138724669',
cspId: '120302184910583',
color: 'gold',
index: 16,
x: 797.2404045271334,
y: 125.92288751769426,
vy: 0.012860239609528442,
vx: -0.09032431957008168,
},
{
id: 119736,
name: '康检辉',
phone: '15002109448',
csId: '1708091145667207',
cspId: '120302184910583',
color: 'gold',
index: 17,
x: 355.80332485200705,
y: 416.5572948119201,
vy: 0.017158228064442914,
vx: -0.0957443021328764,
},
{
id: 118613,
name: '方军',
phone: '13917673000',
csId: '1607191109712112',
cspId: '120302184910583',
color: 'gold',
index: 18,
x: 599.2752268657393,
y: 99.66883752039296,
vy: 0.013466373771190195,
vx: -0.09377294936457928,
},
{
id: 119337,
name: '丁维金',
phone: '15921867750',
csId: '1806021175624149',
cspId: '120302184910583',
color: 'gold',
index: 19,
x: 1193.1940530232691,
y: 178.20591204480817,
vy: 0.014190380569656623,
vx: -0.08359320003914546,
},
{
id: 119465,
name: '叶丽英',
phone: '15301895050',
csId: '1609271116614035',
cspId: '120302184910583',
color: 'gold',
index: 20,
x: 751.5964969385469,
y: 469.05778121108244,
vy: 0.015950775153532167,
vx: -0.08732774840489065,
},
{
id: 118644,
name: '俞彬',
phone: '13917775142',
csId: '1805311175270744',
cspId: '120302184910583',
color: 'gold',
index: 21,
x: 721.0724539538485,
y: -58.75798539996169,
vy: 0.01142861234508079,
vx: -0.0935050071815398,
},
{
id: 118693,
name: '陈江',
phone: '13818867858',
csId: '1805311175264810',
cspId: '120302184910583',
color: 'gold',
index: 22,
x: 873.3847715232649,
y: 310.6003453085982,
vy: 0.014291556709615679,
vx: -0.08690507955543568,
},
{
id: 119557,
name: '杨爱芳',
phone: '13916463385',
csId: '1805311175301525',
cspId: '120302184910583',
color: 'gold',
index: 23,
x: 401.2518989133498,
y: 73.47511161414829,
vy: 0.013447469674636874,
vx: -0.09656100674935258,
},
],
links: [
{
id: '382328211169456128',
source: 119828,
target: 2191,
relation: '项目',
},
{
id: '382328211626635327',
source: 119828,
target: 2303,
relation: '项目',
},
{
id: '382328212079620211',
source: 119828,
target: 3314,
relation: '项目',
},
{
id: '382328212499050555',
source: 119828,
target: 3523,
relation: '项目',
},
{
id: '382328212914286596',
source: 119828,
target: 3889,
relation: '项目',
},
{
id: '382328213295968256',
source: 119828,
target: 3919,
relation: '项目',
},
{
id: '382328213681844224',
source: 119828,
target: 4527,
relation: '项目',
},
{
id: '382328214076108800',
source: 119828,
target: 4553,
relation: '项目',
},
{
id: '382328214487150687',
source: 119828,
target: 4949,
relation: '项目',
},
{
id: '382328214893998113',
source: 119828,
target: 5148,
relation: '项目',
},
{
id: '382328215363760128',
source: 119828,
target: 5197,
relation: '项目',
},
{
id: '382328215774801920',
source: 119828,
target: 5434,
relation: '项目',
},
{
id: '382328216177455104',
source: 119828,
target: 5458,
relation: '项目',
},
{
id: '382328211169456129',
source: 119743,
target: 2191,
relation: '管理',
},
{
id: '382328211626635328',
source: 118977,
target: 2303,
relation: '管理',
},
{
id: '382328212079620212',
source: 119737,
target: 3314,
relation: '管理',
},
{
id: '382328212499050556',
source: 119736,
target: 3523,
relation: '管理',
},
{
id: '382328213295968257',
source: 118613,
target: 3919,
relation: '管理',
},
{
id: '382328213681844225',
source: 119337,
target: 4527,
relation: '管理',
},
{
id: '382328214076108801',
source: 119465,
target: 4553,
relation: '管理',
},
{
id: '382328214487150688',
source: 119743,
target: 4949,
relation: '管理',
},
{
id: '382328214893998114',
source: 118644,
target: 5148,
relation: '管理',
},
{
id: '382328215363760129',
source: 118693,
target: 5197,
relation: '管理',
},
{
id: '382328215774801921',
source: 119337,
target: 5434,
relation: '管理',
},
{
id: '382328216177455105',
source: 119557,
target: 5458,
relation: '管理',
},
{
id: '382328216567525376',
source: 118613,
target: 119828,
relation: '属于',
},
{
id: '382328216567525377',
source: 118644,
target: 119828,
relation: '属于',
},
{
id: '382328216567525378',
source: 118693,
target: 119828,
relation: '属于',
},
{
id: '382328216567525379',
source: 118977,
target: 119828,
relation: '属于',
},
{
id: '382328216567525380',
source: 119337,
target: 119828,
relation: '属于',
},
{
id: '382328216567525381',
source: 119465,
target: 119828,
relation: '属于',
},
{
id: '382328216567525382',
source: 119557,
target: 119828,
relation: '属于',
},
{
id: '382328216567525383',
source: 119736,
target: 119828,
relation: '属于',
},
{
id: '382328216567525384',
source: 119737,
target: 119828,
relation: '属于',
},
{
id: '382328216567525385',
source: 119743,
target: 119828,
relation: '属于',
},
],
}
import * as d3 from 'd3'
// 求两点间的距离
function getDis(s, t) {
return Math.sqrt((s.x - t.x) * (s.x - t.x) + (s.y - t.y) * (s.y - t.y))
}
// 求元素移动到目标位置所需要的 transform 属性值
function getTransform(source, target, _dis) {
let r
if (target.x > source.x) {
if (target.y > source.y) {
r = Math.asin((target.y - source.y) / _dis)
} else {
r = Math.asin((source.y - target.y) / _dis)
r = -r
}
} else {
if (target.y > source.y) {
r = Math.asin((target.y - source.y) / _dis)
r = Math.PI - r
} else {
r = Math.asin((source.y - target.y) / _dis)
r -= Math.PI
}
}
r = r * (180 / Math.PI)
return 'translate(' + source.x + ',' + source.y + ')rotate(' + r + ')'
}
// 使文字折行
function textWrap(text, width) {
text.each(function () {
let text = d3.select(this),
words = text.text().split('').reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.1, // ems
x = text.attr('x'),
y = text.attr('y'),
// dy = parseFloat(text.attr("dy")),
dy = 0,
tspan = text
.text(null)
.append('tspan')
.attr('x', x)
.attr('y', y)
.attr('dy', dy + 'em')
while ((word = words.pop())) {
line.push(word)
tspan.text(line.join(' '))
if (tspan.node().getComputedTextLength() > width) {
line.pop()
tspan.text(line.join(' '))
line = [word]
tspan = text
.append('tspan')
.attr('x', x)
.attr('y', y)
.attr('dy', ++lineNumber * lineHeight + dy + 'em')
.text(word)
}
}
})
}
// 默认配置
const defaultConfig = {
nodes: [], // 节点数组
links: [], // 线数组
isHighLight: true, // 是否启动 鼠标 hover 到节点上高亮与节点有关的节点,其他无关节点透明的功能
isScale: true, // 是否启用缩放平移zoom功能
scaleExtent: [0.2, 1.5], // 缩放的比例尺
chargeStrength: -300, // 万有引力
collide: 100, // 碰撞力的大小 (节点之间的间距)
alphaDecay: 0.0228, // 控制力学模拟衰减率
r: 45, // 圈圈的半径 [30 - 45]
nodeColor: 'skyblue', // 圈圈节点背景颜色
fontColor: '#000', // 圈圈内文字的颜色
linkSrc: 30, // 划线时候的弧度
linkColor: 'gray', // 链接线默认的颜色
strokeColor: 'gray', // 圈圈外围包裹的颜色
strokeWidth: 0, // 圈圈外围包裹的宽度
}
export default class RelationGraph {
constructor(selector, data, configs = {}) {
const mapW = selector.offsetWidth
const mapH = selector.offsetHeight
const defaultWH = {
width: mapW,
height: mapH,
}
// 画布
this.graph = d3.select(selector)
// 转换links的source和target
const nodes = [...data.nodes]
const links = [...data.links]
links.forEach((item) => {
nodes.forEach((e, i) => {
if (e.id === item.source) {
item.source = i
} else if (e.id === item.target) {
item.target = i
}
})
})
// 合并配置
this.config = Object.assign(
{},
defaultConfig,
{ links, nodes },
defaultWH,
configs
)
// 需要高亮的node和link
this.dependsNode = []
this.dependsLinkAndText = []
// 创建力学模拟器
this.initSimulation()
return this.graph
}
// 创建力学模拟器
initSimulation() {
const self = this
// 1. 创建一个力学模拟器
this.simulation = d3
.forceSimulation(this.config.nodes)
// simulation.force(name,[force])函数,添加某种力
.force('link', d3.forceLink(this.config.links))
// 万有引力
.force('charge', d3.forceManyBody().strength(this.config.chargeStrength))
// d3.forceCenter()用指定的x坐标和y坐标创建一个新的居中力。
.force(
'center',
d3.forceCenter(this.config.width / 2, this.config.height / 2)
)
// 碰撞作用力,为节点指定一个radius区域来防止节点重叠,设置碰撞力的强度,范围[0,1], 默认为0.7。设置迭代次数,默认为1,迭代次数越多最终的布局效果越好,但是计算复杂度更高
.force(
'collide',
d3.forceCollide(this.config.collide).strength(0.2).iterations(5)
)
// 在计时器的每一帧中,仿真的alpha系数会不断削减,当alpha到达一个系数时,仿真将会停止,也就是alpha的目标系数alphaTarget,该值区间为[0,1]. 默认为0,
// 控制力学模拟衰减率,[0-1] ,设为0则不停止 , 默认0.0228,直到0.001
.alphaDecay(this.config.alphaDecay)
// 监听事件 ,tick|end ,例如监听 tick 滴答事件
.on('tick', () => this.ticked())
// 2.创建svg标签
this.SVG = this.graph
.append('svg')
.attr('class', 'svgclass')
.attr('width', this.config.width)
.attr('height', this.config.height)
.call(
d3
.zoom()
.scaleExtent(this.config.scaleExtent)
.on('zoom', () => {
if (this.config.isScale) {
this.relMap_g.attr('transform', d3.event.transform)
}
})
)
.on('click', () => console.log('画布 click'))
.on('dblclick.zoom', null)
// 3.defs <defs>标签的内容不会显示,只有调用的时候才显示
this.defs = this.SVG.append('defs')
// 3.1 添加箭头
const border =
this.config.strokeWidth == 0 ? 7.5 : 1.8 * this.config.strokeWidth
this.marker = this.defs
.append('marker')
.attr('id', 'marker')
.attr('markerWidth', 10) //marker视窗的宽
.attr('markerHeight', 10) //marker视窗的高
.attr('refX', this.config.r + border) //refX和refY,指的是图形元素和marker连接的位置坐标
.attr('refY', 4)
.attr('orient', 'auto') //orient="auto"设置箭头的方向为自动适应线条的方向
.attr('markerUnits', 'userSpaceOnUse') //marker是否进行缩放 ,默认值是strokeWidth,会缩放
.append('path')
.attr('d', 'M 0 0 8 4 0 8Z') //箭头的路径 从 (0,0) 到 (8,4) 到(0,8)
.attr('fill', this.config.linkColor)
// 3.2 添加多个圈圈图片的 <pattern>
this.patterns = this.defs
.selectAll('pattern.patternclass')
.data(this.config.nodes)
.enter()
.append('pattern')
.attr('class', 'patternclass')
.attr('id', function (d) {
return d.id
})
.attr('width', '1')
.attr('height', '1')
this.patterns
.append('rect')
.attr('width', 2 * this.config.r)
.attr('height', 2 * this.config.r)
.attr('fill', function (d) {
return d.color || self.config.nodeColor
})
this.patterns
.append('text')
.attr('class', 'nodetext')
.attr('x', this.config.r)
.attr('y', this.config.r * 0.9) // edit
.attr('text-anchor', 'middle')
.attr('fill', self.config.fontColor)
.style('font-size', this.config.r / 3.8)
.text(function (d) {
// if (d.name.length > 4) {
// return [].slice.call(d.name, 0, 4).join('') + '...'
// }
return d.name
})
// .text(d => {
// const {name} = d
// const arr = name.split('')
// let result = ''
// while (arr.length > 4){
// result += (arr.splice(0, 4).join('') + '<br/>')
// }
// return result
// })
// 4.放关系图的容器
this.relMap_g = this.SVG.append('g')
.attr('class', 'relMap_g')
.attr('width', this.config.width)
.attr('height', this.config.height)
// 5.关系图添加线
// 5.1 每条线是个容器,有线 和一个装文字的容器
this.edges = this.relMap_g
.selectAll('g.edge')
.data(this.config.links)
.enter()
.append('g')
.attr('class', 'edge')
.on('mouseover', function (d) {
if (self.config.isHighLight) {
self.highlightLinks(d)
}
d3.select(this).selectAll('path.links').attr('stroke-width', 2)
d3.select(this).selectAll('.rect_g text').style('font-weight', 'bold')
})
.on('mouseout', function () {
if (self.config.isHighLight) {
self.highlightLinks(null)
}
d3.select(this).selectAll('path.links').attr('stroke-width', 1)
d3.select(this).selectAll('.rect_g text').style('font-weight', 'normal')
})
.on('click', function (d) {
console.log('线click')
})
.attr('fill', function (d) {
return d.color || self.config.linkColor
})
// 5.2 添加线
this.links = this.edges
.append('path')
.attr('class', 'links')
.attr('d', (d) => {
return (
'M' +
this.config.linkSrc +
',' +
0 +
' L' +
getDis(d.source, d.target) +
',0'
)
})
.style('marker-end', 'url(#marker)')
.attr('stroke', (d) => {
return d.color || this.config.linkColor
})
// 5.3 添加关系文字的容器
this.rect_g = this.edges.append('g').attr('class', 'rect_g')
// 5.4 添加rect
this.rects = this.rect_g
.append('rect')
.attr('x', 40)
.attr('y', -10)
.attr('width', 40)
.attr('height', 20)
.attr('fill', 'white')
.attr('stroke', (d) => {
// var str = d.color ? '#' + d.color : this.config.linkColor
// return str
// return 'gray'
return 'transparent'
})
// 5.5 文本标签 坐标(x,y)代表 文本的左下角的点
this.texts = this.rect_g
.append('text')
.attr('x', 40)
.attr('y', 5)
.attr('text-anchor', 'middle') // <text>文本中轴对齐方式居中 start | middle | end
.style('font-size', 12)
.text((d) => {
return d.relation
})
const tooltip = d3
.select('body')
.append('div')
.attr('class', 'relation-tooltip')
.style('opacity', 0)
// 6.关系图添加用于显示圈圈的节点
this.circles = this.relMap_g
.selectAll('circle.circleclass')
.data(this.config.nodes)
.enter()
// .append('rect')
// .attr('width', this.config.r * 2)
// .attr('height', this.config.r * 2)
.append('circle')
.attr('r', this.config.r)
.attr('class', 'circleclass')
.style('cursor', 'pointer')
// .attr("cx", function (d) {
// return d.x;
// })
// .attr("cy", function (d) {
// return d.y;
// })
.attr('fill', function (d) {
return 'url(#' + d.id + ')'
})
.attr('stroke', this.config.strokeColor)
.attr('stroke-width', this.config.strokeWidth)
.on('mouseover', function (d) {
d3.select(this).attr(
'stroke-width',
self.config.strokeWidth == 0 ? 5 : 1.5 * self.config.strokeWidth
)
d3.select(this).attr('stroke', d.strokeColor || self.config.strokeColor)
if (self.config.isHighLight) {
self.highlightObject(d)
}
tooltip
.html(d.name)
.style('left', d3.event.pageX + 10 + 'px')
.style('top', d3.event.pageY + 10 + 'px')
.style('opacity', 1)
})
.on('mouseout', function (d) {
d3.select(this).attr('stroke-width', self.config.strokeWidth)
d3.select(this).attr('stroke', d.strokeColor || self.config.strokeColor)
if (self.config.isHighLight) {
self.highlightObject(null)
}
tooltip.style('opacity', 0)
})
// .on('click', function (d) {
// console.log('圈圈节点click', d)
// // 展示方式2 :浮窗展示
// event = d3.event || window.event
// var pageX = event.pageX ? event.pageX : (event.clientX + (document.body.scrollLeft || document.documentElement.scrollLeft))
// var pageY = event.pageY ? event.pageY : (event.clientY + (document.body.scrollTop || document.documentElement.scrollTop))
// // console.log('pagex', pageX);
// // console.log('pageY', pageY);
// //阻止事件冒泡 阻止事件默认行为
// event.stopPropagation ? (event.stopPropagation()) : (event.cancelBubble = true)
// event.preventDefault ? (event.preventDefault()) : (event.returnValue = false)
// })
// .on('contextmenu', function () { //鼠标右键菜单
// event = event || window.event
// event.cancelBubble = true
// event.returnValue = false
// })
// 应用 自定义的 拖拽事件
.call(
d3
.drag()
.on('start', (d) => {
d3.event.sourceEvent.stopPropagation()
// restart()方法重新启动模拟器的内部计时器并返回模拟器。
// 与simulation.alphaTarget或simulation.alpha一起使用时,此方法可用于在交互
// 过程中进行“重新加热”模拟,例如在拖动节点时,在simulation.stop暂停之后恢复模拟。
// 当前alpha值为0,需设置alphaTarget让节点动起来
if (!d3.event.active) this.simulation.alphaTarget(0.3).restart()
d.fx = d.x
d.fy = d.y
})
.on('drag', (d) => {
// d.fx属性- 节点的固定x位置
// 在每次tick结束时,d.x被重置为d.fx ,并将节点 d.vx设置为零
// 要取消节点,请将节点 .fx和节点 .fy设置为空,或删除这些属性。
d.fx = d3.event.x
d.fy = d3.event.y
})
.on('end', (d) => {
// 让alpha目标值值恢复为默认值0,停止力模型
if (!d3.event.active) this.simulation.alphaTarget(0)
d.fx = null
d.fy = null
})
)
// 文字折行
this.SVG.selectAll('text.nodetext').call(textWrap, this.config.r * 1.8)
}
ticked() {
// 7.1 修改每条容器edge的位置
this.edges.attr('transform', function (d) {
return getTransform(d.source, d.target, getDis(d.source, d.target))
})
// 7.2 修改每条线link位置
this.links.attr('d', (d) => {
return (
'M' +
this.config.linkSrc +
',' +
0 +
' L' +
getDis(d.source, d.target) +
',0'
)
})
// 7.3 修改线中关系文字text的位置 及 文字的反正
this.texts
.attr('x', function (d) {
// 7.3.1 根据字的长度来更新兄弟元素 rect 的宽度
const bbox = d3.select(this).node().getBBox()
const { width } = bbox
// ########################
// $(this).prev('rect').attr('width', width + 10);
// d3.select(this).prev('rect').attr('width', width + 10);
// 7.3.2 更新 text 的位置
return getDis(d.source, d.target) / 2
})
.attr('transform', function (d) {
// 7.3.3 更新文本反正
if (d.target.x < d.source.x) {
const x = getDis(d.source, d.target) / 2
return 'rotate(180 ' + x + ' ' + 0 + ')'
} else {
return 'rotate(0)'
}
})
// 7.4 修改线中装文本矩形rect的位置
this.rects.attr('x', function (d) {
// ######################
// return getDis(d.source, d.target) / 2 - $(this).attr('width') / 2
return getDis(d.source, d.target) / 2 - d3.select(this).attr('width') / 2
}) // x 坐标为两点中心距离减去自身长度一半
// 5.修改节点的位置
this.circles
.attr('cx', function (d) {
return d.x
})
.attr('cy', function (d) {
return d.y
})
// .attr('x', function (d) {
// return d.x - d3.select(this).attr('width') / 2
// })
// .attr('y', function (d) {
// return d.y - d3.select(this).attr('height') / 2
// })
}
// 高亮元素及其相关的元素
highlightObject(obj) {
if (obj) {
const objIndex = obj.index
this.dependsNode = this.dependsNode.concat([objIndex])
this.dependsLinkAndText = this.dependsLinkAndText.concat([objIndex])
this.config.links.forEach((lkItem) => {
if (objIndex == lkItem.source.index) {
this.dependsNode = this.dependsNode.concat([lkItem.target.index])
} else if (objIndex == lkItem.target.index) {
this.dependsNode = this.dependsNode.concat([lkItem.source.index])
}
})
// 隐藏节点
this.SVG.selectAll('circle')
.filter((d) => this.dependsNode.indexOf(d.index) == -1)
.transition()
.style('opacity', 0.1)
// 隐藏线
this.SVG.selectAll('.edge')
.filter((d) => {
return (
this.dependsLinkAndText.indexOf(d.source.index) == -1 &&
this.dependsLinkAndText.indexOf(d.target.index) == -1
)
})
.transition()
.style('opacity', 0.1)
} else {
// 取消高亮
// 恢复隐藏的线
this.SVG.selectAll('circle')
.filter(() => true)
.transition()
.style('opacity', 1)
// 恢复隐藏的线
this.SVG.selectAll('.edge')
.filter((d) => {
return (
this.dependsLinkAndText.indexOf(d.source.index) == -1 &&
this.dependsLinkAndText.indexOf(d.target.index) == -1
)
})
.transition()
.style('opacity', 1)
this.dependsNode = []
this.dependsLinkAndText = []
}
}
highlightLinks(obj) {
if (obj) {
// 隐藏节点
this.SVG.selectAll('circle')
.filter((d) => {
const sourceIndex = this.config.nodes.findIndex(
(e) => e.id == obj.source.id
)
const targetIndex = this.config.nodes.findIndex(
(e) => e.id == obj.target.id
)
return d.index != sourceIndex && d.index != targetIndex
})
.transition()
.style('opacity', 0.1)
// 隐藏线
this.SVG.selectAll('.edge')
.filter((d) => d.id != obj.id)
.transition()
.style('opacity', 0.1)
} else {
this.SVG.selectAll('circle')
.filter(() => true)
.transition()
.style('opacity', 1)
this.SVG.selectAll('.edge')
.filter(() => true)
.transition()
.style('opacity', 1)
}
}
}
; (function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
? (module.exports = factory)
: ((global = global || self), (global.RelationGraph = factory))
})(this, RelationGraph)
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
// https://vitejs.dev/config/
export default defineConfig({
base: './',
plugins: [vue()],
resolve: {
alias: {
'@': resolve(__dirname, './src'),
'@images': resolve(__dirname, './src/assets/images'),
},
},
})
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment