I am entering d3.js at ground level. There's a steep learning curve ahead of me, I know, but I didn't quite expect to get stuck on the second simple tutorial I tried. Now's the change to make the maximal marginal contribution to a fellow programmer's understanding!
I'm trying to get a simple svg bar chart to work using my data, which is virtually identical to the sample.
Bostock's data
name value
Locke 4
Reyes 8
Ford 15
Jarrah 16
Shephard 23
Kwon 42
My data
year value
year2013 2476
year2014 7215
year2015 23633
year2016thru229 21752
Note that our second columns have the same name. Both contain numbers. I gave my dataset the same name he did. Thus I should be able to run his code (below) without changing a thing, but it returns this:
Unexpected value NaN parsing width attribute.
...uments);null==e?this.removeAttribute(n):this.setAttribute(n,e)}function
d3.min.js (line 1, col 2575)
Unexpected value NaN parsing x attribute.
...uments);null==e?this.removeAttribute(n):this.setAttribute(n,e)}function
d3.min.js (line 1, col 2575)
His code as well as the tutorial are here.
You're not feeding D3 your data in the correct format.
https://jsfiddle.net/guanzo/kdcLj5jj/1/
var data = [
{year:'year2013',value:2476},
{year:'year2014',value:7215},
{year:'year2015',value:23633},
{year:'year2016thru229',value:21752}
]
var width = 420,
barHeight = 20;
var x = d3.scale.linear()
.range([0, width]);
var chart = d3.select(".chart")
.attr("width", width);
x.domain([0, d3.max(data, function(d) { return d.value; })]);
chart.attr("height", barHeight * data.length);
var bar = chart.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d, i) { return "translate(0," + i * barHeight + ")"; });
bar.append("rect")
.attr("width", function(d) { return x(d.value); })
.attr("height", barHeight - 1);
bar.append("text")
.attr("x", function(d) { return x(d.value) - 3; })
.attr("y", barHeight / 2)
.attr("dy", ".35em")
.text(function(d) { return d.value; });
function type(d) {
d.value = +d.value; // coerce to number
return d;
}
Related
Looking to stylize a bar chart using a D3 SVG. Right now it contains a number on the y axis and, on the x axis, day by day labels. There is a tick mark for each day, where I want only the first date of the week displayed. How can I show just one a week? Code below:
d3.csv("us-counties-cases.csv", function(data) {
filteredData = data.filter(function(row) {
return row['county'] == 'New York City';
});
var x = d3.scaleBand()
.range([ 0, width])
.domain(filteredData.map(function(d) {
return d.date;
}))
.padding(0.2);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.selectAll("text")
.attr("transform", "translate(-10,0)rotate(-45)")
.style("text-anchor", "end");
// Add Y axis
var max = d3.max(filteredData, function(d) { return d.cases; });
var y = d3.scaleLinear()
.domain([0, max * 1.2])
.range([ height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// Bars
svg.selectAll("mybar")
.data(filteredData)
.enter()
.append("rect")
.attr("x", function(d) {
return x(d.date);
})
.attr("width", x.bandwidth())
.attr("fill", "#b3b3b3")
.attr("height", function(d) {
return height - y(0);
})
.attr("y", function(d) {
return y(0);
})
// Animation
svg.selectAll("rect")
.transition()
.duration(200)
.attr("y", function(d) {
return y(d.cases);
})
.attr("height", function(d) {
return height - y(d.cases);
})
.delay(function(d,i){
return(i*50)
})
})
Usually you can use axis.tick() to set the arguments that controls how ticks are displayed. Among other things, you can set how many ticks to visualize.
However, as you can read in the documentation, the effect of this method and its arguments depends on the axis’ scale type. If you are using scaleBand, axis.tick() has no effect and you should use axis.tickValues to set the tick values explicitly. Similarly, if you want to change the tick format, it's necessary to use axis.tickFormat.
For your specific case, the following code modifies the x axis to show only one tick every 7 days:
d3.axisBottom(x).tickValues(x.domain().filter( (d,i) => !(i % 7) ))
Current situation: I already have a small multiple visualization for my data. What it represents is the stress intensity over time for six different days. It plots the graphs correctly. Now I wanted to add dots on the existing graph if the person smoked at that time. I am reading a csv file which consists of date, time, stress level and whether the person smoked or not (so 1 if they did and -1 if they didn't). I am using d3 v4.
This is what I am currently getting but the red dots are obviously in the wrong spot because they are showing up places I don't even have data.
What I wanted was for the red dots to be on the graph and represent the times the user smoked.
Code:
<script>
var margin = {top: 8, right: 10, bottom: 2, left: 10},
width = 1160 - margin.left - margin.right,
height = 100 - margin.top - margin.bottom;
var parseDate = d3.timeParse("%H:%M:%S");
var x = d3.scaleTime()
.range([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var area = d3.area()
.x(function (d) {
return x(d.time);
})
.y0(height)
.y1(function (d) {
return y(d.stress);
});
var line = d3.line()
.x(function (d) {
return x(d.time);
})
.y(function (d) {
return y(d.stress);
});
d3.csv("6000smokedData3.csv", type, function (error, data) {
// Nest data by date.
var dates = d3.nest()
.key(function (d) {
return d.date;
})
.entries(data);
// Compute the maximum stress per date, needed for the y-domain.
dates.forEach(function (s) {
s.maxPrice = d3.max(s.values, function (d) {
return d.stress;
});
});
// Compute the minimum and maximum time across dates.
// We assume values are sorted by time.
x.domain([
d3.min(dates, function (s) {
return s.values[0].time;
}),
d3.max(dates, function (s) {
return s.values[s.values.length - 1].time;
})
]);
// Add an SVG element for each date, with the desired dimensions and margin.
var svg = d3.select("body").selectAll("svg")
.data(dates)
.enter().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 + ")");
//Add the scatterplot
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", 4)
.style("fill", function (d) {
return "red";
})
.attr("cx", function (d) {
if (d.smoked == 1) {
return x(d.time);
}
})
.attr("cy", function (d) {
if (d.smoked == 1) {
return y(d.stress);
}
});
// Add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add the Y Axis
svg.append("g")
.call(d3.axisLeft(y));
// Add the area path elements. Note: the y-domain is set per element.
svg.append("path")
.attr("class", "area")
.attr("d", function (d) {
y.domain([0, d.maxPrice]);
return area(d.values);
});
// Add the line path elements. Note: the y-domain is set per element.
svg.append("path")
.attr("class", "line")
.attr("d", function (d) {
y.domain([0, d.maxPrice]);
return line(d.values);
});
// Add a small label for the date name.
svg.append("text")
.attr("x", width - 6)
.attr("y", height - 6)
.style("text-anchor", "end")
.text(function (d) {
return d.key;
});
});
function type(d) {
d.stress = +d.stress;
d.time = parseDate(d.time);
d.smoked = +d.smoked;
return d;
}
</script>
Few lines of csv file:
date,time,stress,smoked
2014-08-04,11:24:28,0.026191,-1
2014-08-04,11:24:29,0.026183,-1
2014-08-04,11:24:30,0.031845,-1
2014-08-04,11:24:31,0.01235,-1
Thank you
You're drawing the dots before you set the y scale for each element. I usually like to make small multiples inside of an each loop to avoid tricky things like. It looks like the y axis is also off - they should be different on each plot.
I'm using D3 to present some data as a horizontal bar chart. Values will typically range between -10 and +10 on 8 different scales. I have the bars rendering as I want, but I can't work out how to add lables for each of the extreems of the axes.
so far I have:
but I want to achieve something like:
In other words a label for each extreme of each scale.
I have found lots of examples that add data labels to the bars them selves (e.g. the value), but I want to some how force the array of strings to be rendered at the extremes of the container.
At the moment, I am rendering the data from an array, and I have the labels stored in 2 other arrays e.g.
var data = [10, 5, -5, -10, 2, -2, 8, -8];
var leftLabels = ["label 1","label 2", ...];
var rightLabels = ["label 1", "label 2", ...];
Any ideas or links to examples most welcome.
I am not an expert in d3.js, but I think this can be easily done. There are different ways to go about it. I have created a pen for your use case.
I will paste the important part of the code below. In your chart, you will have to certainly make some adjustments to suit your needs. Feel free to play around with the values until you feel they are stable.
// Your array containing labels for left and right values
var leftSideData = ["left1", "left2", "left3", "left4", "left5", "left6", "left7", "left8"];
var rightSideData = ["right1", "right2", "right3", "right4", "right5", "right6", "right7", "right8"];
var left = svg.selectAll(".leftData")
.data(leftSideData)
.enter().append("g")
.attr("class", "leftVal")
.attr("transform", function(d, i) {
return "translate(0," + i * 57 + ")";
});
left.append("text")
.attr("x", 0)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) {
return d;
});
var right = svg.selectAll(".rightData")
.data(rightSideData)
.enter().append("g")
.attr("class", "rightVal")
.attr("transform", function(d, i) {
return "translate(0," + i * 57 + ")";
});
right.append("text")
.attr("x", width + 30)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) {
return d;
});
I won't say this is perfect, but I hope you get an idea about how to approach it. All the best!!
It's funny, just by asking the q on SE I find it helps me reformulate the problem.. and then some time later a new try yields a result. Anyone else find that?
I managed to make it work by changing the way the SVG was created. So I now have the following structure:
<SVG>
><g> (one for each bar)
>><text>
>><rect>
>><text>
><other stuff like axies>
It turns out that <text> elements cannot be added to <rect> elements (well they can, be added but they won't render).
the code is:
var data = [10,2,4,-10,...etc...];
var leftLabels = ["left 1","left 1", ...etc...];
var rightLabels = ["right 1","right 2", ...etc...];
//chart dimentions
var margin = { top: 20, right: 30, bottom: 40, left: 30 },
width = 600 - margin.left - margin.right,
barHeight = 30,
height = barHeight * data.length;
//chart bar scaling
var x = d3.scale.linear()
.range([100, width-100]);
var y = d3.scale.ordinal()
.rangeRoundBands([0, height], 0.1);
var chart = d3.select(".chartsvg")
.attr("width", width + margin.left + margin.right)
.attr("height", barHeight * data.length + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
x.domain([d3.min(data), d3.max(data)]);
//append a g for each data item
var bar = chart.selectAll(".bar")
.data(data)
.enter()
.append("g");
//in each bar add a rect for the bar chart bar
bar.append("rect")
.attr("class", function (d) { return "bar--" + (d < 0 ? "negative" : "positive"); })
.attr("x", function (d) { return x(Math.min(0, d)); })
.attr("y", function (d, i) { return i* barHeight; })
.attr("width", function (d) { return Math.abs(x(d) - x(0)); })
.attr("height", barHeight-1);
//append the labels to each g using the label data
bar.append("text")
.data(rightLabels)
.attr("x", width)
.attr("y", function (d, i) { return (i * barHeight)+barHeight/2; })
.attr("dy", ".5em")
.attr("fill","steelblue")
.attr("text-anchor","end")
.text(function (d) { return d; });
bar.append("text")
.data(leftLabels)
.attr("x", 0)
.attr("y", function (d, i) { return (i * barHeight) + barHeight / 2; })
.attr("dy", ".5em")
.attr("fill","darkorange")
.attr("text-anchor", "start")
.text(function (d) { return d; });
//then append axis etc...
Formatting: something else to note. It turns out that to color the text in the label you need to use "stroke" and "fill" attributes. These are broadly equiv to the HTML "color" attribute on text.
I have a json object that I am trying to visualize with D3.js. I want the x axis to represent the date in the json object which is stored as a string and the y axis to represent sales projections which is also a number in a string i.e "85,000.00"
example of my json object:
[{"Num":78689,"Client":"Health Services" ,"TotalEstSales":"85,000,000.00","Date ":"2/15/2015","RFP Receipt Date":null,"Exp. Proposal Due Date":"3/6/2015","Proposal Submission Date":null,"estAwardDate":"4/15/2015","Procurement Type":"New - Incumbent","Bid Type":"Standalone Contract"}]
and my d3 code:
// Various accessors that specify the four dimensions of data to visualize.
function x(d) { return d.date; }
function y(d) { return d.TotalEstSales; }
function radius(d) { return parseFloat(d.TotalEstSales);}
function color(d) { return d.region; }
function key(d) { return d.Title;}
// Chart dimensions.
var margin = {top: 19.5, right: 19.5, bottom: 19.5, left: 39.5},
width = 960 - margin.right,
height = 500 - margin.top - margin.bottom;
// Various scales. These domains make assumptions of data, naturally.
var xScale = d3.scale.log().domain([300, 1e5]).range([0, width]),
yScale = d3.scale.linear().domain([10000, 85000000]).range([height, 0]),
radiusScale = d3.scale.sqrt().domain([0, 5e8]).range([0, 40]),
colorScale = d3.scale.category10();
// The x & y axes.
var xAxis = d3.svg.axis().orient("bottom").scale(xScale).ticks(12, d3.format(",d")),
yAxis = d3.svg.axis().scale(yScale).orient("left");
// Create the SVG container and set the origin.
var svg = d3.select("#chart").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 + ")");
// Add the x-axis.
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the y-axis.
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
// Add an x-axis label.
svg.append("text")
.attr("class", "x label")
.attr("text-anchor", "end")
.attr("x", width)
.attr("y", height - 6)
.text("Data of RFP");
// Add a y-axis label.
svg.append("text")
.attr("class", "y label")
.attr("text-anchor", "end")
.attr("y", 6)
.attr("dy", ".75em")
.attr("transform", "rotate(-90)")
.text("Award amount");
// Add the year label; the value is set on transition.
var label = svg.append("text")
.attr("class", "year label")
.attr("text-anchor", "end")
.attr("y", height - 24)
.attr("x", width)
.text(2015);
// Load the data.
d3.json("rfpdata.json", function(data) {
// A bisector since many nation's data is sparsely-defined.
// var bisect = d3.bisector(function(d) { return d[0]; });
// Add a dot per nation. Initialize the data at 1800, and set the colors.
var dot = svg.append("g")
.attr("class", "dots")
.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.style("fill", function(d) { return colorScale(color(d)); })
.call(position)
.sort(order);
// Add a title.
dot.append("title")
.text(function(d) { return d.Client; })
// Positions the dots based on data.
function position(dot) {
dot .attr("cx", function(d) { return xScale(x(d)); })
// .attr("cy", function(d) { return yScale(y(d)); })
.attr("r", function(d) { return radiusScale(radius(d)); });
}
// Defines a sort order so that the smallest dots are drawn on top.
function order(a, b) {
return radius(b) - radius(a);
}
// After the transition finishes, you can mouseover to change the year.
function enableInteraction() {
var yearScale = d3.scale.linear()
.domain([1800, 2009])
.range([box.x + 10, box.x + box.width - 10])
.clamp(true);
// Cancel the current transition, if any.
function mouseover() {
label.classed("active", true);
}
function mouseout() {
label.classed("active", false);
}
function mousemove() {
displayYear(yearScale.invert(d3.mouse(this)[0]));
}
}
// this is the function needed to bring in data
// Interpolates the dataset for the given (fractional) year.
function interpolateData(date) {
return data.map(function(d) {
return {
title: d.Title,
client: d.Client,
sales: parseFloat(d.TotalEstSales),
sales: interpolateValues(d.TotalEstSales, date),
};
});
}
// Finds (and possibly interpolates) the value for the specified year.
function interpolateValues(values, date) {
var i = bisect.left(values, date, 0, values.length - 1),
a = values[i];
if (i > 0) {
var b = values[i - 1],
t = (date - a[0]) / (b[0] - a[0]);
return a[1] * (1 - t) + b[1] * t;
}
return a[1];
}
});
I am not sure what I am doing wrong but the data is not displaying? Am i properly parsing the date string? This was a graph available on the d3 site. I want a bubble graph where the radius changes depending on the size of the sale and the date is on the x axis.
#all Update:
I was able to make the proper adjustment for date on the xaxis here:
var xAxis = d3.svg.axis().orient("bottom").scale(xScale).tickFormat(d3.time.format("%m/%d")),
yAxis = d3.svg.axis().scale(yScale).orient("left").ticks(23, d3.format(" ,d"));
d3.time.format was what I was looking for. Once data was loaded I needed to parse the date:
month = data.Date;
parseDate = d3.time.format("%m/%d/%Y").parse;
data.forEach(function(d) {
d.Date = parseDate(d.Date);
});
// update Dates here when new report comes in monthly
xScale.domain([parseDate("1/1/2015"),parseDate("6/1/2015")]);
obviously, using "Date" as a name column in the excel file was not idea for "Date" in js(because it is an oject).
I'm trying to build a stacked bar chart in D3js. I have problems to set properly y and y0 attributes and draw the bars on their right positions. Probably I have a calculation mistake but I cannot find it. This is the link to the example code FIDDLE
The scenario is:
I group the data first by "period" and the periods are shown on xAxis
Then I have grouping by "type" - MONTH and ENTRY which should be stacked bars in different colors.
The sum "amount" for each type per each period is shown on yAxis.
I use nest function with 2 keys to structure the data. The problem appears when I draw the bars in the actual stacked bar chart. I'm not sure whether the problem is in the way I access the data (key and values) or in the way I set the attributes "y" and "height".
selection.selectAll("rect")
.data(function (d) { return d.values; })
.enter().append("rect")
.attr("width", x.rangeBand())
.attr("y", function (d) { return y(d.values); })
.attr("height", function (d) { return y(d.y0) + y(d.values); })
//.attr("height", function (d) { return y(d.y0) - y(d.values); })
.style("fill", function (d) { return color(d.key); })
The obvious errors are that one of the bars is hidden behind another one. And the second bar is under the xAxis.
I'm beginner in d3js and I cannot find the solution. Can somebody help me?
I can see a few things:
It looks like you're overcomplicating the nest. You should only need to nest a single level.
The max value that you're calculating will only ever be the maximum of a single element of the stack, when you actually want the maximum to be the total of the stack.
The group elements that you're creating (g), seem to be grouped the "wrong" way. You generally want to group the same "bit" of each stack. That is, you want the first rect of each stack to be in the same group as the other first rects. Then the second one in each stack will be grouped with the other second rects and so on. This is probably due to the nesting error in the first point.
You actually need to calculate the valueOffset, which you've got in your fiddle, but is commented out. This value is used to set the relative position when constructing the stack.
To help, I've put together what seems right based on what you've written. Check out the snippet below.
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 400 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var color = d3.scale.category10();
var data = [
{
"period":201409,
"type":"MONTH",
"amount":85.0
},
{
"period":201409,
"type":"ENTRY",
"amount":111.0
},
{
"period":201410,
"type":"MONTH",
"amount":85.0
},
{
"period":201410,
"type":"ENTRY",
"amount":55.0
}
];
var x = d3.scale.ordinal().rangeRoundBands([0, width], .1, 0);
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").ticks(10);
var svg = d3.select("#chart")
.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 + ")");
data.forEach(function(d) {
d["period"] = d["period"];
d["amount"] = +d["amount"];
d["type"] = d["type"];
});
var nest = d3.nest()
.key(function(d) { return d["type"];});
var dataByType = nest.entries(data);
//var max = d3.max(dataByGroup, function(d) { return d3.sum(d.values, function(e) { return e.values; }); })
//console.log("dataByGroup", dataByGroup);
var stack = d3.layout.stack()
.values(function(d) { return d.values; })
.x(function(d) { return d.period; })
.y(function(d) { return d.amount; })
.out(function(d, y0) {
d.valueOffset = y0;
});
//data: key: group element, values: values in each group
stack(dataByType);
var yMax = d3.max(dataByType, function(type) { return d3.max(type.values, function(d) { return d.amount + d.valueOffset; }); });
color.domain(dataByType[0].values.map(function(d) { return d.type; }));
x.domain(dataByType[0].values.map(function(d) { return d.period; }));
y.domain([0, yMax]);
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", 3)
.attr("dy", ".71em")
.style("text-anchor", "end");
var selection = svg.selectAll(".group")
.data(dataByType)
.enter().append("g")
.attr("class", "group");
//.attr("transform", function(d) { return "translate(0," + y0(y0.domain()[0]) + ")"; });
selection.selectAll("rect")
.data(function (d) { return d.values; })
.enter().append("rect")
.attr("width", x.rangeBand())
.attr("x", function(d) { return x(d.period); })
.attr("y", function (d) { return y(d.amount + d.valueOffset); })
.attr("height", function (d) { return y(d.valueOffset) - y(d.valueOffset + d.amount); })
.style("fill", function (d) { return color(d.type); })
.style("stroke", "grey");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="chart"></div>
Some notes on the above snippet (that match my comments):
A much simpler nest:
var nest = d3.nest()
.key(function(d) { return d["type"];});
This is much simpler than your previous one, and there is no need to do the rollup function. Rollups are generally required when you want to aggregate your data, in this case you don't need to, which should be a giveaway that your nesting was too complex.
The calculation of the maximum value for the y axis:
var yMax = d3.max(dataByType, function(type) { return d3.max(type.values, function(d) { return d.amount + d.valueOffset; }); });
This will calculate the maximum value that your axis needs to take, making everything fit nicely.
If you look at the resulting SVG, you'll see what I mean about the grouping of the rects in each stack. I generally find that it's easier to group this way. I guess there's no "right" way, but this typically works best for me.
The calculation of the valueOffset in the stack:
d3.layout.stack()
.values(function(d) { return d.values; })
.x(function(d) { return d.period; })
.y(function(d) { return d.amount; })
.out(function(d, y0) {
d.valueOffset = y0;
});
The calculated valueOffset is used to "move" each rect in the stack into position relative to the other rects. You'll see it used a few times, calculating the max y value, the y attr of each rect, and the height of each rect.
I haven't explained every change that I've made, but hopefully with the above and the snippet you'll be able to work through the differences and apply it your exact use case.