I want to add spacing between 0 and March in the x axis so that it looks better.
startdate and enddate are calculated by d3.min and d3.max.
So those columns looks bad because of their width.
var x = d3.scaleTime().range([0, width]);
var datestart = d3.min(data, function(d) { return parseDate(d.date); });
var dateend = d3.max(data, function(d) { return parseDate(d.date);});
x.domain([datestart, dateend]);
How can I do this? How to add a padding using d3.scaleTime()?
As you mentioned in your comment (maybe a reply?), there is no padding in a time scale.
Therefore, the problem here is one that from time to time (no pun intended) appears at SO: you are using the wrong tool for the task. Bar charts should always use a categorical (qualitative) scale, not a quantitative scale or a time scale.
However, if you really need to use the time scale here (for whatever reason), you can add the padding in the domain, using offset.
For instance, this will subtract 15 days at the beginning of your domain, and add 15 days at the end:
var datestart = d3.min(data, function(d) {
return d3.timeDay.offset(parseDate(d.date), -15);
//subtract 15 days here ---------------------^
});
var dateend = d3.max(data, function(d) {
return d3.timeDay.offset(parseDate(d.date), 15);
//add 15 days here -------------------------^
});
You can tweak that value until you have an adequate padding. As I don't know how you are calculating the width of the bars (again, bars should not be used in a time scale), 15 days is just a guess here.
Related
Background
I'm using D3 4.x to build a multi-series line chart (see an example) to compare multiple years of annual data, where the Y-axis is the range of possible values throughout the years and the X-axis represents the time interval between the dates of 01/01-12/31.
Issues
While building this chart, it appears as though time intervals are year specific preventing data from different years to be rendered on the same chart. To reiterate, the current approach is to overlaying each year on the same chart; a line for each year. And to do this with time intervals being year specific, requires significant (and erroneous) data manipulation.
In the example chart below, one can see that the year 2014 approaches from the left (color: blue) and 2015 continues on to the right (color: orange). What I'd like to see is both of these to overlay on top of each other.
Acknowledgement / Caveat
Some of the complexities of comparing multiple years worth of data by overlaying the data on top of each other (ie. multiple lines, each representing a year) are acknowledged. Such as how to deal with years with leap days compared with those that do not. This complicates thing programmatically but visually should not be an issue.
Questions
1) What is the best approach to using time intervals when the year just gets in the way?
2) Is there a better, more elegant, approach to creating multi-series line charts that represent multiple years layered on top of each other, instead of hacking together the pieces?
3) Is there a way (not statically) to remove the year (ie. 2015) from the axis and use the month (January) instead?
4) Is it possible to shift the text on the X-axis to the right (programmatically), so that the month appears between the tick marks (IMO, it's visually more accurate)?
There are several ways to solve this. Actually, so many of them that this question qualifies as too broad. Every approach, of course, has its pros and cons. For instance, a simple one is dropping the time scale altogether, and using a point scale for the x axis.
I'll suggest a different approach, a little bit more complex, but still using time scales: create several different time scales, one for each year. Each of them has the same range: that way, the lines for each year can overlay on top of each other.
For instance, from 2013 to 2017:
var years = [2013, 2014, 2015, 2016, 2017];
var scales = {};
years.forEach(d => {
scales["scale" + d] = d3.scaleTime()
.domain([new Date(+d, 0, 1), new Date(+d, 11, 31)])
.range([30, 470]);
});
That way, you can keep using a time scale for the x axis. The problem is this: time scales deal with each year as they are: different. Some years have more days, some of them have less days. Even the days can have more hours or less hours.
So, I chose one year (2017) and displayed only the months for that year in the x axis. The result may not be extremely precise, but it's good enough if you have an entire year in the x axis.
Here is the demo. The data is randomly generated, each line is a different year (from 2013 to 2017), going from Jan 1st to Dec 31st:
var svg = d3.select("body").append("svg")
.attr("width", 500)
.attr("height", 200);
var color = d3.scaleOrdinal(d3.schemeCategory10);
var thisYear;
var line = d3.line()
.x(d => scales[thisYear](d.x))
.y(d => yScale(d.y))
.curve(d3.curveMonotoneX);
var years = [2013, 2014, 2015, 2016, 2017];
var data = [];
years.forEach((d, i) => {
data.push({
year: d,
points: []
});
for (var j = 0; j < 12; j++) {
data[i].points.push({
y: Math.random() * 80 + 20,
x: new Date(d, j)
})
}
});
var scales = {};
var yScale = d3.scaleLinear()
.domain([0, 100])
.range([170, 30]);
years.forEach(d => {
scales["scale" + d] = d3.scaleTime()
.domain([new Date(+d, 0, 1), new Date(+d, 11, 31)])
.range([30, 470]);
});
var paths = svg.selectAll("foo")
.data(data)
.enter()
.append("path");
paths.attr("stroke", (d, i) => color(i))
.attr("d", d =>{
thisYear = "scale" + d.year;
return line(d.points);
})
.attr("fill", "none");
var xAxis = d3.axisBottom(scales.scale2017).tickFormat(d=>d3.timeFormat("%b")(d));
var yAxis = d3.axisLeft(yScale);
var gX = svg.append("g").attr("transform", "translate(0,170)").call(xAxis);
var gY = svg.append("g").attr("transform", "translate(30,0)").call(yAxis);
<script src="https://d3js.org/d3.v4.js"></script>
Code link: https://plnkr.co/edit/jLkoMxdzArBBULHF80nb?p=preview
I have a data with some disperse values. It ranges from 61 to 1.2m.
How can I represent it in a Histogram in a way that makes sense?
Can I have the last bucket on d3 that is > 2000 for instance?
Something like this (greater than 5 minutes):
First of all you will need to arrange your data (if you haven't yet), where you just need to create a variable with the > 2000 values.
This is the way I did it (I started d3 last week and I don't have any previous knowledge on JavaScript, so there's probably a better way to do it):
var data = [];
for (var i = 0; i < oldData.length; ++i) {
if (oldData[i] >= 2000) {
data[i] = 2000;
}
else data[i] = oldData[i];
}
Next thing, is o set manually the ticks you want and the tickLabels that correspond to it:
var ticks = [0,200,400,600,800,1000,1200,1400,1600,1800,2000];
var tickLabels = [0,200,400,600,800,1000,1200,1400,1600,1800,"> 2000"];
(notice that you can change to "1,200" and so on if you want the separator)
And instead of calling the d3.axisBottom(x) directly to your chart, I like to create a separate xAxis variable, and set the ticks and tickLabels to it:
var xAxis = d3.axisBottom(x)
.tickValues(ticks)
.tickFormat(function(d,i){ return tickLabels[i] });
Finally you call the xAxis on your chart:
chart.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
I am using this example to create my own real-time graph using d3. In my version the graph is initialized with existing data. Problem is, the x-axis initialization causes a very small portion of the graph to show while it is transitioning or collapsing on the right before finally showing the normal scale and resultantly the normal graph. I am pretty sure the axis is causing it because the moment the axis returns to normal so does the graph. Is there a way to remove this transition at the begging or otherwise have it not skew the graph or not show until it is ready? Here is the problem in action, better than me trying to explain it: http://codepen.io/Dordan/pen/NbBjPB/
Here is the code snippet for creating the x-axis:
var limit = 60 * 1;
var duration = 750;
var now = new Date(Date.now() - duration);
var x = d3.time.scale().domain([now - (limit - 2), now - duration]).range([0, width]);
var axis = svg.append('g')
.attr('class', 'x-axis')
.attr('transform', 'translate(0,' + height + ')')
.call(x.axis = d3.svg.axis().scale(x).orient('bottom'));
The instantiation of your x scale is missing the '* duration' when you're calculating the domain. Use this instead and it works well:
var x = d3.time.scale().domain([now - (limit - 2) * duration, now - duration]).range([0, width]);
I am using NVD3js with lineChart graph,
My dataset contains timestamps and values for 24 hour view, each point at xx:30,
example:
(00:30 - val_X, 01:30 - val_Y, 02:30 - val_Z, ...,23:30 - val_W)
[{"values":[{"x":1471552200000,"y":64262.58061338499},{"x":1471555800000,"y":62678.866737321965},{"x":1471559400000,"y":61750.00767023745},{"x":1471563000000,"y":61326.61449656117},{"x":1471566600000,"y":60561.389682317065},{"x":1471570200000,"y":60144.93124866629},{"x":1471573800000,"y":59785.83442165563},{"x":1471577400000,"y":69062.50859207465},{"x":1471581000000,"y":75910.99616145018},{"x":1471584600000,"y":75444.86937388242},{"x":1471588200000,"y":74308.98109650609},{"x":1471591800000,"y":73645.18914600794},{"x":1471595400000,"y":72899.18083184483},{"x":1471599000000,"y":72500.14363895029},{"x":1471602600000,"y":71881.80897423408},{"x":1471606200000,"y":71608.16164435333},{"x":1471609800000,"y":71560.75929676845},{"x":1471613400000,"y":71693.39707357567},{"x":1471617000000,"y":71662.43123936957},{"x":1471620600000,"y":70112.4723063366},{"x":1471624200000,"y":67639.43208386854},{"x":1471627800000,"y":65567.79922797237},{"x":1471631400000,"y":64098.448319984534},{"x":1471635000000,"y":62591.10055950839},{"x":1471638600000,"y":61341.82034098393},{"x":1471642200000,"y":60428.577018083095}],"key":"System Score","color":"#325c80","area":true}]
When I display it on a lineChart graph I see x domain from 00:30 to 23:30, I wanted to present 00:00 - 23:59, so I added 23:30 the previous day and 00:30 the next day and then I set the xDomain to [00:00, 23:59], However, the points are a bit off from its correct location althought the data is correct.
Is there anoter way to solve that problem?
Graph init:
nv.addGraph(function() {
score_graph = nv.models.lineChart()
.xScale(d3.time.scale())
.useInteractiveGuideline(true)
.showLegend(false)
;
score_graph.xAxis
.axisLabel('')
;
score_graph.yAxis
.axisLabel('y axis')
.ticks(5)
.tickFormat(d3.format('.3s'))
;
return score_graph;
});
Graph update data:
startTime is 00:00 and endTime is 23:59 of same day,
dataset has also 23:30 of previous day and 00:30 of next day points
var tooltip_obj = score_graph.interactiveLayer.tooltip;
tooltip_obj.headerFormatter(function(d, i) {
return graphLocales.timeFormat(time_format)(new Date(+d));
});
score_graph.xAxis
.tickFormat( function(d) { return graphLocales.timeFormat(time_format)(new Date(+d)); } );
var line_chart_data = d3.select('#score-history-block svg').datum(parsed_data_system);
var y_max = d3.max(parsed_data_system[0].values, function(d) { return +d.y; }) * 1.2;
if (y_max < 100) y_max = 100;
score_graph.yDomain([0, y_max] );
score_graph.xDomain([startTime.toDate(), endTime.toDate()]);
line_chart_data.transition().duration(500)
.call(score_graph)
;
I'm trying to create a line graph with D3.js and I want my X axis to start from 1 instead of 0.
The code looks as follows:
var temp = [36.5, 37.2, 37.8, 38.2, 36.8, 36.5, 37.3, 38.2, 38.3, 37];
var x = d3.scale.linear().domain([0,temp.length]).range([0, w]);
var xAxis = d3.svg.axis().scale(x).tickSize(-h).tickSubdivide(false);
graph.append("svg:g")
.attr("class", "x axis")
.attr("transform", "translate(0," + h + ")")
.call(xAxis);
When I change this to:
var x = d3.scale.linear().domain([1,temp.length]).range([0, w]);
the scale get's edited but the graph starts outside of the graph itself.
I tried to use tickvalues but I can't get this to work.
How can I let my scale start from 1?
I'm assuming you are labeling your axis by using the index of the data.
When you set your domain starting at 1, you're actually just telling your chart to make the left-most part of your graph be the x-coordinate of the second datum (index 1).
When you actually create the chart, there is a datum (index 0, value 36.5) that is outside of your defined domain, and d3 uses linear extrapolation to determine where it should be placed, making it end up to the left of the start of your chart.
What you really want to do is start your domain at 0, so that the first datum is in your domain, but to reformat your tick labels so that they show the index incremented by 1.
You can use axis.tickFormat() to do this.
var xAxis = d3.svg.axis()
.scale(x)
.tickSize(-h)
.tickFormat(function(d) { return d + 1; })
Side note: you shouldn't specify .tickSubdivide(false), since:
That function expects a number, not a boolean, and false will be coerced to 0.
The default value is 0 anyways.
axis.tickSubdivide is deprecated and does nothing as of version 3.3.0