Reputation: 2085
I am using D3 to create a visualization. The visualization fits well within the available canvas using viewBox
graphArea.attr("viewBox",bbox.x+" "+bbox.y+" "+bbox.width+" "+bbox.height);
This works great for smaller graphs. However, for some of the larger graphs, users need to zoom out the diagram for additional clarity.
When the user zooms out the diagram, parts of the diagram are hidden as they are outside the viewBox.
My requirement is that, when user zooms out the diagram, a scroll bar is automatically provided which would enable the user to see the hidden parts by scrolling horizontally or vertically.
I tried dynamically setting the div width and height (which encloses the svg) but that did not help
d3.select("#containerdiv")
.attr('width', bbox.width)
.attr('height', bbox.height);
});
Below is the complete code
var width = 960, height = 400;
d3.select("body").append("div")
.attr("id", "containerdiv");
// REMOVE OLD SVG
d3.select("#wkfsvg").remove();
var bbox = null;
// ADD NEW SVG
var graphArea = d3.select("#containerdiv").append("svg")
.attr({ width: width, height: height, "pointer-events": "all" })
.attr("id","wkfsvg");
var g = new dagreD3.graphlib.Graph().setGraph({});
graphArea.insert("g", "g");
var nodesJson = [
{
"nodes": "Initiate",
"status": "startend",
"creation_date": "",
"performer_name": "",
"execution_type": "Automatic"
},
{
"nodes": "Find the Next Approver",
"status": "completed",
"creation_date": "",
"performer_name": "",
"execution_type": "Automatic"
},
{
"nodes": "Check for Manager",
"status": "completed",
"creation_date": "",
"performer_name": "",
"execution_type": "Automatic"
},
{
"nodes": "Set Status & ACL for IT Project Manager",
"status": "completed",
"creation_date": "",
"performer_name": "",
"execution_type": "Automatic"
},
{
"nodes": "Set Status & ACL for IT Sign Off Approvers",
"status": "completed",
"creation_date": "",
"performer_name": "",
"execution_type": "Manual"
},
{
"nodes": "IT Project Manager Approves",
"status": "completed",
"creation_date": "",
"performer_name": "",
"execution_type": "Manual"
},
{
"id": "Finance Approver",
"nodes": "Finance Approver",
"status": "dormant",
"creation_date": "",
"performer_name": "",
"execution_type": "Manual"
},
{
"nodes": "Send Email for Completion",
"status": "future",
"creation_date": "",
"performer_name": "",
"execution_type": "Manual"
},
{
"nodes": "Send to Requestor",
"status": "future",
"creation_date": "",
"performer_name": "",
"execution_type": "Manual"
},
{
"nodes": "Send Email to Requestor",
"status": "completed",
"creation_date": "",
"performer_name": "",
"execution_type": "Manual"
},
{
"id": "Set ACL on Form for Requestor",
"nodes": "Set ACL on Form for Requestor",
"status": "future",
"creation_date": "",
"performer_name": "",
"execution_type": "Manual"
},
{
"nodes": "Set Completion ACL on Form",
"status": "future",
"creation_date": "",
"performer_name": "",
"execution_type": "Manual"
},
{
"id": "Set Completion ACL on PO",
"nodes": "Set Completion ACL on PO",
"status": "future",
"creation_date": "",
"performer_name": "",
"execution_type": "Manual"
},
{
"nodes": "Set Completion ACL on Attachments",
"status": "future",
"creation_date": "",
"performer_name": "",
"execution_type": "Manual"
},
{
"nodes": "Set ACL on Attachment",
"status": "completed",
"creation_date": "",
"performer_name": "",
"execution_type": "Manual"
},
{
"nodes": "Update Comments of PM Rejection",
"status": "future",
"creation_date": "",
"performer_name": "",
"execution_type": "Manual"
},
{
"nodes": "Update Comments of FA Rejection",
"status": "future",
"creation_date": "",
"performer_name": "",
"execution_type": "Manual"
},
{
"nodes": "Update Comments of PM",
"status": "completed",
"creation_date": "",
"performer_name": "",
"execution_type": "Manual"
},
{
"nodes": "Update Comments of FA",
"status": "future",
"creation_date": "",
"performer_name": "",
"execution_type": "Manual"
},
{
"nodes": "Update Comments of Requestor",
"status": "future",
"creation_date": "",
"performer_name": "",
"execution_type": "Manual"
},
{
"nodes": "End",
"status": "startend",
"creation_date": "",
"performer_name": "",
"execution_type": "Manual"
}
];
// Automatically label each of the nodes
nodesJson.forEach(function(node) {
if(node.status == "future") {
if(node.execution_type == "Manual") {
g.setNode(node.nodes, { labelType: "html", label: node.nodes+' <tspan dx="0" dy="-10">\uf007</tspan>', class: "future" });
} else {
g.setNode(node.nodes, { label: node.nodes, class: "future" });
}
} else if(node.status == "completed") {
if(node.execution_type == "Manual") {
g.setNode(node.nodes, { labelType: "html", label: node.nodes+' <tspan dx="0" dy="-10">\uf007</tspan>', class: "completed" });
} else {
g.setNode(node.nodes, { label: node.nodes, class: "completed" });
}
} else if(node.status == "dormant") {
if(node.execution_type == "Manual") {
g.setNode(node.nodes, { labelType: "html", label: node.nodes, class: "dormant" });
} else {
g.setNode(node.nodes, { label: node.nodes, class: "dormant" });
}
} else if(node.status == "startend") {
g.setNode(node.nodes, { label: node.nodes, class: "startend" });
} else {
g.setNode(node.nodes, { label: node.nodes });
}
});
var edgesJson = [
{
"type": "approve",
"source": "Find the Next Approver",
"target": "Check for Manager"
},
{
"type": "approve",
"source": "Check for Manager",
"target": "Set Status & ACL for IT Sign Off Approvers"
},
{
"type": "approve",
"source": "Check for Manager",
"target": "Set Status & ACL for IT Project Manager"
},
{
"type": "approve",
"source": "Set Status & ACL for IT Project Manager",
"target": "IT Project Manager Approves"
},
{
"type": "approve",
"source": "Set Status & ACL for IT Sign Off Approvers",
"target": "Finance Approver"
},
{
"type": "approve",
"source": "Set ACL on Form for Requestor",
"target": "Send to Requestor"
},
{
"type": "approve",
"source": "Set Completion ACL on Form",
"target": "Set Completion ACL on PO"
},
{
"type": "approve",
"source": "IT Project Manager Approves",
"target": "Send Email to Requestor"
},
{
"type": "approve",
"source": "Set Completion ACL on PO",
"target": "Set Completion ACL on Attachments"
},
{
"type": "approve",
"source": "Set Completion ACL on Attachments",
"target": "Send Email for Completion"
},
{
"type": "approve",
"source": "Initiate",
"target": "Set ACL on Attachment"
},
{
"type": "approve",
"source": "Set ACL on Attachment",
"target": "Find the Next Approver"
},
{
"type": "approve",
"source": "Update Comments of PM Rejection",
"target": "Set ACL on Form for Requestor"
},
{
"type": "reject",
"source": "IT Project Manager Approves",
"target": "Update Comments of PM Rejection"
},
{
"type": "approve",
"source": "Update Comments of FA Rejection",
"target": "Set ACL on Form for Requestor"
},
{
"type": "approve",
"source": "Send Email to Requestor",
"target": "Update Comments of PM"
},
{
"type": "approve",
"source": "Update Comments of PM",
"target": "Set Status & ACL for IT Sign Off Approvers"
},
{
"type": "approve",
"source": "Finance Approver",
"target": "Update Comments of FA"
},
{
"type": "approve",
"source": "Update Comments of FA",
"target": "Set Completion ACL on Form"
},
{
"type": "reject",
"source": "Finance Approver",
"target": "Update Comments of FA Rejection"
},
{
"type": "approve",
"source": "Send to Requestor",
"target": "Update Comments of Requestor"
},
{
"type": "approve",
"source": "Update Comments of Requestor",
"target": "Check for Manager"
},
{
"type": "approve",
"source": "Send Email for Completion",
"target": "End"
}
];
edgesJson.forEach(function(edge) {
if(edge.type == "approve") {
g.setEdge(edge.source, edge.target, { label: "" });
}
// Make the edge of rejected paths red and dashed
if(edge.type == "reject") {
g.setEdge(edge.source, edge.target, {
label: "", class: "rejectEdgePath"
});
}
});
var inner = graphArea.select("g");
// Set the rankdir
//g.graph().rankdir = "LR";
g.graph().nodesep = 10;
// Create the renderer
var render = new dagreD3.render();
// Run the renderer. This is what draws the final graph.
render(inner, g);
// Center the graph
/*var initialScale = 0.75;
zoom
.translate([(graphArea.attr("width") - g.graph().width * initialScale) / 2, 20])
.scale(initialScale)
.event(graphArea); */
//graphArea.attr('height', g.graph().height * initialScale + 40);
bbox = graphArea.node().getBBox();
var selectedNode = inner.selectAll("g.node");
selectedNode.on('click', function (d) {
var nodeInfo = getNodeInfo(d);
console.log('clicked '+nodeInfo.nodes+' (status: '+nodeInfo.status+')');
});
function getNodeInfo(name) {
for (var i=0;i<nodesJson.length;i++)
if (nodesJson[i].nodes==name) return nodesJson[i];
}
console.log('bbox.x '+bbox.x);
console.log('bbox.y '+bbox.y);
console.log('bbox.width '+bbox.width);
console.log('bbox.height '+bbox.height);
graphArea.attr("viewBox",bbox.x+" "+bbox.y+" "+bbox.width+" "+bbox.height);
/*
d3.select("#containerdiv")
.attr('width', 200)
.attr('height', 200);
*/
// Set up zoom support
var zoom = d3.behavior.zoom().on("zoom", function() {
inner.attr("transform", "translate(" + d3.event.translate + ")" +
"scale(" + d3.event.scale + ")");
d3.select("#containerdiv")
.attr('width', bbox.width)
.attr('height', bbox.height);
});
graphArea.call(zoom);
.node rect {
stroke: #333;
fill: #fff;
}
.edgePath path {
stroke: #333;
fill: #333;
stroke-width: 1.5px;
}
.rejectEdgePath path {
stroke: red;
fill: red;
stroke-width: 1.5px;
stroke-dasharray: 5, 5;
}
g.dormant > rect {
fill: #CC66FF;
}
g.completed > rect {
fill: #66FF99;
}
g.future > rect {
fill: #99CCFF;
}
g.acquired > rect {
fill: #EBBFFF;
}
g.paused > rect {
fill: #FF0000;
}
g.startend > rect {
fill: #CC6666;
}
foreignobject {
fill: black;
font-family: FontAwesome;
font-size: 15px;
text-anchor: middle;
// cursor: move;
}
#wkfsvg {
border: 1px solid BLUE;
overflow: scroll;
}
#containerdiv {
border: 1px solid RED;
overflow-y:scroll;
overflow-x:scroll;
}
<script src="http://cpettitt.github.io/project/dagre-d3/latest/dagre-d3.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Any suggestions will be highly appreciated as I have hit the wall and totally clueless on how to proceed.
Thank you
Upvotes: 3
Views: 5211
Reputation: 101800
You are zooming by changing the viewBox
.
If you want scrollbars, then you should leave the viewBox alone and change your svg width
and height
attributes instead. That way you zoom by making your SVG render larger. Then to get your scrollbars, set your container <div>
to a fixed size and give it overflow: auto
.
var zoomBtn = document.getElementById("zoom");
var graphSVG = document.getElementById("graph");
var zoomed = false;
zoomBtn.addEventListener("click", function(e) {
if (zoomed)
{
graphSVG.style.width = "100%";
zoomBtn.textContent = "Zoom in";
zoomed = false;
}
else
{
graphSVG.style.width = "200%";
zoomBtn.textContent = "Zoom out";
zoomed = true;
}
});
#container
{
width: 300px;
height: 300px;
overflow: auto;
}
#graph
{
width: 100%;
}
<div id="container">
<svg viewBox="0 0 100 100" id="graph">
<circle cx="50" cy="50" r="40" fill="red"/>
<circle cx="50" cy="50" r="20" fill="green"/>
</svg>
</div>
<button id="zoom">Zoom in</button>
Upvotes: 4