2014-05-24 13 views
7

FIDDLE < < < < hat dies auf dem aktuellen Stand Code mehr ein- als in der Frage.D3.JS Zeitreihenliniendiagramm mit Echtzeitdaten, Panning und

Ich versuche ein Echtzeit (Live-Update) Zeitreihendiagramm in d3 zu erstellen, das auch (in X) geschwenkt und gezoomt werden kann. Idealerweise ist die Funktionalität, die ich haben möchte, wenn der rechte Teil der Linie für den Benutzer sichtbar ist, wenn neue Daten zu dem Graphen hinzugefügt werden, wird er automatisch seitlich verschoben, um die neuen Daten aufzunehmen (ohne die Achsenskalen zu ändern).

Mein d3.json() Anfragen sollten JSON-Arrays zurück, die wie folgt aussehen:

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

Wenn die Seite geladen wird, habe ich eine Anfrage machen und alle verfügbaren Datum bekommen bis jetzt, und Zeichnen Sie das Diagramm - einfach. Der folgende Code tut dies, es erlaubt auch das Schwenken (in X) und das Zoomen.

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

} 

Im folgenden Codeblock mache ich eine HTTP-Anfrage, um alle neuen Daten zu erhalten und diese zum Diagramm hinzuzufügen. Das funktioniert gut. Jetzt brauche ich nur die Pfanne Logik für die neuen Daten zu sortieren - was ich würde sich vorstellen, hier:

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

     }); 
} 

Was ich versuche zu tun (ich denke, das ist, was ich tun sollte) ist erhalten die Bereich der Zeitwerte für die neuen Daten (dh die Differenz zwischen dem ersten Wert und dem letzten Wert des Arrays newData [], konvertieren Sie diese in Pixel und schwenken Sie dann die Zeile mit dieser Nummer.

Dies scheint zu sortieren funktionieren, aber nur beim ersten Update.Ein weiteres Problem ist, wenn ich schwenken/zoomen mit der Maus, während die Daten versuchen, aktualisiert zu werden, die Zeile verschwindet und nicht unbedingt bei der nächsten Aktualisierung.Ich würde es wirklich begrüßen einige Rückmeldungen über mögliche Fehler können Sie s Pot in den Code und/oder wie dies getan werden soll. Vielen Dank.

UPDATE 1:

Okay, ich habe herausgefunden, was das Problem mit dem automatischen Schwenk war. Ich erkannte, dass der Übersetzungsvektor einen kumulativen Wert von einem Ursprung haben muss, also sobald ich dx kumulativ (dx = dx + (x(x2) - x(x1)); machte, begann das Seitwärtsschwenken zu arbeiten, wenn neue Daten hinzugefügt wurden.

UPDATE 2:

Ich habe enthalten jetzt eine fiddle, die auf die tatsächliche Art und Weise nahe I-Daten erwarten abgerufen und aufgezeichnet werden. Es scheint es außer zu einem gewissen Grad, wie ich für arbeiten wollen:

  1. X-Achse Striche mit den neuen Daten nicht schwenken
  2. Wenn ich manuell Pfanne tun, wird das Verhalten ein wenig seltsam auf die erste wanne (springt ein wenig zurück)
  3. Wenn ich Schwenken/Zoomen, wenn die neuen Daten hinzugefügt werden versucht - die Linie verschwindet:
+0

Dies ist ein ziemlich komplexes Problem. Ohne Geige nehme ich an, dass niemand Ihnen helfen kann. BTW hast du dir NVD3 angesehen? Vielleicht hat es etwas für dich out-of-the-box ... – hgoebl

+0

Danke Kumpel, ja ich habe, aber ich möchte in der Lage sein, die Dinge ein wenig mehr anpassen und auch lernen, D3 zu verwenden. Ich würde hoffen, zumindest Teile der Lösung zu bekommen; zum Beispiel, wie man die neuen Daten dem Plot oder der Linie hinzufügt (unabhängig vom Aussehen), etc .. – rex

+1

Ich kann dich verstehen, ich bin in der gleichen Situation. D3 ist großartig, aber es ist schwer zu debuggen. Es empfiehlt sich, die Versionskontrolle (Git) zu verwenden und alle Änderungen und kleinen Schritte beim Vorrücken und Zurücksetzen zu übernehmen, wenn etwas kaputt geht. Kleine, winzige Schritte ... Und es ist wichtig, den gesamten Copy-and-Paste-Code zu verstehen. Jede Visualisierung hat ihr eigenes Gesicht. – hgoebl

Antwort

-1

statt (‚Multi-Threading‘ Fragen S?) Wenn Sie die gesamten Daten plotten und die unnötigen Teile abschneiden, können Sie ein globales Array aller Werte behalten, die Sie jedes Mal schneiden, wenn Sie t brauchen o Aktualisiere das Diagramm. Dann können Sie Ihre x-Achse & Werte einfacher neu berechnen. Nachteil ist, dass Sie nicht leicht einen Übergang haben können.

Also, würden Sie zwei Variablen:

var globalOffset = 0; 
var panOffset = 0; 

globalOffset würde jedes Mal, wenn neue Werte zu füllen aktualisiert werden und panOffset jedes Mal, wenn Sie schwenken. Dann schneiden Sie Ihre Daten, die Sie gerade vor dem Plotten:

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

fiddle für eine vollständige Lösung See.

+0

hey Kumpel, danke Ihre Antwort, aber das Diagramm schwenkt nicht in X? – rex

+0

Erprobt sowohl mit Firefox und Chrome, Desktop, und ziehen Sie die Grafik nach links oder rechts ohne Probleme, solange es Daten gibt natürlich. – Fradenger

+0

Das ist wirklich komisch für mich auf Chrome das rechte Ende der Zeile ist am rechten Rand verankert und die Grafiklinie erstreckt sich nach links, anstatt zu schwenken: / – rex