I'm new to D3.js and am trying to build a very-wide line chart that pans horizontally to show more data from a .csv file. I haven't been able to find many good resources for the latest version of D3.
What I'm trying to achieve:
chart fills the screen with overflow hidden
drag/mousewheel reveals more data from the right
axis labels stay put (but x-axis changes to reflect new data)
no zooming
The code I have right now renders the chart, and it can be clicked and dragged, but the whole thing just moves off the screen... Here's the code:
var margin = {top: 50, right: 50, bottom: 50, left: 50},
width = window.innerWidth - margin.left - margin.right,
height = window.innerHeight - margin.top - margin.bottom;
var x = d3.scaleLinear().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var line = d3.line()
.x(function(d) { return x(d.mile); })
.y(function(d) { return y(d.elevation); })
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.call(d3.zoom().on("zoom", function () {
svg.attr("transform", d3.event.transform)
}))
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
function draw(data) {
data.forEach(function(d) {
d.mile = +d.mile;
d.elevation = +d.elevation;
});
x.domain(d3.extent(data, function(d) { return d.mile }) )
y.domain([0, d3.max(data, function(d) {
return Math.max(d.elevation) }) ]);
svg.append("path")
.data([data])
.attr("class", "line")
.attr("d", line)
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
svg.append("g")
.call(d3.axisLeft(y));
}
d3.json("data.json", function(error, data) {
if (error) throw error;
draw(data)
});
And here's a sample of what the data looks like:
mile,elevation
1505.9,1800
1506.4,1360
1507.0,1340
1507.9,1750
1509.7,2365
Thanks in advance for any resources anyone can offer me to help solve this!
Related
Dear stack overflow community,
I've been trying to create a bar chart using d3v7 for one of my Uni projects, following the _Blocks_ example. However, I am getting an error as mentioned in this question title. Apparently, my code has a problem reading my data but I can't figure out how to fix this. It would be really helpful if you could help me here :)
Here is my code:
var margin = {top: 5, right:10, bottom:5, left:10},
width = 575 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
// set the ranges
var x = d3.scaleBand()
.range([0, width])
.padding(0.1);
var y = d3.scaleLinear()
.range([height, 0]);
// append the svg object to the body of the page
// append a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("#bar").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 + ")");
// get the data
d3.csv("canton_pop.csv").then(function(data) {
// format the data
data.forEach(function(d) {
d.Population = +d.Population;
});
// Scale the range of the data in the domains
x.domain(data.map(function(d) { return d.Canton; }));
y.domain([0, d3.max(data, function(d) { return d.Population; })]);
// append the rectangles for the bar chart
svg.selectAll(".bar_chart")
.data(data)
.enter().append("rect")
.attr("class", "bar_chart")
.attr("x", function(d) { return x(d.Canton); })
.attr("width", x.bandwidth())
.attr("y", function(d) { return y(d.Population); })
.attr("height", function(d) { return height - y(d.Population); });
// 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));
});
Here is what my CSV file looks like:
Canton;Population
Zürich;1553423
Bern;1043132
Luzern;416347
Uri;36819
Schwyz;162157
Obwalden;38108
Nidwalden;43520
Glarus;40851
Zug;128794
Fribourg;325496
Solothurn;277462
Basel-Stadt;196735
Basel-Landschaft;290969
Schaffhausen;83107
Appenzell Ausserrhoden;55309
Appenzell Innerrhoden;16293
St. Gallen;514504
Graubünden;200096
Aargau;694072
Thurgau;282909
Ticino;350986
Vaud;814762
Valais;348503
Neuchâtel;175894
Genève;506343
Jura;73709
When running my code I am able to visualize the graphics axis but no bar appears and the axis doesn't have any graduation (below is an image of the result I get and of the error that appears in my console). Hopefully, someone can help me here.
Blank bar chart:
The error message that appears in my console:
Okay so I just realized the problem was coming from my CSV file, indeed I was using ";" as separation when I needed to use ","
When building an area chart in D3.js, when you have only a single value the chart does not render.
For demonstration purposes, I modified the following example: https://d3-graph-gallery.com/graph/area_basic.html to illustrate the problem.
<script>
// set the dimensions and margins of the graph
var margin = {top: 10, right: 30, bottom: 30, left: 50},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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 + ")");
//Read the data
d3.csv("https://raw.githubusercontent.com/holtzy/data_to_viz/master/Example_dataset/3_TwoNumOrdered_comma.csv",
// When reading the csv, I must format variables:
function(d){
return { date : d3.timeParse("%Y-%m-%d")(d.date), value : d.value }
},
// Now I can use this dataset:
function(data) {
data = [data[0]]
// Add X axis --> it is a date format
var x = d3.scaleTime()
.domain(d3.extent(data, function(d) { return d.date; }))
.range([ 0, width ]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return +d.value; })])
.range([ height, 0 ]);
svg.append("g")
.call(d3.axisLeft(y));
// Add the area
svg.append("path")
.datum(data)
.attr("fill", "#cce5df")
.attr("stroke", "#69b3a2")
.attr("stroke-width", 1.5)
.attr("d", d3.area()
.x(function(d) { return x(d.date) })
.y0(y(0))
.y1(function(d) { return y(d.value) })
)
})
</script>
I would expect that chart to look something like:
If you inspect the element the path element you can see it is rendering, just 0 width/height:
I am trying to make simple chart right now importing data from a CSV. Everything on the chart is working great except for the labels. In element inspect I can see that they are being appended and that their x and y coordinates are even correct, but for some reason they are all trapped in the top left corner in the SVG itself.
I have tried changing the x placement function at first because I thought it just wasn't giving the labels a x position, but upon further inspection the labels have the correct metadata.
//Graph Dimensions
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 1000 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
//Set Ranges
var x_scale = d3.scaleBand()
.range([0, width])
.padding(0.1);
var y_scale = d3.scaleLinear()
.range([height, 0]);
//Create SVG object
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 + ")");
//Retrieve data
d3.csv('sales.csv').then(function(data){
//Set domains based on data
x_scale.domain(data.map(function(d) { return d.month; }));
y_scale.domain([0, d3.max(data, function(d) { return d.sales; })]);
//Create bars
svg.selectAll("rect")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x_scale(d.month); })
.attr("width", x_scale.bandwidth())
.attr("y", function(d) { return y_scale(d.sales); })
.attr("height", function(d) { return height - y_scale(d.sales); });
//Create labels
svg.selectAll('text')
.data(data)
.enter().append('text')
.attr('class', 'label')
.attr("x", function(d) { return x_scale(d.month); })
.attr("y", function(d) { return y_scale(d.sales); })
.attr( 'font-size', 14 )
.attr( 'fill', '#555555' )
.attr( 'text-anchor', 'middle' );
//Add Axes
svg.append("g") //X Axis
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x_scale));
svg.append("g") //Y Axis
.call(d3.axisLeft(y_scale));
})
The only thing im looking for is the labels actually appearing. I can change their location later if needed.
I'm using Polymer to render some d3 charts. When the Polymer is initially rendered I only draw a graph with axes and no data, since the data comes later once the API calls succeed. However, when I get around to adding the 'rect' elements in the svg, despite them showing up in the Chrome devtools element inspector, they don't show up in the chart itself.
dataChanged: function() {
var data = this.data;
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = this.width - margin.left - margin.right,
height = this.height - margin.top - margin.bottom;
// format the data
data.forEach(function(d) {
d.date = d3.isoParse(d.date);
});
// set the ranges
var x = d3.scaleTime()
.domain(d3.extent(data, function(d) { return d.date; }))
.rangeRound([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var svg = d3.select(this.$.chart);
var histogram = d3.histogram()
.value(function(d) { return d.date; })
.domain(x.domain())
.thresholds(x.ticks(d3.timeMonth));
var bins = histogram(data);
y.domain([0, d3.max(bins, function(d) { return d.length; })]);
var t = d3.transition()
.duration(750)
.ease(d3.easeLinear);
svg.selectAll("rect")
.data(bins)
.enter().append("rect")
.attr("class", "bar")
.attr("x", 1)
.attr("transform", function(d) {
return "translate(" + x(d.x0) + "," + y(d.length) + ")";
})
.attr("width", function(d) { return x(d.x1) - x(d.x0) -1 ; })
.attr("height", function(d) { return height - y(d.length); });
svg.select(".xAxis")
.transition(t)
.call(d3.axisBottom(x));
svg.select(".yAxis")
.transition(t)
.call(d3.axisLeft(y));
},
ready: function() {
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = this.width - margin.left - margin.right,
height = this.height - margin.top - margin.bottom;
// set the ranges
var x = d3.scaleTime()
.domain([new Date(2010, 6, 3), new Date(2012, 0, 1)])
.rangeRound([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
// Add the SVG to my 'chart' div.
var svg = d3.select(this.$.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","xAxis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add the Y Axis
svg.append("g")
.attr("class","yAxis")
.call(d3.axisLeft(y));
}
ready() gets called upon rendering, dataChanged() when the parent component passes a chunk of data down.
The axes get rendered correctly, with the right transitions and the right dimensions, but the rects don't. They show up in the chrome element inspector with a 0x17 size, even though this is what they look like: <rect class="bar" x="1" transform="translate(0,24.06417112299465)" width="101" height="275.93582887700535"></rect>
In your ready function, you are grabbing your div creating an svg element adding a g element and then appending your axis to that g.
In your dataChanged function, you are grabbing your div and appending rects to it.
See the disconnect? You can't parent svg to HTML.
In ready do this:
var svg = d3.select(this.$.chart).append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("id", "canvas")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
In dataChanged:
var svg = d3.select("#canvas");
This will allow you to "find" the appropriate g to append your rects to.
I have unable draw for the line graph in the d3.js,i wrote some code but enable show the graph,how to draw a line for the given data.I have a problem for the how to parse the data for the given data and draw a lines ffor the given data of code.
var data=[{"key":[2000,1,1,12],"value":1000},{"key":[2000,2,1,12],"value":38965},
[{"key":[2000,7,1,24],"value":289},{"key":[2000,8,1,24],"value":389}
]
var parseDate = d3.time.format("%Y.%m.%d.%H").parse;
var map=data.map(function (d){
return {
key:parseDate((d.key[0])+"."+(d.key[1]+1)+"."+(d.key[2])+"."+(d.key[3]+1)),value:+d.value
}
});
console.log(map);
var margin = {top: 15, right: 20, bottom: 70, left: 85},
width = 440,height = 450;
var x = d3.time.scale().range([0, width]);
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")
var line = d3.svg.line()
.x(function(d) { return x(d.key); })
.y(function(d) { return y(d.value); })
.interpolate("cardinal")
var svg = d3.select("body").select("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
x.domain(d3.extent(d3.map(function (d){return d.key;})));
y.domain(d3.extent(d3.map(function (d){return d.key;})));
svg.append("g")
.attr("class", "x axis x-axis")
.attr("transform", "translate(0," + height + ")").attr("fill","steelblue")
.call(xAxis);
svg.append("g").attr("class", "y axis y-axis").attr("fill","steelblue").call(yAxis)
svg.append("path").datum(map).attr("class", "line").attr("d", line);
There seem to be a number of problems with the above code including an extra [ in the data, which should be:
var data=[
{"key":[2000,1,1,12],"value":1000},
{"key":[2000,2,1,12],"value":38965},
{"key":[2000,7,1,24],"value":289},
{"key":[2000,8,1,24],"value":389}
]
Other than that, the way you parse the data seems fine, but there are some issues with the way you set up the chart.
I've created a new line chart using your data here: http://jsfiddle.net/henbox/9etp0xap/, based on this example: http://bl.ocks.org/mbostock/3883245
You'll probably find it easier to modify that jsfiddle rather than to debug the specific issues.