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:
- marchi asse X tick non lo fanno padella con i nuovi dati
- Quando faccio scansione manuale, il comportamento diventa un po 'strano su il primo pan (salta un po 'indietro)
- Se sto panoramica/zoom quando i nuovi dati sta cercando di aggiungere - la linea scompare:
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
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
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