简体   繁体   中英

d3js v4: independent multiple axes scaling and panning

I'm trying to implement a chart framework which is able to create line and area charts with multiple axes for each orientation, ie 2-y-axis left, 1-yaxis right and 1-x-axis bottom.

That I got to work. My next task would be to implement zoom behaviour to the chart. I indented to have a global zoom behaviour, which is trigged if the user uses his mouse within the plot area. The displayed series would get rescaled and it would be possible to pan the plot. This one I got to work too.

In addition I wanted an independent zoom/scaling for each axis. I got the scaling, but I still have problems with the global zooming and panning. If I scale one axis, the associated series in the plot area gets rescaled but the panning does not work. And after the independent scaling of an axis, if I use the global rescale, the scaling gets reset and then gets scaled by the global zoom behaviour.

On the d3.js page I found an simple example for independent and global scaling and panning, but written with d3v3 .

I changed the example in such a way, so that it displays my problem jsfiddle demo . Use you mouse on the axes and in the plot area.

<!DOCTYPE html>
<html>
  <head>
    <title>TODO supply a title</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Simple Independent Axis Zooms on x, y, or xy</title>
    <script src="//d3js.org/d3.v4.min.js"></script>
    <style>
        .axis path, .axis line {
            fill: none;
            stroke: #000;
            shape-rendering: crispEdges;
        }
    </style>
</head>
<body>
    <div id="chart"></div>
    <script>


        var data = [];
        for (var i = 0; i < 100; i++) {
            data.push([Math.random(), Math.random()]);
        }
        var svg = d3.select('#chart')
                .append("svg")
                .attr("width", window.innerWidth).attr("height", window.innerHeight);

        function example(svg, data) {
            var svg;
            var margin = {
                top: 60,
                bottom: 80,
                left: 60,
                right: 0
            };
            var width = 500;
            var height = 400;
            var xaxis = d3.axisBottom();
            var yaxis = d3.axisLeft();
            var xscale = d3.scaleLinear();
            var yscale = d3.scaleLinear();
            var xcopyScale, ycopyScale;
            var xyzoom, xzoom, yzoom;
            updateZooms();





            function update() {

                var gs = svg.select("g.scatter");

                var circle = gs.selectAll("circle")
                        .data(data);

                circle.enter().append("svg:circle")
                        .attr("class", "points")
                        .style("fill", "steelblue")
                        .attr("cx", function (d) {
                            return X(d);
                        })
                        .attr("cy", function (d) {
                            return Y(d);
                        })
                        .attr("r", 4);

                circle.attr("cx", function (d) {
                    return X(d);
                })
                        .attr("cy", function (d) {
                            return Y(d);
                        });

                circle.exit().remove();
            }

            function updateZooms() {
                xyzoom = d3.zoom()
                        .on("zoom", function () {
                            xaxis.scale(d3.event.transform.rescaleX(xscale));
                            yaxis.scale(d3.event.transform.rescaleY(yscale));
                            draw();
                        });

                xzoom = d3.zoom()
                        .on("zoom", function () {
                            xaxis.scale(d3.event.transform.rescaleX(xscale));
                            draw();
                        });

                yzoom = d3.zoom()
                        .on("zoom", function () {
                            yaxis.scale(d3.event.transform.rescaleY(yscale));
                            draw();
                        });
            }

            function draw() {
                svg.select('g.x.axis').call(xaxis);
                svg.select('g.y.axis').call(yaxis);
                update();
                // After every draw, we reinitialize zoom. After every zoom, we reexecute draw, which will reinitialize zoom.
                // This is how we can apply multiple independent zoom behaviors to the scales.
                // (Note that the zoom behaviors will always end up with zoom at around 1.0, and translate at around [0,0])
                svg.select('rect.zoom.xy.box').call(xyzoom);
                svg.select('rect.zoom.x.box').call(xzoom);
                svg.select('rect.zoom.y.box').call(yzoom);
            }
            // X value to scale
            function X(d) {
                return xaxis.scale() !== undefined && xaxis.scale() !== null
                        ? xaxis.scale()(d[0])
                        : xscale(d[0]);
            }

            // Y value to scale
            function Y(d) {
                return yaxis.scale() !== undefined && yaxis.scale() !== null
                        ? yaxis.scale()(d[1])
                        : yscale(d[1]);
            }


            var g = svg.append('g')
                    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

            g.append("defs").append("clipPath")
                    .attr("id", "clip")
                    .append("rect")
                    .attr("width", width - margin.left - margin.right)
                    .attr("height", height - margin.top - margin.bottom);

            g.append("svg:rect")
                    .attr("class", "border")
                    .attr("width", width - margin.left - margin.right)
                    .attr("height", height - margin.top - margin.bottom)
                    .style("stroke", "black")
                    .style("fill", "none");

            g.append("g").attr("class", "x axis")
                    .attr("transform", "translate(" + 0 + "," + (height - margin.top - margin.bottom) + ")");

            g.append("g").attr("class", "y axis");

            g.append("g")
                    .attr("class", "scatter")
                    .attr("clip-path", "url(#clip)");

            g
                    .append("svg:rect")
                    .attr("class", "zoom xy box")
                    .attr("width", width - margin.left - margin.right)
                    .attr("height", height - margin.top - margin.bottom)
                    .style("visibility", "hidden")
                    .attr("pointer-events", "all")
                    .call(xyzoom);

            g
                    .append("svg:rect")
                    .attr("class", "zoom x box")
                    .attr("width", width - margin.left - margin.right)
                    .attr("height", height - margin.top - margin.bottom)
                    .attr("transform", "translate(" + 0 + "," + (height - margin.top - margin.bottom) + ")")
                    .style("visibility", "hidden")
                    .attr("pointer-events", "all")
                    .call(xzoom);

            g
                    .append("svg:rect")
                    .attr("class", "zoom y box")
                    .attr("width", margin.left)
                    .attr("height", height - margin.top - margin.bottom)
                    .attr("transform", "translate(" + -margin.left + "," + 0 + ")")
                    .style("visibility", "hidden")
                    .attr("pointer-events", "all")
                    .call(yzoom);

            // Update the x-axis
            xscale.domain(d3.extent(data, function (d) {
                return d[0];
            })).range([0, width - margin.left - margin.right]);

            xaxis.scale(xscale)
                    .tickPadding(10);

            svg.select('g.x.axis').call(xaxis);

            // Update the y-scale.
            yscale.domain(d3.extent(data, function (d) {
                return d[1];
            })).range([height - margin.top - margin.bottom, 0]);

            yaxis.scale(yscale)
                    .tickPadding(10);

            svg.select('g.y.axis').call(yaxis);

            draw();

        }

        var exampleChart = example(svg, data);
    </script>
  </body>
</html>

To put it briefly: How can I solve my problem by using d3v4 to create a chart with multiple axes that has global and independent scaling and panning behaviour ?

as of now the current release of d3v4 doesn't natively support multiple independent zoom behaviours.

A possible solution would be to reset the internal transform state stored inside the selection on which you called the appropriate zoom behaviour. There is an already open issue on the argument and i encourage you to go and read it and offer your input as well.

Best of luck!

There seems to be no correct solution (see https://github.com/d3/d3-zoom/issues/48 ).

But if you clear the scale after each zoom it seems to work (see https://jsfiddle.net/DaWa/dLmp8zk8/2/ ).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM