I'm trying to make a singular column for a bar graph in d3.js, the purpose of which is to bar-graph the coefficients of the other line graph in my program. I'm familiar with how they are made when the data is in .csv format, but in this case right now I'm trying to make it from three variables. The three variables are:
var xtwo;
var xone;
var xzero;
which have values put into them in a later part. I've built a skeleton based on what I know and have seen, which is right here:
//Bar Graph
var barmargin = {
top: 20,
right: 20,
bottom: 30,
left: 60
},
barwidth = 500 - barmargin.left - barmargin.right,
barheight = 350 - barmargin.top - barmargin.bottom;
//X scale
var barx = d3.scale.ordinal()
.rangeRoundBands([0, barwidth], .1);
//Y scale
var bary = d3.scale.linear()
.rangeRound([barheight, 0]);
//bar graph colors
var color = d3.scale.ordinal()
.range(["#FF5C33", "#F48C00", "#FFFF5C"]);
// Use X scale to set a bottom axis
var barxAxis = d3.svg.axis()
.scale(barx)
.orient("bottom");
// Same for y
var baryAxis = d3.svg.axis()
.scale(bary)
.orient("left")
.tickFormat(d3.format(".2s"));
// Addchart to the #chart div
var svg = d3.select("#chart").append("svg")
.attr("width", barwidth + barmargin.left + barmargin.right)
.attr("height", barheight + barmargin.top + barmargin.bottom)
.append("g")
.attr("transform", "translate(" + barmargin.left + "," + barmargin.top + ")");
//Where data sorting happens normally
var bardata.data([xzero, xone, xtwo]);
//Y domain is from zero to 5
y.domain([0, 5]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + barheight + ")")
.call(barxAxis);
svg.append("g")
.attr("class", "y axis")
.call(baryAxis);
bardata.selectAll("rect")
.data(function(d) {
return d.types;
})
.enter().append("rect")
.attr("width", barx.rangeBand())
.attr("y", function(d) {
return bary(d.y1);
})
.attr("height", function(d) {
return bary(d.y0) - bary(d.y1);
})
.style("fill", function(d) {
return color(d.name);
});
but I can't really figure out how to make it work correctly. I thought that I could manually make the .data but it seems to not be working that way.
Full code if necessary: https://jsfiddle.net/tqj5maza/1/
Broadly speaking: you want to create three bars, sat on top of each other, from three different values. The three values will be enough to scale the bars, but they in themselves won't be enough to position the bars- each bar needs to be offset by the size of the bars that have gone before.
d3 can only read the values that are already in the data you send it- you can't really access the previous values as you go, as each datum is bound to a separate element. Thus, what you need to do is to create some new data, which has all the numbers required to display it.
Here's one way that you might do that:
var canvas = d3.select("#canvas").append("svg").attr({width: 400, height: 400})
var values = [50, 90, 30]
var colours = ['#FA0', '#0AF', '#AF0']
var data = []
var yOffset = 0
//Process the data
for(var i = 0; i < values.length; i++) {
var datum = {
value : values[i],
colour : colours[i],
x: 0,
y: yOffset
}
yOffset += values[i]
data.push(datum)
}
var bars = canvas.selectAll('rect').data(data)
bars
.enter()
.append('rect')
.attr({
width : 30,
height : function(d) {
return d.value
},
y : function(d) {
return d.y //
}
})
.style({
fill : function(d) {
return d.colour
}
})
http://jsfiddle.net/r3sazt7m/
d3's layout functions all do more or less this- you pass them a set of data, and they return new data containing the values that the SVG drawing instructions require.
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 am making an interactive version of this infographic
This is proving difficult in D3. There will be multiple ordinal scales:
One Y scale for each country
One Y scale for each period in each country
One X scale
For each period, there will be two values (visualized as two different colored bars). The main problem is that not all countries will have three periods; the data does not take a completely even shape. This is to say that some segments of the first Y scale will have different heights. I'm not sure how to take care of this.
Suppose I have this data:
CountryName Period ValueA ValueB
Argentina 2004-2008 12 5
Argentina 2004-2013 10 5
Argentina 2008-2013 8 4
Bolivia 2002-2008 4 2
Bolivia 2002-2013 6 18
Brazil 2003-2008 9 2
Brazil 2003-2013 2 19
Brazil 2008-2013 1 3
And I use the d3.nest() function:
d3.nest()
.key(function(d) { return d.CountryName; })
.key(function(d) { return d.Period; })
.map(data);
Now I'll have the data in the form that I want it, but note that there is some missing data - Bolivia only has two periods of data, in this case. I have created a JSFiddle for this, but as you can see, it has some problems. The height of the bars should be the same, always. I want to use yScale.rangeBand(), but the problem is that some countries will have three periods, where some will only have two. I also need to find a way to display the country names and periods to the left of the bars
If anybody has a better means of approaching this problem, please let me know. I have been struggling for this for a couple of days. As you can see from the JSFiddle, I only have one yScale but I'm sure it's preferable to use two given my situation - I do not know how to implement this.
Any and all help is greatly appreciated
You may not really need the nesting.
This is a special case bar chart so you will need to make axis' bars ticks grids all by yourself.
I have added comments in the code for you to follow
var outerWidth = 1000;
var outerHeight = 500;
var margin = {
left: 100,
top: 0,
right: 100,
bottom: 90
};
var barPadding = 0.6;
var barPaddingOuter = 0.3;
var innerWidth = outerWidth - margin.left - margin.right;
var innerHeight = outerHeight - margin.top - margin.bottom;
//make your svg
var svg = d3.select("body").append("svg")
.attr("width", outerWidth)
.attr("height", outerHeight);
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + 30 + ")");
//the axis is on the right
var xAxisG = g.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + 10 + ")")
//the y axis is common for all
var yAxisG = g.append("g")
.attr("class", "y axis");
var xScale = d3.scale.linear().range([0, innerWidth]);
var yScale = d3.scale.ordinal().rangeRoundBands([0, innerHeight], barPadding, barPaddingOuter);
var yAxis = d3.svg.axis().scale(yScale).orient("left")
.outerTickSize(0);
function render(data) {
var xAxis = d3.svg.axis().scale(xScale).orient("top")
//.tickValues([-5, -4, -3, 0, 3, 5, 10, 15, 20, 25])
.outerTickSize(0)
.innerTickSize(-1 * outerHeight);
xScale.domain([0, 30]);
//this will make the y axis labels.
var country = "";
data.forEach(function(d) {
if (d.CountryName == country) {
d.label = d.Period
} else {
d.label = d.Period
d.heading = d.CountryName;
}
country = d.CountryName;
});
var labels = data.map(function(d) {
return d.label
});
//set the domain for all y labels,
yScale.domain(labels);
//makes the x Axis
xAxisG.call(xAxis);
//makes the y Axis
yAxisG.call(yAxis);
//make the bar chart for valueB
var bars = g.selectAll(".ValueA").data(data);
bars.enter().append("rect")
.attr("height", yScale.rangeBand() / 2);
bars
.attr("x", function(d) {
return xScale(0)
})
.attr("y", function(d) {
return yScale(d.label);
})
.attr("width", function(d) {
console.log(d.ValueA)
return xScale(d.ValueA);
})
.style("fill", "red")
.attr("class", "ValueA");
//make the bar chart for valueB
var bars = g.selectAll(".ValueB").data(data);
bars.enter().append("rect")
.attr("height", yScale.rangeBand() / 2);
bars
.attr("x", function(d) {
return xScale(0)
})
.attr("y", function(d) {
return yScale.rangeBand() / 2 + yScale(d.label);
})
.attr("width", function(d) {
return xScale(d.ValueB);
})
.style("fill", "blue")
.attr("class", "ValueA");
//make grid lines
var lines = g.selectAll(".xLine").data(data.filter(function(d){return !(d.heading == undefined) }));
lines.enter().append("line")
lines
.attr("x1", 0)
.attr("y1", function(d) {
return (yScale(d.label) - yScale.rangeBand())
})
.attr("x2", innerWidth)
.attr("y2", function(d) {
return (yScale(d.label) - yScale.rangeBand())
})
.style("stroke", "blue")
.attr("class", "xLine")
.style("display", function(s) {
if((yScale(s.label) - yScale.rangeBand()) < 0){
return "none";//not show grids when y comes negative
}
});
//make heading
var headings = g.selectAll(".heading").data(data.filter(function(d){return !(d.heading == undefined) }));
headings.enter().append("text")
.text(function(d){return d.heading})
.attr("x", -100)
.attr("y", function(d) {
return (yScale(d.label) - yScale.rangeBand()) +30
})
}
function type(d) {
d.ValueA = +d.ValueA;
d.ValueB = +d.ValueB;
console.log(d)
return d;
} d3.csv("https://gist.githubusercontent.com/cyrilcherian/e2dff83329af684cde78/raw/f85ce83497553c360d2af5d5dcf269390c44022f/cities.csv", type, render);
.tick line {
opacity: 0.2;
}
.xLine {
opacity: 0.2;
}
.axis text {
font-size: 10px;
}
.axis .label {}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.y.axis path,
.y.axis line {
stroke: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Hope this helps!
I'm pretty new to D3 and am working on a reusable bar chart which can be sent new data and it will update the existing graph based on the new data that it receives.
Currently, it is correctly drawing the chart, but when the data changes it is overlaying a graph of the new data over the old graph instead of updating it.
I believe the issue must be with my bars variable and how I'm binding my data but I can't be sure. The data is simply the score of a reddit post from the reddit JSON API.
Example:
My code for the chart is:
d3.namespace.barChart = function() {
var data, group, height, margin = {top: 0, right: 0, bottom: 0, left: 0}, width;
function chart(container) {
group = container;
// Determine the max score and timestamps from the Reddit data for use in our scales
var maxScore = d3.max(data, function(d) { return d.data.score; });
data.forEach(function(d) {
d.data.created *= 1000;
});
// Create the scales to ensure data fits in the graphs
var xScale = d3.scale.ordinal()
.domain(d3.range(data.length))
.rangeBands([0, width], 0.25);
var yScale = d3.scale.linear()
.domain([0, maxScore])
.range([0, height]); // .range(starting, )
var yScaleLine = d3.scale.linear()
.domain([0, maxScore])
.range([height, 0]);
var colorScale = d3.scale.linear()
.domain(d3.extent(data, function(d) { return d.data.score; }))
.range(['#DA4453', '#A0D468'])
.interpolate(d3.interpolateRgb);
// Create the Y-Axis function. yScaleLine is used as the scale to ensure
// that the data goes from 0 at the bottom left to max score in the top left
var yAxis = d3.svg.axis()
.scale(yScaleLine)
.orient('left');
// Setup our chart using our width, height and margins.
group.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom);
// Add a group element for the primary graphic that is moved
// using the margins. Left and top are used since 0,0 is top left corner
var graph = group.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// Append our yAxis to the SVG canvas and move it according to the margins
group.append('g')
.attr('class', 'y axis')
.call(yAxis)
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// Inside of our group, we need to make a 'rect' for each item in the dataset
// We link the data, and append one rect for each piece of data.
var bars = graph.selectAll('rect')
.data(data, function(d) { return d.data.id; });
bars.enter()
.append('rect');
// We begin with the height of each bar as 0 allowing us to transition to their full height
bars.attr({
x: function(d, i) { return xScale(i); },
y: function() { return height; },
height: 0,
width: xScale.rangeBand(),
fill: function(d) { return colorScale(d.data.score); }
});
// Events must be linked before a transition to function
bars.on('mouseover', function() {
d3.select(this).attr('fill', '#000');
})
.on('mouseleave', function() {
d3.select(this).attr('fill', function(d) { return colorScale(d.data.score); });
});
bars.transition().duration(150)
.attr({
x: function(d, i) { return xScale(i); },
y: function(d) { return height - yScale(d.data.score); },
height: function(d) { return yScale(d.data.score); },
width: xScale.rangeBand()
});
bars.exit().transition().duration(150).attr({ height: 0.0001 }).remove();
}
EDIT:
After digging into the created source it seems it's not updating but rather just continuing to create a new svg g element for all the rects. Removing the group.append('g') chain fixes the graph not updating but the graph is no longer in a group and makes the SVG very messy.
I'm building a bar chart in d3.js with an ordinal x-axis whose ticks should label the chart with text. Could anyone explain how the ordinal scale "maps" x ticks to the corresponding bar positions? Specifically, if I want to designate the x tick labels with an array of text values to the corresponding bars in a bar chart.
Currently I'm setting the domain as the following:
var labels = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t"];
var xScale = d3.scale.ordinal()
.domain(labels)
However, values of 1-19 are showing after the text labels.
As seen in this fiddle:
http://jsfiddle.net/chartguy/FbqjD/
Associated Fiddle Source Code:
//Width and height
var margin = {top: 20, right: 20, bottom: 30, left: 40};
var width = 600 - margin.left - margin.right;
var height= 500-margin.top -margin.bottom;
var w = width;
var h = height;
var dataset = [ 5, 10, 13, 19, 21, 25, 22, 18, 15, 13,
11, 12, 15, 20, 18, 17, 16, 18, 23, 25 ];
var labels = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t"];
var xScale = d3.scale.ordinal()
.domain(labels)
.rangeRoundBands([margin.left, width], 0.05);
var xAxis = d3.svg.axis().scale(xScale).orient("bottom");
var yScale = d3.scale.linear()
.domain([0, d3.max(dataset)])
.range([h,0]);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
//Create bars
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return yScale(d);
})
.attr("width", xScale.rangeBand())
.attr("height", function(d) {
return h - yScale(d);
})
.attr("fill", function(d) {
return "rgb(0, 0, 0)";
});
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + 0 + ")")
.call(xAxis);
//Create labels
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {
return d;
})
.attr("x", function(d, i) {
return xScale(i) + xScale.rangeBand() / 2;
})
.attr("y", function(d) {
return h - yScale(d) + 14;
});
You can set the tick values of an ordinal axis explicitly using d3.svg.axis().tickValues(*array*).
But this is an odd way to do it because it dangerously separates your keys and values, meaning you have to take care to manually align the scales and make sure that your data corresponds correctly. It helps to group the keys and values in a single object and then use the format:
axis.domain(array.map(function (d) { return d.value; }))
to map your axis domains.
I have reworked your data and fiddle to do it in what I see as the more d3 way. (Also note that I made some other changes just for fun, namely improved the margins and cleaned up the axis alignment, etc.)
I've an incredibly basic syntax question. I've been learning d3, SVG, and Javascript mostly by editing someone else's code, which is challenging.
The goal is to update a y axis after updating the data and the scale, which is based on the data. I want the axis--ticks and labels and all--to transition with the domain of the data. The axis isn't getting updated. The problem might be related to scope, or I'm referencing the wrong SVG element. (There are actually several plots getting updated simultaneously, but I'm just focusing on the axis of one of them here.)
function makeYaxis (chart, scale, nticks, label, width, height, xmf, visName)
{
var yAxis = d3.svg.axis()
.scale(scale)
.orient("left")
.ticks(nticks);
chart.append("svg:g")
.attr("class","y axis")
.append("g")
.attr("transform","translate(60,1)") // fix magic #
.call(yAxis);
var xMove = xmf.yylMarginFactor * width - 1;
var yMove = (((1 - xmf.xxbMarginFactor) * height +
xmf.xxtMarginFactor * height) / 2);
chart.append("svg:text")
.attr("class", visName + "xLabel")
.attr("x", 0)
.attr("y", 0)
.attr("dy", "-2.8em")
.text(label)
.attr("transform", "rotate(-90) translate(-" + yMove + "," + xMove + ")");
}
function makeYscale (plotMargin, thedata, xmf)
{
var dataMin = d3.min(thedata[0]);
var dataMax = d3.max(thedata[0]);
var yyMargin = d3.max([plotMargin * (dataMax - dataMin),0.05]);
var yy = d3.scale.linear()
.domain([dataMin - yyMargin, yyMargin + dataMax])
.range([(1 - xmf.xxbMarginFactor) * height, xmf.xxtMarginFactor * height]);
return yy;
}
// Set up this visualization
var chart = d3.select("#vis1")
.append("svg:svg")
.attr("class", "vis1chart")
.attr("width", width)
.attr("height", height);
var yy = makeYscale(plotMargin, thetas, xmf);
makeYaxis(chart, yy, nYTicks, "parameter", width, height, xmf, "vis1");
var points = chart.selectAll("circle.vis1points")
.data(thetas)
.enter().append("svg:circle")
.attr("class", "vis1points")
.attr("cx", function (d, i) { return xx(i); })
.attr("cy", function (d, i) { return yy(d); })
.attr("r", 3);
points.transition()
.attr("cx", function (d, i) { return xx(i); })
.attr("cy", yy)
.duration(dur);
vis1move();
function vis1move ()
{
function movePoints ()
{
var tmp = chart.selectAll(".vis1points").data(thetas[0], function (d, i) { return i; } );
var opa1 = points.style("stroke-opacity");
var opa2 = points.style("fill-opacity");
tmp
.enter().insert("svg:circle")
.attr("class", "vis1points")
.attr("cx", function (d, i) { return xx(i); })
.attr("cy", yy)
.attr("r", 3)
.style("stroke-opacity", opa1)
.style("fill-opacity", opa2);
tmp.transition()
.duration(10)
.attr("cx", function (d, i) { return xx(i); })
tmp.exit()
.transition().duration(10).attr("r",0).remove();
};
// (Code for updating theta, the data, goes here)
// Update axes for this vis
yy = makeYscale(plotMargin, thetas, xmf);
var transition = chart.transition().duration(10);
var yAxis = d3.svg.axis()
.scale(yy)
.orient("left")
.ticks(4);
transition.select("y axis").call(yAxis);
movePoints(); // Previously had been before axis update (fixed)
}
Sorry I can't get this self-contained.
Is transition.select.("y axis").call(yAxis) correct? Is there anything glaringly off here?
Edit: To keep things simple, I now have
// Update axes for this vis
yy = makeYscale(plotMargin, thetas, xmf);
var yAxis = d3.svg.axis().scale(yy);
chart.select("y axis").transition().duration(10).call(yAxis);
I'm wondering if something in the way I created the axis in the first place (in makeYaxis()) prevents me from selecting it properly. The yy function is returning the right values under the hood, and the data points are getting plotted and rescaled properly. It's just that the axis is "stuck."
Following meetamit's suggestions and the example here, I have stumbled on what appears to be a solution.
First, in function makeYaxis(), I removed the line .append("g"). I also updated the class name to yaxis. The function now reads
function makeYaxis (chart, scale, nticks, label, width, height, xmf, visName)
{
var yAxis = d3.svg.axis()
.scale(scale)
.orient("left")
.ticks(nticks);
chart.append("svg:g")
.attr("class","yaxis") // note new class name
// .append("g")
.attr("transform","translate(60,1)")
.call(yAxis);
// everything else as before
}
I then added an extra period in my call to select(".yaxis"):
chart.select(".yaxis").transition().duration(10).call(yAxis);
I would be very grateful if anyone could explain to me exactly why this solution appears to work.