2013-02-25 26 views
6

Ho costruito un diagramma a dispersione d3.js con funzionalità zoom/panoramica. Si può vedere la cosa completa (clicca 'Apri in una nuova finestra' per vedere il tutto): http://bl.ocks.org/129f64bfa2b0d48d27c9d3.js diagramma a dispersione - zoom/trascinamento confini, pulsanti zoom, reimpostazione zoom, calcolo mediana

ci sono un paio di caratteristiche che sono stato in grado di capire, che mi piacerebbe una mano con esso se qualcuno mi può puntare nella giusta direzione:

  1. voglio applicare X/Y confini zoom/pan per l'area, in modo che non si può trascinarla sotto un certo punto (ad esempio pari a zero).
  2. Ho anche provato a creare pulsanti zoom +/- di stile di Google Maps, senza alcun successo. Qualche idea?

Molto meno importante, ci sono anche un paio di aree in cui ho capito una soluzione ma è molto agitato, quindi se avete una soluzione migliore, allora si prega di fare fatemi sapere:

  1. Ho aggiunto un pulsante 'reset zoom' ma semplicemente elimina il grafico e ne genera uno nuovo al suo posto, piuttosto che lo zoom effettivo degli oggetti. Idealmente dovrebbe effettivamente resettare lo zoom.
  2. Ho scritto la mia funzione per calcolare la mediana dei dati X e Y. Comunque sono sicuro che ci deve essere un modo migliore per farlo con d3.median ma non riesco a capire come farlo funzionare.

    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; 
    }; 
    

A (cioè vecchia) versione molto semplificata della JS è inferiore. Potete trovare lo script completo al https://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)+")"; 
      }); 
    }; 
    } 
}); 

Qualsiasi aiuto sarebbe massicciamente apprezzato. Grazie!

Edit: Ecco un riepilogo delle correzioni che ho usato, sulla base dei suggerimenti di Superboggly qui sotto:

// 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]]); 
     } 
     ... 
    }; 

La traduzione di zoom che ho usato è molto ad hoc e utilizza fondamentalmente costanti abitrary per mantenere il posizionamento più o meno nel posto giusto. Non è l'ideale, e sarei disposto a intrattenere suggerimenti per una tecnica più universalmente valida. Tuttavia, funziona abbastanza bene in questo caso.

risposta

11

Per iniziare con la funzione mediana è sufficiente un array e un accessorio opzionale. Così si può utilizzare lo stesso modo di usare max:

var med = d3.median(data, function(d) { return +d.TotalEmployed2011; }); 

Per quanto riguarda gli altri, se si tira fuori il vostro comportamento di zoom è possibile controllare un po 'meglio. Così, per esempio, invece di

var svg = d3.select()...call(d3.behavior.zoom()...) 

prova:

var zm = d3.behavior.zoom().x(x).y(y).scaleExtent([1, 8]).on("zoom", zoom); 
var svg = d3.select()...call(zm); 

quindi è possibile impostare il livello di zoom e la traduzione direttamente:

function zoomIn() { 
    zm.scale(zm.scale()*2); 
    // probably need to compute a new translation also 
} 

function reset() { 
    zm.scale(1); 
    zm.translate([0,0]); 
} 

La limitazione del campo di panning è un po 'più complicato.Semplicemente non puoi aggiornare quando la traduzione o la scala non sono di tuo gradimento all'interno della tua funzione di zoom (o imposta la "traduzione" dello zoom su ciò che ti serve). Qualcosa di simile (penso che nel tuo caso):

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

Tenete presente che se si vuole lo zoom per consentire un negativo sull'asse, ma panning non si troverà di entrare in alcuni scenari difficili.

Questo potrebbe essere datata, ma guarda che Limiting domain when zooming or panning in D3.js

Si noti inoltre che il comportamento di zoom ha avuto la funzionalità per limitare panning e zoom a one point. Ma il codice è stato estratto in un secondo momento update.

+0

Grazie ancora Superboggly! Il tuo codice zoomIn/reset sembra funzionare, ma la scala cambia solo sull'evento di zoom successivo (ad esempio su trascinamento o rotellina del mouse). Sto facendo fatica a far aggiustare le cose al clic del pulsante. Deve essere semplice ma non riesco a capirlo. Il codice di clic del mio pulsante è: d3.select ('# zoomIn'). Call (zoom) .on ('clic', funzione() { d3.event.preventDefault(); zm.scale (zm.scale () * 2); }); Ho provato vari usi di .call(), zoom() e .on() senza alcun risultato. – richardwestenra

+0

Ho aggiunto un piccolo quadratino zoom blu rapido nella parte inferiore del mio [ultimo jsfiddle per te] (http://jsfiddle.net/superboggly/SD5cK/2/). Tutto ciò che ti manca è una chiamata a zoom() dopo aver impostato la scala. Pensa alla funzione di zoom che hai fornito applicando lo stato di zoom calcolato dal comportamento. – Superboggly

+0

okay! Smistato! Si scopre che è esattamente quello che stavo facendo ma non funzionava per me ... Per farla breve: stavi usando d3 versione 3 mentre stavo usando la versione 2, ed è per questo che non ha funzionato quando ho lasciato cadere il tuo quadrato blu nel mio codice. Sono passati alla versione 3 e ora funziona. Saluti :) – richardwestenra

-1

Non mi piace reinventare la ruota. Stavo cercando i grafici a dispersione che consentono lo zoom. Highcharts è uno di questi, ma c'è la trama, che è basata su D3 e non consente solo lo zoom, ma puoi anche avere dataset di linea sulla trama scatter, che desidero con alcuni dei miei set di dati, ed è difficile da trovare con altri biblioteche di trama. Mi piacerebbe fare un tentativo:

https://plot.ly/javascript/line-and-scatter/

https://github.com/plotly/plotly.js

Utilizzando tale libreria bello si può risparmiare un sacco di tempo e di dolore.

Problemi correlati