I have a vertical bar chart that is grouped in pairs. I was trying to play around with how to flip it horizontally. In my case, the keywords would appear on the y axis, and the scale would appear on the x-axis.
I tried switching various x/y variables, but that of course just produced funky results. Which areas of my code do I need to focus on in order to switch it from vertical bars to horizontal ones?
My JSFiddle: Full Code
var xScale = d3.scale.ordinal()
.domain(d3.range(dataset.length))
.rangeRoundBands([0, w], 0.05);
// ternary operator to determine if global or local has a larger scale
var yScale = d3.scale.linear()
.domain([0, d3.max(dataset, function (d) {
return (d.local > d.global) ? d.local : d.global;
})])
.range([h, 0]);
var xAxis = d3.svg.axis()
.scale(xScale)
.tickFormat(function (d) {
return dataset[d].keyword;
})
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(5);
var commaFormat = d3.format(',');
//SVG element
var svg = d3.select("#searchVolume")
.append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Graph Bars
var sets = svg.selectAll(".set")
.data(dataset)
.enter()
.append("g")
.attr("class", "set")
.attr("transform", function (d, i) {
return "translate(" + xScale(i) + ",0)";
});
sets.append("rect")
.attr("class", "local")
.attr("width", xScale.rangeBand() / 2)
.attr("y", function (d) {
return yScale(d.local);
})
.attr("x", xScale.rangeBand() / 2)
.attr("height", function (d) {
return h - yScale(d.local);
})
.attr("fill", colors[0][1])
;
sets.append("rect")
.attr("class", "global")
.attr("width", xScale.rangeBand() / 2)
.attr("y", function (d) {
return yScale(d.global);
})
.attr("height", function (d) {
return h - yScale(d.global);
})
.attr("fill", colors[1][1])
;
sets.append("rect")
.attr("class", "global")
.attr("width", xScale.rangeBand() / 2)
.attr("y", function (d) {
return yScale(d.global);
})
.attr("height", function (d) {
return h - yScale(d.global);
})
.attr("fill", colors[1][1])
;
I just did the same thing last night, and I basically ended up rewriting the code as it was quicker than fixing all the bugs but here's the tips I can give you.
The biggest issues with flipping the x and y axis will be with things like return h - yScale(d.global) because height is calculated from the "top" of the page not the bottom.
Another key thing to remember is that when you set .attr("x", ..) make sure you set it to 0 (plus any padding for the left side) so = .attr("x", 0)"
I used this tutorial to help me think about my own code in terms of horizontal bars instead - it really helped
http://hdnrnzk.me/2012/07/04/creating-a-bar-graph-using-d3js/
here's my own code making it horizontal if it helps:
var w = 600;
var h = 600;
var padding = 30;
var xScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d){
return d.values[0]; })]) //note I'm using an array here to grab the value hence the [0]
.range([padding, w - (padding*2)]);
var yScale = d3.scale.ordinal()
.domain(d3.range(dataset.length))
.rangeRoundBands([padding, h- padding], 0.05);
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h)
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", 0 + padding)
.attr("y", function(d, i){
return yScale(i);
})
.attr("width", function(d) {
return xScale(d.values[0]);
})
.attr("height", yScale.rangeBand())
An alternative is to rotate the chart (see this). This is a bit hacky as then you need to maintain the swapped axes in your head (the height is actually the width etc), but it is arguably simpler if you already have a working vertical chart.
An example of rotating the chart is below. You might need to rotate the text as well to make it nice.
_chart.select('g').attr("transform","rotate(90 200 200)");
Here is the procedure I use in this case:
1) Inverse all Xs and Ys
2) Remember that the 0 for y is on top, thus you will have to inverse lots of values as previous values for y will be inversed (you don't want your x axis to go from left to right) and the new y axis will be inversed too.
3) Make sure the bars display correctly
4) Adapt legends if there are problems
This question may help in the sense that it shows how to go from horizontal bar charts to vertical: d3.js histogram with positive and negative values
Related
I am trying to create a barplot using javascript. I have created a barplot, but want to add two axis. Currently stuck on the x-axis.
I am unable to move my x-axis to the bottom of my barplot. I am Using d3 to tailor the svg. I am currently able to showcase it at the top, but want to show it at the bottom.
Any input would be useful!
My attempts thus far have been to use transform, but when I execute this my axis disappears.
Googled several other solutions, none of them being successful.
Code:
<script>
d3.json("data_week3.json", function(data){
var data_renewables = [];
var data_nations = [];
for (i = 0; i < data.length; i++)
{
data_renewables.push(data[i].Renewable);
data_nations.push(data[i].Nation)
}
var width = 1000,
height = 500;
var y = d3.scale.linear()
.domain([0, d3.max(data_renewables)])
.range([height, 0]);
var chart = d3.select(".chart")
.attr("width", width)
.attr("height", height);
var barWidth = width / data_renewables.length;
var bar = chart.selectAll("g")
.data(data_renewables)
.enter().append("g")
.attr("transform", function(d, i) { return "translate(" + i * barWidth + ",0)"; });
bar.append("rect")
.attr("y", function(d) { return y(d); })
.attr("height", function(d) { return height - y(d); })
.attr("width", barWidth - 1);
bar.append("text")
.attr("x", barWidth / 2)
.attr("y", function(d) { return y(d); + 3; })
.attr("dy", ".75em")
.text(function(d) { return d; });
var axisScale = d3.scale.linear()
.domain([0, 30])
.range([0, 1000]);
var xAxis = d3.svg.axis()
.scale(axisScale)
.orient("bottom");
chart.append("g")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
})
</script>
You're giving the chart a height of height and then your transform is moving the top of the x axis by a value of height so it will always be cut off. I suggest you look at the margin convention: https://bl.ocks.org/mbostock/3019563
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
I ma shifting the X-Axis to bottom, it is not visible and only coming when its on the bar chart. There is some svg area problem which I ma not able to find out. how to shift the barchart a bit upwards so that X=Axis labeling could be accommodated.
Here is the fiddle link Working but X-Axis Label is on the Top
a = 100;
b = 150;
c = 103;
dataset= [a,b,c];
var w = 500;
var h = 250;
var barPadding = 1;
var marginleft = 1;
var margintop =1;
var marginbottom =15;
margin = {top:1, right:10, bottom:1, left:1};
colors = ["#e41a1c", "#377eb8", "#4daf4a"];
h = h ;
var category= ['A', 'B', 'C'];
var x = d3.scale.ordinal()
.domain(category)
.rangeRoundBands([0, w]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(0);;
//Create SVG element
var svg = d3.select("#hello")
.append("svg")
.attr("width", w )
.attr("height", h )
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + h + ")")
.call(xAxis);
// GENERATING RECTANGLES AND MAKING BAR CHART
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return i * (w / dataset.length);
})
.attr("y", function(d) {
return h - (d*1.5) ;
})
.attr("width", w / dataset.length - barPadding)
.attr("height", function(d) {
return (d*2 );
})
.attr("fill", function(d,i) {
return colors[i];
// .attr("fill", function(d) {
// return "rgb(0, 0, " + (d * 10) + ")";
});
var x_Axis = svg.append('g')
.attr('class','xnewaxis')
.attr("transform", "translate(0," + (20) + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "start")
.attr("dx", "-2.5em")
.attr("dy", ".5em")
.attr("transform", "rotate(-15)" );
Your code has several problems:
two different datasets for the bars;
lacks an ordinal scale for positioning the bars (actually, there is one, which you don't use);
lacks a linear scale for the bars values;
calls xAxis twice, with different translations;
But, for solving the axis problem, you just need to translate it correctly:
var x_Axis = svg.append('g')
.attr('class','xnewaxis')
.attr("transform", "translate(0," + (h- 30) + ")")
//30 here is the padding from the bottom of the SVG
Here is your fiddle: https://jsfiddle.net/gfwo0br9/
The bars are still showing up behind the axis (actually, the bars are going way below the end of the SVG itself). To fix that, you'll have to draw the bars properly (with a scale setting the range and the domains).
I have an otherwise fine working grouped bar chart script to which I'm trying to add simple reference lines. The relevant code:
//Set up margins and dimensions according to http://bl.ocks.org/3019563
var margin = {top: 20, right: 10, bottom: 20, left: 30},
width = 810 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
/* Set up the primary x scale */
var x0 = d3.scale.ordinal()
.rangeRoundBands([0, width], .1)
.domain(data.map(function (d) {
return options.xPrimaryScaleAccessor(d);
}));
/* Set up the secondary x scale */
var x1 = d3.scale.ordinal()
.domain(xSecondaryScaleValues)
.rangeRoundBands([0, x0.rangeBand()]);
/* Set up the y scale as a linear (continous) scale with a total range of 0 - full height and a domain of 0-100 */
var y = d3.scale.linear()
.range([height, 0])
.domain([0, 100]);
/* Set up a color space of 20 colors */
var color = d3.scale.category20();
/* Set up the x axis using the primary x scale and align it to the bottom */
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom");
/* Set up the y axis using the y scale and align it to the left */
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
/* Create an SVG element and append it to the body, set its dimensions, append a <g> element to
* it and apply a transform translating all coordinates according to the margins set up. */
var svg = d3.select(options.target).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 + ")");
//Create a space for definitions
var defs = svg.append("defs");
setupDropShadowFilter(defs, 3, 3, 3); //Sets up a gaussian blur filter with id 'drop-shadow'
/* Append a <g> element to the chart and turn it into a representation of the x axis */
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
/* Append a <g> element to the chart and turn it into a representation of the y axis */
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(options.yLabel);
var dataArr = y.ticks(yAxis.ticks());
/* Draw the reference lines */
svg.selectAll("line")
.data(dataArr)
.enter().append("line")
.attr("x1", 0)
.attr("x2", width)
.attr("y1", y)
.attr("y2", y)
.style("stroke", "#ccc");
/* Set up the bar groups */
var group = svg.selectAll(".group")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + x0(options.xPrimaryScaleAccessor(d)) + ",0)"; });
/* Draw the bars */
group.selectAll("rect")
.data(options.valueAccessor)
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("x", function(d) { return x1(d.label); })
.attr("y", function(d) { return y(d.value); })
.attr('rx', options.barCornerRadius)
.attr('ry', options.barCornerRadius)
.attr("height", function(d) { return height - y(d.value); })
.style("fill", function(d) { return getStripedPattern(defs, color(d.label)); //Sets up a pattern and returns its ID })//Todo: fill with pattern instead. see http://tributary.io/tributary/2929255
.style("filter", "url(#drop-shadow)");
/* Draw a legend */
var legend = svg.selectAll(".legend")
.data(xSecondaryScaleValues)
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + (xSecondaryScaleValues.length-i-.25) * (height/xSecondaryScaleValues.length) + ")"; });
legend.append("rect")
.attr("x", width - 9)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("y", 9)
//.attr("dy", ".35em")
.attr("transform", "translate(" + (width - 6) + ",-8)rotate(-90)" )
.style("text-anchor", "start")
.text(function(d) { return d; });
EDIT: I have also tried to append rect elements instead with hardcoded coordinates and dimensions, but those also didn't make it to the DOM.
EDIT 2: More or less full code now included.
Basically, nothing happens. No lines are appended and there are no errors in the console. The dataArr is a plain array of numbers and y(number) is confirmed to return good values in the output range.
I think (and debug suggests) that the chain dies at the append() stage, possibly because .enter() return something useless.
Console log after .data():
Console log after .enter():
Console log after .append():
I've been stuck on this for a good while now, so grateful for any ideas about what may go wrong. I'm sure I'm overlooking something obvious...
The problem is that the code that generates the axes appends line elements to the SVG. As it is run before appending the reference lines, calling svg.selectAll("line").data(...) matches the existing lines with the data. There are more lines than data elements, so no new elements need to be added and the .enter() selection is empty.
There are a few ways to fix this. You could move the code that generates the reference lines further up. You add a g element that contains these lines. You could have a special class for these lines and adjust the selector accordingly. Or you could provide a custom matching function to .data().
Not sure why but my code...which is also very closely following the D3 Bar graph .js tutorial found here: http://mbostock.github.com/d3/tutorial/bar-1.html
Does not draw rectangles for data with the same values from the variable "dataset". Can anyone explain why? or how to fix it?
var dataset = [5, 2, 1, 1, 1, 50] ;
var w = setWidthToWindow(); //setWidthToWindow
var h = setHeightToWindow(); //setHeightToWindow
var x = d3.scale.linear()
.domain([0, d3.max(dataset)])
.range([0, w/2]);
var y = d3.scale.ordinal()
.domain(dataset)
.rangeBands([0, 120]);
var chart = d3.select("#over_rating")
.append("svg")
.attr("class", "chart")
.attr("width", w)
.attr("height", 20 * dataset.length);
chart.selectAll("rect")
.data(dataset)
.enter().append("rect")
.attr("y", y)
.attr("width", x )
.attr("height", y.rangeBand());
chart.selectAll("text")
.data(dataset)
.enter().append("text")
.attr("x", w/2 + 15)
.attr("y", function(d) { return y(d) + y.rangeBand() / 2; })
.attr("dx", 3) // padding-right
.attr("dy", ".35em") // vertical-align: middle
.attr("text-anchor", "end") // text-align: right
.text(String);
You're using an ordinal scale and positioning your bars based on the data value, not the index, so all the data with the value '1' scales to exactly the same position. If you look at your svg, you'll see there are three bars drawn in exactly the same place.
I guess you could set up the scale with index values:
var y = d3.scale.ordinal()
.domain(d3.range(dataset.length))
.rangeBands([0, 120]);
and then scale by the index:
.attr("y", function(d,i) { return y(i); })
which would allow you to add more data and have the width of the bars adjust to accomodate it.
http://jsfiddle.net/findango/nfdST/