2014-01-10 30 views
4

我正在創建類似於Mike Bostock's可縮放區域圖的圖表。d3JS:在線/面積圖上縮小時繪製較大數據集的較低密度數據版本

對於我的具體項目,我有一堆傳感器每隔30秒記錄一次數值(溫度,光線,溼度和聲音)。我有縮放實現工作,但是當縮小到一年的縮放比例時,圖表密度會減慢瀏覽器速度,並且圖形也不會讀取。

如何編輯腳本,以便線圖的密度相對於縮放量發生變化?換句話說,x域控制着價值鏈上的點數。當我放大到一個小時的時間範圍時,我想擁有全密度(每30秒記錄一次),而當我縮小時,我想要更低的密度(每天記錄一次)。有任何想法嗎?來自上面鏈接的腳本的實現將會很有幫助。

謝謝!

<!DOCTYPE html> 
    <html> 
     <head> 
     <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/> 
     <script type="text/javascript" src="d3/d3.js"></script> 
     <script type="text/javascript" src="d3/d3.csv.js"></script> 
     <script type="text/javascript" src="d3/d3.time.js"></script> 
     <link type="text/css" rel="stylesheet" href="style.css"/> 
     <style type="text/css"> 

    svg { 
     font-size: 10px; 
    } 

    .axis { 
     shape-rendering: crispEdges; 
    } 

    .axis path, .axis line { 
     fill: none; 
     stroke-width: .5px; 
    } 

    .x.axis path { 
     stroke: #000; 
    } 

    .x.axis line { 
     stroke: #fff; 
     stroke-opacity: .5; 
    } 

    .y.axis line { 
     stroke: #ddd; 
    } 

    path.line { 
     fill: none; 
     stroke: #000; 
     stroke-width: .5px; 
    } 

    rect.pane { 
     cursor: move; 
     fill: none; 
     pointer-events: all; 
    } 

     </style> 
     </head> 
     <body> 
     <div id="body"> 
      <div id="footer"> 
      <span>…</span> 
      <div class="hint">mousewheel to zoom, drag to pan</div> 
      </div> 
     </div> 
     <script type="text/javascript"> 

    var m = [79, 80, 160, 79], 
     w = 1280 - m[1] - m[3], 
     h = 800 - m[0] - m[2], 
     parse = d3.time.format("%Y-%m-%d").parse, 
     format = d3.time.format("%Y"); 

    // Scales. Note the inverted domain for the y-scale: bigger is up! 
    var x = d3.time.scale().range([0, w]), 
     y = d3.scale.linear().range([h, 0]), 
     xAxis = d3.svg.axis().scale(x).orient("bottom").tickSize(-h, 0).tickPadding(6), 
     yAxis = d3.svg.axis().scale(y).orient("right").tickSize(-w).tickPadding(6); 

    // An area generator. 
    var area = d3.svg.area() 
     .interpolate("step-after") 
     .x(function(d) { return x(d.date); }) 
     .y0(y(0)) 
     .y1(function(d) { return y(d.value); }); 

    // A line generator. 
    var line = d3.svg.line() 
     .interpolate("step-after") 
     .x(function(d) { return x(d.date); }) 
     .y(function(d) { return y(d.value); }); 

    var svg = d3.select("body").append("svg:svg") 
     .attr("width", w + m[1] + m[3]) 
     .attr("height", h + m[0] + m[2]) 
     .append("svg:g") 
     .attr("transform", "translate(" + m[3] + "," + m[0] + ")"); 

    var gradient = svg.append("svg:defs").append("svg:linearGradient") 
     .attr("id", "gradient") 
     .attr("x2", "0%") 
     .attr("y2", "100%"); 

    gradient.append("svg:stop") 
     .attr("offset", "0%") 
     .attr("stop-color", "#fff") 
     .attr("stop-opacity", .5); 

    gradient.append("svg:stop") 
     .attr("offset", "100%") 
     .attr("stop-color", "#999") 
     .attr("stop-opacity", 1); 

    svg.append("svg:clipPath") 
     .attr("id", "clip") 
     .append("svg:rect") 
     .attr("x", x(0)) 
     .attr("y", y(1)) 
     .attr("width", x(1) - x(0)) 
     .attr("height", y(0) - y(1)); 

    svg.append("svg:g") 
     .attr("class", "y axis") 
     .attr("transform", "translate(" + w + ",0)"); 

    svg.append("svg:path") 
     .attr("class", "area") 
     .attr("clip-path", "url(#clip)") 
     .style("fill", "url(#gradient)"); 

    svg.append("svg:g") 
     .attr("class", "x axis") 
     .attr("transform", "translate(0," + h + ")"); 

    svg.append("svg:path") 
     .attr("class", "line") 
     .attr("clip-path", "url(#clip)"); 

    svg.append("svg:rect") 
     .attr("class", "pane") 
     .attr("width", w) 
     .attr("height", h) 
     .call(d3.behavior.zoom().on("zoom", zoom)); 

    d3.csv("flights-departed.csv", function(data) { 

     // Parse dates and numbers. 
     data.forEach(function(d) { 
     d.date = parse(d.date); 
     d.value = +d.value; 
     }); 

     // Compute the maximum price. 
     x.domain([new Date(1999, 0, 1), new Date(2003, 0, 0)]); 
     y.domain([0, d3.max(data, function(d) { return d.value; })]); 

     // Bind the data to our path elements. 
     svg.select("path.area").data([data]); 
     svg.select("path.line").data([data]); 

     draw(); 
    }); 

    function draw() { 
     svg.select("g.x.axis").call(xAxis); 
     svg.select("g.y.axis").call(yAxis); 
     svg.select("path.area").attr("d", area); 
     svg.select("path.line").attr("d", line); 
     d3.select("#footer span").text("U.S. Commercial Flights, " + x.domain().map(format).join("-")); 
    } 

    function zoom() { 
     d3.event.transform(x); // TODO d3.behavior.zoom should support extents 
     draw(); 
    } 

     </script> 
     </body> 
    </html> 
+0

這實際上並不是一件微不足道的事情,因爲您必須更改底層數據如何「翻譯」爲DOM元素。具體而言,您將不得不減少取決於縮放級別的代表數據點的數量。 –

+0

@LarsKotthoff是對的,它不是微不足道的。我正在做很多事情,我會聽取縮放事件,並根據縮放比例重新計算傳遞給圖表的數據。請記住,「平移」會被記錄爲縮放比例爲1的縮放。另外請記住,d3的縮放只是縮放矢量繪圖,它不縮放實際數據。 –

+0

只是爲了解決@LarsKotthoff的評論:因爲這是一個線條/面積圖,因此對於整個數據集總是隻有一個DOM對象,因此更容易隨時更改數據集。在其他類型的圖表中,當您更改數據集的精度時,您需要輸入和退出DOM對象,顯着降低速度(儘管可能比嘗試立即繪製所有內容更好)。 – AmeliaBR

回答

6

拉爾斯和阿里是正確的,這絕對不是一個小問題。但我認爲這很重要,對很多人(包括未來可能包括我自己)都有用,因此值得花時間弄清楚。

因此,您可以按照以下步驟對邁克·博斯托克的每日航班計數圖表進行調整,以便在縮小時顯示每週/每月/每年的平均每日飛行計數(而不是單個日期),並且只繪製子集可在任何縮放級別顯示的數據:
http://fiddle.jshell.net/ncy5J/2/

這裏是什麼,我必須做的一步一步的細分:

  1. 獲取非常大的CSV數據表工作作爲一個emb JSFiddle腳本中的edded變量。我假設你不會這樣做,但我提到它是因爲這很麻煩。必須在每行的末尾添加一個\n\,然後才能在字符串上運行d3.csv.parse()。與使用d3's interval.floor() functions讓所有從日期的關鍵功能

    • 使用d3.nest

    • 這些時間段創建了幾個星期,幾個月甚至幾年備用數據陣列,並計算平均每日值同年,月份等一起築巢;

    • 上與自定義功能的嵌套數組使用Array.forEach訪問嵌套對象的陣列,計算出其值的平均值,然後用一個對象,該原始數據的格式相匹配替換由nest()創建的對象(代碼如下)。

  2. 移動從初始化數據綁定步驟的再繪製函數,並更改功能以接受的數據陣列作爲參數。

  3. 更新d3.behavior.zoom方法以匹配D3版本3 API(原始示例使用v2.4,其中有不同的方法用於將縮放行爲鏈接到比例)。

  4. 更改zoom函數由所述縮放行爲叫

    • 訪問從x刻度(其由縮放行爲自動更新)可見數據域;

    • 計算該域所覆蓋的時間跨度;

    • 從我的四個數據數組中選擇一個,該數組具有適當的精度;

    • 掃描數組以找到位於可見域中的元素部分(如我在代碼註釋中提到的,當您完全放大時,這會稍微慢一些;也可以使用日期 - 時間的數學計算出數組的右側部分,因爲連續元素之間的時間間隔始終相同)。

    • 使用數組的適當切片調用重新繪製函數作爲傳入的數據。

下面是步驟2中的自定義嵌套/平均常規:

AllData.yearly = d3.nest().key(function(d){ 
        return d3.time.year.floor(d.date); 
       }) 
       .entries(data); 
AllData.yearly.forEach(meanDaily); 

function meanDaily(nestedObject, i, array){ 
    //This function is passed to the Array.forEach() method 
    //which passes in: 
    // (1) the element in the array 
    // (2) the element's index, and 
    // (3) the array as a whole 

    //It replaces the element in the array 
    //(an object with properties key and values, where 
    // values is an array of nested objects) 
    //with a single object that has the nesting key 
    //value parsed back into a date, 
    //and the mean of the nested values' value 
    //as the value. It makes sense, I swear. 
    array[i] = {date:new Date(nestedObject.key), 
      value:d3.mean(nestedObject.values, 
          function(d) {return d.value;} 
         )}; 
} 

縮放方法僅僅是基本的JavaScript,關鍵部分是,你可以從直接訪問可見域x-scale,然後用它來確定哪些數據點傳遞給繪圖函數。

P.S.查看不同平均尺度的數據很有意思。 9月11日之後的航班急劇下降突出每日,每週和每月圖表,但從年平均值中消失。相反,年平均值顯示2002年的整體平均日航班次比2001年低,這提醒人們許多人害怕飛行禁令解除後很長時間飛行。

+0

P.S.剛回去重讀這個問題,當然,如果您只想繪製每小時/每天的第一次讀數而不是平均讀數,那麼您可以跳過步驟2,然後更改第5步以創建適當過濾的數組。如果您的數據是實時的,那麼這可能比保持所有平均值數組更新更簡單! – AmeliaBR

+0

太神奇了,謝謝! – ekatz