D3 v4带刷牙的实时图表

时间:2017-09-08 15:36:28

标签: javascript d3.js

我正试图让this block使用d3 v4并且无法弄清楚我做错了什么。我把我的代码放在一个小提琴中,setInterval和刷子有一个问题 - 范围是期待一个日期对象,但我不知道如何得到它。

function realTimeChart() {

  var version = "0.1.0",
      datum, initialData, data,
      maxSeconds = 300, pixelsPerSecond = 10,
      svgWidth = 700, svgHeight = 300,
      margin = { top: 20, bottom: 20, left: 50, right: 30, topNav: 10, bottomNav: 20 },
      dimension = { chartTitle: 20, xAxis: 20, yAxis: 20, xTitle: 20, yTitle: 20, navChart: 70 },
      barWidth = 3,
      maxY = 100, minY = 0,
      chartTitle, yTitle, xTitle,
      drawXAxis = true, drawYAxis = true, drawNavChart = true,
      border,
      selection,
      barId = 0;

  // create the chart
  var chart = function(s) {
    selection = s;
    if (selection == undefined) {
      console.error("selection is undefined");
      return;
    };

    // process titles
    chartTitle = chartTitle || "";
    xTitle = xTitle || "";
    yTitle = yTitle || "";

    // compute component dimensions
    var chartTitleDim = chartTitle == "" ? 0 : dimension.chartTitle;
    var xTitleDim = xTitle == "" ? 0 : dimension.xTitle;
    var yTitleDim = yTitle == "" ? 0 : dimension.yTitle;
    var xAxisDim = !drawXAxis ? 0 : dimension.xAxis;
    var yAxisDim = !drawYAxis ? 0 : dimension.yAxis;
    var navChartDim = !drawNavChart ? 0 : dimension.navChart;

    // compute chart dimension and offset
    var marginTop = margin.top + chartTitleDim;
    var height = svgHeight - marginTop - margin.bottom - chartTitleDim - xTitleDim - xAxisDim - navChartDim + 30;
    var heightNav = navChartDim - margin.topNav - margin.bottomNav;
    var marginTopNav = svgHeight - margin.bottom - heightNav - margin.topNav;
    var width = svgWidth - margin.left - margin.right;
    var widthNav = width;

    // append the svg
    var svg = selection.append("svg")
        .attr("width", svgWidth)
        .attr("height", svgHeight)
        .style("border", function(d) { 
          if (border) return "1px solid lightgray"; 
          else return null;
        });

    // create main group and translate
    var main = svg.append("g")
        .attr("transform", "translate (" + margin.left + "," + marginTop + ")");

    // define clip-path
    main.append("defs").append("clipPath")
        .attr("id", "myClip")
      .append("rect")
        .attr("x", 0)
        .attr("y", 0)
        .attr("width", width)
        .attr("height", height);

    // create chart background
    main.append("rect")
        .attr("x", 0)
        .attr("y", 0)
        .attr("width", width)
        .attr("height", height)
        .style("fill", "#f5f5f5");

    // note that two groups are created here, the latter assigned to barG;
    // the former will contain a clip path to constrain objects to the chart area; 
    // no equivalent clip path is created for the nav chart as the data itself
    // is clipped to the full time domain
    var barG = main.append("g")
        .attr("class", "barGroup")
        .attr("transform", "translate(0, 0)")
        .attr("clip-path", "url(#myClip")
      .append("g");

    // add group for x axis
    var xAxisG = main.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")");

    // add group for y axis
    var yAxisG = main.append("g")
        .attr("class", "y axis");

    // in x axis group, add x axis title
    xAxisG.append("text")
        .attr("class", "title")
        .attr("x", width / 2)
        .attr("y", 25)
        .attr("dy", ".71em")
        .text(function(d) { 
          var text = xTitle == undefined ? "" : xTitle;
          return text; 
        });

    // in y axis group, add y axis title
    yAxisG.append("text")
        .attr("class", "title")
        .attr("transform", "rotate(-90)")
        .attr("x", - height / 2)
        .attr("y", -35)
        .attr("dy", ".71em")
        .text(function(d) { 
          var text = yTitle == undefined ? "" : yTitle;
          return text; 
        });

    // in main group, add chart title
    main.append("text")
        .attr("class", "chartTitle")
        .attr("x", width / 2)
        .attr("y", -20)
        .attr("dy", ".71em")
        .text(function(d) { 
          var text = chartTitle == undefined ? "" : chartTitle;
          return text; 
        });

    // define main chart scales
    var x = d3.scaleTime().range([0, width]);
    var y = d3.scaleLinear().domain([minY, maxY]).range([height, 0]);

    // define main chart axis
    var xAxis = d3.axisBottom(x);
    var yAxis = d3.axisLeft(y);

    // add nav chart
    var nav = svg.append("g")
        .attr("transform", "translate (" + margin.left + "," + marginTopNav + ")");

    // add nav background
    nav.append("rect")
        .attr("x", 0)
        .attr("y", 0)
        .attr("width", width)
        .attr("height", heightNav)
        .style("fill", "#F5F5F5")
        .style("shape-rendering", "crispEdges")
        .attr("transform", "translate(0, 0)");

    // add group to hold line and area paths
    var navG = nav.append("g")
        .attr("class", "nav");

    // add group to hold nav x axis
    var xAxisGNav = nav.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + heightNav + ")");

    // define nav scales
    var xNav = d3.scaleTime().range([0, widthNav]);
    var yNav = d3.scaleLinear().domain([minY, maxY]).range([heightNav, 0]);

    // define nav axis
    var xAxisNav = d3.axisBottom(xNav);

    // define function that will draw the nav area chart
    var navArea = d3.area()
        .x(function (d) { return xNav(d.time); })
        .y1(function (d) { return yNav(d.value); })
        .y0(heightNav);

    // define function that will draw the nav line chart
    var navLine = d3.line()
        .x(function (d) { return xNav(d.time); })
        .y(function (d) { return yNav(d.value); });

    // compute initial time domains...
    var ts = new Date().getTime();

    // first, the full time domain
    var endTime = new Date(ts);
    var startTime = new Date(endTime.getTime() - maxSeconds * 1000);
    var interval = endTime.getTime() - startTime.getTime();

    // then the viewport time domain (what's visible in the main chart 
    // and the viewport in the nav chart)
    var endTimeViewport = new Date(ts);
    var startTimeViewport = new Date(endTime.getTime() - width / pixelsPerSecond * 1000);
    var intervalViewport = endTimeViewport.getTime() - startTimeViewport.getTime();
    var offsetViewport = startTimeViewport.getTime() - startTime.getTime();

    // set the scale domains for main and nav charts
    x.domain([startTimeViewport, endTimeViewport]);
    xNav.domain([startTime, endTime]); 

    // update axis with modified scale
    xAxis.scale(x)(xAxisG);
    yAxis.scale(y)(yAxisG);
    xAxisNav.scale(xNav)(xAxisGNav);

    // create brush (moveable, changable rectangle that determines 
    // the time domain of main chart)
    var sel;
    var viewport = d3.brush()
        // .x(xNav)
        .extent([startTimeViewport, endTimeViewport])
        .on("brush", function () {
          // get the current time extent of viewport
          // var extent = viewport.extent();
          var extent = d3.event.selection || x.range();

          console.log(extent);
          startTimeViewport = extent[0];
          endTimeViewport = extent[1];
          intervalViewport = endTimeViewport.getTime() - startTimeViewport.getTime();
          offsetViewport = startTimeViewport.getTime() - startTime.getTime();

          // handle invisible viewport
          if (intervalViewport == 0) {
            intervalViewport = maxSeconds * 1000;
            offsetViewport = 0;
          }

          // update the x domain of the main chart
          x.domain((extent === null) ? xNav.domain() : extent);

          // update the x axis of the main chart
          xAxis.scale(x)(xAxisG);

          // update display
          refresh();
        });

      window.viewport = viewport;

    // create group and assign to brush
    var viewportG = nav.append("g")
        .attr("class", "viewport")
        .call(viewport)
        .selectAll("rect")
        .attr("height", heightNav);


    // initial invocation
    data = initialData || [];

    // update display
    refresh();


    // function to refresh the viz upon changes of the time domain 
    // (which happens constantly), or after arrival of new data,
    // or at init
    function refresh() {

      // process data to remove too late or too early data items 
      // (the latter could occur if the chart is stopped, while data
      // is being pumped in)
      data = data.filter(function(d) {
        if (d.time.getTime() > startTime.getTime() &&
            d.time.getTime() < endTime.getTime()) 
          return true;
      })

      // here we bind the new data to the main chart
      // note: no key function is used here; therefore the data binding is
      // by index, which effectivly means that available DOM elements
      // are associated with each item in the available data array, from 
      // first to last index; if the new data array contains fewer elements
      // than the existing DOM elements, the LAST DOM elements are removed;
      // basically, for each step, the data items "walks" leftward (each data 
      // item occupying the next DOM element to the left);
      // This data binding is very different from one that is done with a key 
      // function; in such a case, a data item stays "resident" in the DOM
      // element, and such DOM element (with data) would be moved left, until
      // the x position is to the left of the chart, where the item would be 
      // exited
      var updateSel = barG.selectAll(".bar")
          .data(data);

      // remove items
      updateSel.exit().remove();

      // append items
      updateSel.enter().append("rect")
          .attr("class", "bar")
          .attr("id", function() { 
            return "bar-" + barId++; 
          })
          .attr("shape-rendering", "crispEdges");

      // update items
      updateSel
          .attr("x", function(d) { return Math.round(x(d.time) - barWidth); })
          .attr("y", function(d) { return y(d.value); })
          .attr("width", barWidth)
          .attr("height", function(d) { return height - y(d.value); })
          .style("fill", function(d) { return d.color == undefined ? "black" : d.color; })
          //.style("stroke", "none")
          //.style("stroke-width", "1px")
          //.style("stroke-opacity", 0.5)
          .style("fill-opacity", 1);

      // also, bind data to nav chart
      // first remove current paths
      navG.selectAll("path").remove();

      // then append area path...
      navG.append('path')
          .attr('class', 'area')
          .attr('d', navArea(data));

      // ...and line path
      navG.append('path')
          .attr('class', 'line')
          .attr('d', navLine(data)); 

    } // end refreshChart function


    // function to keep the chart "moving" through time (right to left) 
         window.intvl = setInterval(function() {

      // get current viewport extent
      // var extent = viewport.empty() ? xNav.domain() : viewport.extent();
      var extent = (sel === null) ? xNav.domain() : viewport.extent();
      window.extent = extent;
      var interval = extent[1].getTime() - extent[0].getTime();
      var offset = extent[0].getTime() - xNav.domain()[0].getTime();

      // compute new nav extents
      endTime = new Date();
      startTime = new Date(endTime.getTime() - maxSeconds * 1000);

      // compute new viewport extents 
      startTimeViewport = new Date(startTime.getTime() + offset);
      endTimeViewport = new Date(startTimeViewport.getTime() + interval);
      viewport.extent([startTimeViewport, endTimeViewport])

      // update scales
      x.domain([startTimeViewport, endTimeViewport]);
      xNav.domain([startTime, endTime]);

      // update axis
      xAxis.scale(x)(xAxisG);
      xAxisNav.scale(xNav)(xAxisGNav);

      // refresh svg
      refresh();

    }, 200)

    // end setInterval function

    return chart;

  } // end chart function


  // chart getter/setters

  // array of inital data
  chart.initialData = function(_) {
    if (arguments.length == 0) return initialData;
    initialData = _;
    return chart;
  }

  // new data item (this most recent item will appear 
  // on the right side of the chart, and begin moving left)
  chart.datum = function(_) {
    if (arguments.length == 0) return datum;
    datum = _;
    data.push(datum);
    return chart;
  }

  // svg width
  chart.width = function(_) {
    if (arguments.length == 0) return svgWidth;
    svgWidth = _;
    return chart;
  }

  // svg height
  chart.height = function(_) {
    if (arguments.length == 0) return svgHeight;
    svgHeight = _;
    return chart;
  }

  // svg border
  chart.border = function(_) {
    if (arguments.length == 0) return border;
    border = _;
    return chart;       
  }

  // chart title
  chart.title = function(_) {
    if (arguments.length == 0) return chartTitle;
    chartTitle = _;
    return chart;   
  }

  // x axis title
  chart.xTitle = function(_) {
    if (arguments.length == 0) return xTitle;
    xTitle = _;
    return chart;       
  }

  // y axis title
  chart.yTitle = function(_) {
    if (arguments.length == 0) return yTitle;
    yTitle = _;
    return chart;       
  }

  // bar width
  chart.barWidth = function(_) {
    if (arguments.length == 0) return barWidth;
    barWidth = _;
    return chart;       
  }

  // version
  chart.version = version;

  return chart;

} // end realTimeChart function

https://jsfiddle.net/drhhw8c2/

提前感谢您的帮助!

1 个答案:

答案 0 :(得分:1)

最后根据Mark的建议得到了一个有效的解决方案:https://jsfiddle.net/drhhw8c2/1/

function realTimeChart() {

  var version = "0.1.0",
      datum, initialData, data,
      maxSeconds = 300, pixelsPerSecond = 10,
      svgWidth = 700, svgHeight = 300,
      margin = { top: 20, bottom: 20, left: 50, right: 30, topNav: 10, bottomNav: 20 },
      dimension = { chartTitle: 20, xAxis: 20, yAxis: 20, xTitle: 20, yTitle: 20, navChart: 70 },
      barWidth = 3,
      maxY = 100, minY = 0,
      chartTitle, yTitle, xTitle,
      drawXAxis = true, drawYAxis = true, drawNavChart = true,
      border,
      selection,
      barId = 0;

  // create the chart
  var chart = function(s) {
    selection = s;
    if (selection == undefined) {
      console.error("selection is undefined");
      return;
    };

    // process titles
    chartTitle = chartTitle || "";
    xTitle = xTitle || "";
    yTitle = yTitle || "";

    // compute component dimensions
    var chartTitleDim = chartTitle == "" ? 0 : dimension.chartTitle;
    var xTitleDim = xTitle == "" ? 0 : dimension.xTitle;
    var yTitleDim = yTitle == "" ? 0 : dimension.yTitle;
    var xAxisDim = !drawXAxis ? 0 : dimension.xAxis;
    var yAxisDim = !drawYAxis ? 0 : dimension.yAxis;
    var navChartDim = !drawNavChart ? 0 : dimension.navChart;

    // compute chart dimension and offset
    var marginTop = margin.top + chartTitleDim;
    var height = svgHeight - marginTop - margin.bottom - chartTitleDim - xTitleDim - xAxisDim - navChartDim + 30;
    var heightNav = navChartDim - margin.topNav - margin.bottomNav;
    var marginTopNav = svgHeight - margin.bottom - heightNav - margin.topNav;
    var width = svgWidth - margin.left - margin.right;
    var widthNav = width;

    // append the svg
    var svg = selection.append("svg")
        .attr("width", svgWidth)
        .attr("height", svgHeight)
        .style("border", function(d) { 
          if (border) return "1px solid lightgray"; 
          else return null;
        });

    // create main group and translate
    var main = svg.append("g")
        .attr("transform", "translate (" + margin.left + "," + marginTop + ")");

    // define clip-path
    main.append("defs").append("clipPath")
        .attr("id", "myClip")
      .append("rect")
        .attr("x", 0)
        .attr("y", 0)
        .attr("width", width)
        .attr("height", height);

    // create chart background
    main.append("rect")
        .attr("x", 0)
        .attr("y", 0)
        .attr("width", width)
        .attr("height", height)
        .style("fill", "#f5f5f5");

    // note that two groups are created here, the latter assigned to barG;
    // the former will contain a clip path to constrain objects to the chart area; 
    // no equivalent clip path is created for the nav chart as the data itself
    // is clipped to the full time domain
    var barG = main.append("g")
        .attr("class", "barGroup")
        .attr("transform", "translate(0, 0)")
        .attr("clip-path", "url(#myClip")
      .append("g");

    // add group for x axis
    var xAxisG = main.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")");

    // add group for y axis
    var yAxisG = main.append("g")
        .attr("class", "y axis");

    // in x axis group, add x axis title
    xAxisG.append("text")
        .attr("class", "title")
        .attr("x", width / 2)
        .attr("y", 25)
        .attr("dy", ".71em")
        .text(function(d) { 
          var text = xTitle == undefined ? "" : xTitle;
          return text; 
        });

    // in y axis group, add y axis title
    yAxisG.append("text")
        .attr("class", "title")
        .attr("transform", "rotate(-90)")
        .attr("x", - height / 2)
        .attr("y", -35)
        .attr("dy", ".71em")
        .text(function(d) { 
          var text = yTitle == undefined ? "" : yTitle;
          return text; 
        });

    // in main group, add chart title
    main.append("text")
        .attr("class", "chartTitle")
        .attr("x", width / 2)
        .attr("y", -20)
        .attr("dy", ".71em")
        .text(function(d) { 
          var text = chartTitle == undefined ? "" : chartTitle;
          return text; 
        });

    // define main chart scales
    var x = d3.scaleTime().range([0, width]);
    var y = d3.scaleLinear().domain([minY, maxY]).range([height, 0]);

    // define main chart axis
    var xAxis = d3.axisBottom(x);
    var yAxis = d3.axisLeft(y);

    // add nav chart
    var nav = svg.append("g")
        .attr("transform", "translate (" + margin.left + "," + marginTopNav + ")");

    // add nav background
    nav.append("rect")
        .attr("x", 0)
        .attr("y", 0)
        .attr("width", width)
        .attr("height", heightNav)
        .style("fill", "#F5F5F5")
        .style("shape-rendering", "crispEdges")
        .attr("transform", "translate(0, 0)");

    // add group to hold line and area paths
    var navG = nav.append("g")
        .attr("class", "nav");

    // add group to hold nav x axis
    var xAxisGNav = nav.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + heightNav + ")");

    // define nav scales
    var xNav = d3.scaleTime().range([0, widthNav]);
    var yNav = d3.scaleLinear().domain([minY, maxY]).range([heightNav, 0]);

    // define nav axis
    var xAxisNav = d3.axisBottom(xNav);

    // define function that will draw the nav area chart
    var navArea = d3.area()
        .x(function (d) { return xNav(d.time); })
        .y1(function (d) { return yNav(d.value); })
        .y0(heightNav);

    // define function that will draw the nav line chart
    var navLine = d3.line()
        .x(function (d) { return xNav(d.time); })
        .y(function (d) { return yNav(d.value); });

    // compute initial time domains...
    var ts = new Date().getTime();

    // first, the full time domain
    var endTime = new Date(ts);
    var startTime = new Date(endTime.getTime() - maxSeconds * 1000);
    var interval = endTime.getTime() - startTime.getTime();

    // then the viewport time domain (what's visible in the main chart 
    // and the viewport in the nav chart)
    var endTimeViewport = new Date(ts);
    var startTimeViewport = new Date(endTime.getTime() - width / pixelsPerSecond * 1000);
    var intervalViewport = endTimeViewport.getTime() - startTimeViewport.getTime();
    var offsetViewport = startTimeViewport.getTime() - startTime.getTime();

    // set the scale domains for main and nav charts
    x.domain([startTimeViewport, endTimeViewport]);
    xNav.domain([startTime, endTime]); 

    // update axis with modified scale
    xAxis.scale(x)(xAxisG);
    yAxis.scale(y)(yAxisG);
    xAxisNav.scale(xNav)(xAxisGNav);

    // create brush (moveable, changable rectangle that determines 
    // the time domain of main chart)
    var sel;
    var viewport = d3.brushX()
        // .x(xNav)
        // .extent([startTimeViewport, endTimeViewport])
        .extent([ [0,0], [widthNav,heightNav] ])
        .on("brush end", function () {
          // get the current time extent of viewport
          var extent = d3.event.selection || xNav.range();
          // var extent = d3.event.selection || x.range();

          // console.log(extent);
          startTimeViewport = xNav.invert(extent[0]);
          endTimeViewport = xNav.invert(extent[1]);

          console.log(startTimeViewport.getMinutes() + ':' + startTimeViewport.getSeconds() + ' - ' + endTimeViewport.getMinutes() + ':' + endTimeViewport.getSeconds());
          intervalViewport = endTimeViewport.getTime() - startTimeViewport.getTime();
          offsetViewport = startTimeViewport.getTime() - startTime.getTime();

          // handle invisible viewport
          if (intervalViewport == 0) {
            intervalViewport = maxSeconds * 1000;
            offsetViewport = 0;
          }

          // update the x domain of the main chart
          x.domain((extent === null) ? xNav.domain() : extent);

          // update the x axis of the main chart
          xAxis.scale(x)(xAxisG);/**/

          sel = d3.event.selection || xNav.range();
          x.domain(sel.map(xNav.invert, xNav));




          // update display
          refresh();
        });

      window.viewport = viewport;

    // create group and assign to brush
    var viewportG = nav.append("g")
        .attr("class", "viewport")
        .call(viewport)
        .selectAll("rect")
        .attr("height", heightNav);


    // initial invocation
    data = initialData || [];

    // update display
    refresh();


    // function to refresh the viz upon changes of the time domain 
    // (which happens constantly), or after arrival of new data,
    // or at init
    function refresh() {



      // process data to remove too late or too early data items 
      // (the latter could occur if the chart is stopped, while data
      // is being pumped in)
      data = data.filter(function(d) {
        if (d.time.getTime() > startTime.getTime() &&
            d.time.getTime() < endTime.getTime()) 
          return true;
      })


      // here we bind the new data to the main chart
      // note: no key function is used here; therefore the data binding is
      // by index, which effectivly means that available DOM elements
      // are associated with each item in the available data array, from 
      // first to last index; if the new data array contains fewer elements
      // than the existing DOM elements, the LAST DOM elements are removed;
      // basically, for each step, the data items "walks" leftward (each data 
      // item occupying the next DOM element to the left);
      // This data binding is very different from one that is done with a key 
      // function; in such a case, a data item stays "resident" in the DOM
      // element, and such DOM element (with data) would be moved left, until
      // the x position is to the left of the chart, where the item would be 
      // exited
      var updateSel = barG.selectAll(".bar")
          .data(data);

      // remove items
      updateSel.exit().remove();

      // append items
      updateSel.enter().append("rect")
          .attr("class", "bar")
          .attr("id", function() { 
            return "bar-" + barId++; 
          })
          .attr("shape-rendering", "crispEdges");
      // update items
      updateSel
          .attr("x", function(d) { 
            // console.log(x(d.time));
            var val = Math.round(x(d.time) - barWidth);
            if(val !== val) {
              val = 0;
            }
            return val; })

          .attr("y", function(d) { return y(d.value); })
          .attr("width", barWidth)
          .attr("height", function(d) { return height - y(d.value); })
          .style("fill", function(d) { return d.color == undefined ? "black" : d.color; })
          //.style("stroke", "none")
          //.style("stroke-width", "1px")
          //.style("stroke-opacity", 0.5)
          .style("fill-opacity", 1);

      // also, bind data to nav chart
      // first remove current paths
      navG.selectAll("path").remove();

      // then append area path...
      navG.append('path')
          .attr('class', 'area')
          .attr('d', navArea(data));

      // ...and line path
      navG.append('path')
          .attr('class', 'line')
          .attr('d', navLine(data)); 

    } // end refreshChart function


    // function to keep the chart "moving" through time (right to left) 
         window.intvl = setInterval(function() {

      // get current viewport extent
      // var extent = viewport.empty() ? xNav.domain() : viewport.extent();
      // var extent = (sel === null) ? xNav.domain() : viewport.extent()();
      var extent = xNav.domain();
      window.extent = extent;
           var interval = extent[1] - extent[0];
      var offset = extent[0] - xNav.domain()[0];
      // var interval = extent[1].getTime() - extent[0].getTime();
      // var offset = extent[0].getTime() - xNav.domain()[0].getTime();
      // compute new nav extents
      endTime = new Date();
      startTime = new Date(endTime.getTime() - maxSeconds * 1000);

      // compute new viewport extents 
      startTimeViewport = new Date(startTime.getTime() + offset);
      endTimeViewport = new Date(startTimeViewport.getTime() + interval);
      viewport.extent([startTimeViewport, endTimeViewport])

      // update scales

      x.domain([startTimeViewport, endTimeViewport]);
      if(sel) {
        x.domain(sel.map(xNav.invert, xNav));
      }
      xNav.domain([startTime, endTime]);

      // update axis
      xAxis.scale(x)(xAxisG);
      xAxisNav.scale(xNav)(xAxisGNav);


      // refresh svg
      refresh();

    }, 200)

    // end setInterval function

    return chart;

  } // end chart function


  // chart getter/setters

  // array of inital data
  chart.initialData = function(_) {
    if (arguments.length == 0) return initialData;
    initialData = _;
    return chart;
  }

  // new data item (this most recent item will appear 
  // on the right side of the chart, and begin moving left)
  chart.datum = function(_) {
    if (arguments.length == 0) return datum;
    datum = _;
    data.push(datum);
    return chart;
  }

  // svg width
  chart.width = function(_) {
    if (arguments.length == 0) return svgWidth;
    svgWidth = _;
    return chart;
  }

  // svg height
  chart.height = function(_) {
    if (arguments.length == 0) return svgHeight;
    svgHeight = _;
    return chart;
  }

  // svg border
  chart.border = function(_) {
    if (arguments.length == 0) return border;
    border = _;
    return chart;       
  }

  // chart title
  chart.title = function(_) {
    if (arguments.length == 0) return chartTitle;
    chartTitle = _;
    return chart;   
  }

  // x axis title
  chart.xTitle = function(_) {
    if (arguments.length == 0) return xTitle;
    xTitle = _;
    return chart;       
  }

  // y axis title
  chart.yTitle = function(_) {
    if (arguments.length == 0) return yTitle;
    yTitle = _;
    return chart;       
  }

  // bar width
  chart.barWidth = function(_) {
    if (arguments.length == 0) return barWidth;
    barWidth = _;
    return chart;       
  }

  // version
  chart.version = version;

  return chart;

} // end realTimeChart function


'use strict';

// mean and deviation for time interval
var meanMs = 1000, // milliseconds
    dev = 150;

// define time scale
var timeScale = d3.scaleLinear()
    .domain([300, 1700])
    .range([300, 1700])
    .clamp(true);

// define value scale
var valueScale = d3.scaleLinear()
    .domain([0, 1])
    .range([30, 95]);

// generate initial data
var normal = d3.randomNormal(1000, 150);
var currMs = new Date().getTime() - 300000 - 4000;
var data = d3.range(300).map(function(d, i, arr) {
  var value = valueScale(Math.random()); // random data
  //var value = Math.round((d % 60) / 60 * 95); // ramp data
  var interval = Math.round(timeScale(normal()));
  currMs += interval;
  var time = new Date(currMs);
  var obj = { interval: interval, value: value, time: time, ts: currMs }
  return obj;
})

// create the real time chart
var chart = realTimeChart()
    .title("Chart Title")
    .yTitle("Y Scale")
    .xTitle("X Scale")
    .border(true)
    .width(600)
    .height(290)
    .barWidth(1)
    .initialData(data);

console.log("Version: ", chart.version);
console.dir("Dir++");
console.trace();
console.warn("warn")

// invoke the chart
var chartDiv = d3.select("#viewDiv").append("div")
    .attr("id", "chartDiv")
    .call(chart);

// alternative invocation
//chart(chartDiv); 


// drive data into the chart roughly every second
// in a normal use case, real time data would arrive through the network or some other mechanism
var d = 0;
function dataGenerator() {

  var timeout = Math.round(timeScale(normal()));

  setTimeout(function() {

    // create new data item
    var now = new Date();
    var obj = {
      value: valueScale(Math.random()), // random data
      //value: Math.round((d++ % 60) / 60 * 95), // ramp data
      time: now,
      color: "red",
      ts: now.getTime(),
      interval: timeout
    };

    // send the datum to the chart
    chart.datum(obj);

    // do forever
    dataGenerator();

  }, timeout);
}

// start the data generator
dataGenerator();