Felix
Felix

Reputation: 1017

D3.js: Wrong position of horizontal and vertical focus lines (crosshairs) in line chart

I adapted this example for my data: D3js Graph with X and Y crosshairs, and a threshold line.

It works pretty good, but the lines are not shown on the right point. See screenshot. enter image description here

I think there is a problem with the domain, but I can't figure it out. Here is my script:

Update: The problem is caused by the way I position the text. Any ideas how to position the text in another way?

var svg = d3.select("svg"),
        margin = {top: 20, right: 50, bottom: 30, left: 50},
        width = +svg.attr("width") - margin.left - margin.right,
        height = +svg.attr("height") - margin.top - margin.bottom,
        g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");

var parseTime = d3.utcParse("%Y-%m-%dT%H:%M:%S.%LZ");
var bisectDate = d3.bisector(function(d) { return d.timestamp; }).left;
var formatValue = d3.format(",.2f");


d3.json("data.json", function(error, json) {
    if (error) throw error;
    var data = json[0].tideData;
    data.forEach(function(d) {
        d.timestamp = parseTime(d.timestamp);
    });

    var xDomain = d3.extent(data, function(d) { return d.timestamp; });
    var yDomain = d3.extent(data, function(d) { return d.tide; });

    var xScale =  d3.scaleTime().rangeRound([0, width]).domain(xDomain);
    var yScale = d3.scaleLinear().rangeRound([height, 0]).domain(yDomain);

    var line = d3.line()
            .defined(function(d) { return d.tide!=null; })
            .x(function(d) { return xScale(d.timestamp); })
            .y(function(d) { return yScale(d.tide); });

    g.append("g")
            .attr("class", "axis axis--x")
            .attr("transform", "translate(0," + height + ")")
            .call(d3.axisBottom(xScale));

    g.append("g")
            .attr("class", "axis axis--y")
            .call(d3.axisLeft(yScale))
            .append("text")
            .attr("fill", "#000")
            .attr("transform", "rotate(-90)")
            .attr("y", 6)
            .attr("dy", "0.71em")
            .style("text-anchor", "end")
            .text("Sea Level");

    g.append("path")
            .datum(data)
            .attr("class", "line")
            .attr("d", line);

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

    focus.append("circle")
            .attr("r", 4.5);

    focus.append("text")
            .attr("x", 9)
            .attr("dy", ".35em");

    focus.append('line')
            .attr('id', 'focusLineX')
            .attr('class', 'focusLine');

    focus.append('line')
            .attr('id', 'focusLineY')
            .attr('class', 'focusLine');

    g.append("rect")
            .attr("class", "overlay")
            .attr("width", width)
            .attr("height", height)
            .on("mouseover", function() { focus.style("display", null); })
            .on("mouseout", function() { focus.style("display", "none"); })
            .on("mousemove", mousemove);

    function mousemove() {
        var mouse = d3.mouse(this);
        var mouseDate = xScale.invert(mouse[0]);
        var i = bisectDate(data, mouseDate); // returns the index to the current data item

        var d0 = data[i - 1];
        var d1 = data[i];
        // work out which date value is closest to the mouse
        var d = mouseDate - d0[0] > d1[0] - mouseDate ? d1 : d0;

        var x = xScale(d.timestamp);
        var y = yScale(d.tide);

        focus.attr("transform", "translate(" + x + "," + y + ")");
        focus.select("text").text(formatValue(d.tide));
        focus.select('#focusLineX')
                .attr('x1', x).attr('y1', yScale(yDomain[0]))
                .attr('x2', x).attr('y2', yScale(yDomain[1]));
        focus.select('#focusLineY')
                .attr('x1', xScale(xDomain[0])).attr('y1', y)
                .attr('x2', xScale(xDomain[1])).attr('y2', y);

    }
});

Upvotes: 0

Views: 1003

Answers (2)

Felix
Felix

Reputation: 1017

The problem is the transformation of the whole focus variable. I positioned the circle and the text separately and now it works:

        // focus tracking

        var focus = g.append('g').style('display', 'none')

        focus.append('circle')
                .attr('id', 'focusCircle')
                .attr('r', 4.5)
                .attr('class', 'circle focusCircle')

        focus.append("text")
                .attr("x", 9)
                .attr("dy", ".35em")

        focus.append('line')
                .attr('id', 'focusLineX')
                .attr('class', 'focusLine')

        focus.append('line')
                .attr('id', 'focusLineY')
                .attr('class', 'focusLine')

        g.append("rect")
                .attr("class", "overlay")
                .attr("width", width)
                .attr("height", height)
                .on("mouseover", function() { focus.style("display", null) })
                .on("mouseout", function() { focus.style("display", "none") })
                .on("mousemove", mousemove)

        function mousemove() {
            var mouse = d3.mouse(this)
            var mouseDate = xScale.invert(mouse[0])
            var i = bisectDate(data, mouseDate) // returns the index to the current data item

            var d0 = data[i - 1]
            var d1 = data[i]
            // work out which date value is closest to the mouse
            var d = mouseDate - d0[0] > d1[0] - mouseDate ? d1 : d0

            var x = xScale(d.date)
            var y = yScale(d.tide)

            focus.select("text")
                    .attr("transform", "translate(" + x + "," + y + ")")
                    .text(formatValue(d.tide))

            focus.select('#focusCircle')
                    .attr('cx', x)
                    .attr('cy', y)

            focus.select('#focusLineX')
                    .attr('x1', xScale(d.date)).attr('y1', yScale(yDomain[0]))
                    .attr('x2', xScale(d.date)).attr('y2', yScale(yDomain[1]))
            focus.select('#focusLineY')
                    .attr('x1', xScale(xDomain[0])).attr('y1', yScale(d.tide))
                    .attr('x2', xScale(xDomain[1])).attr('y2', yScale(d.tide))

        }

Upvotes: 1

user2427829
user2427829

Reputation: 128

In the code where you are creating the focus lines, you are using the current data point(mouse position) to start your lines.

    focus.select('#focusLineX')
            .attr('x1', x).attr('y1', yScale(yDomain[0]))
            .attr('x2', x).attr('y2', yScale(yDomain[1]));
    focus.select('#focusLineY')
            .attr('x1', xScale(xDomain[0])).attr('y1', y)
            .attr('x2', xScale(xDomain[1])).attr('y2', y);

You horizontal line should start from y axis and span over the width at the hight of your point. Similarly the vertical line should start at top of graph and go till bottom(x axis).

Try this change

   focus.select('#focusLineX')
            .attr('x1', x).attr('y1', 0)
            .attr('x2', x).attr('y2', heightOfChart);
    focus.select('#focusLineY')
            .attr('x1', 0).attr('y1', y)
            .attr('x2', widthOfChart).attr('y2', y);

Hope it helps

Upvotes: 0

Related Questions