d3.js histogram with positive and negative values - javascript

I can't figure out how to properly create a histogram where there are both positive and negative values in the data array.
I've used the histogram example here http://bl.ocks.org/mbostock/3048450 as a base, and while the x axis values and ticks are correct, the bars are out to lunch.
Data
var values = [-15, -20, -22, -18, 2, 6, -26, -18, -15, -20, -22, -18, 2, 6, -26, -18];
X Scale
var x0 = Math.max(-d3.min(values), d3.max(values));
var x = d3.scale.linear()
.domain([-x0, x0])
.range([0, width])
.nice();
Check the jfiddle here: http://jsfiddle.net/tNdJj/2/
I assume it's something missing from the "rect" creations but I am not seeing it.

Using the example of histogram from the following question: Bar chart with negative values
I inversed x and y and adapted the display. Now you have a nice basis.
Here is the corresponding jsFiddle: http://jsfiddle.net/chrisJamesC/tNdJj/4/
Here is the relevant code:
var data = [-15, -20, -22, -18, 2, 6, -26, -18];
var margin = {top: 30, right: 10, bottom: 10, left: 30},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var y0 = Math.max(Math.abs(d3.min(data)), Math.abs(d3.max(data)));
var y = d3.scale.linear()
.domain([-y0, y0])
.range([height,0])
.nice();
var x = d3.scale.ordinal()
.domain(d3.range(data.length))
.rangeRoundBands([0, width], .2);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", function(d) { return d < 0 ? "bar negative" : "bar positive"; })
.attr("y", function(d) { return y(Math.max(0, d)); })
.attr("x", function(d, i) { return x(i); })
.attr("height", function(d) { return Math.abs(y(d) - y(0)); })
.attr("width", x.rangeBand());
svg.append("g")
.attr("class", "x axis")
.call(yAxis);
svg.append("g")
.attr("class", "y axis")
.append("line")
.attr("y1", y(0))
.attr("y2", y(0))
.attr("x1", 0)
.attr("x2", width);
Note: For simple visualizations like this, I would recommand using nvd3.js

The trick is that the demo code is overly optimistic, assuming that its input is positive:
bar.append("rect")
.attr("x", 1)
// .attr("width", x(data[0].dx) - 1) // Does the wrong thing for negative buckets.
.attr("width", x(data[0].x + data[0].dx) - 1)
.attr("height", function(d) { return height - y(d.y); });
http://jsfiddle.net/tNdJj/46/

Related

r2d3: d3.js bar chart disappears on resize

I'm rendering this d3 chart in an RMarkdown document:
Javascript (test.js):
// !preview r2d3 data=readr::read_tsv("X:/D3 Practice/data.tsv"), d3_version = "3", container="div"
//
// r2d3: https://rstudio.github.io/r2d3
var margin = {top: 40, right: 20, bottom: 30, left: 40},
width = width - margin.left - margin.right,
height = height - margin.top - margin.bottom;
var formatPercent = d3.format(".0%");
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(formatPercent);
var svg = div.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
r2d3.onRender(function(data, s, w, h, options) {
x.domain(data.map(function(d) { return d.letter; }));
y.domain([0, d3.max(data, function(d) { return d.frequency; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Frequency");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.letter); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.frequency); })
.attr("height", function(d) { return height - y(d.frequency); })
});
function type(d) {
d.frequency = +d.frequency;
return d;
}
Data (data.tsv):
letter frequency
A .08167
B .01492
C .02780
D .04253
E .12702
F .02288
G .02022
H .06094
I .06973
J .00153
K .00747
L .04025
M .02517
N .06749
O .07507
P .01929
Q .00098
R .05987
S .06333
T .09056
U .02758
V .01037
W .02465
X .00150
Y .01971
Z .00074
R Code:
library(r2d3)
r2d3(data = readr::read_tsv("X:/D3 Practice/data.tsv"),
script = "X:/D3 Practice/test.js",
d3_version = "3",
container="div")
Chart looks fine in R preview, it also looks fine in the output HMTL document. But when I resize the window up to a certain point, the chart disappears. In the console, I can see a message that says:
Node cannot be found in the current page.
Here's the initial HTML:
Here's when I resize (note the "error" div? No idea what that is).:
It's my understanding that r2d3 has already declared and set width and height on init or resize. So you could give a minimum by changing the top of your js:
var margin = {top: 40, right: 20, bottom: 30, left: 40},
min_width = 250, /// The smallest width your plot area (exluding margins) should have
min_height = 480; /// The smallest height your plot area (exluding margins) should have
width = d3.max([width, min_width]) - margin.left - margin.right;
height = d3.max([height, min_height]) - margin.top - margin.bottom;

wrong date printing on x axis of line graph in d3 with extra tick

i am using D3 to draw line graph in JavaScript. Line of Line graph is drawn right but date on x axis is not correct and it is also showing one extra tick on x axis. I had also tried changing tick format but i failed. I don't know where i am doing wrong. please help. here is my code
var margin = {top: 50, right: 50, bottom: 50, left: 50}
, width = window.innerWidth - margin.left - margin.right // Use the window's width
, height = window.innerHeight - margin.top - margin.bottom; // Use the window's height
// The number of datapoints
var n = 4;
var xScale = d3.scaleLinear()
.domain([0, n-1]) // input
.range([0, width]); // output
var parseTime = d3.timeParse("%d-%b-%y");
// 6. Y scale will use the randomly generate number
var yScale = d3.scaleLinear()
.domain([0, 100]) // input
.range([height, 0]); // output
d3.csv("Data_vis.csv", function(data){
//console.log(data);
var nov_11_percent= 22;
var oct_16_percent= 25;
var nov_13_percent= 24;
var oct_22_percent= 21;
var dataset2=[{"date":'2018-09-11', "value": nov_11_percent},
{"date":'2018-10-16', "value": oct_16_percent},
{"date":'2018-10-22', "value": oct_22_percent},
{"date":'2018-11-13', "value": nov_13_percent}];
dataset2.forEach(function(d) {
d.date = new Date(d.date);
});
var line = d3.line()
.x(function(d, i) {
//alert(parseTime(d.date));
return xScale(d.date);
})
.y(function(d,i ) {
return yScale(d.value);
})
.curve(d3.curveMonotoneX)
xScale.domain(d3.extent(dataset2, function(d) {
return d.date; }));
yScale.domain([0, d3.max(dataset2, function(d) {
return d.value;
})]);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale)
.ticks(4)
.tickFormat(d3.timeFormat("%Y-%m-%d")));
svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yScale));
svg.append("path")
.datum(dataset2)
.attr("class", "line")
.attr("d", line);
svg.selectAll(".dot")
.data(dataset2)
.enter().append("circle") // Uses the enter().append() method
.attr("class", "dot") // Assign a class for styling
.attr("cx", function(d) { return xScale(d.date) })
.attr("cy", function(d) { return yScale(d.value) })
.attr("r", 10);
});
any help would be much appreciated. thank you
Use a point scale instead of a linear scale for x.
var xScale = d3
.scalePoint()
.range([0, width])
.domain(dataset2.map(i => i.date))
const margin = { top: 50, right: 50, bottom: 50, left: 50 }
const width = window.innerWidth - margin.left - margin.right // Use the window's width
const height = window.innerHeight - margin.top - margin.bottom // Use the window's height
const dataset2 = [{ date: "2018-09-11", value: 22 },{ date: "2018-10-16", value: 25 },{ date: "2018-10-22", value: 24 },{ date: "2018-11-13", value: 21 }]
dataset2.forEach(function(d) {
d.date = new Date(d.date)
})
// 6. Y scale will use the randomly generate number
const yScale = d3
.scaleLinear()
.domain([
0,
d3.max(dataset2, function(d) {
return d.value
})
]) // input
.range([height, 0]) // output
const xScale = d3
.scalePoint()
.range([0, width])
.domain(dataset2.map(d => d.date))
const line = d3
.line()
.x(function(d, i) {
return xScale(d.date)
})
.y(function(d, i) {
return yScale(d.value)
})
.curve(d3.curveMonotoneX)
const svg = d3
.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
svg
.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(
d3
.axisBottom(xScale)
.tickFormat(d3.timeFormat("%m/%d"))
)
svg
.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yScale))
svg
.append("path")
.datum(dataset2)
.attr("class", "line")
.attr("d", line)
.attr("fill", "none")
.attr("stroke", "black")
svg
.selectAll(".dot")
.data(dataset2)
.enter()
.append("circle") // Uses the enter().append() method
.attr("class", "dot") // Assign a class for styling
.attr("cx", function(d) {
return xScale(d.date)
})
.attr("cy", function(d) {
return yScale(d.value)
})
.attr("r", 10)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Codepen

Plotting a Histogram in D3 without knowing number of bins

I'm trying to create a function that creates a histogram for a given array. Clearly, there's no data for the X-Axis and I have to choose the bins arbitrarily. What would be the best way to do so?
My code:
var width = 700, height = 500, pad = 30, barPadding = 1;
function plotHistogram(element, dataset) {
var svg = d3.select(element)
.append("svg")
.attr("width", width)
.attr("height", height)
var bars = svg.append("g")
// Container box
var rectangle = svg.append("rect")
.attr("height", height)
.attr("width", width)
.attr("stroke-width", 2).attr("stroke", "#ccc")
.attr("fill", "transparent")
var xScale = d3.scaleLinear()
// Using default domain (0 - 1)
.range([pad, width - pad * 2])
var yScale = d3.scaleLinear()
.domain([0, d3.max(dataset, function(d) { return d; })])
.range([height - pad, pad])
var xAxis = d3.axisBottom(xScale)
var yAxis = d3.axisLeft(yScale)
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (height - pad) + ")")
.call(xAxis)
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + pad +", 0)")
.call(yAxis)
svg.selectAll("bars")
.data(dataset)
.enter()
.append("rect")
// Evenly spacing out bars
.attr("x", function(d, i) { return i * width/dataset.length; })
// Top of each bar as the top of svg. To remove inverted bar graph.
.attr("y", function(d) { return height - (d * 4); })
// To give padding between bars
.attr("width", width / dataset.length - barPadding)
// To make the bars taller
.attr("height", function(d) { return d * 4; })
.attr("fill", "teal");
}
// #normal is the id of the div element.
plotHistogram("#normal", [10, 20, 30, 40, 50, 60, 70, 80]);
Edit 1: I have decided to use the default bin size (0 - 1) for the xScale above. I'm facing problems creating the bars.
Generate the bins as in https://bl.ocks.org/mbostock/3048450
The array in your plotHistogram is like the array data in #mbostock's bl.ock...
HTH

Drawing stacked-bar chart using d3

I'm trying to adapt this code:
http://bl.ocks.org/anupsavvy/9513382
To plot a stacked-bar chart using custom data. I don't need any transitions, just a simple plot.
I end up with this code:
data = jsonArr;
var margin = {
top: 40,
right: 40,
bottom: 40,
left: 40
},
width = 600 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var color = d3.scale.ordinal()
.range(colorrange);
var stack = d3.layout.stack();
stack(data);
var xScale = d3.time.scale()
.domain([new Date(0, 0, 0, data[0][0].label, 0, 0, 0), new Date(0, 0, 0, data[0][data[0].length - 1].label, 0, 0, 0)])
.rangeRound([0, width - margin.left - margin.right]);
var yScale = d3.scale.linear()
.domain([0,
d3.max(data, function(d) {
return d3.max(d, function(d) {
return d.y0 + d.value;
});
})
])
.range([height - margin.bottom - margin.top, 0]);
var xAxis = d3.svg.axis()
.scale(xScale)
.ticks(d3.time.hour, 1)
.tickFormat(d3.time.format("%H"));
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(10);
var svg = d3.select("#info")
.append("svg")
.attr("width", width)
.attr("height", height);
var groups = svg.selectAll("g")
.data(data)
.enter()
.append("g")
.attr("class", "rgroups")
.attr("transform", "translate(" + margin.left + "," + (height - margin.bottom) + ")")
.style("fill", function(d, i) {
return colorrange[i];
});
var rects = groups.selectAll("rect")
.data(function(d) {
return d;
})
.enter()
.append("rect")
.attr("width", 6)
.attr("height", function(d) {
return -yScale(d.value) + (height - margin.top - margin.bottom);
})
.attr("x", function(d) {
return xScale(new Date(0, 0, 0, d.label, 0, 0, 0));;
})
.attr("y", function(d) {
return -(-yScale(d.y0) - yScale(d.value) + (height - margin.top - margin.bottom) * 2);
});
console.log(rects);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(40," + (height - margin.bottom) + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(yAxis);
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - 5)
.attr("x", 0 - (height / 2))
.attr("dy", "1em")
.text("Number of complaints");
svg.append("text")
.attr("class", "xtext")
.attr("x", width / 2 - margin.left)
.attr("y", height - 5)
.attr("text-anchor", "middle")
.text("Hour of day");
More specifically:
yScale(d.y0) is returning NaN.
If I comment this piece of code, I can see the axes:
After a while, I managed to see the and some data (among errors):
I guess I'm not understanding the properly way to plot the data itself.
Any help would be appreciated.
My json label attribute is related to the y coordinate, while value is related to the x coordinate.
EDIT:
It seems that the problem begins when I call stack. The first array has y0 values as 0, but the second and third ones have y0 = NaN. I don't know how to fix this.
This is the relative jsfiddle:
https://jsfiddle.net/rhzkz9gb/13/
You need to provide the accessor functions for the data (because it is not keyed with 'x' and 'y').
var stack = d3.layout.stack().x(function(d,i){return i;}).y(function(d){return d.value;});
https://jsfiddle.net/ermineia/rhzkz9gb/14/

d3.js: how to conditionally render a curve?

I've got a function that draws a curve through dots I give as argument, as follows:
var data = [
// stage 1-9, intensity %, draw disk
{x:1, y: 0, point:true},
{x:4, y: 30, point:true},
{x:5, y: 70, point:true},
{x:6, y:100, point:true},
{x:7, y: 90, point:true},
{x:8, y: 40, point:true},
{x:9, y: 10, point:false}
];
I'd like to handle the point members that tells whether or not to draw an additional spot.
How to do that?
The function that draws the curve as it is per today:
function curveChart(data) {
for (i in data) {
data[i].y = 5.5*data[i].y/100; // normalize
data[i].id = i;
}
var margin = {top: 10, right: 190, bottom: 275, left: 35},
width = 915 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear() //.time.scale()
.domain([1, 9]) // 9 stages
.range([0, width]);
var y = d3.scale.linear()
.domain([0, 6])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.interpolate("monotone")
.x(function(d) { return x(d.x); })
.y(function(d) { return y(d.y); });
var svg = d3.select(".curveChart").append("svg")
.datum(data)
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("path")
.attr("class", "line")
.attr("d", line);
var n = 1;
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("class", "dot")
.attr("cx", line.x())
.attr("cy", line.y())
.attr("r", 2)
.attr("bubbleid", function(d){return d.id; })
.transition(1000)
.duration(800)
.attr("r", 10);
svg.selectAll("circle").on("click", function(){
d3.selectAll(".active").classed("active", false);
d3.select(this).classed("active", true);
var id = $(this).attr('bubbleid');
console.log("clicked on "+$(this).attr('bubbleid'));
$(".bubble").removeClass("show");
$("#bubble"+id).addClass("show");
d3.select(this)
.transition()
.duration(400)
.attr('r', 25)
.transition()
.duration(400)
.attr('r', 10)
;
});
}
The fiddle : http://jsfiddle.net/stephanedeluca/R44cB/
The easiest way to do this is to filter the data before passing it to .data(), retaining only the elements where point is true:
svg.selectAll("circle")
.data(data.filter(function(d) { return d.point; }))
Complete demo here.

Categories