私がしたことは、3djsを使ってグラフを生成し、editabeをノードにしてノードを追加/削除することです。私は
var root;
treeJSON = d3.json("flare.json", function(error, treeData) {
// Calculate total nodes, max label length
var totalNodes = 0;
var maxLabelLength = 0;
// variables for drag/drop
var selectedNode = null;
var draggingNode = null;
// panning variables
var panSpeed = 200;
var panBoundary = 20; // Within 20px from edges will pan when dragging.
// Misc. variables
var i = 0;
var duration = 750;
// var root;
// size of the diagram
var viewerWidth = $('#d3view').width();
var viewerHeight = 800;
var tree = d3.layout.tree()
.size([viewerHeight, viewerWidth]);
// define a d3 diagonal projection for use by the node paths later on.
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
// A recursive helper function for performing some setup by walking through all nodes
function visit(parent, visitFn, childrenFn) {
if (!parent) return;
visitFn(parent);
var children = childrenFn(parent);
if (children) {
var count = children.length;
for (var i = 0; i < count; i++) {
visit(children[i], visitFn, childrenFn);
}
}
}
// Call visit function to establish maxLabelLength
visit(treeData, function(d) {
totalNodes++;
maxLabelLength = Math.max(d.name.length, maxLabelLength);
}, function(d) {
return d.children && d.children.length > 0 ? d.children : null;
});
// sort the tree according to the node names
function sortTree() {
tree.sort(function(a, b) {
return b.name.toLowerCase() < a.name.toLowerCase() ? 1 : -1;
});
}
// Sort the tree initially incase the JSON isn't in a sorted order.
sortTree();
// Define the zoom function for the zoomable tree
function zoom() {
$('.btn-form').remove()
svgGroup.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
// define the zoomListener which calls the zoom function on the "zoom" event constrained within the scaleExtents
var zoomListener = d3.behavior.zoom().scaleExtent([0.1, 1.5]).on("zoom", zoom);
// define the baseSvg, attaching a class for styling and the zoomListener
var baseSvg = d3.select("#d3view").append("svg")
.attr("width", viewerWidth)
.attr("height", viewerHeight)
.attr("class", "overlay")
.call(zoomListener);
// Function to center node when clicked/dropped so node doesn't get lost when collapsing/moving with large amount of children.
function centerNode(source) {
scale = 0.3;//zoomListener.scale();
x = -source.y0;
y = -source.x0;
x = x * scale + viewerWidth/20;
y = y * scale + viewerHeight/2.5;
d3.select('g').transition()
.duration(duration)
.attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")");
zoomListener.scale(scale);
zoomListener.translate([x, y]);
}
// Toggle children function
function toggleChildren(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else if (d._children) {
d.children = d._children;
d._children = null;
}
return d;
}
// Toggle children on click.
function click(d) {
if (d3.event.defaultPrevented) return; // click suppressed
d = toggleChildren(d);
update(d);
// centerNode(d);
}
function remove_link(d){
a = d3.event
a.preventDefault()
p = d.parent
parent_array = [p]
depth = d.depth - 1
for(var i=1; i<depth; i++){
parent_1 = p
parent_2 = parent_1.parent
parent_array.push(parent_2)
p = parent_2
}
parent_array[0].children.splice($.inArray(d, parent_array[0].children), 1)
update(d);
$('#editme').tree('loadData', [root]);
}
function make_editable(d)
{
// console.log("make_editable", arguments);
this
.on("mouseover", function() {
$('.btn-form').remove();
$('.input-form').remove();
d3.selectAll('text').style('fill', null)
d3.select(this).style("fill", "red");
var p = this.parentNode;
var xy = this.getBBox();
var p_xy = p.getBBox();
xy.x -= p_xy.x;
xy.y -= p_xy.y;
xy.x += 25;
xy.y -= 50;
var el = d3.select(this);
var p_el = d3.select(p);
var frm = p_el.append("foreignObject");
var inp = frm
.attr('class', 'btn-form')
.attr("x", xy.x)
.attr("y", xy.y)
.attr("width", 145)
.attr("height", 60)
.append("xhtml:form")
.append('a').text('Add').attr('class', 'btn btn-primary').attr('style', 'width:50px;height:30px;margin-right:2%;').on('click', add_link)
frm.select('form').append('a').text('Remove').attr('class', 'btn btn-danger remove-btn').attr('style', 'width:70px;height:30px;').on('click', remove_link)
frm.on('mouseover', function(){
$('.btn-form').closest('g').find('text').attr("fill", 'red');
})
.on('mouseout', function(){
$('.btn-form').closest('g').find('text').attr("fill", null);
})
})
.on("mouseout", function() {
d3.select(this).style("fill", null);
})
.on("click", function(d, event) {
e = d3.event;
e.preventDefault();
$('.btn-form').remove();
var p = this.parentNode;
// console.log(this, arguments);
// inject a HTML form to edit the content here...
// bug in the getBBox logic here, but don't know what I've done wrong here;
// anyhow, the coordinates are completely off & wrong. :-((
var xy = this.getBBox();
var p_xy = p.getBBox();
xy.x -= p_xy.x;
xy.y -= p_xy.y;
var el = d3.select(this);
var p_el = d3.select(p);
var frm = p_el.append("foreignObject");
var inp = frm
.attr('class', 'input-form')
.attr("x", xy.x)
.attr("y", xy.y)
.style("z-index", 99999)
.attr("width", 300)
.attr("height", 25)
.append("xhtml:form")
.append("input")
.attr("value", function() {
// nasty spot to place this call, but here we are sure that the <input> tag is available
// and is handily pointed at by 'this':
this.focus();
return el.text();
})
.attr("style", "width: 110px;")
// make the form go away when you jump out (form looses focus) or hit ENTER:
.on("blur", function() {
// console.log("blur", this, arguments);
var txt = inp.node().value;
el.text(function(d) { return txt; });
d.name = el.text();
// Note to self: frm.remove() will remove the entire <g> group! Remember the D3 selection logic!
p_el.select("foreignObject").remove();
})
.on("keypress", function() {
// console.log("keypress", this, arguments);
// IE fix
if (!d3.event)
d3.event = window.event;
var e = d3.event;
if (e.keyCode == 13)
{
if (typeof(e.cancelBubble) !== 'undefined') // IE
e.cancelBubble = true;
if (e.stopPropagation)
e.stopPropagation();
e.preventDefault();
var txt = inp.node().value;
el.text(function(d) { return txt; });
p_el.attr('data-name', txt);
d.name = el.text();
$('#editme').tree('loadData', [root]);
}
});
});
}
function add_link(d){
d3.event.preventDefault();
var p = $(this).closest('form')
var xy = $(this).closest('.btn-form')[0].getBBox();
if($(".add-link").length == 0){
var frm = p.append("<div><input type='text' class='add-link' style='width:130px;'></input></div>");
var inp = frm.on('keypress', function(event){
var e = event;
if (e.keyCode == 13)
{
e.preventDefault();
var txt = $('.add-link').val();
var event = document.createEvent('Event');
event.initEvent('click', true, true);
if(d.children){
d.children.push({name: txt})
}else if(d._children){
d._children.push({name: txt})
}else{
d.children = [{name: txt}]
}
$('text[fill="red"]').attr('fill', null);
update(root);
$(".btn-form").hide();
$('#editme').tree('loadData', [root]);
}
})
.on('click', function(event){ event.preventDefault();})
$('.add-link').focus()
}
}
function update(source) {
// Compute the new height, function counts total children of root node and sets tree height accordingly.
// This prevents the layout looking squashed when new nodes are made visible or looking sparse when nodes are removed
// This makes the layout more consistent.
var levelWidth = [1];
var childCount = function(level, n) {
if (n.children && n.children.length > 0) {
if (levelWidth.length <= level + 1) levelWidth.push(0);
levelWidth[level + 1] += n.children.length;
n.children.forEach(function(d) {
childCount(level + 1, d);
});
}
};
childCount(0, root);
var newHeight = d3.max(levelWidth) * 25; // 25 pixels per line
tree = tree.size([newHeight, viewerWidth]);
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Set widths between levels based on maxLabelLength.
nodes.forEach(function(d) {
d.y = (d.depth * (maxLabelLength * 10)); //maxLabelLength * 10px
// alternatively to keep a fixed scale one can set a fixed depth per level
// Normalize for fixed-depth by commenting out below line
// d.y = (d.depth * 500); //500px per level.
});
// Update the nodes…
node = svgGroup.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
// .call(dragListener)
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.on('click', click);
nodeEnter.append("circle")
.attr('class', 'nodeCircle')
.attr("r", 0)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -10 : 10;
})
.attr("dy", ".35em")
.attr('class', 'nodeText')
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.name;
})
.style("fill-opacity", 0)
.call(make_editable);
// phantom node to give us mouseover in a radius around it
// nodeEnter.append("circle")
// .attr('class', 'ghostCircle')
// .attr("r", 30)
// .attr("opacity", 0.2) // change this to zero to hide the target area
// .style("fill", "red")
// .attr('pointer-events', 'mouseover');
// .on("mouseover", function(node) {
// overCircle(node);
// })
// .on("mouseout", function(node) {
// outCircle(node);
// });
// Update the text to reflect whether node has children or not.
node.select('text')
.attr("x", function(d) {
return d.children || d._children ? -10 : 10;
})
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.name;
});
// Change the circle fill depending on whether it has children and is collapsed
node.select("circle.nodeCircle")
.attr("r", 4.5)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
// Fade the text in
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
nodeExit.select("circle")
.attr("r", 0);
nodeExit.select("text")
.style("fill-opacity", 0);
// Update the links…
var link = svgGroup.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
function expand(d) {
if (d._children) {
d.children = d._children;
d.children.forEach(expand);
d._children = null;
}else if(d.children) {
d.children.forEach(expand);
}
}
// Append a group which holds all nodes and which the zoom Listener can act upon.
var svgGroup = baseSvg.append("g");
// Define the root
root = treeData;
root.x0 = viewerHeight/2;
root.y0 = 0;
// Layout the tree initially and center on the root node.
update(root);
centerNode(root);
});
(flare.jsonはJSONファイルをretriveするアクションです)...私は人々が同じことをしたかったために何whith共有するあなたはより具体的な例を提供することはできますか?あなたが求めているのはあまりにも曖昧です。 –
私は何のために構成ファイルのための入れ子になったフォームを作成することですllokingです。 この設定ファイルはymlです。これは次のようになります。
bdd: server1: mysql: 'mariadb' vms: - name: 'test_1' files: 'example'
同じカスケード形式を改造しないでフォームを作成するにはどうすればよいですか。 –