|
|
|
(function (window, d3) { |
|
|
|
"use strict"; |
|
|
|
if (window.PackTree) { return console.log("PackTree has been loaded!"); } |
|
|
|
// svg filter |
|
d3.select(document.body) |
|
.append("svg") |
|
.attr("class", "filter_only_svg") |
|
.attr("style", "position:absolute;width:0;height:0;z-index:-5;") |
|
.html('<defs> \ |
|
<filter id="packTreeBlur" x="-25%" y="-25%" width="150%" height="150%"> \ |
|
<feOffset dx="0" dy="0" in="SourceGraphic" result="o1" /> \ |
|
<feGaussianBlur in="o1" stdDeviation="5" result="b1" /> \ |
|
<feMerge> \ |
|
<feMergeNode in="b1" /> \ |
|
<feMergeNode in="SourceGraphic" /> \ |
|
</feMerge> \ |
|
</filter> \ |
|
</defs>' |
|
); |
|
|
|
function clearEmptyOrOnlyOneChildLogicalNode (node, parent) { |
|
// checking if node is leaf node |
|
if (node.type == "data") { return; } |
|
|
|
var index; |
|
(parent && (index = parent.children.findIndex(function (d) { return d === node; }))); |
|
|
|
if (parent && node.children.length == 0) { |
|
parent.children.splice(index, 1); |
|
} |
|
else if (parent && node.children.length == 1) { |
|
var nNode = node.children[0]; |
|
parent.children.push(nNode); |
|
parent.children.splice(index, 1); |
|
clearEmptyOrOnlyOneChildLogicalNode(nNode, parent); |
|
} |
|
else { |
|
for (var i = 0, l = node.children.length; i < l; i++) { |
|
clearEmptyOrOnlyOneChildLogicalNode(node.children[i], node); |
|
} |
|
} |
|
} |
|
|
|
|
|
var rootInit = (function () { |
|
|
|
var loosePack = d3.pack().padding(15); |
|
|
|
var sum = function (d) { return d.value; }, |
|
sort = function (a, b) { return b.r - a.r; }, |
|
andNodeOnly = function (node) { return node.data.type == "and"; }, |
|
nodeLocReassignment = function (node) { |
|
// container origin is the offset for children |
|
var x = node.x, y = node.y, |
|
circle, |
|
childNodes = node.children; |
|
d3.packSiblings(childNodes); |
|
childNodes.forEach(function (d) { |
|
d.x += x; |
|
d.y += y; |
|
}); |
|
circle = d3.packEnclose(childNodes); |
|
node.r = circle.r; |
|
}; |
|
|
|
var f = function () { |
|
clearEmptyOrOnlyOneChildLogicalNode(this._tree); |
|
|
|
var root = d3.hierarchy(this._tree).sum(sum).sort(sort), |
|
nodes = root.descendants(); |
|
loosePack.radius(() => this._r).size([this._w, this._h])(root); |
|
|
|
// find all "and" container and relocat all data nodes |
|
nodes |
|
.filter(andNodeOnly) |
|
.forEach(nodeLocReassignment); |
|
|
|
return nodes; |
|
}; |
|
|
|
return f; |
|
})(); |
|
|
|
|
|
var uuid = (function () { |
|
var i = 0, prefix = "w", f; |
|
f = function () { return prefix + "" + i++; }; |
|
f.prefix = function (v) { prefix = v; return f; }; |
|
return f; |
|
})(); |
|
|
|
|
|
// extract tspan text based on bind data |
|
var appendText = function (selection, d, clipId) { |
|
var f = [], |
|
i = 0, |
|
q = d.data.bind.querys, |
|
y = {"4": -30, "3": -20, "2": -10, "1": 0}; |
|
f.push("[" + d.data.bind.collection + "]"); |
|
while (q[i] && i < 3) { |
|
f.push(q[i].name + " " + q[i].logic + " " + q[i].query); |
|
i++; |
|
} |
|
|
|
selection.select(".svg__text").remove(); |
|
selection.append("text") |
|
.attr("class", "svg__text") |
|
.attr("y", y[f.length]) |
|
.attr("clip-path", "url(#" + clipId + ")") |
|
.selectAll("tspan") |
|
.data(f) |
|
.enter() |
|
.append("tspan") |
|
.attr("x", (j, i) => i ? 0 : null) |
|
.attr("dy", (j, i) => i ? 20 : null) |
|
.text(j => j); |
|
}; |
|
|
|
|
|
// vm dialog callback function |
|
var dataNodeUpdateFn = function (pack) { |
|
/** |
|
* _node: <g> dom node |
|
* _data: obj.bind |
|
*/ |
|
var _node, _data, fn; |
|
|
|
fn = function (bind) { |
|
// update tree |
|
var child = findItemFromHierarchy(pack._tree, _data).child; |
|
child.bind = JSON.parse(JSON.stringify(bind)); |
|
|
|
// update node |
|
var node = d3.select(_node); |
|
node.datum().data.bind = bind; |
|
node.call(appendText, node.datum(), pack._clipId); |
|
node.select("circle") |
|
.attr("r", 70) |
|
.transition() |
|
.duration(1000) |
|
.ease(d3.easeBounceOut) |
|
.attr("r", pack._r) |
|
.attr("fill", d => d.data.bind.color); |
|
|
|
_node = null; |
|
_data = null; |
|
}; |
|
fn.node = function (n) { _node = n; return fn; }; |
|
fn.data = function (d) { _data = d; return fn; }; |
|
|
|
return fn; |
|
}; |
|
|
|
|
|
|
|
function TreeNode (child, parent, grandParent) { |
|
this.child = child; |
|
this.parent = parent; |
|
this.grandParent = grandParent; |
|
} |
|
TreeNode.prototype = { |
|
parentRemoveNode: function (childNode) { |
|
var index = this.parent.children.findIndex(function (node) { |
|
return node === childNode; |
|
}); |
|
if (index < 0) { |
|
return console.error("child node is NOT exist in parent node!"); |
|
} |
|
this.parent.children.splice(index, 1); |
|
}, |
|
removeChild: function () { |
|
this.parentRemoveNode(this.child); |
|
}, |
|
parentAdd: function (node) { // an node or an node array |
|
if (node instanceof Array) { |
|
Array.prototype.push.apply(this.parent.children, node); |
|
} |
|
else { |
|
this.parent.children.push(node); |
|
} |
|
}, |
|
childAdd: function (node) { // an node or an node array |
|
if (!this.child.children) { |
|
return console.error("child is a prime node that can NOT contain any sub nodes."); |
|
} |
|
|
|
if (node instanceof Array) { |
|
Array.prototype.push.apply(this.child.children, node); |
|
} |
|
else { |
|
this.child.children.push(node); |
|
} |
|
} |
|
}; |
|
|
|
function findItemFromHierarchy (hierarchyData, nodeData) { |
|
// [currentNode, parentNode, grandParentNode, ... root] |
|
var path = nodeData.ancestors(), |
|
length = path.length, |
|
i = length - 2, |
|
child = hierarchyData, |
|
grandParent, |
|
parent, |
|
name; |
|
while (i >= 0) { |
|
name = path[i].data.name; |
|
grandParent = parent; |
|
parent = child; |
|
child = child.children.find(function (d) { return d.name === name; }); |
|
i--; |
|
} |
|
|
|
return new TreeNode(child, parent, grandParent); |
|
} |
|
|
|
// General pick for all PackTreee instance, |
|
// only one PackTree is focused by PackTree.focusMe(). |
|
var ctrPick = (function () { |
|
var pickedNodes = [], |
|
pack = null; |
|
|
|
// global keypress listener |
|
var keyUp = function () { |
|
// In case ctr is pressed while pressing other keys |
|
if (d3.event.key.toUpperCase() !== "CONTROL") { return; } |
|
|
|
// remove keyUp listener |
|
d3.select(this).on("keyup.ctr", null); |
|
|
|
var subChildren = [], |
|
rootChildren = pack._tree.children; |
|
|
|
// if picked node is less than 2 or all ROOT nodes are picked will do nothing |
|
//if (pickedNodes.length < 2 || pickedNodes.length == rootChildren.length ) { |
|
if (pickedNodes.length < 2) { |
|
pack.gMain.selectAll(".svg__picked").classed("svg__picked", false); |
|
return; |
|
} |
|
|
|
rootChildren.push({ |
|
name: uuid.prefix("o")(), |
|
type: "or", |
|
children: subChildren |
|
}); |
|
pickedNodes.forEach(function (node) { |
|
var name, index, target; |
|
name = d3.select(node).datum().data.name; |
|
index = rootChildren.findIndex(function (d) { return d.name === name; }); |
|
target = rootChildren.splice(index, 1)[0]; |
|
subChildren.push(target); |
|
}); |
|
|
|
pack.gMain.selectAll(".svg__picked").classed("svg__picked", false); |
|
pack.nodesUpdate(pack.rootInit()); |
|
}; |
|
var onKeydown = function () { |
|
if (d3.event.key.toUpperCase() !== "CONTROL" || !pack) { return; } |
|
pickedNodes = []; // clear content |
|
d3.select(this).on("keyup.ctr", keyUp); |
|
}; |
|
|
|
d3.select(window).on("keydown.ctr", onKeydown); |
|
|
|
var click = function (d) { |
|
/** |
|
* 1) Ctrl key should be enabled |
|
* 2) only dataNode is pickable |
|
* 3) dataNode but its parent node is orNode and is not the ROOT |
|
*/ |
|
if ( |
|
!d3.event.ctrlKey || |
|
d.data.type !== "data" || |
|
(d.parent.data.type == "or" && d.parent.data.name !== "ROOT") |
|
) { return; } |
|
|
|
var self = this, index; |
|
if (d.parent.data.type == "and") { |
|
self = pack.gMain |
|
.selectAll(".svg__node") |
|
.filter(function ($) { return $ === d.parent; }) |
|
.nodes()[0]; |
|
} |
|
self.classList.toggle("svg__picked"); |
|
index = pickedNodes.findIndex(function (node) { |
|
return node === self; |
|
}); |
|
|
|
// if this node is included in pickNodes remove it, |
|
// or push it into pickNodes. |
|
index > -1 ? pickedNodes.splice(index, 1) : pickedNodes.push(self); |
|
}; |
|
|
|
return { |
|
clickFn: click, |
|
clearNodes: function () { |
|
pickedNodes = []; |
|
return this; |
|
}, |
|
setPack: function (v) { |
|
if (!arguments.length) { return pack; } |
|
pack = v; |
|
return this; |
|
} |
|
}; |
|
})(); |
|
|
|
|
|
var nodeDragFn = function (pack) { |
|
// nodeList is a live HTMLCollection object which contained all g-nodes under gMain |
|
// nodesArray is an alternaltive to nodeList except needed repeately updated |
|
var nodeList, nodesArray = []; |
|
|
|
var targetNode = null, // node element, this element will comparing with matchNodes |
|
triggeredNode = null, // one of "matchNodes" member which have insection with targetNode |
|
relatedNodes = [], // targetNode's related nodes, if any. |
|
matchNodes = []; // targetNode's match node array. |
|
|
|
// calculation functions |
|
var slice = Array.prototype.slice, |
|
getDistance = function (c1, c2) { |
|
return Math.sqrt((c1.x - c2.x) * (c1.x - c2.x) + (c1.y - c2.y) * (c1.y - c2.y)); |
|
}, |
|
innerReset = function () { |
|
targetNode = null; |
|
triggeredNode = null; |
|
relatedNodes = []; |
|
matchNodes = []; |
|
nodesArray = []; |
|
}; |
|
|
|
function onDragStart (d) { |
|
// In case onEnd event don't trigged by user, |
|
// reassure every thing on the same page. |
|
innerReset(); |
|
|
|
// update nodesArray |
|
nodeList = nodeList || pack.gMain.node().getElementsByClassName("svg__node"); |
|
nodesArray = slice.call(nodeList, 0); |
|
|
|
var self = this, parent = d.parent, descendants; |
|
if (parent.data.type == "or") { |
|
targetNode = self; |
|
descendants = d.descendants(); |
|
nodesArray.forEach(function (node) { |
|
if (self === node) { return; } |
|
|
|
var datum = d3.select(node).datum(), |
|
children = parent.children; // or-Node's children |
|
if (children.includes(datum)) { matchNodes.push(node); return; } |
|
if (d.children && descendants.includes(datum)) { relatedNodes.push(node); } |
|
}); |
|
} |
|
else { // d.parent.data.type == "and" |
|
targetNode = nodesArray.find(function (node) { |
|
return d3.select(node).datum() === parent; |
|
}); |
|
descendants = parent.descendants(); |
|
|
|
nodesArray.forEach(function (node) { |
|
//if (self === node) { return; } |
|
|
|
var datum = d3.select(node).datum(), |
|
children = parent.parent.children; // or-Node's children |
|
if (descendants.includes(datum)) { relatedNodes.push(node); return; } |
|
if (children.includes(datum)) { matchNodes.push(node); } |
|
}); |
|
} |
|
|
|
} // onDragStart__End |
|
|
|
|
|
|
|
|
|
function onDrag (d) { |
|
var x = d3.event.x, y = d3.event.y, |
|
dx = d3.event.dx, dy = d3.event.dy; |
|
|
|
/** |
|
* Detecting if targetNode moved outside of parentNode's domain, |
|
* if it does stop coordinates updating by skipping onDrag loop. |
|
* targetNode under the ROOT will be excepted. |
|
*/ |
|
var moveCircle = (this === targetNode ? d : d3.select(targetNode).datum()), |
|
fenceCircle = moveCircle.parent, |
|
maxDis1 = fenceCircle.r - d.r - 1, |
|
maxDis2 = fenceCircle.r - moveCircle.r - 1; |
|
if ( |
|
(fenceCircle.data.name != "ROOT") && |
|
( |
|
((moveCircle === d) && (getDistance(fenceCircle, {x: x, y: y}) > maxDis1)) || |
|
((moveCircle !== d) && (getDistance(fenceCircle, {x: moveCircle.x + dx, y: moveCircle.y + dy}) > maxDis2)) |
|
) |
|
) { |
|
return; |
|
} // true: stop onDrag loop, code underbelow will not be excuted. |
|
|
|
if (this === targetNode) { |
|
Object.assign(d, {x: x, y: y}); |
|
d3.select(this).attr("transform", "translate(" + [x, y] + ")"); |
|
} |
|
relatedNodes.forEach(function (node) { |
|
var _node = d3.select(node), datum = _node.datum(); |
|
datum.x += dx; |
|
datum.y += dy; |
|
_node.attr("transform", "translate(" + [datum.x, datum.y] + ")"); |
|
}); |
|
|
|
/** |
|
* This code block used for testing if targetNode have intersection with matchNodes. |
|
* Find the triggeredNode which is the closest node to targetNode and should |
|
* have intersection with it too. |
|
*/ |
|
var wanted = matchNodes.map(function (node) { |
|
var dm = d3.select(node).datum(), |
|
dt = d3.select(targetNode).datum(), |
|
distance = getDistance(dm, dt), |
|
isIntersected = (distance <= Math.abs(dt.r + dm.r)); |
|
return { |
|
node : node, |
|
distance : distance, |
|
isIntersected: isIntersected |
|
}; |
|
}) |
|
.sort(function (a, b) { return a.distance - b.distance; }) |
|
.find(function (d) { return d.isIntersected; }); |
|
|
|
(triggeredNode && triggeredNode.classList.remove("svg__active")); |
|
if (wanted) { |
|
triggeredNode = wanted.node; |
|
triggeredNode.classList.toggle("svg__active"); |
|
} |
|
else { |
|
triggeredNode = null; |
|
} |
|
|
|
d3.select(targetNode).classed("svg__active", true); |
|
} // onDrag End |
|
|
|
|
|
|
|
function onDragEnd (d) { |
|
pack.gMain.selectAll(".svg__active").classed("svg__active", false); |
|
|
|
if (!triggeredNode) { return innerReset(); } |
|
|
|
var target = d3.select(targetNode).datum(), |
|
trigger = d3.select(triggeredNode).datum(), |
|
tt = findItemFromHierarchy(pack._tree, target), |
|
tg = findItemFromHierarchy(pack._tree, trigger); |
|
|
|
// 1) If two dataNodes come close merge them into a new andNode |
|
if (target.data.type == "data" && trigger.data.type == "data") { |
|
tt.removeChild(); |
|
tg.removeChild(); |
|
tt.parentAdd({ |
|
name: uuid.prefix("a")(), |
|
type: "and", |
|
children: [tt.child, tg.child] |
|
}); |
|
} |
|
|
|
// 2) If two andNodes come close tt grow into a bigger andNode and remove tg |
|
else if (target.data.type == "and" && trigger.data.type == "and") { |
|
tg.removeChild(); |
|
tt.childAdd(tg.child.children); |
|
} |
|
|
|
// 3) If two orNodes come close merge them into a new bigger orNode |
|
else if (target.data.type == "or" && trigger.data.type == "or") { |
|
tt.removeChild(); |
|
tg.removeChild(); |
|
tg.parentAdd({ |
|
name: uuid.prefix("o")(), |
|
type: "or", |
|
children: Array.prototype.concat([], tt.child.children, tg.child.children) |
|
}); |
|
} |
|
|
|
// 4) one node is dataNode and another is logic node(wrapper node) |
|
// dataNode will be merged into that logic node |
|
else if ( |
|
(target.data.type == "data" && trigger.data.type != "data") || |
|
(trigger.data.type == "data" && target.data.type != "data") |
|
) { |
|
if (target.data.type == "data") { |
|
tt.removeChild(); |
|
tg.childAdd(tt.child); |
|
} |
|
else { |
|
tg.removeChild(); |
|
tt.childAdd(tg.child); |
|
} |
|
} |
|
|
|
|
|
// _______________ UNTESTED _______________ |
|
// 5) both are logic node but NOT the same type |
|
// andNode will be merged into orNode |
|
else { |
|
if (target.data.type == "and" && trigger.data.type == "or") { |
|
tt.removeChild(); |
|
tg.childAdd(tt.child); |
|
} |
|
else if (trigger.data.type == "and" && target.data.type == "or") { |
|
tg.removeChild(); |
|
tt.childAdd(tg.child); |
|
} |
|
} |
|
|
|
pack.nodesUpdate(pack.rootInit()); |
|
|
|
// release memory |
|
innerReset(); |
|
} // onDragEnd End |
|
|
|
|
|
return d3.drag() |
|
.filter(function () { |
|
return !d3.event.ctrlKey && !d3.event.button; |
|
}) |
|
.on("start",onDragStart) |
|
.on("drag", onDrag) |
|
.on("end", onDragEnd); |
|
|
|
}; // nodeDragFn END |
|
|
|
|
|
function nodesUpdate (nodes) { |
|
var u = this.gMain.selectAll(".svg__node") |
|
.data(nodes.slice(1), function (d) { return d.data.name; }); |
|
var clipId = this._clipId; |
|
|
|
u.enter() |
|
.append("g") |
|
.attr("class", d => { return "svg__node svg__node-" + d.data.type; }) |
|
.call(this.onNodeDrag) |
|
.on("click", ctrPick.clickFn) |
|
.on("dblclick", this.onDataNodeDBLClick) |
|
.on("contextmenu", this.onNodeContextmenu) |
|
.each(function (d) { |
|
var self = d3.select(this); |
|
|
|
// add circle backgorund, circle is event-responsible |
|
self.append("circle") |
|
.attr("fill", d.children ? null : d.data.bind.color) |
|
.attr("r", d.r); |
|
|
|
// add text content for leaf nodes |
|
if (!d.children) { |
|
self.call(appendText, d, clipId); |
|
} |
|
}) |
|
.attr("transform", "translate(" + [this._w / 2, this._h / 2] + ")") |
|
.merge(u) |
|
.sort(function (a, b) { return b.r - a.r; }) |
|
.each(function (d) { |
|
// data rebine and change apprence |
|
d3.select(this).select("circle").datum(d).attr("r", d.r); |
|
// TODO: text should rebine if neccessory ... ... |
|
}) |
|
.transition() |
|
.duration(800) |
|
.attr("transform", d => { return "translate(" + [d.x, d.y] + ")"; }); |
|
|
|
|
|
u.exit() |
|
.attr("transform", d => { return "translate(" + [d.x, d.y] + ") scale(1) rotate(0)"; }) |
|
.transition() |
|
.duration(400) |
|
.attr("transform", d => { return "translate(" + [d.x, d.y] + ") scale(0.01) rotate(90)"; }) |
|
.remove(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function PackTree () { |
|
this._tree = { name: "ROOT", type: "or", children: [] }; |
|
this._w = 1000; |
|
this._h = 550; |
|
this._r = 50; |
|
this._clipId = uuid.prefix("clip")(); |
|
|
|
|
|
this._count = 0; |
|
Object.defineProperty(this, "count", { |
|
get: function () { return this._count; }, |
|
set: function (v) { |
|
this._count = v; |
|
if (this.onCount) { this.onCount(this._count); } |
|
} |
|
}); |
|
|
|
|
|
this.svg = null; |
|
this.gMain = null; |
|
this.vm = null; // pop window |
|
|
|
var self = this; |
|
this.dataNodeUpdate = dataNodeUpdateFn(this); |
|
this.onNodeDrag = nodeDragFn(this); |
|
this.onDataNodeDBLClick = function (d) { |
|
if (d3.event.ctrlKey || d.data.type !== "data" || !self.vm) { return; } |
|
|
|
self.vm.show( |
|
self.dataNodeUpdate.data(d).node(this), |
|
JSON.parse(JSON.stringify(d.data.bind)) |
|
); |
|
}; // onDataNodeDBLClick END |
|
|
|
this.onNodeContextmenu = function (d) { |
|
d3.event.preventDefault(); |
|
|
|
var node = findItemFromHierarchy(self._tree, d); |
|
|
|
// dataNode under the ROOT will be removed after user confirmed |
|
if (node.parent.name == "ROOT" && node.child.type == "data") { |
|
if (window.confirm("确认删除该节点?")) { |
|
node.removeChild(); |
|
self.count -= 1; |
|
return self.nodesUpdate(self.rootInit()); |
|
} |
|
return; |
|
} |
|
|
|
// this code block will never be excuted as for |
|
// orNode point-event is set to none. |
|
if (node.child.type == "or") { |
|
node.removeChild(); |
|
node.parentAdd(node.child.children); |
|
return self.nodesUpdate(self.rootInit()); |
|
} |
|
|
|
if ( |
|
(node.child.type == "data") && |
|
(node.parent.type == "and" || node.parent.type == "or") |
|
) { |
|
node.removeChild(); |
|
node.grandParent.children.push(node.child); |
|
return self.nodesUpdate(self.rootInit()); |
|
} |
|
}; // onNodeContextmenu END |
|
|
|
} |
|
|
|
// chainabel configure functions |
|
PackTree.prototype.width = function (w) { |
|
if (!arguments.length) { return this._w; } |
|
this._w = w; |
|
return this; |
|
}; |
|
PackTree.prototype.height = function (h) { |
|
if (!arguments.length) { return this._h; } |
|
this._h = h; |
|
return this; |
|
}; |
|
PackTree.prototype.radius = function (r) { |
|
if (!arguments.length) { return this._r; } |
|
this._r = (r < 50 ? 50 : r); |
|
return this; |
|
}; |
|
PackTree.prototype.vForm = function (vm) { |
|
if (!arguments.length) { return this.vm; } |
|
this.vm = vm; |
|
return this; |
|
}; |
|
|
|
// Make ctrPick focus this instance so that subsequent |
|
// ctr + mouse pick actions will be functional on this instance. |
|
PackTree.prototype.focusMe = function () { |
|
ctrPick.setPack(this); |
|
return this; |
|
}; |
|
PackTree.prototype.rootInit = rootInit; |
|
PackTree.prototype.nodesUpdate = nodesUpdate; |
|
PackTree.prototype.svgInit = function (id) { |
|
var svg, gMain, vm = this.vm, |
|
self = this, |
|
clipId = this._clipId, |
|
radius = this._r, w = this._w, h = this._h; |
|
|
|
var formCallback = function (nodeData) { |
|
self.addDataNode(nodeData); |
|
}; |
|
|
|
var canvasDrag = d3.drag() |
|
.subject(function () { return gMain.datum(); }) |
|
.on("drag", function () { |
|
// if chart is empty diseabled drag event |
|
if (!self._tree.children.length) { return; } |
|
|
|
var x = d3.event.x, y = d3.event.y; |
|
gMain.datum({x: x, y: y}) |
|
.attr("transform", "translate(" + [x, y] + ")"); |
|
}); |
|
|
|
svg = d3.select(id).append("svg") |
|
.attr("xmlns", "http://www.w3.org/2000/svg") |
|
.attr("version", "1.1") |
|
.attr("tabIndex", 0) // focus: document.activeElement |
|
.attr("class", "svg") |
|
.attr("width", w) |
|
.attr("height", h); |
|
|
|
svg.append("defs") |
|
.html('<clipPath id="' + clipId + '"><circle r="' + (radius - 2) + |
|
'"></circle></clipPath>'); |
|
|
|
svg.append("rect") |
|
.attr("class", "svg__bg") |
|
.attr("width", w) |
|
.attr("height", h) |
|
.call(canvasDrag) |
|
.on("contextmenu", function () { |
|
d3.event.preventDefault(); |
|
if (vm) { |
|
// vm.show(callbackFn, bind) |
|
vm.show(formCallback); |
|
} |
|
}); |
|
|
|
gMain = svg.append("g") |
|
.attr("class", "svg__main") |
|
.datum({x: 0, y: 0}) |
|
.attr("transform", "translate(0,0)"); |
|
|
|
this.svg = svg; |
|
this.gMain = gMain; |
|
|
|
}; // svgInit END |
|
|
|
PackTree.prototype.initialize = function (id, isFormPop) { |
|
this.svgInit(id); |
|
//this.rootInit() |
|
|
|
if (isFormPop && this.vm && !this.vm.confirmCallbackFn) { |
|
this.svg.select(".svg__bg").dispatch("contextmenu"); |
|
} |
|
|
|
return this; |
|
}; |
|
|
|
PackTree.prototype.addDataNode = function (bind) { |
|
this._tree.children.push({ |
|
name: uuid.prefix("d")(), |
|
type: "data", |
|
bind: bind |
|
}); |
|
this.count += 1; |
|
this.nodesUpdate(this.rootInit()); |
|
}; |
|
PackTree.prototype.clearTree = function () { |
|
this._tree.children = []; |
|
this.count = 0; |
|
this.nodesUpdate([]); |
|
}; |
|
PackTree.prototype.toString = function (space) { |
|
if (!space) { space = 0; } |
|
return JSON.stringify(this._tree, null, space); |
|
}; |
|
PackTree.prototype.toJSON = function () { |
|
return JSON.parse(this.toString()); |
|
}; |
|
|
|
|
|
window.PackTree = PackTree; |
|
|
|
})(window, d3); |
|
|