timeSeriesBar = (data, bucketSize) => {
const width = 600;
const height = 300;
const margin = { top: 20, right: 30, bottom: 30, left: 40 }
const viewportHeight = height;
const viewportWidth = width;
const xMapper = d3
.scaleUtc()
.domain(d3.extent(data, d => d.date))
.range([margin.left, viewportWidth - margin.right]);
const yMapper = d3
.scaleLinear()
.domain([Math.min(0, d3.min(data, d => d.value))*1.3, d3.max(data, d => d.value)*1.3])
.range([viewportHeight - margin.bottom, margin.top]);
const [start, end] = d3.extent(data, d => d.date);
const dateDiff = end.diff(start);
const years = dateDiff.as('years');
let tickFn, tickEvery, fmt;
if (years > 6) {
tickFn = d3.timeYear;
tickEvery = d3.timeYear.every(3);
fmt = 'yyyy';
} else {
tickFn = d3.timeMonth;
tickEvery = d3.timeMonth.every(3);
fmt = 'LLL dd';
}
const xAxis = function(g) {
return g.attr("transform", `translate(0,${height - margin.bottom})`).call(
d3
.axisBottom(xMapper)
.ticks(tickEvery)
.tickFormat(d => d <= tickFn(d) ? luxon.DateTime.fromJSDate(d).toFormat(fmt) /*(fmt)*/: null)
.tickSizeOuter(0)
);
};
function formatTick(d) {
if (d >= 1000) {
const s = (d / 1000).toFixed(0);
return this.parentNode.nextSibling ? `\xa0${s}` : `$${s} thousand`;
}
return d;
}
const yAxis = function(g) {
return g.attr("transform", `translate(${margin.left},0)`).call(
d3
// .axisLeft(yMapper) // this will put the axis on the left, and no horizontal lines
.axisRight(yMapper) // start on right, scoot to left to make value lines
.tickSize(width - margin.left - margin.right) // make the lines really long
.tickFormat(formatTick)
.ticks(5)
// .tickSizeOuter(0) // how long the line sticks out
)
// to remove the axis line, add the following
.call(g => g.select(".domain").remove())
// Make the axis lines dashed
.call(g => g.selectAll(".tick:not(:first-of-type) line")
.attr("stroke-opacity", 0.5)
.attr("stroke-dasharray", "2,2"))
// Scoot text to left axis
.call(g => g.selectAll(".tick text")
.attr("x", 4)
.attr("dy", -4))
;
};
const svg = d3
.create("svg")
.attr("width", width)
.attr("height", height)
.attr("style", "border:1px solid black");
// Should it be by month or by day?
const bucket = bucketSize || 'weeks';
const numDays = dateDiff.as(bucket);
const barWidth = ((xMapper(end) - xMapper(start))/numDays)*0.9;
svg.append("g")
.attr("fill", "steelblue")
.selectAll("rect")
.data(data)
.join("rect")
.attr("x", d => xMapper(d.date))
.attr("y", d => yMapper(Math.max(d.value, 0)))
.attr("height", d => {
return Math.abs(yMapper(0) - yMapper(d.value));
})
.attr("width", barWidth);
// const line = d3
// .line()
// .x(d => xMapper(d.date))
// .y(d => yMapper(d.value));
// svg
// .append("path")
// .datum(data)
// .attr("d", line)
// .attr("fill", "none")
// .attr("stroke", "steelblue")
// .attr("stroke-width", 1.5)
// .attr("stroke-miterlimit", 1)
// .attr("stroke-linejoin", "round")
// .attr("stroke-linecap", "round");
svg.append("g").call(xAxis);
svg.append("g").call(yAxis);
return svg.node();
}