官方文档:
https://d3js.org/communityhttps://d3js.org/community
效果图:
js:
initToggle() {//(2)再调这个方法
const $this = this;
const svgContainer = d3.select('#svg-containerbottom');
var o = document.getElementById("svg-containerbottom");
var height = o.offsetHeight; //高度
var width = o.offsetWidth; //宽度
// const width = svgContainer.style('width').replaceAll('px', '')
// const height = svgContainer.style('height').replaceAll('px', '')
// const svg = svgContainer.select('svg');
let svg = svgContainer
.append('svg')
.attr("preserveAspectRatio", "xMidYMid meet")
.attr("viewBox", "0 0 "+width+" "+height)
// .attr("width", width)
// .attr("height", height)
const toggleHeight = 200;
const innerHeight = height - this.margin.top - this.margin.bottom;
const innerWidth = width - this.margin.left - this.margin.right;
let offsetY = -5;
let g = svg.append("g")
.attr('id', 'toggle-group')
// .attr('transform', `translate(${this.margin.left},${this.margin.top + height / 2 - 100})`)
.attr('transform', `translate(${this.margin.left},0)`)
// .attr('width', innerWidth)
// .attr('height', toggleHeight)
.attr("preserveAspectRatio", "xMidYMid meet")
// .attr("viewBox", "0 0 912.594 200")
g.append("image")
.attr('href', '../../../new/img/sbznxj/icn_go_left.svg')
.attr('class', "img-toggle")
.attr('x', this.margin.left)
.attr('y', (height - 48) / 2 + offsetY)
.attr('width', 48)
.attr('height', 48).on('click', () => {
this.preGroup()
});
g.append("image")
.attr('href', '../../../new/img/sbznxj/icn_go_right.svg')
.attr('class', "img-toggle")
.attr('x', this.margin.left + innerWidth - 96)
.attr('y', (height - 48) / 2 + offsetY)
.attr('width', 48)
.attr('height', 48).on("click", () => {
this.nextGroup()
});
let totalSize = 0;
for (let k = 0; k < 5; k++) {
totalSize += 48 + k * 20;
}
let len = this.groups.length;
let j = 0;
let offsetSize = 0;
let offsetX = (innerWidth - totalSize - 124) / 6;
for (let i = this.groupIndex - 2; i <= this.groupIndex + 2; i++) {
let index = (len + i) % len;
let group = this.groups[index];
offsetSize += 48 + j * 20;
let size = 48 + j % (5 - j) * 20;
let image = g.append("image")
.attr('href', '../../../new/img/sbznxj/icn_group.png')
.attr('class', "img-group")
.attr('id', 'img-group-id-' + j)
.attr('index', index)
.attr('x', offsetSize + offsetX * (j + 1))
.attr('y', (height - size) / 2 + offsetY)
.attr('width', size)
.attr('height', size)
.on("click", function () {
$this.toggle(parseInt(d3.select(this).attr("index")));
});
g.append("text")
.attr('class', "text-group")
.attr('id', 'text-group-id-' + j)
.attr("fill", "#FFFFFF")
.attr("text-anchor", "middle")
.attr('x', parseFloat(image.attr("x")) + size / 2)
.attr('y', parseFloat(image.attr("y")) + size + 20)
.text(group.name);
j++;
}
},
initForceSimulation() {//(1)先调这个方法
let $this = this;
const svgContainer = d3.select('#svg-containertop')
var o = document.getElementById("svg-containertop");
var height = o.offsetHeight; //高度
var width = o.offsetWidth; //宽度
// const width = svgContainer.style('width').replaceAll('px', '')
// const height = svgContainer.style('height').replaceAll('px', '')
let svg;
if (this.forceSimulation) {
d3.select("#main-group").remove();
svg = svgContainer.select("svg");
} else {
svg = svgContainer
.append('svg')
.attr("preserveAspectRatio", "xMidYMid meet")
.attr("viewBox", "0 0 "+width+" "+height)
// .attr("width", width)
// .attr("height", height)
}
const offsetY = -20;
const innerHeight = height - this.margin.top - this.margin.bottom;
const innerWidth = width - this.margin.left - this.margin.right;
let g = svg.append("g")
.attr('id', 'main-group')
.attr('x', this.margin.left)
.attr('y', this.margin.top)
.attr('transform', `translate(${this.margin.left},${this.margin.top})`)
// .attr('width', innerWidth)
// .attr('height', innerHeight)
.attr("preserveAspectRatio", "xMidYMid meet")
// .attr("viewBox", "0 0 912.594 600")
g.append("image")
.attr('href', '../../../new/img/sbznxj/icn_circle.svg')
.attr('x', (this.margin.left + innerWidth / 2) / 2)
.attr('y', (this.margin.top + innerHeight / 2) / 2 + offsetY+20)
.attr('width', innerWidth / 2)
.attr('height', innerHeight / 2);
this.forceSimulation = d3.forceSimulation()
.force("link", d3.forceLink())
.force("charge", d3.forceManyBody().strength(-1200))
.force('collide', d3.forceCollide().radius(48).iterations(1))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("x", d3.forceX())
.force("y", d3.forceY());
//生成节点数据
this.forceSimulation.nodes(this.nodes)
.on("tick", () => {
this.edges
.attr("x1", function (d) {
return d.source.x;
})
.attr("y1", function (d) {
return d.source.y;
})
.attr("x2", function (d) {
return d.target.x;
})
.attr("y2", function (d) {
return d.target.y;
});
this.edgeTexts
.attr("x", function (d) {
return (d.source.x + d.target.x) / 2;
})
.attr("y", function (d) {
return (d.source.y + d.target.y) / 2;
});
this.nodeGs
.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
});//这个函数很重要,后面给出具体实现和说明
//生成边数据
this.forceSimulation.force("link")
.links(this.links)
.distance(function (d) {//每一边的长度
if ($this.groups.length >= 20) {
return d.value * 60;
} else {
return d.value * 80;
}
})
//设置图形的中心位置
this.forceSimulation.force("center")
.x(width / 2)
.y(height / 2 + offsetY);
//绘制边
this.edges = g.append("g")
.selectAll("line")
.data(this.links)
.enter()
.append("line")
.attr("stroke", "#0AE2D7")
.attr("stroke-width", 1);
//边上文字
this.edgeTexts = g.append("g")
.selectAll("text")
.data(this.links)
.enter()
.append("text")
.text(function (d) {
return d.relation;
})
//建立用来放在每个节点和对应文字的分组<g>
this.nodeGs = g.selectAll(".circleText")
.data(this.nodes)
.enter()
.append("g")
.attr("transform", function (d, i) {
let cirX = d.x;
let cirY = d.y;
return "translate(" + cirX + "," + cirY + ")";
})
.call(this.simulation(this.forceSimulation));
$this=this;
//绘制节点
this.nodeGs.append("image")
.attr("class", "img-station")
.attr("href", function (d, i) {
if (d.id === 0)
return "../../../new/img/sbznxj/icn_center.png";
else
return "../../../new/img/sbznxj/icn_station.svg";
})
.attr("width", function (d, i) {
if (d.id === 0)
return 256;
else
return 128;
})
.attr("height", function (d, i) {
if (d.id === 0)
return 256;
else
return 128;
})
.attr("transform", function (d, i) {
if (d.id === 0) {
let cirX = d.x - 128;
let cirY = d.y - 128;
return "translate(" + cirX + "," + cirY + ")";
} else {
let cirX = d.x - 64;
let cirY = d.y - 64;
return "translate(" + cirX + "," + cirY + ")";
}
})
.on('click', function (d, i) {
// console.log(d, i);
$this.viewInfoPages(i.bdzId)
})
.on('mouseover', function (d, i) {
let href = d3.select(this).attr("href");
if (href.endsWith(".svg")) {
d3.select(this).attr("href", href.replaceAll(".svg", "_hover.svg"));
} else if (href.endsWith(".png")) {
d3.select(this).attr("href", href.replaceAll(".png", "_hover.png"));
}
})
.on('mouseout', function (d, i) {
let href = d3.select(this).attr("href");
if (href.endsWith(".svg")) {
d3.select(this).attr("href", href.replaceAll("_hover.svg", ".svg"));
} else if (href.endsWith(".png")) {
d3.select(this).attr("href", href.replaceAll("_hover.png", ".png"));
}
})
//文字
this.nodeGs.append("text")
.attr("fill", "#FFFFFF")
.attr("text-anchor", "middle")
.attr("transform", function (d, i) {
let cirX = d.x;
let cirY = d.y + 50;
return "translate(" + cirX + "," + cirY + ")";
})
.text(function (d) {
if (d.id === 0)
return '';
else
return d.name;
// return d.name + d.id;
});
},
simulation(simulation) {
return d3.drag()
.on("start", function (event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
})
.on("drag", function (event, d) {
d.fx = event.x;
d.fy = event.y;
})
.on("end", function (event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
});
},
preGroup() {
let len = this.groups.length;
let index = (len + (this.groupIndex - 1)) % len;
this.toggle(index);
},
nextGroup() {
let len = this.groups.length;
let index = (this.groupIndex + 1) % len;
this.toggle(index);
},
toggle(selectedIndex) {
if (this.groupIndex !== selectedIndex) {
this.groupIndex = selectedIndex;
let len = this.groups.length;
let j = 0;
for (let i = this.groupIndex - 2; i <= this.groupIndex + 2; i++) {
let index = (len + i) % len;
let group = this.groups[index];
d3.select("#img-group-id-" + j).attr("index", index);
d3.select("#text-group-id-" + j).text(group.name);
j++;
}
this.links = [];
this.nodes = [{
id: 0,
name: "供电公司",
}];
for(var i=0; i<this.groups[selectedIndex].bdz.length; i++){
this.links.push({
source: 0,
target: i+1,
value: 2,//value控制线的长短
})
this.nodes.push({
id: i+1,
name: this.groups[selectedIndex].bdz[i].NAME,
bdzId:this.groups[selectedIndex].bdz[i].ID,
})
}
this.initForceSimulation();
}
},
具体细节不用改太多东西想研究的话去看官方文档,只需要改initForceSimulation()、initToggle()方法里的图片和数据即可
再看一下调用顺序其他的不用看:
这些是需要初始化定义的参数:
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
源码放在最下面有兴趣的小伙伴自己琢磨吧,学习记录仅供参考!,页面用的是Vue写的大屏数据可视化页面,里面有还有很多Echarts图表、点击D3js中的分支节点时可以弹出对应页面...相关功能自己甄别吧,不需要的部分自行删除
const vm = new Vue({
el: '#app',
data() {
return {
//设置当前全屏状态的初始值为 false
fullScreen: null,
//待巡检任务列表
assignmentList:[],
forceSimulation: null,
edges: null,
edgeTexts: null,
nodeGs: null,
margin: {
top: 0,
left: 0,
right: 0,
bottom: 0
},
links: [],//value控制线的长短
nodes: [{
id: 0,
name: "供电公司",
},],
groups: [],
groupIndex: 0
};
},
created() {
this.$nextTick(function(){
// 利用屏幕分辨率和window对象的内高度来判断兼容IE
let winFlag = window.innerHeight === window.screen.height;
// 利用window全屏标识来判断 -- IE无效
let isFull = window.fullScreen || document.webkitIsFullScreen;
if (isFull === undefined) {
this.isFullscreen = winFlag;
} else {
this.isFullscreen = winFlag || isFull;
}
if(winFlag){
this.fullScreen=true;
}else {
this.fullScreen=false;
}
})
},
mounted() {
var $this=this;
this.getwcdChart();
this.getpjhsChart();
this.getfinishChart();
this.getDxjrwlb();
this.getbdz();
//监听页面是否全屏
this.$nextTick(function(){
window.addEventListener('resize', function () {
if (document.webkitIsFullScreen) {
$this.fullScreen=true;
}else{
$this.fullScreen=false;
}
});
})
axios.get('/appPatrolEquipPatrolLine/treeData', {
params: {
//参数ID
isline:1,
}
})
.then(function (response) { //请求成功
console.log(response);
})
.catch(function (error) { //请求失败
console.log(error);
});
},
methods: {
//全屏按钮\退出全屏按钮点击事件
fullScreenClick() {
this.fullScreen = !this.fullScreen;
if (this.fullScreen) {//当前要启动全屏
this.WindowFullScreen();
} else {//当前要退出全屏
this.WindowFullScreen();
setTimeout(() => {
this.windowExitFullScreen();
}, 500);
}
},
//启动全屏
WindowFullScreen() {
let docElm = document.getElementById('app');
if (docElm.requestFullscreen) {
docElm.requestFullscreen();
}else if (docElm.msRequestFullscreen) {
docElm.msRequestFullscreen();
} else if (docElm.mozRequestFullScreen) {
docElm.mozRequestFullScreen();
}else if (docElm.webkitRequestFullScreen) {
docElm.webkitRequestFullScreen();
}
},
//退出全屏
windowExitFullScreen() {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitCancelFullScreen) {
document.webkitCancelFullScreen();
}
},
//各维操队已完成图表
getwcdChart() {
var wcd=[];
var wcddata=[];
var sum=0;
axios.get('/appPatrolPlanInfo/queryCompletionStatus', {
params: {
//参数ID
}
})
.then(function (response) { //请求成功
for(var i=0; i<response.data.data.length; i++){
wcd.push(response.data.data[i].wcd.NAME)
wcddata.push([response.data.data[i].wcd.NAME+' '+response.data.data[i].completedPercentage,response.data.data[i].completed])
sum+=response.data.data[i].completed;
}
var chart = Highcharts.chart('getwcdChart', {
chart: {
type: 'pie',
options3d: {
enabled: true,
alpha: 50
},
backgroundColor: 'rgba(255,255,255,0)',
marginBottom: 100,
},
exporting:{
enabled:false,
},
legend:{
align: 'right',
verticalAlign: 'top',
layout: 'vertical',
x: 0,
y: 30,
symbolHeight:8,
symbolWidth:10,
symbolRadius:0,
itemMarginBottom:10,
itemStyle:{
color:'#FFFFFF',
fontSize:14,
},
itemHiddenStyle:{
color:'#cccccc',
},
itemHoverStyle :{
color:'#FFFFFF',
},
},
credits : {
enabled:false//不显示highCharts版权信息
},
title: {
text: sum+'个',
margin: 0,
y:-10,
x:-70,
align: 'center',
verticalAlign: 'middle',
style: {
color: '#FFFFFF',
fontSize: '24px',
fontFamily: 'Impact',
right:50,
}
},
tooltip:{
distance:0,
},
plotOptions: {
pie: {
innerSize: 130,
depth: 30,
dataLabels:{
distance:0,
enabled: false,
},
showInLegend: true,
colors:['#00EAFF','#20E6A4','#FFCF37','#FF8100','#FC4768','#4D74FF','#1555FF','#B6BED2'],
}
},
series: [{
name: '已完成',
size: '110%',
data: wcddata,
dataLabels: {
formatter: function () {
return this.y < 1 ? this.point.name : null;
},
connectorWidth: 0, // 距离值为负时显示在在扇区里面
},
}],
});
})
.catch(function (error) { //请求失败
console.log(error);
});
},
//未完成/已完成图表
getfinishChart(){
var $this=this;
var categories=[];
var ywcdata=[];
var wwcdata=[];
axios.get('/appPatrolPlanInfo/queryCompletionStatus', {
params: {
//参数ID
}
})
.then(function (response) { //请求成功
for(var i=0; i<response.data.data.length; i++){
categories.push(response.data.data[i].wcd.NAME)
ywcdata.push(response.data.data[i].completed)
wwcdata.push(response.data.data[i].incomplete)
}
var chart = Highcharts.chart('getfinishChart', {
chart: {
type: 'bar',
backgroundColor: 'rgba(255,255,255,0)',
},
exporting:{
enabled:false,
},
credits : {
enabled:false//不显示highCharts版权信息
},
title: {
text: null,
},
xAxis: {
lineWidth: 0,//隐藏x轴刻度线
categories: categories,//Y轴维操队名称
labels: {
style:{
color:'#FFFFFF',
},
},
min: 0,
max: 2,
scrollbar: {
enabled: true,
showFull:false,//当滚动内容缩小到最大范围时,是否显示或隐藏滚动条。
size:8,
barBorderRadius:8,
barBackgroundColor: '#00D8FF',
barBorderColor:'rgba(255,255,255,0)',
buttonBorderRadius:5,
buttonBackgroundColor:'rgba(255,255,255,0)',
buttonBorderColor:'rgba(255,255,255,0)',
rifleColor:'rgba(255,255,255,0)',
trackBackgroundColor: 'rgba(255,255,255,0)',
trackBorderColor: 'rgba(255,255,255,0)',
},
},
yAxis: {
min: 0,
gridLineWidth: 0,//隐藏y轴刻度线
title: {
text: null,
},
labels: {
style:{
color:'#FFFFFF',
},
},
},
legend: {
reversed: true,
itemStyle:{
color:'#FFFFFF',
},
itemHoverStyle :{
color:'#FFFFFF',
},
},
plotOptions: {
series: {
stacking: 'percent'
}
},
series: [{
name: '单位/个',
color: '#FFFFFF',
borderRadius: 10,
},{
name: '已完成',
data: ywcdata,//X轴已完成数量
color: {
linearGradient: { x1: 0, x2: 0, y1: 1, y2: 0 },
stops: [
[0, '#50EFB1'],
[1, '#00E0DB']
]
},
pointWidth: 20,
borderRadius: 10,
},{
name: '未完成',
data: wwcdata,//X轴未完成数量
color: {
linearGradient: { x1: 0, x2: 0, y1: 1, y2: 0 },
stops: [
[0, '#FC4768'],
[1, '#F8D03F']
]
},
pointWidth: 20,
borderRadius: 10,
}]
});
})
.catch(function (error) { //请求失败
console.log(error);
});
},
//巡检平均耗时图表
getpjhsChart() {
var wcd=[];
axios.get('/appPatrolPlanInfo/getTimeSun', {
params: {
//参数ID
}
})
.then(function (response) { //请求成功
for(var item in response.data){
wcd.push([
response.data[item].LINE_NAME,
response.data[item].timesum/response.data[item].connum
])
}
var chart = Highcharts.chart('getpjhsChart', {
chart: {
type: 'column',
backgroundColor: 'rgba(255,255,255,0)',
},
exporting:{
enabled:false,
},
credits : {
enabled:false//不显示highCharts版权信息
},
title: false,
subtitle: false,
xAxis: {
type: 'category',
labels: {
rotation: 60, // 设置轴标签旋转角度
style:{
color:'#FFFFFF',
}
},
},
yAxis: {
min: 0,
title: {
text: false,
},
labels: {
style:{
color:'#FFFFFF',
}
},
},
legend: {
enabled: false
},
tooltip: {
pointFormat: '平均耗时: <b>{point.y:.0f} 分钟</b>'
},
series: [{
data: wcd,
dataLabels: {
enabled: false,
align: 'right',
format: '{point.y:.0f}', // :.1f 为保留 1 位小数
},
zones: [{
color: '#66FFFF'
}],
}]
});
})
.catch(function (error) { //请求失败
console.log(error);
});
},
//获取待巡检任务列表数据
getDxjrwlb(){
var $this=this;
axios.get('/appPatrolPlanInfo/queryIncompletePatrolTask', {
params: {
//参数ID
}
})
.then(function (response) { //请求成功
for(var i=0; i<response.data.data.length; i++){
$this.assignmentList.push({
obj:response.data.data[i],//当前任务对象
code:i,//当前数组下标
planName:response.data.data[i].pstarttime + response.data.data[i].planName,//任务名称
lineIdName:response.data.data[i].tarnMap.lineIdName,//变电站名
},)
}
})
.catch(function (error) { //请求失败
console.log(error);
});
},
//中间图表获取变电站
getbdz(){
var $this=this;
axios.get('/appPatrolPlanInfo/convertingStationList', {
params: {
//参数ID
}
})
.then(function (response) { //请求成功
for(var item in response.data.data){
$this.groups.push({
name:response.data.data[item].wcd.NAME,
bdz:response.data.data[item].bdz,
})
if(item==0){
for(var i=0; i<response.data.data[item].bdz.length; i++){
$this.links.push({
source: 0,
target: i+1,
value: 1.5,//value控制线的长短
})
$this.nodes.push({
id: i+1,
name: response.data.data[item].bdz[i].NAME,
// group: 2,
bdzId:response.data.data[item].bdz[i].ID
})
}
}
}
$this.initForceSimulation();
$this.initToggle();
})
.catch(function (error) { //请求失败
console.log(error);
});
},
initToggle() {
const $this = this;
const svgContainer = d3.select('#svg-containerbottom');
var o = document.getElementById("svg-containerbottom");
var height = o.offsetHeight; //高度
var width = o.offsetWidth; //宽度
// const width = svgContainer.style('width').replaceAll('px', '')
// const height = svgContainer.style('height').replaceAll('px', '')
// const svg = svgContainer.select('svg');
let svg = svgContainer
.append('svg')
.attr("preserveAspectRatio", "xMidYMid meet")
.attr("viewBox", "0 0 "+width+" "+height)
// .attr("width", width)
// .attr("height", height)
const toggleHeight = 200;
const innerHeight = height - this.margin.top - this.margin.bottom;
const innerWidth = width - this.margin.left - this.margin.right;
let offsetY = -5;
let g = svg.append("g")
.attr('id', 'toggle-group')
// .attr('transform', `translate(${this.margin.left},${this.margin.top + height / 2 - 100})`)
.attr('transform', `translate(${this.margin.left},0)`)
// .attr('width', innerWidth)
// .attr('height', toggleHeight)
.attr("preserveAspectRatio", "xMidYMid meet")
// .attr("viewBox", "0 0 912.594 200")
g.append("image")
.attr('href', '../../../new/img/sbznxj/icn_go_left.svg')
.attr('class', "img-toggle")
.attr('x', this.margin.left)
.attr('y', (height - 48) / 2 + offsetY)
.attr('width', 48)
.attr('height', 48).on('click', () => {
this.preGroup()
});
g.append("image")
.attr('href', '../../../new/img/sbznxj/icn_go_right.svg')
.attr('class', "img-toggle")
.attr('x', this.margin.left + innerWidth - 96)
.attr('y', (height - 48) / 2 + offsetY)
.attr('width', 48)
.attr('height', 48).on("click", () => {
this.nextGroup()
});
let totalSize = 0;
for (let k = 0; k < 5; k++) {
totalSize += 48 + k * 20;
}
let len = this.groups.length;
let j = 0;
let offsetSize = 0;
let offsetX = (innerWidth - totalSize - 124) / 6;
for (let i = this.groupIndex - 2; i <= this.groupIndex + 2; i++) {
let index = (len + i) % len;
let group = this.groups[index];
offsetSize += 48 + j * 20;
let size = 48 + j % (5 - j) * 20;
let image = g.append("image")
.attr('href', '../../../new/img/sbznxj/icn_group.png')
.attr('class', "img-group")
.attr('id', 'img-group-id-' + j)
.attr('index', index)
.attr('x', offsetSize + offsetX * (j + 1))
.attr('y', (height - size) / 2 + offsetY)
.attr('width', size)
.attr('height', size)
.on("click", function () {
$this.toggle(parseInt(d3.select(this).attr("index")));
});
g.append("text")
.attr('class', "text-group")
.attr('id', 'text-group-id-' + j)
.attr("fill", "#FFFFFF")
.attr("text-anchor", "middle")
.attr('x', parseFloat(image.attr("x")) + size / 2)
.attr('y', parseFloat(image.attr("y")) + size + 20)
.text(group.name);
j++;
}
},
initForceSimulation() {
let $this = this;
const svgContainer = d3.select('#svg-containertop')
var o = document.getElementById("svg-containertop");
var height = o.offsetHeight; //高度
var width = o.offsetWidth; //宽度
// const width = svgContainer.style('width').replaceAll('px', '')
// const height = svgContainer.style('height').replaceAll('px', '')
let svg;
if (this.forceSimulation) {
d3.select("#main-group").remove();
svg = svgContainer.select("svg");
} else {
svg = svgContainer
.append('svg')
.attr("preserveAspectRatio", "xMidYMid meet")
.attr("viewBox", "0 0 "+width+" "+height)
// .attr("width", width)
// .attr("height", height)
}
const offsetY = -20;
const innerHeight = height - this.margin.top - this.margin.bottom;
const innerWidth = width - this.margin.left - this.margin.right;
let g = svg.append("g")
.attr('id', 'main-group')
.attr('x', this.margin.left)
.attr('y', this.margin.top)
.attr('transform', `translate(${this.margin.left},${this.margin.top})`)
// .attr('width', innerWidth)
// .attr('height', innerHeight)
.attr("preserveAspectRatio", "xMidYMid meet")
// .attr("viewBox", "0 0 912.594 600")
g.append("image")
.attr('href', '../../../new/img/sbznxj/icn_circle.svg')
.attr('x', (this.margin.left + innerWidth / 2) / 2)
.attr('y', (this.margin.top + innerHeight / 2) / 2 + offsetY+20)
.attr('width', innerWidth / 2)
.attr('height', innerHeight / 2);
this.forceSimulation = d3.forceSimulation()
.force("link", d3.forceLink())
.force("charge", d3.forceManyBody().strength(-1200))
.force('collide', d3.forceCollide().radius(48).iterations(1))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("x", d3.forceX())
.force("y", d3.forceY());
//生成节点数据
this.forceSimulation.nodes(this.nodes)
.on("tick", () => {
this.edges
.attr("x1", function (d) {
return d.source.x;
})
.attr("y1", function (d) {
return d.source.y;
})
.attr("x2", function (d) {
return d.target.x;
})
.attr("y2", function (d) {
return d.target.y;
});
this.edgeTexts
.attr("x", function (d) {
return (d.source.x + d.target.x) / 2;
})
.attr("y", function (d) {
return (d.source.y + d.target.y) / 2;
});
this.nodeGs
.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
});//这个函数很重要,后面给出具体实现和说明
//生成边数据
this.forceSimulation.force("link")
.links(this.links)
.distance(function (d) {//每一边的长度
if ($this.groups.length >= 20) {
return d.value * 60;
} else {
return d.value * 80;
}
})
//设置图形的中心位置
this.forceSimulation.force("center")
.x(width / 2)
.y(height / 2 + offsetY);
//绘制边
this.edges = g.append("g")
.selectAll("line")
.data(this.links)
.enter()
.append("line")
.attr("stroke", "#0AE2D7")
.attr("stroke-width", 1);
//边上文字
this.edgeTexts = g.append("g")
.selectAll("text")
.data(this.links)
.enter()
.append("text")
.text(function (d) {
return d.relation;
})
//建立用来放在每个节点和对应文字的分组<g>
this.nodeGs = g.selectAll(".circleText")
.data(this.nodes)
.enter()
.append("g")
.attr("transform", function (d, i) {
let cirX = d.x;
let cirY = d.y;
return "translate(" + cirX + "," + cirY + ")";
})
.call(this.simulation(this.forceSimulation));
$this=this;
//绘制节点
this.nodeGs.append("image")
.attr("class", "img-station")
.attr("href", function (d, i) {
if (d.id === 0)
return "../../../new/img/sbznxj/icn_center.png";
else
return "../../../new/img/sbznxj/icn_station.svg";
})
.attr("width", function (d, i) {
if (d.id === 0)
return 256;
else
return 128;
})
.attr("height", function (d, i) {
if (d.id === 0)
return 256;
else
return 128;
})
.attr("transform", function (d, i) {
if (d.id === 0) {
let cirX = d.x - 128;
let cirY = d.y - 128;
return "translate(" + cirX + "," + cirY + ")";
} else {
let cirX = d.x - 64;
let cirY = d.y - 64;
return "translate(" + cirX + "," + cirY + ")";
}
})
.on('click', function (d, i) {
// console.log(d, i);
$this.viewInfoPages(i.bdzId)
})
.on('mouseover', function (d, i) {
let href = d3.select(this).attr("href");
if (href.endsWith(".svg")) {
d3.select(this).attr("href", href.replaceAll(".svg", "_hover.svg"));
} else if (href.endsWith(".png")) {
d3.select(this).attr("href", href.replaceAll(".png", "_hover.png"));
}
})
.on('mouseout', function (d, i) {
let href = d3.select(this).attr("href");
if (href.endsWith(".svg")) {
d3.select(this).attr("href", href.replaceAll("_hover.svg", ".svg"));
} else if (href.endsWith(".png")) {
d3.select(this).attr("href", href.replaceAll("_hover.png", ".png"));
}
})
//文字
this.nodeGs.append("text")
.attr("fill", "#FFFFFF")
.attr("text-anchor", "middle")
.attr("transform", function (d, i) {
let cirX = d.x;
let cirY = d.y + 50;
return "translate(" + cirX + "," + cirY + ")";
})
.text(function (d) {
if (d.id === 0)
return '';
else
return d.name;
// return d.name + d.id;
});
},
simulation(simulation) {
return d3.drag()
.on("start", function (event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
})
.on("drag", function (event, d) {
d.fx = event.x;
d.fy = event.y;
})
.on("end", function (event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
});
},
preGroup() {
let len = this.groups.length;
let index = (len + (this.groupIndex - 1)) % len;
this.toggle(index);
},
nextGroup() {
let len = this.groups.length;
let index = (this.groupIndex + 1) % len;
this.toggle(index);
},
toggle(selectedIndex) {
if (this.groupIndex !== selectedIndex) {
this.groupIndex = selectedIndex;
let len = this.groups.length;
let j = 0;
for (let i = this.groupIndex - 2; i <= this.groupIndex + 2; i++) {
let index = (len + i) % len;
let group = this.groups[index];
d3.select("#img-group-id-" + j).attr("index", index);
d3.select("#text-group-id-" + j).text(group.name);
j++;
}
this.links = [];
this.nodes = [{
id: 0,
name: "供电公司",
}];
for(var i=0; i<this.groups[selectedIndex].bdz.length; i++){
this.links.push({
source: 0,
target: i+1,
value: 2,//value控制线的长短
})
this.nodes.push({
id: i+1,
name: this.groups[selectedIndex].bdz[i].NAME,
bdzId:this.groups[selectedIndex].bdz[i].ID,
})
}
this.initForceSimulation();
}
},
// 点击中间图表图表时弹出层(这个方法是点击D3js中的分支节点时可以弹出对应页面)
viewInfoPages(lineId){
var obj = new Object();
obj["lineId"] = lineId;
obj["period"] = $("select[name=period]").val();
showWeAdminShow('巡检任务结果','/appPatrolPlanInfo/patrolTaskInfoView', function (body, iframeWin,index) {
layer.full(index);
//赋值回调方法
if (null != obj) {
iframeWin.setPatrolInfoIndexManageData(obj);
}
}, '', '');
}
}
})