I am using D3 to draw a line chart. The value at x=0 does not show up.
The code for the axis is shown below.
const xScale = d3
.scaleTime()
.domain(d3.extent(data[0].series, d => d.time))
.range([xPadding, width - xPadding]);
const xAxis = d3
.axisBottom(xScale)
.ticks(4)
.tickSizeOuter(0)
.tickSizeInner(0)
.tickFormat(d3.timeFormat('%Y'));
I am not sure why it is not showing up the label at x=0, which is 2014. On checking the SVG, only three tick marks are displayed, but the one at x=0 is not in the SVG element.
CodePen for this: https://codepen.io/vijayst/pen/bLJYoK?editors=1111
I see different solutions which have their pros and cons. The third solution should be the cleanest and most generic.
Add the left tick manually:
Since d3 handles itself the location of x-axis ticks, one way of doing so would (if the data set is fixed) would be to manually add the missing tick:
svg
.append("g")
.append("text")
.text("2014-02-01") // can be retrieved from data instead of being harcoded
.style("font-size", 10)
.style("font-family", "sans-serif")
.attr("transform", "translate(0," + (height - yPadding + 10) + ")")
which looks great, but in this case you might have problems if for a given dataset, d3 chooses to display a tick close to the left edge of the axis. Both d3's tick and the label we've included could overlap.
Modify the x-scale to start before the first day of the year:
An other solution would be to increase the x-axis range on the left to make it start one month before the first point's date. To try this out, we can replace:
.domain(d3.extent(data[0].series, d => d.time))
with
.domain(d3.extent([new Date(2013, 12), new Date(2019, 1)]))
which allow d3 to legitimately include a "year-tick" for 2014 at the begin of the x-axis.
but in this case, the first point will have an offset with the begin of the x-axis range.
Push a specific tick to ticks auto-generated by d3:
An other solution: we can push a specific tick to the ticks auto-generated by d3. But this requires to modify the format of ticks to "%Y-%m".
In order to do this, replace:
.tickFormat(d3.timeFormat("%Y"));
with
.tickFormat(d3.timeFormat("%Y-%m"));
we can then push a new specific tick to the set of ticks generated by d3:
var ticks = xScale.ticks();
ticks.push(new Date(2014, 1, 1));
xAxis.tickValues(ticks);
and include some padding in the left and the right of the chart since now tick labels have a part displayed outside the graph:
const svg = d3
.select("#chart")
.append("svg")
.attr("width", width)
.attr("height", height)
.style("padding-left", 15)
.style("padding-right", 15);
Related
I am trying to build a d3js timeline chart with axis grid lines vertically indicating the month region.
https://www.cssscript.com/demo/simple-scrollable-timeline-chart-with-d3-js-d3-timeline/
^ something like this featured some code - which I think is v3
var xAxis = d3.svg.axis().scale(x).orient('bottom').tickSize(-height);
svg.append('g').attr('class', 'x axis').attr('transform', 'translate(0,' + height + ')').call(xAxis);
my current implementation has failed.
https://jsfiddle.net/g89kuoe1/4/
these are the v4 methods to create axis -- but it needs to be repeated on the chart to formulate the grid lines.
var xAxis = d3.axisBottom(xRange).tickFormat(function(d){ return d.x;});
var yAxis = d3.axisLeft(yRange);
or should a new clip path be made to contain these lines - and add them as lines - with x1 values that hit each month?
https://jsfiddle.net/c8gdfxob/
** SOLVED -- had to use minExtent and maxExtent -- and force the scale of the ticks to be a month
this creates non-moving vertical grid lines - but I am unsure how to adapt it to the codebase so they morph with the scrub
it maps the timeline months -- but doesn't flow with the morph..
var scale = d3
.scaleTime()
.range([0, w])
.domain([minExtent, maxExtent]);
// Gridline
var gridlines = d3.axisTop()
.tickFormat("")
.ticks(d3.timeMonth)
.tickSize(-mainHeight)
.scale(scale);
main.selectAll(".grid").remove()
main.append("g")
.attr("class", "grid")
.call(gridlines);
1) I would like to display a vertical line with the "now" value
as well as
2) add a slight margin to the right - Currently "now" events are cropped because there is no space to the right of the last (most recent) event.
UPDATE: thanks to a suggestion in the comments, this is done easily by doing:
maxDate.add(1).hour()
with the fantastic date manipulation library at https://github.com/datejs/Datejs
e.g.:
Current:
Desired:
Questions:
how can I retrieve the x value for the "now" value from the svg so I can draw the line?
How can I add a time margin (e.g. 1 hour) or relative margin (e.g. 10%) after the last event?
To draw the line you would map the time into the scale. Then using that would draw at the returned value. You could do something like:
const chart = eventDrops({ d3 });
var now = new Date()
var x = chart.scale()
d3.select('#chart')
.append('line')
.attr('id', 'ticker')
.attr('y1', 0)
.attr('y2', 100)
.attr('x1', x(now))
.attr('x2', x(now))
.attr('transform', `translate(200, 0)`)
.style('stroke-width', 2)
.style('stroke', 'blue')
.style('fill', 'none')
As commented you would add the date margin to your chart's end date.
var maxDate = Date().today()
const chart = eventDrops({
d3,
range: {
end: maxDate.add(1).hour()
}
});
I'd like to either:
dynamically adjust the tickFormat of an D3 timeaxis depending on current zoom, like first showing years -> zoom in -> show months -> zoom in -> show days and so on up to seconds.
I've tried a bit. See this fiddle.
var w = 700,
h = 50,
xY = d3.time.scale().range([0, w]),
xAxisY = d3.svg.axis()
.scale(xY)
.orient("bottom")
.ticks(10)
.tickSize(10, 1)
var svgY = d3.select("body").append("svg:svg")
.attr("width", w)
.attr("height", h)
.append("svg:g");
svgY.append("svg:g")
.attr("class", "x axis")
.attr("transform", "translate(0," + 10 + ")");
svgY.append("svg:rect")
.attr("class", "pane")
.attr("width", w)
.attr("height", h)
.call(d3.behavior.zoom().on("zoom", zoom));
xY.domain([new Date(2000, 0, 1), new Date(2014, 0, 0)]);
xY.ticks(d3.time.minute, 1);
draw();
function draw() {
console.log("drawing");
svgY.select("g.x.axis").call(xAxisY);
}
function zoom() {
console.log("zooming");
d3.event.transform(xY); // TODO d3.behavior.zoom should support extents
draw();
}
It would be nice if the label format could be adjusted. If I zoom into minutely interval, th complete time shold be displayed ( 12:01 ) instead of sth like 12:00..............10......20......30....
or ( preferred, if possible ) :
add multiple x-axis with different formats below each other. In this case, the labels should disappear on overlap, eg:
-------2012--------------------------------------------------------------2013-----------------
--12----01----02----03----04----05----06----07----08----09----10----11----12----01----02------
Below the monthly axis there should appear a daily and so on. If I zoom into an detailled interval, the overlapping labels should disappear.
For this case I tried playing around with the above fiddle, simply adding futher axis / svgs but they react independently, eg: I have a yearly axis and a monthly axis. Zooming on the first also zooms the second and vice versa, but when I first change from first to second, both axis do a "jump". I think the second one moves back to its initial state on first moving.
Any ways to accomblish this?
In both cases, I'm surprised why the tick-lines have gone away?
In many examples the axis look like:
|2012 |2011 |2010
but the vertical lines are gone in the fiddled example!?
I am pretty new to D3 chart and I created my first donut chart using D3, but I was wondering if there is anyway I can put some padding/spacing between each arc.
I know I could reduce each arc's start and end angles, for example,
arc 1: from 90degree to 120degree
arc 2: from 120degree to 150degree
reduce the angles above like
arc 1: from 92degree to 118degree
arc 2: from 122 degree to 148degree
and so on..
but I am curious if there is any easier way to put some spacing.
Here's my code and you can see the full code in JSfiddle.
var vis = d3.select(elementSelector);
var arc = d3.svg.arc()
.innerRadius(svgInnerRadius)
.outerRadius(svgOuterRadius)
.startAngle(function(d){return anglePercentage(d[0]);})
.endAngle(function(d){return anglePercentage(d[1]);});
...
http://jsfiddle.net/24FaQ/
Thank you so much in advance.
This is actually what you're looking for:
http://bl.ocks.org/mbostock/f098d146315be4d1db52
var pie = d3.layout.pie()
.padAngle(.02);
If you're drawing on top of a solid background (white or otherwise), you can add stroke to achieve this effect without modifying the angles.
Here's a modified fiddle.
vis.selectAll("path")
.data(data)
.enter()
.append("path")
.attr("d", arc)
.style("fill", function(d){return color(d[2]);})
.attr('stroke', '#fff') // <-- THIS
.attr('stroke-width', '6') // <-- THIS
.attr("transform", "translate(" + svgWidth / 2 + ", " + svgHeight / 2 + ")");
This applies the stroke to all the edges, including the curved ones. If you need to avoid that, the you have to instead draw and position lines with white strokes at the start/end of each slice.
I have a CSV file containing the French population by department. I can correctly display a map colored with population density but I encounter problems with the associated legend.
You can see the current result here: http://i.stack.imgur.com/Y26JT.jpg
After loading the CSV, here is my code to add the legend :
legend = svg.append('g')
.attr('transform', 'translate(525, 150)')
.attr('id', 'legend');
legend.selectAll('.colorbar')
.data(d3.range(9))
.enter().append('svg:rect')
.attr('y', function(d) { return d * 20 + 'px'; })
.attr('height', '20px')
.attr('width', '20px')
.attr('x', '0px')
.attr("class", function(d) { return "q" + d + "-9"; })
.attr('stroke', 'none');
legendScale = d3.scale.linear()
.domain([0, d3.max(csv, function(e) { return +e.POP; })])
.range([0, 9 * 20]);
legendAxis = d3.svg.axis()
.scale(legendScale)
.orient('right')
.tickSize(1);
legendLabels = svg.append('g')
.attr('transform', 'translate(550, 150)')
.attr('class', 'y axis')
.call(legendAxis);
Colors are obtain using ColorBrewer CSS (https://github.com/mbostock/d3/tree/master/lib/colorbrewer)
I have two problems:
The Hyphen '-' (a SVG line) is not displayed for each value of my axis
I cannot choose the number of values in my axis, I would like one at the beginning of each color block.
Thanks in advance for any help.
The reason your solution isn't showing the 'hyphen' (svg lines) is because you have the tickSize set very small. Try not setting it and it will default to 6 (according to the API docs - https://github.com/mbostock/d3/wiki/SVG-Axes).
To choose the number of values in your axis, you can add a call to ".ticks(N)", where N is the number of ticks you want. D3 will try to show that many ticks. You could also call ".tickValues([...])" and pass in the exact array of values to use for the ticks.
Here's a JSFiddle that corrects the issues in your example: http://jsfiddle.net/TRkGK/3/
And a sample of the part that fixes your issues:
var legendAxis = d3.svg.axis()
.scale(legendScale)
.orient('right')
.tickSize(6) // Tick size controls the width of the svg lines used as ticks
.ticks(9); // This tells it to 'try' to use 9 ticks
UPDATED:
You also want to make sure you're setting the CSS correctly. Here's what I use:
.y.axis line { stroke: #ccc; }
.y.axis path { display: none; }
In your example, when you add the larger tickSize, you are seeing the path in which the tick lines are defined. If you hide the path and give the lines a color, you'll see the ticks rather than the area in which the ticks are defined.
Hope this helps!
It looks like it could be a css issue, did you look at reblace's css in his fiddle?