1 Star 1 Fork 4

小码农 / D3企业关系图谱

forked from 临月 / D3企业关系图谱 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
relation.js 29.42 KB
一键复制 编辑 原始数据 按行查看 历史
临月 提交于 2018-06-05 17:40 . 2018/6/5完成企业关系图
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007
/* **************************************************************************** */
/* __ */
/* by: joknic .´ _`ヽ */
/* i l/i ̄`l| */
/* date: 2018/05/26 14:16:08 | i| ゚ ヮ゚ノ| */
/* | l|i゙、!i/l.l| */
/* email: 860001401@qq.com | ,liU/|l〉,| */
/* し'ノ */
/* */
/* **************************************************************************** */
(function () {
const chart = function (options) {
this.options = $.extend({
id: "",
spacing: 15,
nodes: [],
edges: [],
color: {
node: {
Person: "#f75000",
Company: "#4682b4"
},
opacityNode: {
Person: "#FFCBB3",
Company: "#dcecfc"
},
line: {
INVEST: "#fd485e",
EMPLOY: "#4ea2f0",
LEGAL: "#4ea2f0"
},
stroke: {
Person: "#FF8247",
Company: "#6495ED"
},
opacityStroke: {
Person: "#FFE7BA",
Company: "#B2DFEE"
},
main: {
node: "#EE3B3B",
stroke: "#EE6363",
opacityNode: "#FFC1C1",
opacityStroke: "#FFE4C4"
}
}
}, options)
let notEnoughOption = false;
for (let k in this.options) {
if (this.isEmpty(this.options[k])) {
notEnoughOption = true;
}
};
!notEnoughOption && this.init();
}
chart.prototype.init = function () {
const _this = this,
nodes = _this.options.nodes,
edges = _this.options.edges;
const zoom = d3.zoom()
.on("zoom", () => {g.attr("transform", d3.event.transform)});
const svg = d3.select("#" + this.options.id),
width = +svg.attr("width"),
height = +svg.attr("height"),
margin = { top: 0, bottom: 0, left: 0, right: 0 },
g = svg.append("g").attr("transform", "translate(" + margin.left + ", " + margin.top + ")");
let edges2 = [], edges3 = [], edges4 = [];
let state = {
clicked: false,
drag: false
}
const MIDDLE = "middle", TOP = "top", BOTTOM = "bottom";
const SELECTED = "selected", UNSELECTED = "unselected";
const COMPANY = "Company", PERSON = "Person";
const strokeWidthClick = 5, strokeWidth = 1.5;
_this.dataSet(edges, nodes);
const simulation = d3.forceSimulation(nodes)
.force("charge", d3.forceManyBody().strength(-400))
.force("link", d3.forceLink(edges).distance(200).strength(2))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("collide", d3.forceCollide().radius(function (d) {
return d.r + 50;
}));
_this.unique(spliceArray(edges, edges2));
_this.unique(spliceArray(edges2, edges3));
_this.unique(spliceArray(edges3, edges4));
_this.setIndex(edges, edges2);
_this.setIndex(edges3, edges2);
_this.setIndex(edges4, edges3);
_this.replaceEmptyItem(edges);
_this.replaceEmptyItem(edges2);
_this.replaceEmptyItem(edges3);
_this.replaceEmptyItem(edges4);
_this.distinctIndex(edges, edges2);
_this.distinctIndex(edges2, edges3);
_this.distinctIndex(edges3, edges4);
const defs = svg.append("defs");
for (let i in edges) {
appendArrow("middlePathArrow" + i, edges[i].index);
}
for (let i in edges2) {
appendArrow("topPathArrow" + i, edges2[i].index);
}
for (let i in edges3) {
appendArrow("bottomPathArrow" + i, edges3[i].index);
}
// middle path
const middlePath = initPath(MIDDLE, edges),
middlePathTextMask = initPathTextMask(MIDDLE, edges),
middlePathText = initPathText(MIDDLE, edges),
// top path
topPathRect = initPathRect(TOP),
topPath = initPath(TOP, edges2),
topPathTextMask = initPathTextMask(TOP, edges2),
topPathText = initPathText(TOP, edges2),
//bottom path
bottomPathRect = initPathRect(BOTTOM),
bottomPath = initPath(BOTTOM, edges3),
bottomPathTextMask = initPathTextMask(BOTTOM, edges3),
bottomPathText = initPathText(BOTTOM, edges3);
//nodes g
const gs = g.selectAll(".circle")
.data(nodes)
.enter()
.append("g")
.attr("class", "gNodes")
.attr("transform", "translate(-9999, -9999)")
.call(d3.drag()
.on("start", dragstart)
.on("drag", dragged)
.on("end", dragend)
);
d3.selection.prototype.moveToFront = function() {
return this.each(function(){
this.parentNode.appendChild(this);
});
};
d3.selection.prototype.moveToBack = function() {
return this.each(function() {
var firstChild = this.parentNode.firstChild;
if (firstChild) {
this.parentNode.insertBefore(this, firstChild);
}
});
};
gs.on("click", function(d) {
d3.selectAll("circle").attr("stroke-width", o => {
return o !== d ? `${strokeWidth}px` : `${strokeWidthClick}px`;
})
.attr("r", d => d.r - strokeWidth)
if(state.clicked) {
if(!d.selectedNode) {
//这是点击没有关联的node
// unSelected(d, true, this)
// state.clicked = false;
} else {
//这是点击关联node
Link(d);
selected(d, true, this);
}
} else {
//这是第一次点击
selected(d, true, this);
state.clicked = true;
}
$(document).click(e => {
//这是点击非nodes
if(e.target.getAttribute("class") !== "selected"){
d3.selectAll("circle").attr("stroke-width", `${strokeWidth}px`)
unSelected(d, true, this);
unLink(d);
state.clicked = false;
}
});
})
//nodes circle
const nodesCircle = gs.append("circle")
.attr("r", d => d.r - strokeWidth)
.attr("id", d => `circle-${d.index}`)
.attr("fill", d => d.color)
.attr("stroke", d => d.stroke)
.style("cursor", "pointer");
//nodes text
const text = gs.append("text")
.attr("id", "text")
.style("fill", "#fff")
.style("cursor", "pointer");
/**
* 根据类型决定node文字的属性
* @param {object} d
*/
function sType(d) {
const offset = 5;
const type = {
Company: {
x: -(d.r / 2 + offset),
y: -d.r / 2 + offset,
text: d.properties.name.substring(0, 4)
},
Person: {
x: -(d.r / 2 + offset),
y: offset,
text: d.properties.name
}
}
return type;
}
text.append("tspan")
.attr("x", d => {
return sType(d)[d.labels[0]].x;
})
.attr("y", d => {
return sType(d)[d.labels[0]].y;
})
.text(d => {
if (!sType(d)[d.labels[0]]) return;
return sType(d)[d.labels[0]].text;
});
text.append("tspan")
.attr("x", (d) => {
if (d.labels[0] === COMPANY) {
return sType(d)[d.labels[0]].x - 8;
}
})
.attr("y", (d) => {
if (d.labels[0] === COMPANY) {
return sType(d)[d.labels[0]].y + 18;
}
})
.text((d) => {
return d.properties.name.substring(4, 9);
});
text.append("tspan")
.attr("x", (d) => {
if (d.labels[0] === COMPANY) {
return sType(d)[d.labels[0]].x;
}
})
.attr("y", (d) => {
if (d.labels[0] === COMPANY) {
return sType(d)[d.labels[0]].y + 36;
}
})
.text((d) => {
if (d.properties.name.length > 13) {
return d.properties.name.substring(9, 11) + "...";
} else {
return d.properties.name.substring(9, d.properties.name.length);
}
});
function appendArrow(id, _class) {
defs.append("marker")
.attr("class", "arrow")
.attr("markerUnits", "strokeWidth")
.attr("markerWidth", 10)
.attr("markerHeight", 10)
.attr("refY", 3)
.attr("id", `arrow${_class}`)
.attr("fill-opacity", .5)
.append("path")
.attr("d", "M0,1 L0,5 L5,3 z");
}
function ticked() {
gs.attr("transform", d => {
let cirX = d.x;
let cirY = d.y;
return "translate(" + cirX + ", " + cirY + ")";
});
updatePath(middlePath, "middle");
updateText(middlePathText, "middle");
updateText(middlePathTextMask, "middle");
updateRect(topPathRect);
updatePath(topPath, "top");
updateText(topPathText, "top");
updateText(topPathTextMask, "top");
updateRect(bottomPathRect);
updatePath(bottomPath, "bottom");
updateText(bottomPathText, "bottom");
updateText(bottomPathTextMask, "bottom");
}
const scale = d3.scaleLinear();
scale.domain([0, 200])
.range([1500, 5000])
setTimeout(() => {
$('body')
.removeClass('loading')
.addClass('loaded');
ticked();
simulation.stop();
svg.call(zoom);
}, scale(nodes.length));
// simulation.on("tick", ticked);
function initPathRect(direction) {
let offset = direction === "top" ? 0 : _this.options.spacing;
let pathLength = document.querySelector(".path-middle").getBBox().width;
const rect = g.selectAll(".path-rect")
.data(edges)
.enter()
.append("rect")
.attr("fill", "none")
.attr("width", document.querySelector(".path-middle").getBBox().width)
.attr("height", _this.options.spacing);
return rect;
}
function topPathAttrD(d, i) {
//x偏移
const x = trigonoFun(d.x, _this.options.spacing, true).x;
//y偏移
const y = trigonoFun(d.x, _this.options.spacing, true).y;
return `M${d.target.x - x} ${d.target.y + y}L${d.source.x - x} ${d.source.y + y}Z`;
}
function bottomPathAttrD(d, i) {
//x偏移
const x = trigonoFun(d.x, _this.options.spacing, true).x;
//y偏移
const y = trigonoFun(d.x, _this.options.spacing, true).y;
return `M${d.target.x + x} ${d.target.y - y}L${d.source.x + x} ${d.source.y - y}Z`;
}
function middlePathAttrD(d, i) {
let x1 = d.source.x;
let y1 = d.source.y;
let x2 = d.target.x;
let y2 = d.target.y;
//斜率
let x = Math.atan2(y2 - y1, x2 - x1);
x = 180 * x / Math.PI;
d["x"] = x;
edges2.map(function (w, j) {
if (d.index === w.originalIndex) {
//set x to the other edge
w["x"] = d.x;
}
});
edges3.map(function (w, j) {
if (d.index === w.originalIndex) {
//set x to the other edge
w["x"] = d.x;
}
});
return `M${x1} ${y1}L${x2} ${y2}`;
}
function appendText(direction, arr, isMask) {
let selection = direction + "-path-text";
let text = g.selectAll(selection)
.data(arr)
.enter()
.append("text")
.attr("id", (d, i) => {
return `${direction}Text${i}`;
})
.attr("dy", 3);
isMask
? text.attr("class", `path-${direction}-mask-text`)
: text.attr("class", `path-${direction}-text`);
return text;
}
function appendTextPath(direction, mask) {
let idSelection = "#" + direction + "Path";
mask.append("textPath")
.attr("xlink:href", (d, i) => {
return `#d${d.index}`;
})
.attr("id", "text")
.text((d, i) => {
let type = {
EMPLOY: d.properties.role || "任职",
LEGAL: "法人",
INVEST: "参股"
}
return type[d.type];
});
return mask;
}
function initPathTextMask(direction, arr) {
const mask = appendText(direction, arr, true);
appendTextPath(direction, mask)
.style("stroke", "white")
.style("stroke-width", "0.15em");
return mask;
}
function initPathText(direction, arr) {
const text = appendText(direction, arr);
appendTextPath(direction, text);
return text;
}
function initPath(direction, arr) {
const attrClass = "path-" + direction,
attrId = direction + "Path",
selection = "." + attrId,
links = g.selectAll(selection)
.data(arr)
.enter()
.append("path")
.attr("class", attrClass)
.attr("id", d => {
return `d${d.index}`;
});
return links;
}
/**
* 三角形根据侧边求其他两边
* @param {number} x //侧边长度
* @param {number} a //角度
* @param {boolean} isComplementaryAngle //是否余角
*/
function trigonoFun(x, a, isComplementaryAngle) {
const o = {
x: 0,
y: 0
};
let alpha = isComplementaryAngle ? 90 - x : x;
alpha = alpha * Math.PI / 180;
//偏移
o.y = Math.sin(alpha) * a;
o.x = Math.cos(alpha) * a;
return o;
}
function Link(d) {
linkActive(middlePath, d);
linkActive(topPath, d);
linkActive(bottomPath, d);
}
gs.on("mouseover", function(d) {
if(state.clicked || state.drag) return;
Link(d);
let msg = d.properties.name;
if($(this).find("title")[0]) return;
const title = document.createElementNS("http://www.w3.org/2000/svg","title"),
text = document.createTextNode(msg);
title.appendChild(text);
this.appendChild(title);
});
function linkActive(link, d) {
link.style("stroke-opacity", o => {
if(o.source === d || o.target === d) {
d3.select(`#arrow${o.index}`).attr("fill-opacity", 1);
return 1;
}
})
}
function unLinkActive(link, d) {
link.style("stroke-opacity", o => {
if(o.source === d || o.target === d) {
d3.select(`#arrow${o.index}`).attr("fill-opacity", .5);
return .3;
}
})
}
gs.on("mouseout", d => {
if(state.clicked || state.drag) return;
unLink(d);
});
/**
* 取消线的hover高亮
* @param {Object} d
*/
function unLink(d) {
unLinkActive(middlePath, d);
unLinkActive(topPath, d);
unLinkActive(bottomPath, d);
}
/**
* link置顶
* @param {Object} d
*/
function stickyLink(d, nodes) {
selectFilter(".path-middle", d).raise();
selectFilter(".path-middle-mask-text", d).raise();
selectFilter(".path-middle-text", d).raise();
selectFilter(".path-top", d).raise();
selectFilter(".path-top-mask-text", d).raise();
selectFilter(".path-top-text", d).raise();
selectFilter(".path-bottom", d).raise();
selectFilter(".path-bottom-mask-text", d).raise();
selectFilter(".path-bottom-text", d).raise();
}
/**
* node置顶
* @param {Object} d
*/
function stickNode(d) {
nodesCircle.style("stroke-opacity", function (o) {
if (isConnect(d, o)) {
d3.select($(`#circle-${o.index}`).parent("g")[0]).raise();
}
})
}
const linkIndex = {};
edges.map(d => {
linkIndex[`${d.source.index},${d.target.index}`] = 1;
});
/**
* 两个node是否关联
* @param {Object} a node1 d
* @param {Object} b node2 d
*/
function isConnect(a, b) {
return linkIndex[`${a.index},${b.index}`] || linkIndex[`${b.index},${a.index}`] || a.index === b.index;
}
/**
* link交互相关
* @param {number} opacity 透明度
* @param {Object} d 数据集
* @param {Object} link d3 link obj
* @param {String} direction 方向
* @param {String} state start or end
*/
function fadePath(opacity, d, link, direction, state) {
link.style("stroke-opacity", function(o, i) {
if(o.source === d || o.target === d) {
//关联的link
return 1;
} else {
//不关联的link
const arrowOpacity = state === "end" ? .5 : opacity;
d3.select(`#arrow${o.index}`).attr("fill-opacity", arrowOpacity);
return opacity;
}
})
}
function fadePathText(opacity, d, text, mask) {
text.attr("fill-opacity", o =>
(o.source === d || o.target === d ? 1 : opacity));
mask.attr("opacity", o =>
(o.source === d || o.target === d ? 1 : opacity));
}
function fadeNodeText(opacity, d, text) {
text.style("fill-opacity", function(o) {
const thisOpacity = isConnect(d, o) ? 1 : opacity;
return thisOpacity;
})
}
/**
* 关联node的link过滤器(两边)
* @param {String} doc d3 selection
* @param {Object} d
*/
function selectFilter(doc, d) {
return d3.selectAll(doc)
.filter(o => (o.source.index === d.index || o.target.index === d.index));
}
/**
* 关联node的link过滤器(一边)
* @param {String} doc d3 selection
* @param {Object} d
*/
function selectFilterNot(doc, d) {
return d3.selectAll(doc)
.filter(o => (o.source.index !== d.index || o.target.index !== d.index));
}
/**
* 给高亮nodes和其他nodes添加类名
* @param {String} _class 类名
* @param {Object} _this node doc
*/
function toggleSelected(_class, _this) {
_this.setAttribute("class", _class);
const text = $(_this).next("text")[0];
text.setAttribute("class", _class);
const tspan = $(text).find("tspan");
tspan.each((i, v) => {
v.setAttribute("class", _class);
});
}
/**
* 选中node及其关联node设置颜色
* @param {Object} o 数据集
* @param {Object} _this2 node doc
*/
function selectedNodeSetColor(o, _this2) {
if(o.properties.name === "易关通(广州)网络科技有限公司") {
_this2.setAttribute("fill", _this.options.color.main.node);
_this2.setAttribute("stroke", _this.options.color.main.stroke);
} else {
if (o.color === _this.options.color.node.Company) {
_this2.setAttribute("fill", _this.options.color.node.Company);
_this2.setAttribute("stroke", _this.options.color.stroke.Company);
} else {
_this2.setAttribute("fill", _this.options.color.node.Person);
_this2.setAttribute("stroke", _this.options.color.stroke.Person);
}
}
}
function findCircle(o) {
return $(`#circle-${o.index}`);
}
/**
* 切换鼠标手势
* @param {Object} o 数据集
* @param {String} cursor 鼠标手势
*/
function toggleCursor(o, cursor) {
let circle = findCircle(o);
let text = circle.next("#text");
d3.select(circle[0]).style("cursor", cursor);
d3.select(text[0]).style("cursor", cursor);
}
/**
* 高亮显示关联的node,其他透明化
* @param {Object} d 数据集
* @param {Boolean} clicked 是否是点击事件
* @param {Object} _this2 点击的document
*/
function selected(d, clicked, _this2) {
nodesCircle.style("stroke-opacity", function (o) {
if (!isConnect(d, o)) {
//没有关联的nodes
if(o.properties.name === _this.options.dominant) {
this.setAttribute("fill", _this.options.color.main.opacityNode);
this.setAttribute("stroke", _this.options.color.main.opacityStroke);
} else {
o.color === _this.options.color.node.Company
?this.setAttribute("fill", _this.options.color.opacityNode.Company)
:this.setAttribute("fill", _this.options.color.opacityNode.Person);
o.stroke === _this.options.color.stroke.Company
? this.setAttribute("stroke", _this.options.color.opacityStroke.Company)
: this.setAttribute("stroke", _this.options.color.opacityStroke.Person);
}
o["selectedNode"] = false;
toggleSelected("unSelected", this);
toggleCursor(o, "default");
} else {
//关联的nodes
o["selectedNode"] = true;
selectedNodeSetColor(o, this);
toggleSelected("selected", this);
toggleCursor(o, "pointer");
}
});
fadePath(.1, d, middlePath, "middle", "start");
fadePath(.1, d, topPath, "top", "start");
fadePath(.1, d, bottomPath, "top", "start");
fadePathText(0, d, middlePathText, middlePathTextMask, true);
fadePathText(0, d, topPathText, topPathTextMask, true);
fadePathText(0, d, bottomPathText, bottomPathTextMask, true);
fadeNodeText(0, d, text);
}
function dragstart(d) {
state.drag = true;
if(state.clicked) return;
selected(d, false, this);
}
function dragend(d) {
d3.selectAll(".gNodes").sort((a, b) => {
if(a.index === d.index) return 1;
else return -1;
});
state.drag = false;
if(state.clicked) return;
unSelected(d, false, this);
}
/**
* 取消选中高亮
* @param {Object} d 数据集
* @param {Boolean} clicked 是否点击事件
* @param {Object} _this2 点击或者拖拽的document
*/
function unSelected(d, clicked, _this2) {
d3.select(_this2).attr("stroke-width", `${strokeWidth}px`);
nodesCircle.style("stroke-opacity", function (o) {
toggleCursor(o, "pointer");
selectedNodeSetColor(o, this);
})
fadeNodeText(1, d, text);
fadePath(.3, d, middlePath, MIDDLE, "end");
fadePath(.3, d, topPath, TOP, "end");
fadePath(.3, d, bottomPath, TOP, "end");
fadePathText(1, d, middlePathText, middlePathTextMask);
fadePathText(1, d, topPathText, topPathTextMask);
fadePathText(1, d, bottomPathText, bottomPathTextMask);
}
/**
* 置顶操作
* @param {String} _class 类名
* @param {Object} d
*/
function sticky(_class, d) {
d3.selectAll(_class).filter(o => (o.source.index !== d.index || o.target.index !== d.index)).lower()
}
function dragged(d) {
if (state.clicked) {
if(findCircle(d).attr("class") === "unSelected") return;
}
$(".unSelected").parent("g").each((i, v) => {
d3.select(v).lower();
})
sticky(".path-middle", d);
sticky(".path-top", d);
sticky(".path-bottom", d);
d.x = d3.event.x;
d.y = d3.event.y;
const node = d3.select(this);
node.attr("transform", function (v, i) {
return "translate(" + d.x + " ," + d.y + ")";
});
updatePath(selectFilter(".path-middle", d), MIDDLE, true);
updateText(selectFilter(".path-middle-text", d), MIDDLE);
updateText(selectFilter(".path-middle-mask-text", d), MIDDLE);
updateRect(selectFilter(".path-top-rect", d), TOP);
updatePath(selectFilter(".path-top", d), TOP, true);
updateText(selectFilter(".path-top-text", d), TOP);
updateText(selectFilter(".path-top-mask-text", d), TOP);
updateRect(selectFilter(".path-bottom-rect", d), BOTTOM);
updatePath(selectFilter(".path-bottom", d), BOTTOM, true);
updateText(selectFilter(".path-bottom-text", d), BOTTOM);
updateText(selectFilter(".path-bottom-mask-text" , d), BOTTOM);
}
function textFixedX(direction) {
return (d, i) => {
const pathDom = $(`#d${d.index}`)[0];
const pathWidth = pathDom.getTotalLength();
const scale = direction === "middle" ? 2 : 4;
let offset = 0;
const role = d.properties.role;
if (role) offset = role.length > 6 ? 26 : 0;
let x = pathWidth / scale - d.source.r / 2 - offset;
return x;
}
}
function textFixedTransform(direction) {
return (d, i) => {
const svgDom = $(`#d${d.index}`)[0].getBBox();
const x = svgDom.x;
const y = svgDom.y;
const rotate0 = `rotate(0, ${x + svgDom.width / 2}, ${y + svgDom.height / 2})`;
const rotate180 = `rotate(180, ${x + svgDom.width / 2}, ${y + svgDom.height / 2})`;
let rotate = -90 < d.x && d.x < 90
? (direction === "middle" ? rotate0 : rotate180)
: (direction === "middle" ? rotate180 : rotate0);
return rotate;
}
}
function updateText(text, direction) {
text.attr("x", textFixedX(direction))
.attr("transform", textFixedTransform(direction));
}
function updateRect(rect) {
rect.attr("x", (d) => {
return d.source.x;
})
.attr("y", (d) => {
return d.source.y;
})
.attr("transform", (d, i) => {
return "rotate(" + d.x + "," + d.source.x + "," + d.source.y + ")";
})
}
/**
* 重绘链接线
* @param {Object} path d3 Selection Object
* @param {String} direction 上中下哪条线
* @param {Boolean} isDrag 是否是拖拽事件
*/
function updatePath(path, direction, isDrag) {
path.attr("d", (d, i) => {
if (direction === TOP) {
return topPathAttrD(d, i);
} else if (direction === BOTTOM) {
return bottomPathAttrD(d, i);
} else if (direction === MIDDLE) {
return middlePathAttrD(d, i);
}
})
.attr("stroke", (d) => {
return d.color;
})
.attr("stroke-width", 1.5)
if (direction !== MIDDLE) {
path.attr("marker-end", (d, i) => {
const arrow = d3.select(`#arrow${d.index}`);
arrow.attr("fill", _this.options.color.line[d.type])
.attr("refX", 26)
.attr("orient", d.x)
return `url(#arrow${d.index})`;
});
} else {
path.attr("marker-end", (d, i) => {
const arrow = d3.select(`#arrow${d.index}`);
arrow.attr("fill", _this.options.color.line[d.type])
.attr("refX", 28)
.attr("orient", "auto")
return `url(#arrow${d.index})`;
});
}
}
}
/**
* 判断是否为空
* @param {object} obj
*/
chart.prototype.isEmpty = function (obj) {
if (obj == null) return true;
if (obj.length > 0) return false;
if (obj.length === 0) return true;
for (let key in obj) {
if (hasOwnProperty.call(obj, key)) return false;
}
return false;
}
/**
* 数组去重
* @param {Array} arr
*/
chart.prototype.unique = function (arr) {
const res = new Map();
return arr.filter(a => !res.has(a) && res.set(a, 1));
}
chart.prototype.unrepeat = function (obj) {
var isArray = obj instanceof Array;
for (var k in obj) {
if (k === "undefined") delete obj[k];
else if (typeof obj[k] == "object") this.unrepeat(obj[k]);
}
return obj;
}
/**
* 去除数组empty元素
* @param {Array} arr
*/
chart.prototype.replaceEmptyItem = function (arr) {
for (let i = 0, len = arr.length; i < len; i++) {
if (!arr[i] || arr[i] == '') {
arr.splice(i, 1);
len--;
i--;
}
}
}
/**
* 去除数组index与另一个数组相同的部分
* @param {Array} arr1 要去除重复的数组
* @param {Array} arr2 与之重复的数组
*/
chart.prototype.distinctIndex = function (arr1, arr2) {
for (let i in arr1) {
for (let j in arr2) {
if (arr1[i].index === arr2[j].index) {
arr1.splice(i, 1);
}
}
}
}
/**
* 把关系数组的id指向nodes数组
* @param {Array} d1 关系数组
* @param {Array} d2 Nodes数组
*/
chart.prototype.dataSet = function (d1, d2) {
const _this = this;
d1.map(v => {
v["source"] = v.startNode;
v["target"] = v.endNode;
})
d1.some((v, i) => {
let type = this.options.color.line;
v["color"] = type[v.type];
d2.some(function (w, j) {
const type = {
Company: function () {
w["r"] = 35;
w["stroke"] = _this.options.color.stroke.Company;
w["color"] = _this.options.color.node.Company;
},
Person: function () {
w["r"] = 25;
w["stroke"] = _this.options.color.stroke.Person;
w["color"] = _this.options.color.node.Person;
}
};
new type[w.labels[0]];
if(w.properties.name === _this.options.dominant) {
w["color"] = _this.options.color.main.node;
w["stroke"] = _this.options.color.main.stroke;
}
if (v.source === w.id) {
v.source = w;
}
if (v.target === w.id) {
v.target = w;
}
});
});
}
/**
*
* @param {Array} d1
* @param {Array} d2
*/
chart.prototype.setIndex = function (d1, d2) {
d1.map(function (v, i) {
d2.map(function (w, j) {
if (v.originalIndex === j) {
v.originalIndex = w.originalIndex;
}
});
});
}
/**
* 把一对一的多重关系分离出新的数组
* @param {Array} arr1 要从中取数据的数组,不改变原数组
* @param {Array} arr2 要把数据放到的新数组
*/
function spliceArray(arr1, arr2) {
let arr = [];//存放原数组切割出去的元素的index
for (let i = 0; i < arr1.length - 1; i++) {
for (let j = i + 1; j < arr1.length; j++) {
if (arr1[j] && arr1[i]) {
if (arr1[i].source === arr1[j].source && arr1[i].target === arr1[j].target) {
arr2[j] = arr1[j];
arr2[j]["originalIndex"] = arr1[i].index;
arr.push(j);
arr1[j]["isRedundancy"] = true;
}
}
}
}
return arr;
}
window.chart = chart;
}());
JavaScript
1
https://gitee.com/dawangka_admin/relationshipMap.git
git@gitee.com:dawangka_admin/relationshipMap.git
dawangka_admin
relationshipMap
D3企业关系图谱
master

搜索帮助