2014-05-24 23 views
7

FIDDLE < < < < questo ha più fino al codice data che in questione.d3.js grafico a linee serie temporali con dati in tempo reale, panning e zoom

Sto provando a creare un grafico delle serie temporali in tempo reale (aggiornamento in tempo reale) in d3, che può anche essere spostato (in X) e ingrandito. Idealmente, la funzionalità che voglio è se la parte più a destra della linea è visibile all'utente, quindi quando vengono aggiunti nuovi dati al grafico, verrà automaticamente spostato lateralmente per includere i nuovi dati (senza modificare le scale degli assi).

mio d3.json() le richieste dovrebbero restituire array JSON che assomigliano a questo:

[{"timestamp":1399325270,"value":-0.0029460209892230222598710528},{"timestamp":1399325271,"value":-0.0029460209892230222598710528},{"timestamp":1399325279,"value":-0.0029460209892230222598710528},....] 

Quando la pagina viene caricata, mi facciano richiesta e ottenere tutte le informazioni disponibili fino ad ora, e disegna il grafico - facile. Il codice seguente lo fa, consente anche di effettuare panoramiche (in X) e di zoomare.

var globalData; 
var lastUpdateTime = "0"; 
var dataIntervals = 1; 

var margin = { top: 20, right: 20, bottom: 30, left: 50 }, 
    width = document.getElementById("chartArea").offsetWidth - margin.left - margin.right, 
    height = document.getElementById("chartArea").offsetHeight - margin.top - margin.bottom; 

var x = d3.time.scale() 
    .range([0, width]); 

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

var xAxis = d3.svg.axis() 
    .scale(x) 
    .orient("bottom") 
    .ticks(10) 
    .tickFormat(d3.time.format('%X')) 
    .tickSize(1); 
    //.tickPadding(8); 

var yAxis = d3.svg.axis() 
    .scale(y) 
    .orient("left"); 

var valueline = d3.svg.line() 
    .x(function (d) { return x(d.timestamp); }) 
    .y(function (d) { return y(d.value); }); 

var zoom = d3.behavior.zoom() 
    .x(x) 
    .y(y) 
    .scaleExtent([1, 4]) 
    .on("zoom", zoomed); 

var svg = d3.select("#chartArea") 
    .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(zoom); 

svg.append("rect") 
    .attr("width", width) 
    .attr("height", height) 
    .attr("class", "plot"); // ???? 

var clip = svg.append("clipPath") 
    .attr("id", "clip") 
    .append("rect") 
    .attr("x", 0) 
    .attr("y", 0) 
    .attr("width", width) 
    .attr("height", height); 

var chartBody = svg.append("g") 
    .attr("clip-path", "url(#clip)"); 

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

svg.append("g")   // Add the Y Axis 
    .attr("class", "y axis") 
    .call(yAxis); 

svg.append("text") 
    .attr("transform", "rotate(-90)") 
    .attr("y", 0 - margin.left) 
    .attr("x", (0 - (height/2))) 
    .attr("dy", "1em") 
    .style("text-anchor", "middle") 
    .text("Return (%)"); 

// plot the original data by retrieving everything from time 0 
d3.json("/performance/benchmark/date/0/interval/" + dataIntervals, function (error, data) { 
    data.forEach(function (d) { 

     lastUpdateTime = String(d.timestamp); // this will be called until the last element and so will have the value of the last element 
     d.timestamp = new Date(d.timestamp); 
     d.value = d.value * 100; 

    }); 

    globalData = data; 

    x.domain(d3.extent(globalData, function (d) { return d.timestamp; })); 
    y.domain(d3.extent(globalData, function (d) { return d.value; })); 

    chartBody.append("path")  // Add the valueline path 
     .datum(globalData) 
     .attr("class", "line") 
     .attr("d", valueline); 

    var inter = setInterval(function() { 
     updateData(); 
    }, 5000); 

}); 

var panMeasure = 0; 
var oldScale = 1; 
function zoomed() { 
    //onsole.log(d3.event); 
    d3.event.translate[1] = 0; 
    svg.select(".x.axis").call(xAxis); 

    if (Math.abs(oldScale - d3.event.scale) > 1e-5) { 
     oldScale = d3.event.scale; 
     svg.select(".y.axis").call(yAxis); 
    } 

    svg.select("path.line").attr("transform", "translate(" + d3.event.translate[0] + ",0)scale(" + d3.event.scale + ", 1)"); 
    panMeasure = d3.event.translate[0]; 

} 

Nella seguente blocco di codice, faccio una richiesta HTTP per ottenere tutti i nuovi dati e aggiungere questo al grafico. Funziona bene Ora ho solo bisogno di risolvere la logica pan per i nuovi dati - che immagino sarebbe andare qui:

var dx = 0; 
function updateData() { 

    var newData = []; 

     d3.json("/performance/benchmark/date/" + lastUpdateTime + "/interval/" + dataIntervals, function (error, data) { 
      data.forEach(function (d) { 

       lastUpdateTime = String(d.timestamp); // must be called before its converted to Date() 
       d.timestamp = new Date(d.timestamp); 
       d.value = d.value * 100; 

       globalData.push(d); 
       newData.push(d); 

      }); 

      // panMeasure would be some measure of how much the user has panned (ie if the right-most part of the graph is still visible to the user. 
      if (panMeasure <= 0) { // add the new data and pan 

       x1 = newData[0].timestamp; 
       x2 = newData[newData.length - 1].timestamp; 
       dx = dx + (x(x1) - x(x2)); // dx needs to be cummulative 

       d3.select("path") 
        .datum(globalData) 
        .attr("class", "line") 
        .attr("d", valueline(globalData)) 
       .transition() 
        .ease("linear") 
        .attr("transform", "translate(" + String(dx) + ")"); 

      } 

      else { // otherwise - just add the new data 
       d3.select("path") 
        .datum(globalData) 
        .attr("class", "line") 
        .attr("d", valueline(globalData)); 
      } 

      svg.select(".x.axis").call(xAxis); 

     }); 
} 

Quello che sto cercando di fare (credo che sia quello che dovrei fare) è ottenere il intervallo dei valori temporali per i nuovi dati (ovvero la differenza tra il primo valore e l'ultimo valore dell'array newData [], convertirlo in pixel e quindi eseguire il pan della linea utilizzando questo numero.

Questo sembra essere una sorta di lavoro, ma solo al primo aggiornamento, un altro problema è che se faccio qualche panning/zoom usando il mouse mentre i dati stanno cercando di essere aggiornati, la linea sparisce e non torna necessariamente al prossimo aggiornamento. qualche feedback su potenziali errori che puoi pentola nel codice e/o come questo dovrebbe essere fatto. Grazie.

UPDATE 1:

Va bene, così ho capito qual è il problema con il pan automatico è stato. Mi sono reso conto che il vettore di traduzione deve avere un valore cumulativo da qualche origine, quindi una volta reso dx cumulativo (dx = dx + (x(x2) - x(x1));, il panning laterale ha iniziato a funzionare quando sono stati aggiunti nuovi dati.

UPDATE 2:

ora ho incluso un fiddle che è vicino al modo in cui mi aspetto attuale ai dati di essere recuperati e tracciati. Sembra funzionare in una certa misura il modo in cui voglio che ad eccezione di:

  1. marchi asse X tick non lo fanno padella con i nuovi dati
  2. Quando faccio scansione manuale, il comportamento diventa un po 'strano su il primo pan (salta un po 'indietro)
  3. Se sto panoramica/zoom quando i nuovi dati sta cercando di aggiungere - la linea scompare:
+0

Questo è un problema abbastanza complesso. Senza il violino suppongo che non sia probabile che nessuno possa aiutarti. BTW hai dato un'occhiata a NVD3? Forse ha qualcosa per te subito ... – hgoebl

+0

Grazie amico, sì, ma voglio essere in grado di personalizzare un po 'di più le cose e anche imparare a usare D3. Spero di ottenere almeno parti della soluzione; per esempio, come aggiungere i nuovi dati alla trama o alla linea (indipendentemente dall'aspetto), ecc. – rex

+1

Posso capirti, sono nella stessa situazione. D3 è fantastico ma è difficile eseguire il debug. È meglio usare il controllo della versione (Git) e commettere tutte le modifiche e piccoli passi quando si avanza e si arresta quando qualcosa si rompe. Piccoli, piccoli passaggi ... Ed è importante capire tutto il codice copiato e incollato. Ogni visualizzazione ha il suo volto. – hgoebl

risposta

-1

Invece di ('multi-threading' problemi di S?) tracciando i dati interi e ritagliando le parti non necessarie, è possibile mantenere una matrice globale di tutti i valori che si tagliano ogni volta che è necessario t o aggiorna il grafico. Quindi è possibile più facilmente ricalcolare i valori dell'asse x &. Il lato negativo è che non si può facilmente avere una transizione.

Quindi, si avrebbe due variabili:

var globalOffset = 0; 
var panOffset = 0; 

globalOffset sarebbero stati aggiornati ogni volta che si popola di nuovi valori e panOffset ogni volta che si Pan. Poi basta affettare i dati prima della stampa:

var offset = Math.max(0, globalOffset + panOffset); 
var graphData = globalData.slice(offset, offset + 100); 

Vedi fiddle per soluzione completa.

+0

hey mate, grazie della tua risposta ma il grafico non esplora in X? – rex

+0

Provato entrambi con Firefox e Chrome, desktop, e trascinando il grafico a sinistra o a destra, senza problemi, a patto che ci siano dati, naturalmente. – Fradenger

+0

Questo è davvero strano per me su Chrome: l'estremità destra della linea è ancorata sul lato destro e la linea del grafico si estende verso sinistra invece del panning: / – rex

Problemi correlati