My problem is best explained by the images included further down in the question. Hoping one of you D3 experts (such as Lars) discover this problem and help me out. Thanks in advance, any help is much appreciated!
I am building on http://bl.ocks.org/d3noob/8375092, having expanded the example data set with both depth and metadata, and using that metadata to determine the stroke-width of the node-links ("amount" attribute) as well as the radius of the circles ("avg_dwell" attribute).
So far so good, D3 is awesome and the layout.tree component works wonders. However, I've hit a dead end. I need to achieve what is shown in these two images: http://imgur.com/a/0aHWq#37MvNa7
So, stacking those paths on top of each other means using the nodes' "amount" attribute for a height, looping through them and determining their y-coordinates one after another.
My problem is really that I do not know where to do this in the code. My code below:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Collapsible Tree Example</title>
<style>
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.node text { font: 12px sans-serif; }
.link {
fill: none;
/*stroke: #ccd;
stroke-width: 5px;*/
}
</style>
</head>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var treeData = [
{
"name": "Root",
"parent": null,
"amount": 10000,
"maxAmount": 3500,
"maxDwell": 19,
"avg_dwell": 2,
"children": [
{
"name": "Zone 1",
"amount": 500,
"avg_dwell": 2,
"children": [
{
"name": "Zone 3",
"amount": 400,
"avg_dwell": 8,
"children": [
{
"name": "Zone 4",
"amount": 250,
"avg_dwell": 19
},
{
"name": "Zone 7",
"amount": 130,
"avg_dwell": 5
}
]
},
{
"name": "Zone 5",
"amount": 45,
"avg_dwell": 4
}
]
},
{
"name": "Zone 4",
"amount": 1200,
"avg_dwell": 4,
"children": [
{
"name": "Zone 7",
"amount": 1000,
"avg_dwell": 4,
"children": [
{
"name": "Zone 5",
"amount": 800,
"avg_dwell": 4
},
{
"name": "Zone 6",
"amount": 200,
"avg_dwell": 10
}
]
},
{
"name": "Zone 2",
"amount": 200,
"avg_dwell": 4
}
]
},
{
"name": "Zone 8",
"amount": 2500,
"avg_dwell": 4,
"children": [
{
"name": "Zone 6",
"amount": 1000,
"avg_dwell": 15,
"children": [
{
"name": "Zone 1",
"amount": 350,
"avg_dwell": 18
},
{
"name": "Zone 2",
"amount": 650,
"avg_dwell": 15
}
]
},
{
"name": "Zone 3",
"amount": 1500,
"avg_dwell": 4
}
]
},
{
"name": "Zone 6",
"amount": 800,
"avg_dwell": 15
}
,
{
"name": "Zone 7",
"amount": 1000,
"avg_dwell": 8
}
,
{
"name": "Zone 3",
"amount": 3500,
"avg_dwell": 9,
"children": [
{
"name": "Zone 4",
"amount": 1000,
"avg_dwell": 15,
"children": [
{
"name": "Zone 1",
"amount": 350,
"avg_dwell": 18
},
{
"name": "Zone 2",
"amount": 650,
"avg_dwell": 15
}
]
},
{
"name": "Zone 9",
"amount": 1500,
"avg_dwell": 4,
"children": [
{
"name": "Zone 8",
"amount": 1000,
"avg_dwell": 18
},
{
"name": "Zone 5",
"amount": 500,
"avg_dwell": 15
}
]
}
]
}
]
}
];
// tooltip div
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// ************** Generate the tree diagram *****************
var margin = {top: 20, right: 120, bottom: 20, left: 120},
width = 960 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var radiusDivider = treeData[0].maxDwell/20; // for circle size
var pathThicknessDivider = treeData[0].maxAmount/30; // relative thickness of path connectors
var textDistanceQuotient = 1.0 // how far is the text from the nodes?
var allValues = d3.values(treeData);
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
root = treeData[0];
root.x0 = height / 2;
root.y0 = 0;
update(root);
d3.select(self.frameElement).style("height", "500px");
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) { d.y = d.depth * 180; });
// Update the nodes…
var node = svg.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")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; })
.on("click", click);
nodeEnter.append("circle")
.attr("r", function(d){
return 1e-6;
//return d.amount/radiusDivider;
})
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? (d.avg_dwell * textDistanceQuotient) * -1 : d.avg_dwell * textDistanceQuotient;
})
.attr("dy", ".35em")
.attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
.text(function(d) { return d.name; })
.style("fill-opacity", 1e-6);
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
nodeUpdate.select("circle")
.attr("r", function(d){
return d.avg_dwell/radiusDivider; // circle size depends on avg_dwell
//return 1e-6;
})
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
})
.style("fill-opacity", 1);
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", function(d){
//return d.amount/radiusDivider; // circle size depends on avg_dwell
return 1e-6;
});
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
var link = svg.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};
console.log(o);
return diagonal({source: o, target: o});
})
.attr("id", function(d, i){
return i;
})
.style("stroke", "blue")
.style("stroke-opacity", 0.5)
.style("stroke-width", function(d){
return d.target.amount / pathThicknessDivider;
//return Math.ceil(d.amount/80);
})
.on("click", click);
// 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};
//console.log(o);
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;
});
}
// Toggle children and do other stuff on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
</script>
</body>
</html>
I know the original coordinates for each path are generated in this part:
// 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};
console.log(o);
return diagonal({source: o, target: o});
})
.attr("id", function(d, i){
return i;
})
.style("stroke", "blue")
.style("stroke-opacity", 0.5)
.style("stroke-width", function(d){
return d.target.amount / pathThicknessDivider;
//return Math.ceil(d.amount/80);
})
.on("click", click);
Related
I am trying to convert a D3 force-directed graph codebase.
Source code:
https://codesandbox.io/s/d3js-draggable-force-directed-graph-py3rf
var nodes = [
{ color: "red", size: 5 },
{ color: "orange", size: 10 },
{ color: "yellow", size: 15 },
{ color: "green", size: 20 },
{ color: "blue", size: 25 },
{ color: "purple", size: 30 }
];
var links = [
{ source: "red", target: "orange" },
{ source: "orange", target: "yellow" },
{ source: "yellow", target: "green" },
{ source: "green", target: "blue" },
{ source: "blue", target: "purple" },
{ source: "purple", target: "red" },
{ source: "green", target: "red" }
];
var svg = d3
.select("svg")
.attr("width", width)
.attr("height", height);
var linkSelection = svg
.selectAll("line")
.data(links)
.enter()
.append("line")
.attr("stroke", "black")
.attr("stroke-width", 1);
var nodeSelection = svg
.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("r", d => d.size)
.attr("fill", d => d.color)
.call(
d3
.drag()
.on("start", dragStart)
.on("drag", drag)
.on("end", dragEnd)
);
the current product looks like this:
And the snippet is here:
/* eslint-disable no-undef */
var width = 600;
var height = 600;
var nodes = [
{ color: "red", size: 5 },
{ color: "orange", size: 10 },
{ color: "yellow", size: 15 },
{ color: "green", size: 20 },
{ color: "blue", size: 25 },
{ color: "purple", size: 30 }
];
var links = [
{ source: "red", target: "orange" },
{ source: "orange", target: "yellow" },
{ source: "yellow", target: "green" },
{ source: "green", target: "blue" },
{ source: "blue", target: "purple" },
{ source: "purple", target: "red" },
{ source: "green", target: "red" }
];
var svg = d3
.select("svg")
.attr("width", width)
.attr("height", height);
var linkSelection = svg
.selectAll("line")
.data(links)
.enter()
.append("line")
.attr("stroke", "black")
.attr("stroke-width", 1);
var nodeSelection = svg
.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("r", d => d.size)
.attr("fill", d => d.color)
.call(
d3
.drag()
.on("start", dragStart)
.on("drag", drag)
.on("end", dragEnd)
);
var simulation = d3.forceSimulation(nodes);
simulation
.force("center", d3.forceCenter(width / 2, height / 2))
.force("nodes", d3.forceManyBody())
.force(
"links",
d3
.forceLink(links)
.id(d => d.color)
.distance(d => 5 * (d.source.size + d.target.size))
)
.on("tick", ticked);
function ticked() {
// console.log(simulation.alpha());
nodeSelection.attr("cx", d => d.x).attr("cy", d => d.y);
linkSelection
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
}
function dragStart(d) {
// console.log('drag start');
simulation.alphaTarget(0.5).restart();
d.fx = d.x;
d.fy = d.y;
}
function drag(d) {
// console.log('dragging');
// simulation.alpha(0.5).restart()
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragEnd(d) {
// console.log('drag end');
simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<svg
version="1.1"
baseProfile="full"
xmlns="http://www.w3.org/2000/svg"
></svg>
But my goal is draw lines with arrows on the end, where the arrows are pointing to a target from a source. Does anyone know how to properly change the line type, and have it point the right way?
You can create a marker representing the arrow tip in your svg, then use it in a marker-end attribute on your lines.
Here is an example: http://thenewcode.com/1068/Making-Arrows-in-SVG
There is a reasonable solution here but it is D3 v3 and uses a path not line.
Basically, using a marker you calculate the amount to shorten your line so that it starts at the boundary of the source node (i.e. shorten by the source radius) and ends at the boundary of the target node (i.e. shorten at the end by the radius of the target node plus the dimension of the marker).
The hard-coded 12 in the snippet is the dimension of the marker - if you change e.g. markerWidth then you will probably need to play with this constant in the tick function.
So, there are 3 edits to your original code: add the marker, append the marker to the line and in tick, do the math to shorten each end and replot the line. See below:
var width = 600;
var height = 200;
var nodes = [
{ color: "red", size: 5 },
{ color: "orange", size: 10 },
{ color: "yellow", size: 15 },
{ color: "green", size: 20 },
{ color: "blue", size: 25 },
{ color: "purple", size: 30 }
];
var links = [
{ source: "red", target: "orange" },
{ source: "orange", target: "yellow" },
{ source: "yellow", target: "green" },
{ source: "green", target: "blue" },
{ source: "blue", target: "purple" },
{ source: "purple", target: "red" },
{ source: "green", target: "red" }
];
var svg = d3
.select("svg")
.attr("width", width)
.attr("height", height);
// append a path marker to svg defs
svg.append("defs").selectAll("marker")
.data(["dominating"])
.enter().append("marker")
.attr("id", function(d) { return d; })
.attr("viewBox", "0 -5 10 10")
.attr("refX", 0)
.attr("refY", 0)
.attr("markerWidth", 12)
.attr("markerHeight", 12)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5");
//
var linkSelection = svg
.selectAll("line")
.data(links)
.enter()
.append("line")
.attr("stroke", "black")
.attr("stroke-width", 1)
// add marker to line
.attr("marker-end", d => "url(#dominating)");
//
var nodeSelection = svg
.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("r", d => d.size)
.attr("fill", d => d.color)
.call(
d3
.drag()
.on("start", dragStart)
.on("drag", drag)
.on("end", dragEnd)
);
var simulation = d3.forceSimulation(nodes);
simulation
.force("center", d3.forceCenter(width / 2, height / 2))
.force("nodes", d3.forceManyBody())
.force(
"links",
d3
.forceLink(links)
.id(d => d.color)
.distance(d => 5 * (d.source.size + d.target.size))
)
.on("tick", ticked);
function ticked() {
// console.log(simulation.alpha());
nodeSelection.attr("cx", d => d.x).attr("cy", d => d.y);
linkSelection
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
// recalculate and back off the distance
linkSelection.each(function(d, i, n) {
// current path length
const pl = this.getTotalLength();
// radius of marker head plus def constant
const mrs = (d.source.size);
const mrt = (d.target.size) + 12;
// get new start and end points
const m1 = this.getPointAtLength(mrs);
const m2 = this.getPointAtLength(pl - mrt);
// new line start and end
d3.select(n[i])
.attr("x1", m1.x)
.attr("y1", m1.y)
.attr("x2", m2.x)
.attr("y2", m2.y);
});
}
function dragStart(d) {
// console.log('drag start');
simulation.alphaTarget(0.5).restart();
d.fx = d.x;
d.fy = d.y;
}
function drag(d) {
// console.log('dragging');
// simulation.alpha(0.5).restart()
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragEnd(d) {
// console.log('drag end');
simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<svg
version="1.1"
baseProfile="full"
xmlns="http://www.w3.org/2000/svg"
></svg>
This works on v4.13 and v5.7.
const { createInvoice } = require("./createInvoice.js");
const invoice = {
shipping: {
name: "John Doe",
address: "1234 Main Street",
city: "San Francisco",
state: "CA",
country: "US",
postal_code: 94111
},
items: [
{
item: "TC 100",
description: "The quick brown fox jumps right over the lazy little dog. The quick brown fox jumps right over the lazy little dog. The quick brown fox jumps right over the lazy little dog.",
quantity: 2,
amount: 6000
},
{
item: "USB_EXT",
description: "USB Cable Extender",
quantity: 1,
amount: 2000
}
],
subtotal: 8000,
paid: 0,
invoice_nr: 1234
};
createInvoice(invoice, "invoice.pdf");
Invoice.js
const fs = require("fs");
const PDFDocument = require("pdfkit");
function createInvoice(invoice, path) {
console.log(incoice)
let doc = new PDFDocument({ size: "A4", margin: 50 });
generateHeader(doc);
generateCustomerInformation(doc, invoice);
generateInvoiceTable(doc, invoice);
generateFooter(doc);
doc.end();
doc.pipe(fs.createWriteStream(path));
}
function generateHeader(doc) {
doc
.image("logo.png", 50, 45, { width: 50 })
.fillColor("#444444")
.fontSize(20)
.text("ACME Inc.", 110, 57)
.fontSize(10)
.text("ACME Inc.", 200, 50, { align: "right" })
.text("123 Main Street", 200, 65, { align: "right" })
.text("New York, NY, 10025", 200, 80, { align: "right" })
.moveDown();
}
function generateCustomerInformation(doc, invoice) {
doc
.fillColor("#444444")
.fontSize(20)
.text("Invoice", 50, 160);
generateHr(doc, 185);
const customerInformationTop = 200;
doc
.fontSize(10)
.text("Invoice Number:", 50, customerInformationTop)
.font("Helvetica-Bold")
.text(invoice.invoice_nr, 150, customerInformationTop)
.font("Helvetica")
.text("Invoice Date:", 50, customerInformationTop + 15)
.text(formatDate(new Date()), 150, customerInformationTop + 15)
.text("Balance Due:", 50, customerInformationTop + 30)
.text(
formatCurrency(invoice.subtotal - invoice.paid),
150,
customerInformationTop + 30
)
.font("Helvetica-Bold")
.text(invoice.shipping.name, 300, customerInformationTop)
.font("Helvetica")
.text(invoice.shipping.address, 300, customerInformationTop + 15)
.text(
invoice.shipping.city +
", " +
invoice.shipping.state +
", " +
invoice.shipping.country,
300,
customerInformationTop + 30
)
.moveDown();
generateHr(doc, 252);
}
function generateInvoiceTable(doc, invoice) {
let i;
const invoiceTableTop = 330;
doc.font("Helvetica-Bold");
generateTableRow(
doc,
invoiceTableTop,
"Item",
"Description",
"Unit Cost",
"Quantity",
"Line Total"
);
generateHr(doc, invoiceTableTop + 20);
doc.font("Helvetica");
for (i = 0; i < invoice.items.length; i++) {
const item = invoice.items[i];
const position = invoiceTableTop + (i + 1) * 30;
generateTableRow(
doc,
position,
item.item,
item.description,
formatCurrency(item.amount / item.quantity),
item.quantity,
formatCurrency(item.amount)
);
generateHr(doc, position + 20);
}
const subtotalPosition = invoiceTableTop + (i + 1) * 30;
generateTableRow(
doc,
subtotalPosition,
"",
"",
"Subtotal",
"",
formatCurrency(invoice.subtotal)
);
const paidToDatePosition = subtotalPosition + 20;
generateTableRow(
doc,
paidToDatePosition,
"",
"",
"Paid To Date",
"",
formatCurrency(invoice.paid)
);
const duePosition = paidToDatePosition + 25;
doc.font("Helvetica-Bold");
generateTableRow(
doc,
duePosition,
"",
"",
"Balance Due",
"",
formatCurrency(invoice.subtotal - invoice.paid)
);
doc.font("Helvetica");
}
function generateFooter(doc) {
doc
.fontSize(10)
.text(
"Payment is due within 15 days. Thank you for your business.",
50,
780,
{ align: "center", width: 500 }
);
}
function generateTableRow(
doc,
y,
item,
description,
unitCost,
quantity,
lineTotal
) {
doc
.fontSize(10)
.text(item, 50, y)
.text(description, 150, y)
.text(unitCost, 280, y, { width: 90, align: "right" })
.text(quantity, 370, y, { width: 90, align: "right" })
.text(lineTotal, 0, y, { align: "right" });
}
function generateHr(doc, y) {
doc
.strokeColor("#aaaaaa")
.lineWidth(1)
.moveTo(50, y)
.lineTo(550, y)
.stroke();
}
function formatCurrency(cents) {
return "$" + (cents / 100).toFixed(2);
}
function formatDate(date) {
const day = date.getDate();
const month = date.getMonth() + 1;
const year = date.getFullYear();
return year + "/" + month + "/" + day;
}
module.exports = {
createInvoice
};
The height and width is not adjustable in pdfkit. If I added long text into description under items its messed up the content into lines.
What I expect it should be dynamically generatehr based on content rather than fixed. how this is possible please guide on it.
Also it should work on address if address text become larger it should be adjustable.
Any help ?
Thanks
I'm using d3.js but I'm having a problem. When I zoom in SVG, is not commensurate. Also, after dragging, the handling of SVG I made; a further drifting. I'm giving the necessary code. What can I do to fix this code?
Code:
/* Tünelin bütün elemanları için bir dizi oluşturuldu
Daha sonra her ayrı eleman için dizi oluşturulup,
datalar bu doğrultuda çekilecektir.
*/
var tunnelElements = new Array();
tunnelElements = [{ "x": 0, "y": 0, "radius": 10, "color" : "green" },
{ "x": 25, "y": 25, "radius": 10, "color" : "green"},
{ "x": 50, "y": 50, "radius": 10, "color" : "green" },
{ "x": 75, "y": 75, "radius": 10, "color" : "green"},
{ "x": 100, "y": 100, "radius": 10, "color" : "green" },
{ "x": 125, "y": 125, "radius": 10, "color" : "green" },
{ "x": 62.5, "y": 62.5, "radius": 10, "color" : "red" },
{ "x": 0, "y": 125, "radius": 10, "color" : "purple" },
{ "x": 25, "y": 100, "radius": 10, "color" : "purple" },
{ "x": 50, "y": 75, "radius": 10, "color" : "purple" },
{ "x": 75, "y": 50, "radius": 10, "color" : "purple" },
{ "x": 100, "y": 25, "radius": 10, "color" : "purple" },
{ "x": 125, "y": 0, "radius": 10, "color" : "purple" }];
/* Tünel elemanları datadan çekilip circles dizisine
kopyalanmaktadır.
*/
var circles = []
for (var i = 0; i < tunnelElements.length; i++) {
circles[i] = tunnelElements[i];
};
console.log(circles);
var width = 719, height = 262;
var X = d3.scale.linear()
var Y = d3.scale.linear()
/* Semantic zoom için zoom değişkeni oluşturuldu */
var zoom = d3.behavior.zoom()
.x(X).y(Y)
.scaleExtent([1, 10])
.on("zoom", zoomed);
/* Alternatif zoom
.on("zoom", function () {
circle.attr("transform", transform)
});
*/
/* Elementler svg olarak oluşturuldu */
var svg = d3.select("#d3")
.append("svg")
.attr("width", width)
.attr("height", height)
.call(zoom);
/* Road background çağırdık */
var road = svg.selectAll("image").data([0]);
road.enter()
.append("svg:image")
.attr("xlink:href", "http://ahmetates.com.tr/road.svg")
.attr("width", width)
.attr("height", height);
var circle;
/* Bütün elemenlar seçilip transform fonksiyonu
sayesinde drag and drop özelliği kazandı */
circle = svg.selectAll("circle")
.data(circles)
.enter().append("circle")
.attr("transform", transform);
var drag = d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended);
/* Fonksiyonlar */
function zoomed() {
road.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
circle.attr("transform", transform)
}
function transform(d) {
return "translate("+X(d.x)+", "+Y(d.y)+")";
}
function generateit(){
var circles = svg.selectAll("circle");
var json_circles = JSON.stringify(circles.data());
d3.select("#console").html('tunnelElements = '+json_circles+';');
}
d3.select("#exportit").on("click", generateit);
var circleAttributes = circle
.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; })
.attr("r", function (d) { return d.radius; })
.style("fill", function(d) { return d.color; })
.call(drag);
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
}
function dragged(d) {
d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
}
function dragended(d) {
d3.select(this).classed("dragging", false);
}
#d3 svg{
margin:30px;
padding:10px;
border:1px solid #333;
width: 100%;
height: auto;
}
.line{
stroke-width: 3px;
}
#exportit {
background:#000;
cursor:pointer;
color:#fff;
width:45px;
padding:2px 4px;
}
<script src="http://d3js.org/d3.v3.min.js"></script>
<div id="exportit">Export</div>
<div id="d3"></div>
<div id="console"></div>
EDIT:
It seems that problem is not explained very well. The first problem can be traced like this: drag any circle, then drag background and you will see that dragged circle moved to random position. The second problem is scaling and you can trace that zooming in and out, then you can see that positions of circles are changed according to background.
You should make a main group and in that add all the components to which you want to give zoom and drag.
Something like this:
var maingroup = svg.append("g").call(zoom);
//add road and circles in this maingroup
//drag events on circle as you had done no change in that.
Here is a working fiddle this should give more clarity:
http://jsfiddle.net/cyril123/ow8r5n6f/3/
I'm trying to use d3js to lay out a diagram of servers at locations. Here's a sample extract of the input data.
{"type": "customer", "name": "Acme", "children": [
{
"type": "site",
"name": "Wichita",
"children": [
{
"type": "server",
"name": "server1"
},
{
"type": "server",
"name": "server2"
}
]
},
{
"type": "site",
"name": "Springfield",
"children": [
{
"type": "server",
"name": "server1"
},
{
"type": "server",
"name": "server2"
},
{
"type": "server",
"name": "server3"
}
]
}
]}
The sites will have varying numbers of servers. Each server should be a rectangle within a rectangle representing a site.
I can create the site and server rectangles individually, but I think I want the server nodes bound under their parent site's
node for proper grouping, particulary if we later import the svg to Visio. The input json is in the var treeData. Also, I'm
having trouble getting the positioning correct, which it seems might be easier with the proper grouping.
var viz = d3.select("#viz").append("svg:svg")
.attr("width", 1200)
.attr("height", layoutHeight)
.append("svg:g");
var tree = d3.layout.tree().size([layoutHeight, 300]);
var nodes = tree.nodes(treeData);
viz.append("svg:text")
.attr("dx", "0")
.attr("dy", 20)
.attr("font-size", 24)
.attr("text-anchor", "start")
.text(json.customerName);
var siteNodes = viz.selectAll("g.node")
.data(nodes)
.enter().append("svg:g")
.filter(function(d) { return d.type === "site" })
.attr("transform", function(d) { return "translate(" + d.y + "," + (d.x * 1.5 - (d.children.length * serverHeight * 1.1 / 2)) + ")"; });
siteNodes.append("svg:rect")
.attr("width", 300)
.attr("height", function(d) { return serverHeight * (d.children.length + 1); })
.attr("stroke", "black")
.attr("stroke-width", "1")
.attr("fill", "white");
var serverNodes = viz.selectAll("g.node")
.data(nodes)
.enter().append("svg:g")
.filter(function(d) { return d.type === "server" })
.attr("transform", function(d) { return "translate(" + (d.y - 125) + "," + d.x + ")"; });
serverNodes.append("svg:rect")
.attr("width", 250)
.attr("height", serverHeight)
.attr("font-size", 10)
.attr("stroke", "black")
.attr("stroke-width", "1")
.attr("fill-opacity", "0.1")
.attr("fill", "blue");
I'm new to d3js, and I suspect a major factor is that I'm not quite yet thinking about d3js in the proper way.
I had to make a few assumptions about values since you did not provide the complete code. The changes I made should be easy to follow. Here is the fiddle to help you out. The transformation you are using for the sites is not clear to me and I put an approximation to get you going. You will definitely need to tweak it. Hope this helps.
.attr("transform", function(d) { return "translate(" + (d.y) + "," + (d.x * 1.25 - (d.children.length * serverHeight * 0.9)) + ")"; });
Here's the solution I worked out with the help of knowledge stockpile and this discussion on Google:
I changed some field names in the data, so that it now looks more like:
var treeData = {type: "customer", name: "Acme", children: [
{type: "site", name: "Wichita", servers: [
{type: "server", name: "server1", status: "Powered On"},
{type: "server", name: "server2", status: "Powered On"}]},
{type: "site", name: "Springfield", servers: [
{type: "server", name: "server1", status: "Powered On"},
{type: "server", name: "server2", status: "Powered On"},
{type: "server", name: "server3", status: "Powered On"}]
}
]};
Calculate the space needed for the layout:
var serverHeight = 50;
var serverWidth = 250;
var siteWidth = serverWidth + 50;
var layoutVerticalOffset = 20;
var layoutHeight = 150 + (maxServerCount + 2) * serverHeight * 1.5;
var layoutWidth = treeData.children.length * siteWidth * 1.5;
var viz = d3.select("#viz").append("svg:svg")
.attr("height", layoutHeight)
.attr("width", layoutWidth + siteWidth)
.append("svg:g");
var tree = d3.layout.tree().size([layoutWidth, 0]);
I added a class to the site nodes:
var siteNodes = viz.selectAll("g.node")
.data(nodes)
.enter().append("svg:g")
.filter(function(d) { return d.type === "site" })
.attr("class", "site")
.attr("transform", function(d) {
return "translate(" + d.x + "," + 100 + ")";
});
and use that class for selection when building the server nodes:
var serverNodes = siteNodes.selectAll(".site")
.data(function(d) { return d.servers; })
.enter().append("svg:g")
.attr("class", "server")
.attr("transform", function(d, i) {
return "translate(" + 25 + "," + (serverHeight / 2 + i * serverHeight * 1.5) + ")";
});
The result is that the servers are represented by g-nodes properly nested within the site nodes:
<g class="site" transform="translate(300,35)">
<rect width="300" height="165" stroke="black" stroke-width="1" fill="white"/>
<text dx="0" dy="12" font-size="18" text-anchor="end">Site</text>
<text dx="0" dy="34" font-size="18" text-anchor="end">Wichita</text>
<g class="server" transform="translate(25,25)">
<rect width="250" height="50" font-size="10" stroke="black" stroke-width="1" fill-opacity="0.1" fill="blue"/>
</g>
<g class="server" transform="translate(25,100)">
<rect width="250" height="50" font-size="10" stroke="black" stroke-width="1" fill-opacity="0.1" fill="blue"/>
</g>
</g>
This forms the proper grouping, which is important for the export, and simplifies the calculations of server graphics by making them relative to the site graphic.
Is there a way to color specific bars? If a bar is less than the line, color it red.
Code: https://github.com/tvinci/webs/blob/gh-pages/lineplusbar.html
Example: http://tvinci.github.io/webs/lineplusbar.html
I'd like to do something like this but the value for i is not the y value that I send in. It has been modified:
d3.selectAll("rect.nv-bar")
.style("fill", function(d, i){
return i > 50 ? "red":"blue";
});
Data:
var overview_data=[
{
"key" : "Achieved",
"bar": true,
"values" : [ [ "1x" , 30] , [ "2x" , 70] , [ "3x" , 200] ]
},
{
"key" : "Required",
"values" : [ [ "1x" , 50] , [ "2x" , 100] , [ "3x" , 150] ]
}
].map(function(series) {
series.values = series.values.map(function(d) { return {x: d[0], y: d[1] } });
return series;
});
You can do:
d3.selectAll("rect.nv-bar")
.style("fill", function(d, i){
return d.y > 50 ? "red":"blue";
});
The upward answer is right but it won't work when you change the data dynamically in my-case.
You can use the following configuration to get different bar colors based on the value.
var data=[ {
"key": "data",
"values": [
{
"x": 1,
"y": 20
},
{
"x": 2,
"y": 15
},
{
"x": 3,
"y": 85
},
{
"x": 4,
"y": 66
},}];
nv.addGraph(function() {
var chart = nv.models.multiBarChart()
.barColor(getColorArrayForGraph())
.transitionDuration(350)
.reduceXTicks(true)
.rotateLabels(0)
.showControls(true)
.groupSpacing(0.1);
chart.xAxis
.tickFormat(d3.format(',f'));
chart.yAxis
.tickFormat(d3.format(',.1f'));
d3.select('#chart1 svg')
.datum(data)
.call(chart);
nv.utils.windowResize(chart.update);
return chart;
}
// GET color array based on the value
function getColorArrayForGraph() {
let colorArray: any = [];
for (let item of data) {
if (item.y > 50) {
colorArray.push('#FF0000');
} else {
colorArray.push('#004c00');
}
}
return colorArray;
};
So here, barcolor(["#FF0000","#00FFCC",....]) function takes arguments as array of colors.
This way you can get the array of colors and use it into barcolor().