I don't understand why the first label (2000) is missing. I compared it to many examples without finding why.
let xAxisScale = d3
.scaleTime()
.domain([min, max]) // 2000, 2013
.range([0, width]);
xAxis = d3
.axisBottom()
.tickFormat(d3.timeFormat('%Y'))
.tickPadding(5)
.ticks(d3.timeYear)
.scale(xAxisScale);
gx = innerSpace
.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
Do you see any errors ?
Thanks !
This problem may be due to small variations in min date. It depends on the way you construct min date.
For example, new Date('2000-01-01') does not include year 2000 if your time zone is >0. You can write new Date('2000-01-01T00:00:00Z') to use UTC.
Also, if you use new Date(YYYY, MM, DD), you should take into account, that month starts from zero, while day starts from one.
Read about correct way of dealing with that problem here: http://bl.ocks.org/jebeck/9671241
Next to your nice answer I tried different ways to get a datetime without utc / gmt problems but didn't find a nice solution of dealing with it for all web explorer without using library.
The best solution I find is to use the library momentjs because momentjs will nicely handle the timezone :
const minMoment = moment(`01/01/${minDate}`, 'DD/MM/YYYY', true);
const maxMoment = moment(`01/01/${maxDate}`, 'DD/MM/YYYY', true);
this.xAxisScale = d3
.scaleTime()
.domain([minMoment.toDate(), maxMoment.toDate()])
.range([0, width]);
Related
I have been plotting a multi-series line chart to show covid cases by using d3.js. I used the country as the variable for each time series. But some countries have a too-small values and some countries have too high at some point of time. Because of that, it's complicated to read the diagram. How to solve these differences in data.
The above image is the result of my work. Some part of code I used for the diagram is following. Since the data is in csv format, the following is the sample data.
iso_code,continent,location,date,total_cases,case_per_date,total_deaths,death_per_date,total_cases_million,case_per_million,death_per_million,total_death_million
AFG,Asia,Afghanistan,24-02-2020,5,5,0,0,0.126,0.126,0,0
AFG,Asia,Afghanistan,25-02-2020,5,0,0,0,0.126,0,0,0
AFG,Asia,Afghanistan,26-02-2020,5,0,0,0,0.126,0,0,0
AFG,Asia,Afghanistan,27-02-2020,5,0,0,0,0.126,0,0,0
AFG,Asia,Afghanistan,28-02-2020,5,0,0,0,0.126,0,0,0
AFG,Asia,Afghanistan,29-02-2020,5,0,0,0,0.126,0,0,0
AFG,Asia,Afghanistan,01-03-2020,5,0,0,0,0.126,0,0,0
AFG,Asia,Afghanistan,02-03-2020,5,0,0,0,0.126,0,0,0
AFG,Asia,Afghanistan,03-03-2020,5,0,0,0,0.126,0,0,0
AFG,Asia,Afghanistan,04-03-2020,5,0,0,0,0.126,0,0,0
AFG,Asia,Afghanistan,05-03-2020,5,0,0,0,0.126,0,0,0
AFG,Asia,Afghanistan,06-03-2020,5,0,0,0,0.126,0,0,0
AFG,Asia,Afghanistan,07-03-2020,8,3,0,0,0.201,0.075,0,0
This is the scales I used for the diagram.
const x = d3.scaleTime().range([0, vis.WIDTH])
const y = d3.scaleLinear().range([vis.HEIGHT, 0])
const z = d3.scaleOrdinal(d3.schemeCategory10);
I used d3.nest to make data more convenient to use.
const covidData = d3.nest()
.key(d => d.location)
.entries(data)
I used the below code for making single series.
const line = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.case_per_date); });
The following codes are used to draw the diagram.
const country = g.selectAll(".countries").data(data);
country.exit().remove();
country.enter().insert("g", ".focus")
.append("path")
.attr("class","chart-line")
.style("stroke", d => z(d.id))
.merge(country)
.transition(vis.t)
.attr("d",d => line(d.values))
Since my doubt is regarding the big differences among series, I posted only few part of my code.
You can design your axis to have polylinear scale:
yScale = d3.scaleLinear().domain([0, 50000, 200000]).range([ vis.height, vis.height/2, 0])
you can change based on your stop values( can increase the no of values) and desired graph
Or if you want to show just the trend, use log or sqrt scales
d3.scaleSqrt() or d3.scaleLog()
https://observablehq.com/#d3/introduction-to-d3s-scales
These scales will be a better representation than using above as user will need to relate to axis ticks to understand the graph. You will also need to provide ticks values at smaller intervals to present it better in the former approach
I have a Dimple.JS scatter plot with a time-based (in years) X-axis. I'd like (in a similar manner to this D3 question) to be able to shade in an arbitrary area (ideally the start and end positions wouldn't necessarily be data points in the series).
Is there an existing function that will let me supply a year and give me the X co-ordinate the correct position on the scale in the SVG, which I can then use the construct my rectangle (I tried to look at the source code to figure out how dimple does it's positioning...)?
Alternatively, if it's more practical to use points already plotted on the chart, what's the correct way to use d3.select with dimple to access a specific one? My series has a date field (dd/mm/yyyy) so I have SVG elements like this:
<circle id="All_Wed Mar 18 1931 00:00:00 GMT+0000 (GMT)__" class="series0 bubble All Wed_Mar_18_1931_00:00:00_GMT+0000_(GMT) " cx="465.0000000006503" cy="362.1714285714286" r="2" opacity="0.8" fill="#e90e0e" stroke="#c20b0b"></circle>
… my guess was I should use mySeries.shapes.select(id) to access that, but for:
mySeries.shapes.select("#All_Wed Mar 18 1931 00:00:00 GMT+0000 (GMT)__");
or (if I escape it, unless there's a silly syntax error):
mySeries.shapes.select("#All_Wed Mar\ 18\ 1931\ 00:00:00\ GMT+0000\ (GMT)__");
I get "Not a valid selector".
(Thanks)
You need to use a non-public method of the axes to do this, so it may not work this way in future versions (>1.1.5) however between you and me, I don't think the scale method of the axis is going to be disappearing any time soon.
The _scale method is the raw d3 scale method added once the draw method of the chart is called so it can convert the values for you. I've created a fiddle to illustrate the solution. This will need a little tweaking if you are dealing with negative values or log axes:
// Draw a simple chart
var svg = dimple.newSvg("body", 800, 600);
var data = [
{ "a":300, "b":2000, "c":"a" },
{ "a":400, "b":3000, "c":"b" },
{ "a":340, "b":2200, "c":"c" },
{ "a":300, "b":5000, "c":"d" }
];
var chart = new dimple.chart(svg, data);
var x = chart.addMeasureAxis("x", "a");
var y = chart.addMeasureAxis("y", "b");
chart.addSeries("c", dimple.plot.bubble);
chart.draw();
// Draw a grey region using the following co-ordinates
var fromX = x._scale(210),
toX = x._scale(320),
fromY = y._scale(2200),
toY = y._scale(3100)
svg.append("rect")
.attr("x", fromX)
.attr("y", toY)
.attr("width", toX - fromX)
.attr("height", fromY - toY)
.style("fill", "grey")
.style("opacity", 0.2);
Here's the fiddle: http://jsfiddle.net/T6ZDL/7/
I would like to add a marker for a specific date for a date axis. Please see the line chart below.
My xAxis is drawn by this function, where dateMin and dateMax can be set by the user through the front end (or a brush, etc.).
d3.time.scale.utc().domain([dateMin,dateMax]);
This means the tickValues are calculated automatically.
Now, there is a certain fixed date for our data where there is a cutoff. For example, we may have consolidated figures up to Jan. 31st 2014, and then projected data from February 1st 2014 onwards.
What I need to do is make it visually clear at what date the cutoff point is. I have manually drawn a red vertical line at the date into the JPG below. But how do I do this programatically with d3?
One caveat is that the user might choose a date range (using the brush, etc.) which does not include the cutoff date (say, Jan 1st 2014 to Jan 20th, 2014). In this case, no line should be drawn.
If possible, it would be even better if the actual lines of the line chart would look different from the cutoff date onwards. They could be dotted instead of solid, or their colours could be less saturated (.brighter ?), to make visually clear that the underlying data is not consolidated yet.
Thanks for any hints you can give me.
Sorry I can't post images to StackOverflow yet, hence I uploaded the example here:
Trying out code from the answers
Using the code below, the line and label get drawn, but not at the given x value (cutoffDate), but too "early" on the time scale, approximately on the evening of 2014-01-29.
var cutoffDate = new Date("2014-02-01T00:00:00Z");
seriesChart.svg().append("svg:line")
.attr("x1", xScale(cutoffDate))
.attr("x2", xScale(cutoffDate))
.attr("y1", yScale.range()[1])
.attr("y2", yScale.range()[0])
.style("stroke", "rgb(225,0,0)")
.style("stroke-width", "1");
seriesChart.svg()
.append("text")
.attr("text-anchor", "start")
.attr("x", xScale(cutoffDate))
.attr("y", 80)
.text("Projected data");
See the result here:
http://i.imgur.com/0PXKFup.jpg
In my original question, I didn't mention I am using seriesChart from dc.js:
seriesChart API docs
I suppose this does something with the xScale when it composes the seriesChart so setting a value on the xScale later on will result in a shifted display. Will investigate further.
Update: x position fixed
The correct way to append svg elements to a dc.js chart is not to use
chart.svg().append()
but
chart.chartBodyG().append()
This fixes the position offset for custom elements added to the chart. Using this in combination with Lars' answer works.
This would be difficult to achieve with one axis, but easy with separate axes. First, for the dividing line, you can use code like this:
svg.append("line")
.attr("x1", xScale(cutoffDate))
.attr("x2", xScale(cutoffDate))
.attr("y1", yScale.range()[0])
.attr("y2", yScale.range()[1]);
svg.append("text").attr("x", xScale(cutoffDate) + 10).attr("y", yCoord)
.text("projected");
To have different styles, use two different axes:
var xScale1 = d3.time.scale().domain([..., cutoffDate]).range([0, 100]),
xScale2 = d3.time.scale().domain([cutoffDate, ...]).range([100, 200]);
svg.append("g").attr("class", "before")
.call(d3.svg.axis().scale(xScale1));
svg.append("g").attr("class", "after")
.attr("transform", "translate(" + xScale1.range()[1] + ",0)")
.call(d3.svg.axis().scale(xScale2));
Once appended, you can style the axes using the classes or by selecting the individual DOM elements. This means you end up with two scales -- to make using them for computing coordinates easier, you could wrap them:
var xScale = function(x) { return x < cutoffDate ? xScale1(x) : xScale2(x); };
The exact best way to implement this will depend on your specific application, but the above should give you a rough guide how to do it.
I've created a pretty simple scatterplot showing a person's weightlifting progress over time. The x-axis is a time scale in days (there's one circle for each day) and the y-axis is the number of pounds lifted that day.
The scatterplot works in Chrome, but not Firefox. It has something to do with using a date object as the x-position of the circles.
Take a look at the chart in Chrome
Here are two rows of my dataset, to show how it is formatted (the date is the first item of each row):
["2011-09-16",150,"Cottage cheese and 1 apple",170,16,"4 Chicken breast strips",130,17,"Hibachi grill: onion soup, salad, 2 shrimp pieces, vegetables. 12 oz chicken, rice",880,99,"Small hard frozen yogurt",300,6,"Cottage cheese, greek yogurt, a bunch of icebreaker sours",230,26,1710,164,175,"Back/biceps, 31 pushups",135,0,0],
["2011-09-17",150,"15 peanuts",80,4,"Dim Sum",1000,40,"Azn salad, 2 serv chicken breast",490,57,"8.8 oz mixx",264,9,"(No Data)",0,0,1833.6875,109.55,174.2," ",135,0,0],
Here's how I draw the circles, and some relevant functions/variables I used to format the dates:
//Width and height
var w = 4200;
var h = 200;
var padding = 35;
var mindate = new Date("2011-9-15");
var maxdate = new Date("2012-5-29");
//Create scale functions
var xScale = d3.time.scale()
.domain([mindate, maxdate])
.range([padding, w - padding * 2]);
var format = d3.time.format("%Y-%m-%d");
var dateFn = function(d) {return format.parse(d[0])};
//Create circles
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d) {
return xScale(dateFn(d));
})
.attr("cy", function(d) {
return yScale(d[1]);
})
.attr("r", 6)
.attr("fill", "gray")
.attr("opacity", .5)
Like I said, it works exactly how I want in Chrome, but in Firefox, all the circles are on top of one another with cx=0. Any ideas would be appreciated. I asked my local d3 expert, and he showed me a project he did which also fails in Firefox due to drawing circles using date objects. He just gave up. a link to his project
The problem isn't the way you're parsing the dates, but the way you're setting up the scale.
var mindate = new Date("2011-9-15");
var maxdate = new Date("2012-5-29");
This is the code that works properly only in Chrome because you're relying on its date parsing by using the constructor rather than parsing explicitly like you're doing for the rest of the dates.
The fix is simple -- just parse the dates you're using to set the scale domain as well:
var format = d3.time.format("%Y-%m-%d"),
mindate = format.parse("2011-09-15"),
maxdate = format.parse("2012-05-29");
Hey so I'm having difficulty with the positioning of my stacked bar chart. It's showing up, I'm just having difficulty declaring it's x axis. Here is the jsfiddle: http://jsfiddle.net/E2HST/
var xTimeScale = d3.time.scale().
domain([new Date(data[0].date), d3.time.day.offset(new Date(data[data.length - 1].date), 1)])
.range([0, width]);
is obviously part of the problem, I pulled code and have unfortunately fallen into the trap of not fully understanding it.
var bars = svg.selectAll(".bar")
.data(data).enter()
.append("g")
.attr("class","bar")
.attr("transform", function(d){
return "translate("+xTimeScale(d.date)+",0)"
})
I've tried swapping in d.year for d.date seeing as there is no more d.date but it's throwing up errors. Any help would be greatly appreciated.
The simple answer is that the objects in your data array do not have a date key, so your xTimeScale domain is undefined. An easy way to correct this would be to create this key for each data item:
data.forEach( function(d) { d.date = new Date(d.year, 0, 1); });
Which creates the date as January 1st of the year variable. This basically solves your x-axis problem.
I would, however, suggest that your data would be better suited to using an ordinal scale, since you only have yearly data.
A couple of other small things:
Your x-axis definition has way too many ticks, consider changing this
Consider adding css styles for .axis elements, to improve readability
An updated fiddle with these slight changes is at http://jsfiddle.net/E2HST/1/