How can I implement an invert function for a point

2020-04-05 08:48发布

问题:

I am trying to add a tooltip for my dual line chart graph.

However, instead of using timeScale or scaleLinear, I used scalePoint to graph my chart.

I am trying to achieve the following effect: https://bl.ocks.org/mbostock/3902569

this.x  = d3.scalePoint().range([ this.margin.left, this.width - this.margin.right ]);
this.xAxis      = d3.axisBottom(this.x);
this.x.domain(
       this.dataArray.map(d => {
       return this.format(d[ 'year' ]);
}));

Here is my mouseover function,

function mousemove() {
            //d3.mouse(this)[ 0 ]
            //x.invert

            var x0 = d3.mouse(this)[ 0 ],
                i  = bisectDate(data, x0, 1),
                d0 = data[ i - 1 ],
                d1 = data[ i ],
                d  = x0 - d0.year > d1.year - x0 ? d1 : d0;

            console.log(x0);
            // focus.attr("transform", "translate(" + x(format(d.year)) + "," + y(d.housing_index_change) + ")");
            // focus.select("text").text(d.housing_index_change);
        }

Since I am using scalePoint, there is obviously no invert function to map the coordinates to my data. and I am only retrieving the first element in the array and it is the only one that is being display regardless of the position of the mouse.

So my question is, how can I implement the invert functionality here while still using scalePoint?

Thank you :)

回答1:

You are right, there is no invert for a point scale. But you can create your own function to get the corresponding domain of a given x position:

function scalePointPosition() {
    var xPos = d3.mouse(this)[0];
    var domain = xScale.domain(); 
    var range = xScale.range();
    var rangePoints = d3.range(range[0], range[1], xScale.step())
    var yPos = domain[d3.bisect(rangePoints, xPos) -1];
    console.log(yPos);
}

Step by step explanation

First, we get the x mouse position.

var xPos = d3.mouse(this)[0];

Then, based on your scale's range and domain...

var domain = xScale.domain(); 
var range = xScale.range();

...we create an array with all the steps in the point scale using d3.range:

var rangePoints = d3.range(range[0], range[1], xScale.step())

Finally, we get the corresponding domain using bisect:

var yPos = domain[d3.bisect(rangePoints, xPos) -1];

Check the console.log in this demo:

var data = [{
    A: "groupA",
    B: 10
}, {
    A: "groupB",
    B: 20
}, {
    A: "groupC",
    B: 30
}, {
    A: "groupD",
    B: 10
}, {
    A: "groupE",
    B: 17
}]

var width = 500,
    height = 200;

var svg = d3.selectAll("body")
    .append("svg")
    .attr("width", width)
    .attr("height", height);

var color = d3.scaleOrdinal(d3.schemeCategory10)
    .domain(data.map(function(d) {
        return d.A
    }));

var xScale = d3.scalePoint()
    .domain(data.map(function(d) {
        return d.A
    }))
    .range([50, width - 50])
    .padding(0.5);

var yScale = d3.scaleLinear()
    .domain([0, d3.max(data, function(d) {
        return d.B
    }) * 1.1])
    .range([height - 50, 10]);

var circles = svg.selectAll(".circles")
    .data(data)
    .enter()
    .append("circle")
    .attr("r", 8)
    .attr("cx", function(d) {
        return xScale(d.A)
    })
    .attr("cy", function(d) {
        return yScale(d.B)
    })
    .attr("fill", function(d) {
        return color(d.A)
    });

var xAxis = d3.axisBottom(xScale);
var yAxis = d3.axisLeft(yScale);

svg.append("g").attr("transform", "translate(0,150)")
    .attr("class", "xAxis")
    .call(xAxis);

svg.append("g")
    .attr("transform", "translate(50,0)")
    .attr("class", "yAxis")
    .call(yAxis);

svg.append("rect")
    .attr("opacity", 0)
    .attr("x", 50)
    .attr("width", width - 50)
    .attr("height", height)
    .on("mousemove", scalePointPosition);

function scalePointPosition() {
    var xPos = d3.mouse(this)[0];
    var domain = xScale.domain();
    var range = xScale.range();
    var rangePoints = d3.range(range[0], range[1], xScale.step())
    var yPos = domain[d3.bisect(rangePoints, xPos) - 1];
    console.log(yPos);
}
.as-console-wrapper { max-height: 20% !important;}
<script src="https://d3js.org/d3.v4.min.js"></script>