Xelad1 Xelad1 - 1 month ago 13
CSS Question

Getting d3 axis tick marks to line up center with graph points

For reference here is an image. Notice that the date on hover doesn't perfectly align with the date on the axis. If I try to change the margins I can shift the dates around but still with uneven spacing. I'm looking for a way to more manually control the spacing of the dates on the axis.
enter image description here

Here is the code in question:



const dataGroup = [
{
key: props.lineChart.key,
values: props.lineChart.values,
startColor: '#E2FFE4',
endColor: '#6FFCAF',
area: false,
}];
const vis = d3.select(ReactDOM.findDOMNode(this));
const bisectDate = d3.bisector(d => d.x).left;
const WIDTH = 1000;
const HEIGHT = 480;
const MARGINS = {
top: 20,
right: 40,
bottom: 20,
left: 30,
};
const minYValue = 0;
const maxYValue = d3.max(dataGroup.map(group =>
d3.max(group.values, row => row.y)
));

const format = d3.time.format('%m/%d');
const xScale = d3.time.scale()
.range([MARGINS.left, WIDTH - MARGINS.right])
.domain(d3.extent(dataGroup[0].values, d => d.x));

const customScale = d3.time.scale()
.range([8, WIDTH - MARGINS.right])
.domain(d3.extent(dataGroup[0].values, d => d.x));

const yScale = d3.scale.linear().range([HEIGHT - MARGINS.top, MARGINS.bottom])
.domain([minYValue, maxYValue + 200]);

const xAxis = d3.svg.axis()
.scale(customScale)
.ticks(d3.time.days, 1)
.tickFormat(format);

const yAxis = d3.svg.axis()
.scale(yScale)
.ticks(5)
.tickFormat(d => (d === 0 ? 0 : numeral(d).format('0a')))
.orient('left');

vis.append("g")
.attr("class", "grid")
.attr("transform", "translate(" + MARGINS.left + ",0)")
.call(makeXAxis(yScale)
.tickSize(-(WIDTH - MARGINS.right - MARGINS.left), 0, 0)
.tickFormat("")
)

var lineGen = d3.svg.line()
.x(function(d) {
return xScale(new Date(d.x));
})
.y(function(d) {
return yScale(d.y);
})
console.log('Data Group', dataGroup);

dataGroup.forEach(function(d, i) {
vis.append("linearGradient")
.attr("id", "line-gradient")
.attr("gradientUnits", "userSpaceOnUse")
.attr("x1", 0).attr("y1", "0")
.attr("x2", "100%").attr("y2", "0")
.selectAll("stop")
.data([
{offset: "0%", color: "yellow"},
{offset: "100%", color: "orange"}
])
.enter().append("stop")
.attr("offset", function(d) { return d.offset; })
.attr("stop-color", function(d) { return d.color; });

vis.append('svg:path')
.attr('class', "line clickable")
.attr('d', lineGen(d.values))
.attr('stroke', 'blue')
.attr('stroke-width', 2)
.attr('fill', 'none');

});


vis.append("svg:g")
.attr("class", "x axis")
.attr("transform", "translate(5," + (HEIGHT - MARGINS.bottom + 20) + ")")
.call(xAxis);
vis.append("svg:g")
.attr("class", "y axis")
.attr("transform", "translate(" + (MARGINS.left) + ",0)")
.call(yAxis);

var focus = vis.append("g")
.attr("class", "focus")
.style("display", "none")
.style("opacity", "1");

focus.append("circle")
.attr("r", 8)
.attr("fill", 'orange')
.attr("stroke", 'white')
.attr("stroke-width", '2px')

var tooltip = vis.append("g")
.attr("class", "tooltip")
.style("display", "none");


tooltip.append("rect")
.attr("width", 90)
.attr("height", 20)
.attr("rx", 2.5)
.attr("ry", 2.5)
.attr("fill", 'lightgray');

var tooltipText = tooltip.append("text")
.attr("dy", 15)
.attr("dx", 15);

tooltipText.text(null).append("tspan")
.attr('class', 'date');

tooltipText.append("tspan").text(' ');

tooltipText.append("tspan")
.attr('class', 'volume');

vis.on("mouseover", function() { focus.style("display", null); verticalLine.style("display", null); tooltip.style("display", null);})
.on("mouseout", function() {
focus.style("display", "none");
verticalLine.style("display", "none");
tooltip.style("display", "none");
updateInfluencerSample({
});
})
.on("mousemove", mousemove);

function mousemove() {
if(dataGroup.length === 1) {
index = 0;
}
else {
var isPositive = (yScale.invert(d3.mouse(this)[1]) > 0),
index = isPositive ? 0 : 1;
}
var x0 = xScale.invert(d3.mouse(this)[0]),
i = bisectDate(dataGroup[index].values, x0, 1),
d0 = dataGroup[index].values[i - 1],
d1 = dataGroup[index].values[i];

if(d0 && d1) {
var d = x0 - d0.x > d1.x - x0 ? d1 : d0;
if(dataGroup.length === 1) d.y = Math.abs(d.y);
var criticalPointYPos = (d.y > 0) ? -0.6 : 1.2;
focus.attr("transform", "translate(" + xScale(d.x) + "," + yScale(d.y) + ")");
tooltip.attr("transform", "translate(" + (xScale(d.x) - 45) + "," + (HEIGHT - MARGINS.bottom) + ")");
tooltip.select(".date").text(format(new Date(d.x))).attr('fill', '#929292');
tooltip.select(".volume").text(numeral(d.y).format('0a')).attr('fill', '#929292');
d3.select(".verticalLine").attr({
x1: 0,
x2: 0,
y1: MARGINS.top,
y2: HEIGHT - MARGINS.bottom,
})
.style("stroke-dasharray", ("3, 3"))
.attr("stroke-width", 3)
.attr("transform", function () {
return "translate(" + xScale(d.x) + ",0)";
});
updateInfluencerSample({
author: new Date().getMilliseconds(),
date: d.x,
});
}
}

var verticalLine = vis.append('line')
.attr({
x1: 0,
x2: 0,
y1: yScale(maxYValue),
y2: HEIGHT - MARGINS.bottom,
})
.attr('stroke', 'gray')
.attr('class', 'verticalLine')
.style('display', 'none');

vis.transition().style("opacity", 1);
}




Answer

Changed translate attribute to 0 which was offsetting the axis.

 vis.append("svg:g")
                      .attr("class", "x axis")
                      .attr("transform", "translate(0," + (HEIGHT - MARGINS.bottom + 20) + ")")
                      .call(xAxis);
Comments