d3.js: zoom and drag failure - svg

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/

Related

Using lines with arrows pointing from source to target

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.

y coordinates of links in d3.layout.tree

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);

How to mark discrete points on a time series graph using D3 / Rickshaw?

I'm using Rickshaw to create a live-updating time series graph.
Here is the demo: http://abhshkdz.github.io/icuvisualanalytics/prototypes/rickshaw.html
The data is in csv format (time,value), and this is the core javascript for the visualization:
var count = 0, index=0;
var margin = {top: 10, right: 10, bottom: 10, left: 10},
width = window.innerWidth - margin.right - margin.left - 100,
height = window.innerHeight - margin.top - margin.bottom - 100;
var graph = new Rickshaw.Graph( {
element: document.querySelector("#chart"),
width: width,
height: height,
renderer: 'line',
min: -300,
max: 500,
preserve: true,
series: new Rickshaw.Series.FixedDuration(
[
{
name: 'ECG',
color: palette.color()
}
],
undefined,
{
timeInterval: 12.5,
maxDataPoints: 400,
timeBase: data[index][count].x
})
})
var x_axis = new Rickshaw.Graph.Axis.Time( { graph: graph } );
var y_axis = new Rickshaw.Graph.Axis.Y( {
graph: graph,
orientation: 'left',
tickFormat: Rickshaw.Fixtures.Number.formatKMBT,
element: document.getElementById('y_axis')
} );
var hoverDetail = new Rickshaw.Graph.HoverDetail( {
graph: graph
} );
graph.render();
setInterval(function () {
if (count == 2397) {
count = 0;
index++;
}
var d = {'ECG': data[index][count+=3].y};
graph.series.addData(d);
graph.render();
}, 12.5);
Now there is another set of data which is generated by an algorithm. That data is also in csv format (time,value). It basically finds the peaks of this plot. Using that data, I want to mark those points on this visualization itself.
As far as I looked, Rickshaw does not natively support multiple series using different renderers (either both have to be line or both scatter plots etc.).
So how should I go about this?
The multi renderer feature was added about a month ago. This example shows how to combine several renderers in one chart. The relevant code of the example:
var graph = new Rickshaw.Graph( {
element: document.getElementById("chart"),
renderer: 'multi',
width: 900,
height: 500,
dotSize: 2,
series: [
{
name: 'temperature',
data: seriesData.shift(),
color: 'rgba(255, 0, 0, 0.4)',
renderer: 'stack'
}, {
name: 'heat index',
data: seriesData.shift(),
color: 'rgba(255, 127, 0, 0.4)',
renderer: 'stack'
}, {
name: 'dewpoint',
data: seriesData.shift(),
color: 'rgba(127, 0, 0, 0.3)',
renderer: 'scatterplot'
}, {
name: 'pop',
data: seriesData.shift().map(function(d) { return { x: d.x, y: d.y / 4 } }),
color: 'rgba(0, 0, 127, 0.4)',
renderer: 'bar'
}, {
name: 'humidity',
data: seriesData.shift().map(function(d) { return { x: d.x, y: d.y * 1.5 } }),
renderer: 'line',
color: 'rgba(0, 0, 127, 0.25)'
}
]
});

Transition between a circle and a line with svg and d3

JS Fiddle
I am trying to figure out how to "unfurl"/"unravel" a circle by "snipping" it at the top, and then it would animate to a line.
I have made a circle by using 16 points (and a 17th to close it since if I used a closed interpolation, the transition would look weird)
How (would it be via an animation?, just adjusting the x and y points?, another interpolation?) can you transition between the circle and the line one point at a time?
Circle points:
//The data for our line
var circleData = [ { "x": 150 , "y": 20 },
{ "x": 165.30, "y": 23.04},
{ "x": 178.28, "y": 31.71},
{ "x": 186.95, "y": 44.69},
{ "x": 190 , "y": 60 },
{ "x": 186.95, "y": 75.30},
{ "x": 178.28, "y": 88.28},
{ "x": 165.30, "y": 96.95},
{ "x": 150 , "y": 100 },
{ "x": 134.69, "y": 96.95},
{ "x": 121.71, "y": 88.28},
{ "x": 113.04, "y": 75.30},
{ "x": 110 , "y": 60.00},
{ "x": 113.04, "y": 44.69},
{ "x": 121.71, "y": 31.71},
{ "x": 134.69, "y": 23.04},
{ "x": 150 , "y": 20 } ];
Line Points:
var lineData = [ { "x": 10 , "y": 200 },
{ "x": 20 , "y": 200 },
{ "x": 30 , "y": 200 },
{ "x": 40 , "y": 200 },
{ "x": 50 , "y": 200 },
{ "x": 60 , "y": 200 },
{ "x": 70 , "y": 200 },
{ "x": 80 , "y": 200 },
{ "x": 90 , "y": 200 },
{ "x": 100 , "y": 200 },
{ "x": 110 , "y": 200 },
{ "x": 120 , "y": 200 },
{ "x": 130 , "y": 200 },
{ "x": 140 , "y": 200 },
{ "x": 150 , "y": 200 },
{ "x": 160 , "y": 200 },
{ "x": 170 , "y": 200 } ];
The first thing that I noticed when trying Duopixel's demo was that the circle seemed to shrink to fit on the line. Thus I decided to make the line the same length as the circle. Moreover to have a uniform distribution I wrote two functions to create the line and circle data arrays:
var numberOfPoints = 30;
var radius = 60
var margin = {top: 20,left: 20}
var lineLength = 2 * radius * Math.PI
var circleData = $.map(Array(numberOfPoints), function (d, i) {
var imag = margin.left + lineLength / 2 + radius * Math.sin(2 * i * Math.PI / (numberOfPoints - 1))
var real = margin.top + radius - radius * Math.cos(2 * i * Math.PI / (numberOfPoints - 1))
return {x: imag, y: real}
})
var lineData = $.map(Array(numberOfPoints), function (d, i) {
var y = margin.top + 2 * radius;
var x = margin.left + i * lineLength / (numberOfPoints - 1)
return { x: x, y: y}
}).reverse()
So, now, what effect can we apply? I will go with the easiest one: a transition mapping each point of the circle to its point on the line.
var circle = svgContainer.append("g")
.append("path")
.data([circleData])
.attr("d", lineFunction)
.attr("class", "circle")
.on("click", transitionToLine)
function transitionToLine() {
circle.data([lineData])
.transition()
.duration(1000)
.ease("linear")
.attr('d', lineFunction)
circle.on("click", transitionToCircle)
}
function transitionToCircle() {
circle.data([circleData])
.transition()
.duration(1000)
.ease("linear")
.attr('d', lineFunction)
circle.on("click", transitionToLine)
}
Here is the jsFiddle, you just have to click on the node to see the animation.
One important thing to notice is that the transition takes the same time for each point whereas in reality you would like the points in the end to arrive after the points near the middle. The trick you can use is to make the duration of the animation be proportional to the distance from the source point to the destination one but I don't see how to use it with lines as you pass the whole array so you cannot change the duration for a specific point.

NVD3.js coloring specific bars in graph

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().

Resources