2013-02-25 19 views
6

Zbudowałem wykres rozproszenia d3.js z funkcją powiększania/przesuwania. Możesz zobaczyć pełną treść (kliknij "Otwórz w nowym oknie", aby zobaczyć całość): http://bl.ocks.org/129f64bfa2b0d48d27c9Wykres rozproszenia d3.js - granice powiększania/przeciągania, przyciski powiększania, resetowanie powiększenia, obliczanie mediany

Jest kilka funkcji, których nie mogłem zrozumieć, które chciałbym ręka z nim, jeśli ktoś może wskazać mi w dobrym kierunku:

  1. Chcę zastosować granice X/Y zoom/pan w tym obszarze, aby nie można było przeciągnąć go poniżej określonego punktu (np.).
  2. Udało mi się też nakręcić przyciski stylu +/- Zoom w Mapach Google bez żadnych sukcesów. Jakieś pomysły?

Znacznie mniej ważniejsze, istnieje również kilka obszarów, w których mam zorientowali się rozwiązanie, ale jest to bardzo szorstki, więc jeśli masz lepsze rozwiązanie to proszę daj mi znać:

  1. Dodałem przycisk "resetowania zoomu", ale po prostu usuwa on wykres i zamiast niego generuje nowy obiekt zamiast powiększać obiekty. Idealnie powinien zresetować zoom.
  2. Napisałem własną funkcję obliczania mediany danych X i Y. Jednak jestem pewien, że musi istnieć lepszy sposób na zrobienie tego przy pomocy d3.median, ale nie wiem, jak to zrobić.

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

bardzo uproszczona (np życia) wersja JS jest poniżej. Można znaleźć pełną skrypt na 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)+")"; 
      }); 
    }; 
    } 
}); 

Wszelkie pomoc byłaby znacznie doceniane. Dzięki!

Edycja: Oto podsumowanie poprawek użyłem, na podstawie sugestii Superboggly jest poniżej:

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

Tłumaczenie zoom że użyłem jest bardzo doraźne i zasadniczo wykorzystuje abitrary stałe zachować ustawienie lub więcej mniej w odpowiednim miejscu. To nie jest idealne i chętnie bym zaproponował bardziej uniwersalną technikę dźwiękową. Jednak w tym przypadku działa wystarczająco dobrze.

Odpowiedz

11

Aby rozpocząć z funkcją mediany wystarczy wziąć tablicę i opcjonalny akcesor. Więc można go używać w taki sam sposób użyć max:

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

Co do innych, jeśli wyciągnąć swoje zachowanie powiększenia można go kontrolować nieco lepiej. Tak na przykład zamiast

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

try:

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

Następnie można ustawić poziom powiększenia i translację bezpośrednio:

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

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

ograniczających zakres przesuwu jest nieco trudniejsze.Po prostu nie można aktualizować, gdy translate lub scale nie jest w twoim stylu w funkcji zoomu (lub ustaw "translate" zoomu na to, co potrzebujesz). Coś podobnego (myślę, że w danym przypadku):

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

Pamiętaj, że jeśli chcesz, aby umożliwić powiększenie ujemne na osi, ale nie do panoramowania znajdziesz dostać się do niektórych trudnych sytuacjach.

To może być stary, ale sprawdzić Limiting domain when zooming or panning in D3.js

Należy również zauważyć, że zachowanie zoom miał funkcjonalność ograniczania panoramowanie i powiększanie w one point. Ale kod został usunięty w późniejszym update.

+0

Jeszcze raz dziękuję Superboggly! Twój kod zoomIn/reset wydaje się działać, ale skala zmienia się tylko w następnym zdarzeniu zoomu (tj. Po przeciągnięciu lub obróceniu koła myszy). Mam problem z aktualizacją rzeczy po kliknięciu przycisku. To musi być proste, ale po prostu nie mogę tego rozgryźć. Mój kod kliknięcia przycisku to: d3.select ("# zoomIn"). Call (zoom) .on ("kliknij", funkcja() { d3.event.preventDefault(); zm.scale (zm.scale () * 2); }); Próbowałem różnych zastosowań funkcji .call(), zoom() i .on() bezskutecznie. – richardwestenra

+0

Dodałem szybki mały niebieski kwadrat powiększenia u dołu mojego [ostatniego jsfiddle dla ciebie] (http://jsfiddle.net/superboggly/SD5cK/2/). Brakuje tylko połączenia z zoomem() po ustawieniu skali. Pomyśl o funkcji zoomu, którą dostarczyłeś jako stosującej stan powiększenia obliczony przez zachowanie. – Superboggly

+0

w porządku! Sortowane! Okazuje się, że to jest dokładnie to, co robiłem, ale to nie działało dla mnie ... Krótko mówiąc: Używałeś d3 wersji 3, gdy używałem wersji 2, i dlatego to nie działało, kiedy upuściłem niebieski kwadrat w moim kodzie. Zaktualizowałem do wersji 3 i teraz działa. Pozdrawiam :) – richardwestenra

-1

Nie lubię odkrywać koła. Szukałem wykresów rozproszenia, które umożliwiają powiększanie. Highcharts jest jednym z nich, ale jest sprytnie, który opiera się na D3 i nie tylko pozwala na powiększanie, ale możesz również mieć zbiory danych liniowych na wykresie punktowym, które pragnę z niektórymi z moich zbiorów danych, i które trudno znaleźć w innych biblioteki działek. Chciałbym spróbować:

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

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

Stosując taką miłą bibliotekę można zaoszczędzić dużo czasu i bólu.

Powiązane problemy