I have a simple piece of JavaScript building a time axis in D3:
var xScale = d3.time.scale()
.domain([testData.datapoints[0].at, testData.datapoints[testData.datapoints.length - 1].at])
.range([0, (new Date(testData.datapoints[testData.datapoints.length - 1].at - testData.datapoints[0].at).getTime() / interval) * 21]),
xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks([d3.time.minute, 15]) /* This line causes an exception */
.tickFormat(d3.time.format("%d/%m/%y %H:%M:%S")),
In the D3 documentation on Time Scales ticks it says
While to create ticks at 15-minute intervals, say:
scale.ticks(d3.time.minute, 15);
But I get an exception:
Unhandled exception at line 9204, column 7 in d3.js
0x800a138a - JavaScript runtime error: Function expected
(N.B. moving the offending line from the xAxis chain to the xScale chain makes no difference, it still throws the same exception in the same place. Changing the offending line to
.ticks(20)
removes the exception but is not the functionality I am after.)
The D3 line where the error is thrown is the last line of this function in d3.js:
scale.ticks = function(interval, skip) {
var extent = d3_scaleExtent(scale.domain()), method = interval == null ? tickMethod(extent, 10) : typeof interval === "number" ? tickMethod(extent, interval) : !interval.range && [ {
range: interval
}, skip ];
if (method) interval = method[0], skip = method[1];
return interval.range(extent[0], d3_time_scaleDate(+extent[1] + 1), skip < 1 ? 1 : skip);
};
At the point the exception is thrown skip is undefined.
What's wrong with the line of D3 JavaScript? It is copied from the documentation, but I doubt it's a bug in D3, more likely I have misapplied it somehow.
You have this:
.ticks([d3.time.minute, 15])
But what you want is this:
.ticks(d3.time.minute, 15)
The array would be only if you're passing a specific list of values into the function. And in that case you'd want to use the .tickValues function
Related
I'm following the example here to remove end ticks in my bar graph, as follows:
var yAxis = d3.axisLeft(yScale);
yAxis.tickValues([0, 500, 1000, 1500, 2000, 2500]);
yAxis.tickSizeOuter(0); // <--- This does not remove the end ticks
However, this does not remove the end ticks in the y-axis.
Now, when I enter this line of code:
yAxis.tickSize(0);
I am able to remove all the ticks on the y-axis.
Why isn't the tickSizeOuter(0) code working and how can I remove just the end-ticks?
Here is the version of d3 I am using with my Angular project:
"d3": "^5.9.1"
yAxis.tickSizeOuter(0) remove outer ticks which means that y line is straight line, but it doesn't remove actual ticks (e.g. 0-). If you what that you should use something like:
const tick_count = d3.selectAll("g.axis.axis--y .tick").size();
d3.selectAll("g.axis.axis--y .tick")
.each(function (d, i) {
console.log(d,i)
if ( i == 0 || i == tick_count-1) {
this.remove();
}
});
Hope I understand correctly your misunderstanding :)
Working solution for D3 v5.9.2:
After you declared yAxis and set the attributes, wrap it in a g and call it:
var yAxis = d3.axisLeft(yScale);
yAxis.tickValues([0, 500, 1000, 1500, 2000, 2500]);
svg.append("g").attr("id", "yAxisG").call(yAxis);
Remove the first <path> element from the group:
d3.select("g#yAxisG").select("path").remove();
I have a stackblitz here - https://stackblitz.com/edit/d3-stacked-trend-bar-positioned-months-4sqvwd?embed=1&file=src/app/bar-chart.ts&hideNavigation=1
I'm using D3 to create a stacked bar chart in Angular
I now also need to have a line graph on the same chart.
I think the best way to do this is with a dual axis.
I have the second axis working but can't get the line to work.
Can anyone point me the direction to get this working
The line function (valueline in your case) doesn't seem like its defined correctly as it's missing the accessor functions. Here are the docs for the same.
I couldn't fork your code but here's a snippet (containing the drawLine method) you can try:
private drawLine(linedata:any){
var that = this;
var valueline = d3.line().x(function(d, i) {
return that.x1(d.date);
// return that.x0(d.date) + 0.5 * that.x0.bandwidth();
}).y(function(d) {
return that.y1(d.value);
});
this.x1.domain(this.data.map((d:any)=>{
return d.date
}));
this.y1.domain(d3.extent(linedata, function(d) {
return d.value
}));
this.lineArea.append("path")
.data([linedata])
.attr("class", "line").style('stroke-width', 2)
.attr("d", valueline);
}
It works and I also have included a commented line for the x attribute which matches the way you're offsetting the bars. And another suggestion would be to use the same x0 scale as the newly defined x1 has the same domain as x0. Hope this helps.
This is related to the dc.js boxPlot example. Is there an easy way to add the number of data values to each xAxis label?
exp-1 [10]
I know I can configure the xAxis label by:
chart.xAxis().tickFormat(function(k) {return k + ' []';});
But I only have access to the key and not the value count.
I would do this by keeping a map from keys to counts, refreshing it before the chart is rendered or redrawn:
var counts = {};
function get_counts() {
speedArrayGroup.all().forEach(function(kv) {
counts[kv.key] = kv.value.length;
});
}
chart.on('preRender', get_counts)
.on('preRedraw', get_counts);
Now that we're sure counts is initialized whenever the axis is drawn, we can use it in the tickFormat:
chart.xAxis().tickFormat(function(k) {return k + ' [' + counts[k] + ']';});
Not so interesting with the standard example, but it works:
I would like to create a dynamic graph with multiple (linear) axes. After the axes have been drawn, I would like (as new data arrives) to change the Data Domain and redraw/update the axes. Can I select the existing axis with D3 and do this or do I have to explicitly save each axis in my code? I hope my question is not confusing.
// init all Y-Axis
$.each(chart.YAxes, function (index) {
var yScale, yAxis;
yScale = d3.scale.linear().range([chartHeight, 0]);
yScale.domain([this.YMin, this.YMax]);
yAxis = d3.svg.axis()
.scale(yScale)
.ticks(10, this.Title)
.orient("left");
d3Chart.append("g")
.attr("class", "yAxis " + "y" + this.ID)
.call(yAxis);
......
// update Y-Axis (after new data arrives...)
var myYAxis = d3.select(".y" + yAxis.ID);
var myScale = myYAxis. **// get Scale ??**
myScale.domain([newYMin, newYMax]);
d3Chart.select(".y" + yAxis.ID)
.transition().duration(300).ease("sin-in-out")
.call(myYAxis);
thx...!
You need to keep references to the axis object so that you can call it again. Selecting the DOM element that contains it and calling that won't work. There are lots of examples on how to update axes, e.g. in this question.
I having trouble getting the data on the graph. I only get one data set bar in.
You can see it here : http://infinite-fjord-1599.herokuapp.com/page2.html
But when I console.log the foreach for it. It displays all the objects:
data.days.forEach(function(d) {
d.ages = ageNames.map(function(name) { return {name: name, value: +d.values[name]}; });
console.log(d.ages);
});
The code on jsFiddle. http://jsfiddle.net/arnir/DPM7y/
I'm very new to d3.js and working with json data so I'm kinda lost here. I took the example of the d3.js example site and modified it.
See the updated fiddle here: http://jsfiddle.net/nrabinowitz/NbuFJ/4/
You had a couple of issues here:
Your x0 scale was set to a domain that displayed a formatted date, but when you were calling it later you were passing in d.State (which didn't exist, so I assume it was a copy/paste error). So the later days were being rendered on top of the first day.
There was a mismatch between the way you were selecting the group g element and the way you were appending it - not actually a root cause here, but likely to cause problems later on.
To fix, move the date formatting to a different function:
function formatDate(d) {
var str = d.modified;
d.date = parseDate( str.substring(0, str.length - 3) );
var curr_month = d.date.getMonth() + 1;
var curr_date = d.date.getDate();
var nicedate = curr_date + "/" + curr_month;
return nicedate;
}
and then use the same function for the scale setup:
x0.domain(data.days.map(formatDate));
and the transform (note the fix in the selector and class here as well):
var state = svg.selectAll("g.day")
.data(data.days)
.enter().append("g")
.attr("class", "day")
.attr("transform", function(d) {
return "translate(" + x0(formatDate(d)) + ",0)";
});
There are a couple of small things that threw you off. First, the domain of the x0 scale should be an array of datetime objects, not an array of strings:
x0.domain(data.days.map(function(d) {
var str = d.modified;
d.date = parseDate( str.substring(0, str.length - 3) );
return d.date;
}));
will return datetimes, not strings like it was before (minor nitpick: really not a fan of this use of map, I would add the date property separately in a forEach function as the data is loaded).
Second, x0 needs to be passed a property that actually exists:
var state = svg.selectAll(".state")
.data(data.days)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + x0(d.date) + ",0)"; });
Before, you were using x0(d.state) which is a vestige from the grouped bar example (several others still exist; I've changed the minimum to get your project working). Since the value didn't exist, all of the rectangles were getting drawn over each other.
Additionally, we need to format the axis labels so we aren't printing out the entire datetime object all over the labels:
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom")
.tickFormat(d3.time.format("%m-%d"));
Finally, I noticed that the newest dates were being printed on the left instead of the right. You could sort the results of data.days.map( ... ) to fix that, I just reversed the range of x0:
var x0 = d3.scale.ordinal()
.rangeRoundBands([width, 0], .1);
fixed files