I am trying to build a stacked bar chart in d3.
Here's my jsfiddle:
http://jsfiddle.net/maneesha/gwjkgruk/4/
I'm trying to fix it so that the y-axis starts at zero.
I thought this is where that was set up and this is how it should be:
yScale = d3.scale.linear()
.domain([0, yMax])
.range([0, height]),
but it doesn't work. Changing domain to this gets the scale right but messes up all my rects.
yScale = d3.scale.linear()
.domain([yMax, 0])
.range([0, height]),
What am I doing wrong?
Try to modify your scale like that:
yScale = d3.scale.linear()
.domain([0,yMax])
.range([height,0]),
and then when you fill the rect:
.attr('y', function (d) {
return height - yScale(d.y0);
})
.attr('height', function (d) {
return height - yScale(d.y);
})
Related
I would like to place images between the axis labels and the axis line or the bars in my case. Now it's a bit tricky because I don't have much space. I am restricted by the graph size and I have to work with the current dimensions. I tried the option of adding tickPadding() to the y-axis but that meant I went over the graph size and the labels were cut-off. is there a way I could move the bars to the right? or make the width a bit smaller?
here is my code for the y-axis and the bars:
let yScale_h = d3.scaleBand()
.range([0, height])
.padding(0.2);
let xScale_h = d3.scaleLinear()
.range([0, width]);
let yAxis = d3.axisLeft()
.scale(yScale_h)
.tickSize(0);
svg_bar.selectAll('rect')
.data(dataset_performance, key)
.enter()
.append('rect')
.attr("class", "bar")
.attr('width', function (d) { return xScale_h(d.Award); })
.attr('y', function (d) { return yScale_h(d.clean_test); })
.attr('height', yScale_h.bandwidth())
One way to manually offset the bars to the right is to reduce the scale range, and add the padding to the 'x' property of the bars.
This example adds a padding of 20px:
let xScale_h = d3.scaleLinear()
.range([0, width - 20]); // Reduce the range by 20px
...
svg_bar.selectAll('rect')
.data(dataset_performance, key)
.enter()
.append('rect')
.attr("class", "bar")
.attr('x', 20) // Move bars to the right by 20px
.attr('width', function (d) { return xScale_h(d.Award); })
.attr('y', function (d) { return yScale_h(d.clean_test); })
.attr('height', yScale_h.bandwidth())
I'm trying to prevent clipping at the top most part of my chart by increasing the domain on the yAxis like so:
mainHeight = 640;
yScale = d3.scale.linear()
.range([mainHeight, 0])
.domain(d3.extent([0, d3.max(data, function (d) {
return (d.total)+1000;
})]));
The idea is to get the max data for the yAxis and increase it by 1000.
The highest total is 14348 so with 1000 added on it creates 15348
However the top of the chart is still being clipped off and my axis hasn't increased to prevent the clipping. Even if I increase the number by 9999999999 it still doesn't happen.
The line is generated with:
var totalLine = d3.svg.line()
.interpolate('monotone')
.x(function (d) {
return xScale(d.date);
})
.y(function (d) {
return yScale(d.total);
});
Try throwing a .nice() at the end.
yScale = d3.scale.linear()
.range([mainHeight, 0])
.domain(d3.extent([0, d3.max(data, function (d) {
return (d.total)+1000;
})]))
.nice();
This will try to make the axis end on nice round numbers. You can read more here: https://github.com/d3/d3-3.x-api-reference/blob/master/Quantitative-Scales.md#linear_nice
Hope this helps.
I am building a widget to let users decide what quantities to plot against what quantities (building off this animated scatter plot on bl.ocks. This is working fine for numeric quantities, but I also have date quantities, and I want users to be able to plot these too, in the same way, and against non-date quantities.
The original linear scaling and axes are set up like so as global functions:
var xScale = d3.scale.linear() // xScale is width of graphic
.domain([0, d3.max(dataset, function(d) {
return d[0]; // input domain
})])
.range([padding, canvas_width - padding * 2]); // output range
var yScale = d3.scale.linear() // yScale is height of graphic
.domain([0, d3.max(dataset, function(d) {
return d[1]; // input domain
})])
.range([canvas_height - padding, padding]); // remember y starts on top going down so we flip
// Define X axis
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(5);
// Define Y axis
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(5);
My hope was that I could modify these globals inside the click function and even change the nature of the scaling and that this would feed back into the axis variables as well, so I put this inside the click function:
if(types[xName]==3){
console.log("resetting x scale to time type");
xScale = d3.time.scale().range([padding, canvas_width - padding * 2]); // output range
}
else{
// Create scale functions
xScale = d3.scale.linear() // xScale is width of graphic
.domain([0, d3.max(dataset, function(d) {
return d[0]; // input domain
})])
.range([padding, canvas_width - padding * 2]); // output range
}
xScale.domain([0, d3.max(dataset, function(d) {
return d[0]; })]);
if(types[xName] == 1){
xScale.domain([d3.max(dataset, function(d) {
return d[0]; }), 0]);
}
if(types[yName]==3){
console.log("resetting y scale to time type");
yScale = d3.time.scale().range([canvas_height - padding, padding]); // remember y starts on top going down so we flip
}
else {
yScale = d3.scale.linear() // yScale is height of graphic
.domain([0, d3.max(dataset, function(d) {
return d[1]; // input domain
})])
.range([canvas_height - padding, padding]); // remember y starts on top going down so we flip
}
yScale.domain([0, d3.max(dataset, function(d) {
return d[1]; })]);
if(types[yName] == 1){
yScale.domain([d3.max(dataset, function(d) {
return d[1]; }), 0]);
}
I also use a parseDate as appropriate on the data when it's date data. The above (and full code is here with widget here, the problematic date type being stored in Created) puts all the points in some crazy location all in one straight line off the graph when I choose the date type, and worse still produces the following error:
Error: Invalid value for <circle> attribute cx="naN" where I assume this is giving an error from the following code:
svg.selectAll("circle")
.data(dataset) // Update with new data
.transition() // Transition from old to new
...
.attr("cx", function(d) {
return xScale(d[0]); // Circle's X
})
So I assume the xScale is simply not working when it's been converted to a time scale. What am I doing wrong? Thanks for any corrections or troubleshooting advice.
The cx is calculating as NaN because the data you are storing created, as time stamp example:"created":1447686953 and you are writing a parse date function.
var parseDate = d3.time.format("%Y%m%d").parse;
This is incorrect as the date is not in 20151223 format.
So the scale as you suggesting get calculated wrongly.
if(types[xName]== 3){
newNumber1 = parseDate(String(data[i][xName]));//this is wrong
}
var newNumber2 = data[i][yName]/divisor[types[yName]]//Math.floor(Math.random() * maxRange); // New random integer
if(types[yName]== 3){
newNumber2 = parseDate(String(data[i][yName]));//this is wrong
}
So you need to do this for converting into date:
if(types[xName]== 3){
newNumber1 = new Date(data[i][xName]*1000);
}
var newNumber2 = data[i][yName]/divisor[types[yName]]//Math.floor(Math.random() * maxRange); // New random integer
if(types[yName]== 3){
newNumber2 = new Date(data[i][yName]*1000);
}
Hope this helps!
I'm trying to create a responsive scatterplot with D3js using percentages for the width and height of the chart.
So, I have these variables declared up top:
var w = '100%';
var h = '100%';
My xScale and yScale work properly, along with the placement of my output circles in the SVG:
var xScale = d3.scale.linear()
.domain([0, d3.max(data, function(d) { return d["ttc"]; })])
.range([0, w]);
var yScale = d3.scale.linear()
.domain([0, d3.max(data, function(d) { return d["ctc"]; })])
.range([0, h]);
var svg = d3.select(el)
.append('svg')
.attr('height', h)
.attr('width', w);
svg.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('cx', function(d) { return xScale(d["ttc"]); })
.attr('cy', function(d) { return yScale(d["ctc"]); })
.attr('r', 10);
However, due to the nature of SVGs, the circles are displayed from the top left corner rather than the typical origin in the bottom left. In order to reverse this, usually I would switch the yScale range values, to be [h, 0] instead of [0, h]. When I do this, I get an error Error: Invalid value for <circle> attribute cy="NaN". What can I do to fix this and have the plot work with percentages?
You can declare width and height as numbers, like var h = 100; and then add percentages in the attr function:
.attr('cx', function (d) {
return xScale(d["ttc"]) + '%';
})
Then your svg could be styled with:
.attr('height', h + '%')
.attr('width', w + '%')
Or just use CSS for that. Here's a demo.
I'm trying to update the width of my axis like so:
Initial setup with zero width:
var x2 = d3.scale.linear()
.domain([0, 10])
.range([0, 0])
.clamp(true);
svg.append("g")
.attr("class", "x axis2")
.attr("transform", "translate(0," + height / 2 + ")")
.call(d3.svg.axis()
.scale(x2)
.orient("bottom")
.tickFormat(function(d) { return d; })
.tickSize(0)
.tickPadding(12))
.select(".domain")
.select(function() { return this.parentNode.appendChild(this.cloneNode(true)); })
.attr("class", "bar-highlight");
Later I want to update the width of the path (with a nice transition would be great). I tried it like suggested in this answer, with no luck:
x2 = d3.scale.linear()
.domain([0, data.page_count])
.range([0, 15])
.clamp(true);
d3.selectAll("g.x.axis2")
.call(x2);
You need to call d3.svg.axis().scale(x2) on the axis, not just x2. The following update procedure works for me (albeit on an empty plot):
// Update the range of existing variable, x2
x2.range([0, 15]);
// Select and change the axis in D3
d3.selectAll("g.x.axis2")
.call(d3.svg.axis().scale(x2));