Transition between a circle and a line with svg and d3 - svg

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.

Related

Node-Red chart node with stored data

I have a problem with the chart node. I want to display a line graph, the data source is two MQTT messages. A microcontroller publishes two 2049 element byte arrays in 2 topics (linearccdL and linearccdH). The data are upper and lower bytes of words, and at first I have to join them, it works now.
When the 2049 pcs 16 bit numbers are ready i have to display them on a graph. According to the documentation, the graph node accepts arrays in the form:
[{
"series": ["A", "B", "C"],
"data": [
[{ "x": 1504029632890, "y": 5 },
{ "x": 1504029636001, "y": 4 },
{ "x": 1504029638656, "y": 2 }
],
[{ "x": 1504029633514, "y": 6 },
{ "x": 1504029636622, "y": 7 },
{ "x": 1504029639539, "y": 6 }
],
[{ "x": 1504029634400, "y": 7 },
{ "x": 1504029637959, "y": 7 },
{ "x": 1504029640317, "y": 7 }
]
],
"labels": [""]
}]
I'm using a function to format the data:
var rawDataL = new ArrayBuffer(2049);
var rawDataH = new ArrayBuffer(2049);
var rawData = new Uint16Array(2049);
var outobj = {};
var outarr = [];
var outmsg;
if (msg.topic == "rbmt/linearccdL") {
context.rawDataL = msg.payload;
context.Lset=true;
} else if (msg.topic == "rbmt/linearccdH") {
context.rawDataH = msg.payload;
context.Hset=true;
}
if (context.Lset & context.Hset) {
for (var i=0; i<2049; i++) {
rawData[i]=context.rawDataH[i]*256+context.rawDataL[i];
outobj={"x":i, "y":rawData[i]};
outarr.push(outobj);
}
context.Lset=false;
context.Hset=false;
outmsg = {"series":"[A]", data: outarr};
return outmsg;
}
After putting a debug node i can see the message, seems OK, but on the dashboard i get "No data".
I'm new in JS, can somebody help a bit please?
The debug message: debug msg

How to render a graph as image in node

I want to render a stacked bar graph in image format on the server.
The intended use is to push to a service like twitter that doesn't support SVG. As well as the code being being deployable to services like Heroku
I've already tried Plotly (their node package is horribly out of date and their API docs poor). I've also looked at Google Graph, Chart.js and AnyChart but they do not support rendering images far as I can see
You can accomplish this with Vega
Vega is a visualization grammar, a declarative format for creating, saving, and sharing interactive visualization designs. With Vega you can describe data visualizations in a JSON format, and generate interactive views using either HTML5 Canvas or SVG.
For example, using the stacked bar chart example spec you can render the chart to PNG file with the following code:
// START vega-demo.js
var vega = require('vega')
var fs = require('fs')
var stackedBarChartSpec = require('./stacked-bar-chart.spec.json');
// create a new view instance for a given Vega JSON spec
var view = new vega
.View(vega.parse(stackedBarChartSpec))
.renderer('none')
.initialize();
// generate static PNG file from chart
view
.toCanvas()
.then(function (canvas) {
// process node-canvas instance for example, generate a PNG stream to write var
// stream = canvas.createPNGStream();
console.log('Writing PNG to file...')
fs.writeFile('stackedBarChart.png', canvas.toBuffer())
})
.catch(function (err) {
console.log("Error writing PNG to file:")
console.error(err)
});
// END vega-demo.js
// START stacked-bar-chart.spec.json
{
"$schema": "https://vega.github.io/schema/vega/v3.0.json",
"width": 500,
"height": 200,
"padding": 5,
"data": [
{
"name": "table",
"values": [
{"x": 0, "y": 28, "c":0}, {"x": 0, "y": 55, "c":1},
{"x": 1, "y": 43, "c":0}, {"x": 1, "y": 91, "c":1},
{"x": 2, "y": 81, "c":0}, {"x": 2, "y": 53, "c":1},
{"x": 3, "y": 19, "c":0}, {"x": 3, "y": 87, "c":1},
{"x": 4, "y": 52, "c":0}, {"x": 4, "y": 48, "c":1},
{"x": 5, "y": 24, "c":0}, {"x": 5, "y": 49, "c":1},
{"x": 6, "y": 87, "c":0}, {"x": 6, "y": 66, "c":1},
{"x": 7, "y": 17, "c":0}, {"x": 7, "y": 27, "c":1},
{"x": 8, "y": 68, "c":0}, {"x": 8, "y": 16, "c":1},
{"x": 9, "y": 49, "c":0}, {"x": 9, "y": 15, "c":1}
],
"transform": [
{
"type": "stack",
"groupby": ["x"],
"sort": {"field": "c"},
"field": "y"
}
]
}
],
"scales": [
{
"name": "x",
"type": "band",
"range": "width",
"domain": {"data": "table", "field": "x"}
},
{
"name": "y",
"type": "linear",
"range": "height",
"nice": true, "zero": true,
"domain": {"data": "table", "field": "y1"}
},
{
"name": "color",
"type": "ordinal",
"range": "category",
"domain": {"data": "table", "field": "c"}
}
],
"axes": [
{"orient": "bottom", "scale": "x", "zindex": 1},
{"orient": "left", "scale": "y", "zindex": 1}
],
"marks": [
{
"type": "rect",
"from": {"data": "table"},
"encode": {
"enter": {
"x": {"scale": "x", "field": "x"},
"width": {"scale": "x", "band": 1, "offset": -1},
"y": {"scale": "y", "field": "y0"},
"y2": {"scale": "y", "field": "y1"},
"fill": {"scale": "color", "field": "c"}
},
"update": {
"fillOpacity": {"value": 1}
},
"hover": {
"fillOpacity": {"value": 0.5}
}
}
}
]
}
// END stacked-bar-chart.spec.json
Will output PNG file:
I capture charts, visualizations and reports under Node.js using the Nightmare headless browser.
Using Nightmare allows you to use any of the wide variety of browser-based visualization frameworks under Node.js, including C3 and D3 which are both awesome.
I've actually created a npm module called c3-chart-maker that wraps up Nightmare and allows you to render a chart under Node.js by feeding it some data and a C3 chart definition.
Install it like this:
npm install --save c3-chart-maker
Use it like this:
const c3ChartMaker = require('c3-chart-maker');
const yourData = ... your data ...
const chartDefinition = { ... c3 chart definition ... }
const outputFilePath = "your-chart-output-file.png";
c3ChartMaker(yourData, chartDefinition, outputFilePath)
.then(() => {
console.log('Done');
})
.catch(err => {
console.error(err);
});
Please check out the C3 example gallery for examples of charts and to see what a C3 chart definition looks like.
You can also use Nightmare manually to be able to capture any web page or browser-based visualization.
To install Nightmare:
npm install --save nightmare
Here's an example that can capture a web page:
const Nightmare = require('nightmare');
// This is the web page to capture.
// It can also be a local web server!
// Or serve from the file system using file://
const urlToCapture = "http://my-visualization.com";
const outputFilePath = "your-chart-output-file.png";
const nightmare = new Nightmare(); // Create Nightmare instance.
nightmare.goto(urlToCapture) // Point the browser at the requested web page.
.wait("svg") // Wait until the specified HTML element appears on the screen.
.screenshot(outputImagePath) // Capture a screenshot to an image file.
.end() // End the Nightmare session. Any queued operations are completed and the headless browser is terminated.
.then(() => {
console.log("Done!");
})
.catch(err => {
console.error(err);
});
I've written more extensively about this on my blog.
I've also dedicated a whole chapter to this in my book Data Wrangling with JavaScript.
Simple Headless NodeJS (not localhost or web-based)
For my purposes, I wanted to just plot a chart without spinning up a localhost server or anything. So I used chartjs-node-canvas and chart.js
Install with:
npm i chartjs-node-canvas chart.js
In this I write it to a file to show it worked but I personally just needed the Base64 string to upload somewhere
// Install libs with: npm i chartjs-node-canvas chart.js
// Docs https://www.npmjs.com/package/chartjs-node-canvas
// Config documentation https://www.chartjs.org/docs/latest/axes/
const fs = require('fs');
const { ChartJSNodeCanvas } = require('chartjs-node-canvas');
const width = 400; //px
const height = 400; //px
const backgroundColour = 'white'; // Uses https://www.w3schools.com/tags/canvas_fillstyle.asp
const chartJSNodeCanvas = new ChartJSNodeCanvas({ width, height, backgroundColour });
const configuration = {
type: 'line', // for line chart
data: {
labels: [2018, 2019, 2020, 2021],
datasets: [{
label: "Sample 1",
data: [10, 15, -20, 15],
fill: false,
borderColor: ['rgb(51, 204, 204)'],
borderWidth: 1,
xAxisID: 'xAxis1' //define top or bottom axis ,modifies on scale
},
{
label: "Sample 2",
data: [10, 30, 20, 10],
fill: false,
borderColor: ['rgb(255, 102, 255)'],
borderWidth: 1,
xAxisID: 'xAxis1'
},
],
},
options: {
scales: {
y: {
suggestedMin: 0,
}
}
}
}
async function run() {
const dataUrl = await chartJSNodeCanvas.renderToDataURL(configuration);
const base64Image = dataUrl
var base64Data = base64Image.replace(/^data:image\/png;base64,/, "");
fs.writeFile("out.png", base64Data, 'base64', function (err) {
if (err) {
console.log(err);
}
});
return dataUrl
}
run()
Here's the docs https://www.npmjs.com/package/chartjs-node-canvas and the Config documentation is here https://www.chartjs.org/docs/latest/axes/

d3.js: zoom and drag failure

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/

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

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