d3.js Streudiagramm - Zoomen / Ziehen von Grenzen, Zoomtasten, Zoom zurücksetzen, Median berechnen

Ich habe ein d3.js-Streudiagramm mit Zoom- / Schwenkfunktion erstellt. Sie können das Ganze hier sehen (klicken Sie auf "In einem neuen Fenster öffnen", um das Ganze zu sehen):http://bl.ocks.org/129f64bfa2b0d48d27c9

Es gibt ein paar Features, die ich nicht herausfinden konnte und die ich gerne nutzen würde, wenn mich jemand in die richtige Richtung lenken könnte:

Ich möchte X / Y-Zoom- / Schwenkgrenzen auf den Bereich anwenden, damit Sie ihn nicht unter einen bestimmten Punkt (z. B. Null) ziehen können.Ich habe auch versucht, +/- Zoom-Schaltflächen im Google Maps-Stil zu erstellen, ohne Erfolg. Irgendwelche Ideen?

Viel weniger wichtig ist, dass es auch einige Bereiche gibt, in denen ich eine Lösung gefunden habe, die jedoch sehr rau ist. Wenn Sie also eine bessere Lösung haben, lassen Sie es mich bitte wissen:

Ich habe eine Schaltfläche zum Zurücksetzen des Zooms hinzugefügt, die jedoch lediglich das Diagramm löscht und an seiner Stelle ein neues generiert, anstatt die Objekte tatsächlich zu zoomen. Idealerweise sollte der Zoom tatsächlich zurückgesetzt werden.

Ich habe meine eigene Funktion geschrieben, um den Median der X- und Y-Daten zu berechnen. Ich bin mir jedoch sicher, dass es einen besseren Weg geben muss, dies mit d3.median zu tun, aber ich kann nicht herausfinden, wie es funktioniert.

var xMed = median(_.map(data,function(d){ return d.TotalEmployed2011;}));
var yMed = median(_.map(data,function(d){ return d.MedianSalary2011;}));

function median(values) {
    values.sort( function(a,b) {return a - b;} );
    var half = Math.floor(values.length/2);

    if(values.length % 2)
        return values[half];
    else
        return (parseFloat(values[half-1]) + parseFloat(values[half])) / 2.0;
};

Eine sehr vereinfachte (d. H. Alte) Version des JS finden Sie weiter unten. Das vollständige Skript finden Sie unterhttps://gist.github.com/richardwestenra/129f64bfa2b0d48d27c9#file-main-js

d3.csv("js/AllOccupations.csv", function(data) {

    var margin = {top: 30, right: 10, bottom: 50, left: 60},
        width = 960 - margin.left - margin.right,
        height = 500 - margin.top - margin.bottom;

    var xMax = d3.max(data, function(d) { return +d.TotalEmployed2011; }),
        xMin = 0,
        yMax = d3.max(data, function(d) { return +d.MedianSalary2011; }),
        yMin = 0;

    //Define scales
    var x = d3.scale.linear()
        .domain([xMin, xMax])
        .range([0, width]);

    var y = d3.scale.linear()
        .domain([yMin, yMax])
        .range([height, 0]);

    var colourScale = function(val){
        var colours = ['#9d3d38','#c5653a','#f9b743','#9bd6d7'];
        if (val > 30) {
            return colours[0];
        } else if (val > 10) {
            return colours[1];
        } else if (val > 0) {
            return colours[2];
        } else {
            return colours[3];
        }
    };


    //Define X axis
    var xAxis = d3.svg.axis()
        .scale(x)
        .orient("bottom")
        .tickSize(-height)
        .tickFormat(d3.format("s"));

    //Define Y axis
    var yAxis = d3.svg.axis()
        .scale(y)
        .orient("left")
        .ticks(5)
        .tickSize(-width)
        .tickFormat(d3.format("s"));

    var svg = d3.select("#chart").append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
        .call(d3.behavior.zoom().x(x).y(y).scaleExtent([1, 8]).on("zoom", zoom));

    svg.append("rect")
        .attr("width", width)
        .attr("height", height);

    svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis);

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

    // Create points
    svg.selectAll("polygon")
        .data(data)
        .enter()
        .append("polygon")
        .attr("transform", function(d, i) {
            return "translate("+x(d.TotalEmployed2011)+","+y(d.MedianSalary2011)+")";
        })
        .attr('points','4.569,2.637 0,5.276 -4.569,2.637 -4.569,-2.637 0,-5.276 4.569,-2.637')
        .attr("opacity","0.8")
        .attr("fill",function(d) {
            return colourScale(d.ProjectedGrowth2020);
        });

    // Create X Axis label
    svg.append("text")
        .attr("class", "x label")
        .attr("text-anchor", "end")
        .attr("x", width)
        .attr("y", height + margin.bottom - 10)
        .text("Total Employment in 2011");

    // Create Y Axis label
    svg.append("text")
        .attr("class", "y label")
        .attr("text-anchor", "end")
        .attr("y", -margin.left)
        .attr("x", 0)
        .attr("dy", ".75em")
        .attr("transform", "rotate(-90)")
        .text("Median Annual Salary in 2011 ($)");


    function zoom() {
      svg.select(".x.axis").call(xAxis);
      svg.select(".y.axis").call(yAxis);
      svg.selectAll("polygon")
            .attr("transform", function(d) {
                return "translate("+x(d.TotalEmployed2011)+","+y(d.MedianSalary2011)+")";
            });
    };
    }
});

Jede Hilfe wäre sehr dankbar. Vielen Dank!

Bearbeiten: Hier ist eine Zusammenfassung der von mir verwendeten Korrekturen, basierend auf den unten aufgeführten Vorschlägen von Superboggly:

    // Zoom in/out buttons:
    d3.select('#zoomIn').on('click',function(){
        d3.event.preventDefault();
        if (zm.scale()< maxScale) {
            zm.translate([trans(0,-10),trans(1,-350)]);
            zm.scale(zm.scale()*2);
            zoom();
        }
    });
    d3.select('#zoomOut').on('click',function(){
        d3.event.preventDefault();
        if (zm.scale()> minScale) {
            zm.scale(zm.scale()*0.5);
            zm.translate([trans(0,10),trans(1,350)]);
            zoom();
        }
    });
    // Reset zoom button:
    d3.select('#zoomReset').on('click',function(){
        d3.event.preventDefault();
        zm.scale(1);
        zm.translate([0,0]);
        zoom();
    });


    function zoom() {

        // To restrict translation to 0 value
        if(y.domain()[0] < 0 && x.domain()[0] < 0) {
            zm.translate([0, height * (1 - zm.scale())]);
        } else if(y.domain()[0] < 0) {
            zm.translate([d3.event.translate[0], height * (1 - zm.scale())]);
        } else if(x.domain()[0] < 0) {
            zm.translate([0, d3.event.translate[1]]);
        }
        ...
    };

Die Zoom-Übersetzung, die ich verwendet habe, ist sehr ad hoc und verwendet im Grunde beliebige Konstanten, um die Positionierung mehr oder weniger am richtigen Ort zu halten. Es ist nicht ideal und ich würde gerne Vorschläge für eine allgemeinere Technik machen. In diesem Fall funktioniert es jedoch gut genug.

Antworten auf die Frage(2)

Ihre Antwort auf die Frage