(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.d3ForceEasy = global.d3ForceEasy || {}));
}(this, (function (exports) {
'use strict';
const option = {
dom: document.getElementsByTagName('body'),
color: '',
key: 'id',
nodes: [],
links: [],
icons: [],//[],'circle'
zoom: true,
fixed: true,//拖动锁定
zoomRange: [0.5, 8],
linkLabel: {
show: true,
key: 'relation'
},
curvature: 300,//曲率,越小越弯
text: {
show: true,
style: ''
},
alpha: 1,//衰减系数,[0,1]之间,越小迭代次数越多,0时迭代不会停止。
// forceParams:{ //力导向参数
// center:{
// x:0,
// y:0
// },//向心力
// collide:{
// radius:1,
// strength:1,//[0,1]碰撞强度
// },//碰撞力
// link:{
// distance:0,
// strength:0,
// id:'index',//links使用标识
// },//弹簧模型
// charge:{
// strength:-30,//负值相互排斥
// },//电荷力模型
// }
};
const defaultIcon = 'M938.666667 512A426.666667 426.666667 0 1 1 512 85.333333a426.666667 426.666667 0 0 1 426.666667 426.666667z';
function extend(o, n, override) {
for (let p in n) {
if (n.hasOwnProperty(p) && (!o.hasOwnProperty(p) || override))
o[p] = n[p];
}
}
const drag = simulation => {
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
d3.select(this).classed("fixed", true);
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event, d) {
if (!event.active) simulation.alphaTarget(0);
if (option.fixed) {
d.fx = clamp(event.x, 0, width);
d.fy = clamp(event.y, 0, height);
} else {
d.fx = null
d.fy = null
}
}
return d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
}
const color = (d, i) => {
const scale = d3.schemeCategory10;
return scale[i];
}
let simulation, node, link, text, svg, stage_g, currentClick, width, height,zoom;
function initForce(userOption) {
extend(option, userOption, true);
const dom = option.dom;
height = dom.offsetHeight;
width = dom.offsetWidth;
function zoomed(event) {
const {transform} = event;
stage_g.attr('transform', `translate(${transform.x},${transform.y}),scale(${transform.k})`)
}
zoom = d3.zoom()
.scaleExtent(option.zoomRange)
.on("zoom", zoomed);
simulation = d3.forceSimulation(option.nodes)
.force("link", d3.forceLink(option.links).id(d => d.id).distance(150))
.force("charge", d3.forceManyBody().strength(-400))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("x", d3.forceX())
.force("y", d3.forceY())
.on("tick", ticked);
svg = d3.select(`#${option.dom.id}`).append('svg')
.attr('id', 'd3ForceEasyStage')
.attr("viewBox", [0, 0, width, height])
if (option.zoom) {
svg.call(zoom);
}
stage_g = svg.append('g').classed('stage-g', true);
const marker = stage_g.append("marker")
.attr("id", "resolved")
.attr("markerUnits", "userSpaceOnUse")
.attr("viewBox", "0 -5 10 10")//坐标系的区域
.attr("refX", 20)//箭头坐标
.attr("refY", 0)
.attr("markerWidth", 12)//标识的大小
.attr("markerHeight", 12)
.attr("orient", "auto")//绘制方向,可设定为:auto(自动确认方向)和 角度值
.attr("stroke-width", 2)//箭头宽度
.append("path")
.attr("d", "M0,-3L10,0L0,3L3,0")//箭头的路径
.attr('fill', '#8d8a8e');//箭头颜色
link = stage_g.append("g")
.attr("stroke", "#999")
.attr("fill", "none")
.attr("stroke-width", 1.5)
.attr("stroke-opacity", 0.6)
.attr("marker-end", "url(#resolved)")
.attr("stroke-width", 1)
.selectAll(".link-g");
node = stage_g.append("g")
.selectAll(".node-g");
function ticked() {
link.selectAll('path').attr('d', linkArc)
link.selectAll('.link-label')
.attr('x', d => linkLabelLoc(d)[0])
.attr('y', d => linkLabelLoc(d)[1])
node
// .attr("cx", d => d.x)
// .attr("cy", d => d.y);
.attr("transform", d => `translate(${d.x - 15},${d.y - 15})`);
// text.attr("x", d => d.x)
// .attr("y", d => d.y)
}
// simulation.stop();
return Object.assign(svg.node(), {
update({nodes, links}) {
const old = new Map(node.data().map(d => [d.id, d]));
nodes = nodes.map(d => Object.assign(old.get(d.id) || {}, d));
links = links.map(d => Object.assign({}, d));
node = node
.data(nodes, d => d.id)
.join(enter => enter.append("g")
.classed('force-node', true)
.classed("fixed", d => d.fx !== undefined)
.call(drag(simulation))
.call(node => node.append("title").text(d => d.id))
.call(node => node.append('circle')
.attr("r", 15)
.attr('cx', 15)
.attr('cy', 15)
.attr('fill', '#fff'))
.call(node => node.append('path')
.attr("d", d => {
if (option.icons.length) {
const iconLi = option.icons.find(item => {
return item.type == d.type
});
return iconLi ? iconLi.icon : defaultIcon;
} else {
return defaultIcon
}
})
.classed('icon-path', true)
.attr("fill", (d, i) => {
if (option.color) {
if (typeof option.color == 'string') {
return option.color
} else {
return option.color[i] || option.color.unshift();
}
} else {
return color(d, i)
}
})
.attr('transform', 'scale(0.03)'))
.call(node => node.append('text')
.text(d => d.name)
.classed('node-text', true)
.classed('hide', !option.text.show)
.attr('x',15)
.attr('y',40)
.attr('style','dominant-baseline:middle;text-anchor:middle')
)
)
.on('click', (e, d) => {
currentClickCallBack(e, d);
d3.select(this).classed("fixed", false);
node.selectAll('circle').classed('selected', item => item == d)
})
link = link
.data(links, d => [d.source, d.target])
.join("g")
.call(link => link.append("path"))
.call(link => link.append("text")
.text(d => d[option.linkLabel.key] || '')
.classed('link-label', 'abc')
.classed('hide', !option.linkLabel.show)
);
simulation.nodes(nodes);
simulation.force("link").links(links);
simulation.alpha(1).restart().tick();
ticked(); // render now!
}
});
}
function linkArc(d) {
const r = Math.hypot(d.target.x - d.source.x, d.target.y - d.source.y);
return `
M${d.source.x},${d.source.y}
A${r + option.curvature},${r + option.curvature} 0 0,1 ${d.target.x},${d.target.y}
`;
}
function linkLabelLoc(d) {
const r = Math.hypot(d.target.x - d.source.x, d.target.y - d.source.y);
let dx = (d.source.x + d.target.x) / 2;
let dy = (d.source.y + d.target.y) / 2;
let x,y,l=10;
if((d.source.x<d.target.x)&&(d.source.y>d.target.y)){
x = dx-r/l
y = dy-r/l
}else if((d.source.x>=d.target.x)&&(d.source.y>=d.target.y)){
x = dx-r/l
y = dy+r/l
}else if((d.source.x>d.target.x)&&(d.source.y<d.target.y)){
x = dx+r/l
y = dy+r/l
}else if((d.source.x<=d.target.x)&&(d.source.y<=d.target.y)){
x = dx+r/l
y = dy-r/l
}
//性能受限时放弃判断位置使用dx,dy
// return [dx,dy]
return [x,y]
}
function clamp(x, lo, hi) {
return x < lo ? lo : x > hi ? hi : x;
}
function updateNodes(nodes, links) {
}
function toggleName() {
option.text.show = !option.text.show;
svg.selectAll('.node-text').classed('hide', !option.text.show)
}
function forceToCenter() {
let identity = d3.zoomIdentity;
svg.transition().duration(750).call(zoom.transform, identity);
}
exports.initForce = initForce;
exports.updateNodes = updateNodes;
exports.toggleName = toggleName;
exports.forceToCenter = forceToCenter;
//node-link 576585087 dlw author
window.d3ForceEasy = exports;
Object.defineProperty(exports, '__esModule', {value: true});
})))
console