The date axis of my d3 line chart contains a mixture of months and weekdays. I just want it to be the months and the day of the month (eg 15 Feb).
chart with weird axis
Does anyone know how to fix this?
Super new to d3.js so excuse me.
Thank you!
//create scales
const yScale = d3.scaleLinear()
.domain(d3.extent(dataset, yAccessor))
.range([dimensions.boundedHeight, 0])
const xScale = d3.scaleTime()
.domain(d3.extent(dataset, xAccessor))
.range([0, dimensions.boundedWidth])
// draw data
const lineGenerator = d3.line()
.x(d => xScale(xAccessor(d)))
.y(d => yScale(yAccessor(d)))
const line = bounds.append("path")
.attr("d", lineGenerator(dataset))
.attr("fill", "none")
.attr("stroke", "#af9358")
.attr("stroke-width", 2)
//draw peripherals
const yAxisGenerator = d3.axisLeft()
.scale(yScale)
const yAxis = bounds.append("g")
.call(yAxisGenerator)
const xAxisGenerator = d3.axisBottom()
.scale(xScale)
const xAxis = bounds.append("g")
.call(xAxisGenerator)
.style("transform", `translateY(${
dimensions.boundedHeight
}px)`)
const xAxisLabel = xAxis.append("text")
.attr("x", dimensions.boundedWidth / 2)
.attr("y", dimensions.marginBottom -10)
.attr("fill", "black")
.style("font-size", "1.4em")
//.html("Date")
}
drawLineChart()
Fixed this myself using
.tickFormat(d3.timeFormat ("%d %b"))
as a method on the axis function
ie
const xAxisGenerator = d3.axisBottom()
.scale(xScale)
.tickFormat(d3.timeFormat ("%d %b"))
Related
I'm trying to plot a simple bar chart with date as x axis and temperature value as y axis. I'm using d3 library.
Created the xScale ans yScale as
const xScale = d3
.scaleTime()
.domain(d3.extent(dataset, xAccessor))
.range([0, dimensions.boundWidth]);
const yScale = d3
.scaleLinear()
.domain([0, d3.max(dataset, yAccessor)])
.range([dimensions.boundHeight, 0]);
Set the accessor functions as
const dateParse = d3.timeParse("%Y-%m-%d");
const xAccessor = (d) => dateParse(d["date"]);
const yAccessor = (d) => d["temperatureMax"];
And drew the rectangles using
const barsGroup = bounds.append("g");
const barGroups = barsGroup
.selectAll("mybars")
.data(dataset.slice(0, 10))
.join("g");
const barRects = barGroups
.append("rect")
.attr("x", (d) => xScale(xAccessor(d)))
.attr("y", (d) => yScale(yAccessor(d)))
.attr("width", "10")
.attr("height", (d) => dimensions.boundHeight - yScale(yAccessor(d)))
.attr("fill", "darkgray");
I'm trying to find out how to set the width attribute. Here I set it as a constant "10" just to see something on screen. Also trying to find out how to set proper spacing between each bars. I have a set up a codesandbox.io with the above set up. Any suggestions would be really helpful.
I am working with line-area chart using d3. But somehow the chart is not showing and there's no error in the console. I'm unable to figure out the issue. It will be helpful if anyone can help me with that.
I've added the entire code in Codepen. This is the link
Here's the sample code for ref:
const svg = d3.select("#chart")
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`);
svg.append('defs');
svg.call(createGradient);
svg.call(createGlowFilter);
const xScale = d3.scaleTime()
.domain([
d3.min(parsedData, d => d3.min(d.values, v => v.x)),
d3.max(parsedData, d => d3.max(d.values, v => v.x))
])
.range([0, width]);
const yScale = d3.scaleLinear()
.domain([
d3.min(parsedData, d => d3.min(d.values, v => v.y)),
d3.max(parsedData, d => d3.max(d.values, v => v.y))
])
.range([height, 0]);
const line = d3.line()
.x(d => xScale(d.x))
.y(d => yScale(d.y))
.curve(d3.curveCatmullRom.alpha());
svg.selectAll('.line')
.data(parsedData)
.enter()
.append('path')
.attr('d', (d) => {
const lineValues = line(d.values).slice(1);
const splitedValues = lineValues.split(',');
return `M0,${height},${lineValues},l0,${height - splitedValues[splitedValues.length - 1]}`
})
.style('fill', 'url(#gradient)')
svg.selectAll('.line')
.data(parsedData)
.enter()
.append('path')
.attr('d', d => line(d.values))
.attr('stroke-width', '2')
.style('fill', 'none')
.style('filter', 'url(#glow)')
.attr('stroke', '#e4647f');
Thanks in advance!
I've created a fork of your code that has the line and area showing.
Here are the major changes.
First, I parsed the strings into dates:
const parseTime = d3.timeParse("%b %Y");
const parsedData = bar_chart_data.map(val => ({
close: val.y,
date: parseTime(val.x)
}));
parsedData is now an array of objects, so we can update the scales accordingly:
const xScale = d3.scaleTime()
// extent returns an array containing the min and max
.domain(d3.extent(parsedData, d => d.date))
.range([0, width]);
const yScale = d3.scaleLinear()
.domain([0, d3.max(parsedData, d => d.close)])
.range([height, 0]);
Next, I switched d3.line() to d3.area(), since you want to draw an area underneath the line. This will save you from having to manually construct the "d" attribute for the area.
const area = d3.area()
.x(d => xScale(d.date))
.y1(d => yScale(d.close)) // top line
.y0(yScale(0)) // base line, always at 0
.curve(d3.curveCatmullRom.alpha(0.5));
For drawing the line and area, we do not need to do a data join, we can just use append() and pass the parsedData array to the area generator for the area and to area.lineY1() for the top line:
svg.append("path")
.attr("d", area(parsedData))
.style("fill", "url(#gradient)");
svg.append("path")
.attr("d", area.lineY1()(parsedData))
.attr("stroke-width", "2")
.style("fill", "none")
.style("filter", "url(#glow)")
.attr("stroke", "#e4647f");
I'm trying to create integer bands in the y axes.
Have tried changing .scaleband to .scalelinear and .ticks "arbitraryMetric" is stored as an integer.
Code:
const categories = vizData.map(d => d.arbitraryMetric);
const yScale = d3
.scaleBand()
.domain(categories)
.range([0, vizHeight - timelineMargin.bottom]);
const labels = vizCanvas
.selectAll('text')
.data(categories)
.enter()
.append('text')
.style('font-family', style.fontFamily)
.style('fill', '#3C4043')
.attr('x', timelineMargin.left - 10)
.attr('y', d => yScale(d) + yScale.bandwidth() / 2)
.attr('text-anchor', 'end')
.text(d => d);
This following solution will do the trick - credit to this answer
const yAxisTicks = yScale.ticks()
.filter(tick => Number.isInteger(tick));
const yAxis = d3.axisLeft(yScale)
.tickValues(yAxisTicks)
.tickFormat(d3.format('d'));
I'm writing a simple bar chart that draws a rect element for each piece of data. This is a part of my code without scales, which works fine:
const rect = svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", (d, i) => (i*4))
.attr("y", (d) => h - d[1]/50)
However, if I add y-scale, my bars flip over, and if I add x-scale, I can only see one bar. Here's my code with scales:
const xScale = d3.scaleLinear()
.domain([0, d3.max(dataset, (d) => d[0])])
.range([padding, w - padding]);
const yScale = d3.scaleLinear()
.domain([0, d3.max(dataset, (d) => d[1])])
.range([h - padding, padding]);
//some other code
const rect = svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", (d, i) => xScale(i*4))
.attr("y", (d) => yScale(d[1]/50))
Here's my CodePen with scales, that's what it looks like: http://codepen.io/enk/pen/vgbvWq?editors=1111
I'd be really greatful if somebody told me what am I doing wrong.
D3 scales are not messing with your chart. The problem here is simple.
Right now, this is your dataset:
[["1947-01-01", 243.1], ...]
As you can see, d[0] is a string (representing a date), not a number. And, in d3.scaleLinear, the domain is an array with two values only.
The solution here is using a scaleBand instead:
const xScale = d3.scaleBand()
.domain(dataset.map(d => d[0]))
.range([padding, w - padding]);
And changing the code of the rectangles to actually use the scales (I made several changes, check them):
const rect = svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("width", xScale.bandwidth())
.attr("x", (d => xScale(d[0]))) //xScale
.attr("height", (d => h - yScale(d[1]))) //yScale
.style("y", (d => yScale(d[1])))
.attr("data-date", (d) => d[0])
.attr("data-gdp", (d) => d[1])
.attr("class", 'bar')
Here is your updated CodePen: http://codepen.io/anon/pen/NdJGdo?editors=0010
I'm plotting the graphs with "number of clicks" as Y axis and "date" as X axis. Because of the large amount of data, the X axis is jumbled and couldn't display all the dates. I tried to use ticks(d3.time.months, 1) and tickFormat('%b %Y') to cur off some of the ticks. When I run the code, I got "getMonth() is not defined" for my data.
.tsv file:
date count
2013-01-01 4
2013-03-02 5
sample code:
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1, 0);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(d3.time.months, 1)
.tickFormat(d3.time.format('%b %Y'))
d3.tsv("data_Bar_Chart_Paino_Kristen.tsv", type, function(error, data) {
x.domain(data.map(function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.hits; })]);
var temp = height + 30; //+15
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(-8," + temp + ")")
.call(xAxis);
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.date); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.hits); })
.attr("height", function(d) {return height - y(d.hits); });
}
Is there a way to solve my problem and show ticks properly?
You'll need to tell D3 that your axis is a date. Try this:
//assumes the data is sorted chronologically
var xMin = data[0].dateFieldName;
var xMax = data[data.length-1].dateFieldName;
//set the scale for the x axis
var x = d3.time.scale().domain([xMin, xMax]).range([0, width]);
//straight from your code
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(d3.time.months, 1)
.tickFormat(d3.time.format('%b %Y'))
The trick here is to use the d3.time.scale() method.
If you use the d3.time.scale() you have another problem, you can't use x.rangeBand() when you draw the rect.