updating area chart

邮差的信 提交于 2020-01-30 06:45:08

问题


I have an area chart that I would like to incorporate into an update pattern so I can see the graph live updating. I have been able to do this with a bar chart, but the area chart would be more suitable for the amount of data points I will eventually have.

My best attempt is below. The update process is split into four parts for the two line selections and two area selections. This seems convoluted to me but it is the only way I have been able to get things to appear to work. The problem is as I add data, the graph starts performing very poorly in the browser very quickly, which is hint to me that I am doing something wrong. Also, the elements opacity appears differently than with an earlier static graph, and the part of the graph that appears before new data is added shrinks away to a tiny spike once I add new data.

What is the correct way to incorporate an update pattern into an area chart that is sorted by date?

//////////////////////////////////////////////////
// initial data
//////////////////////////////////////////////////
var tdata = [

{
    "property":"humidity",
    "date":"2016-06-28 05:47:10",
    "value": 40, "unit": "\%"
},
{
    "property":"humidity",
    "date":"2016-06-28 05:47:20",
    "value": 35, "unit": "\%"
},
{
    "property":"humidity",
    "date":"2016-06-28 05:47:30",
    "value": 36, "unit": "\%"
} ,
{
    "property":"humidity",
    "date":"2016-06-28 05:47:40",
    "value": 40, "unit": "\%"
},
{
    "property":"temperature",
    "date":"2016-06-28 05:47:15",
    "value": 75, "unit": "F"
} ,

{
    "property":"temperature",
    "date":"2016-06-28 05:47:25",
    "value": 70, "unit": "F"
} ,

{
    "property":"temperature",
    "date":"2016-06-28 05:47:35",
    "value": 72, "unit": "F"
},

{
    "property":"temperature",
    "date":"2016-06-28 05:47:45",
    "value": 75, "unit": "F"
} , 

];
///////////////////////////////////////////////////////// 
// d3 code:
/////////////////////////////////////////////////////////

    var canvas = d3.select('#disp')
      .append('svg')
      .attr('width', 1200)
      .attr('height', 200);

    var x = d3.scale.linear().range([0,700]);

    var y = d3.scale.linear()
      .domain([0, 100])
      .range([200, 0]);

    var line = d3.svg.line()
     // .interpolate("cardinal")
      .x(function(d) {
        return x(d.date);
      })
      .y(function(d) {
        return y(d.value);
      });

    var area = d3.svg.area()
      //.interpolate("cardinal")
      .x(line.x())
      .y1(line.y())
      .y0(y(0));

var parseDate = d3.time.format("%Y-%m-%d %H:%M:%S").parse;

 var lines = canvas.selectAll('path');

//////////////////// update function:

function update(dataset){
// parse new date strings
 dataset.forEach(function(d) { 
  if(typeof(d.date) === "string"){ d.date = parseDate(d.date); }
});
// sort by date
dataset = dataset.sort(sortByDateAscending);
// update domain
x.domain(d3.extent(dataset, function (d) { return d.date; }));
 
// (1) make temperature line selection, update 
lines = canvas.selectAll('.tline').data(dataset);
    lines
      .attr('class', 'tline')
      .attr("d", function(d) {
        return line(dataset.filter(function(d){ return d.property == 'temperature'}));
      })
      .style("stroke", function(d){ return 'black'; });
 // append new data to selection     
    lines
      .enter().append("path")
      .attr('class', 'tline')
      .attr("d", function(d) {
        return line(dataset.filter(function(d){ return d.property == 'temperature'}));
      })
      .style("stroke", function(d) {
        return 'black';
      });
// remove 
    lines.exit().remove();
// (2) make temperature area selection, update
lines = canvas.selectAll('.tarea').data(dataset);
      lines
        .attr("class", "tarea")
        .style('fill', 'red')
        .style('opacity', '0.3')
        .attr("d", function(d) {
          return area(dataset.filter(function(d){ return d.property == 'temperature'}));
        });
 // append new data to selection     
      lines
        .enter().append("path")
        .attr("class", "tarea")
        .style('fill', 'red')
        .style('opacity', '0.3')
        .attr("d", function(d) {
          return area(dataset.filter(function(d){ return d.property == 'temperature'}));
        });
//remove
      lines.exit().remove();
 // (3) make humidity line selection, update
 lines = canvas.selectAll('.hline').data(dataset);
    lines
      .attr('class', 'hline')
      .attr("d", function(d) {
        return line(dataset.filter(function(d){ return d.property == 'humidity'}));
      })
      .style("stroke", function(d){ return 'black'; });
 // append new data to selection     
    lines
      .enter().append("path")
      .attr('class', 'hline')
      .attr("d", function(d) {
        return line(dataset.filter(function(d){ return d.property == 'humidity'}));
      })
      .style("stroke", function(d) {
        return 'black';
      });
// remove
    lines.exit().remove();
// (4) make humidity area selection, update
lines = canvas.selectAll('.harea').data(dataset);
      lines
        .attr("class", "harea")
        .style('fill', 'steelblue')
        .style('opacity', '0.3')
        .attr("d", function(d) {
          return area(dataset.filter(function(d){ return d.property == 'humidity'}));
        });
// append new data to selection
      lines
        .enter().append("path")
        .attr("class", "harea")
        .style('fill', 'steelblue')
        .style('opacity', '0.3')
        .attr("d", function(d) {
          return area(dataset.filter(function(d){ return d.property == 'humidity'}));
        });
// remove
      lines.exit().remove();     


  }


  function sortByDateAscending(a, b) {
    return Date.parse(a.date) - Date.parse(b.date);
};
//////////////////////////////////////////////////////////////
// main code:
//////////////////////////////////////////////////////////////
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)

update(tdata);  

function btnfunc(){
addObjs(tdata)
update(tdata);  
}
// function to add new objects to dataset incremented by date
function addObjs(arr) {
var z = 1;
var h = JSON.parse(JSON.stringify(arr[arr.length-2])); 
var t = JSON.parse(JSON.stringify(arr[arr.length-1])); 

h.property = 'humidity'; h.unit = '\%';
t.property = 'temperature'; t.unit = 'F';

var time = dparse(t.date);
time+= z;
h.date = timeString(time.toString());
h.value = 15 + Math.floor(Math.random()*31);
time+= z;
t.date = timeString(time.toString());
t.value = 65 + Math.floor(Math.random()*28);

arr.push(h);
arr.push(t);

function timeString(ins) {
    return  ins.slice(0,4) + '-' + ins.slice(4,6) + '-' + ins.slice(6,8) + ' '
         +  ins.slice(8,10) + ':'  +  ins.slice(10,12) + ':'  +  ins.slice(12);
}
function dparse(date){
var d = date.substr(0,10).replace(/-/g , '');
var t = date.substr(11,18).replace(/:/g , '');
var i = parseInt(d+t);
return i;
}
    
}
.axis path,
.axis line {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}

.area {
  fill: steelblue;
}
 body {
      font: 10px sans-serif;
    }
    
    .axis path,
    .axis line {
      fill: none;
      shape-rendering: crispEdges;
    }
    
    .tline {
      fill: none;
      stroke-width: 1px;
      
    }
     .hline {
      fill: none;
      stroke-width: 1px;
      
    }   
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="disp"></div> 
<button onclick="btnfunc()">add</button>

回答1:


The problem was that (in an area chart) I shouldn't be appending anything at all in the update function.

Since there is just a continuous path there is no need to append anything new, as I would in a bar chart, to reflect new data. Just updating the path's data attribute is all that is needed since a path already exists across the range of the graph.

I put the initial append in an init function.

The domain update still exhibits that strange behavior of squishing the init data, but other than that it works as it should:

//////////////////////////////////////////////
// initial data
//////////////////////////////////////////////
var tdata = [

{
    "property":"humidity",
    "date":"2016-06-28 05:47:10",
    "value": 40, "unit": "\%"
},
{
    "property":"humidity",
    "date":"2016-06-28 05:47:20",
    "value": 35, "unit": "\%"
},
{
    "property":"humidity",
    "date":"2016-06-28 05:47:30",
    "value": 36, "unit": "\%"
} ,
{
    "property":"humidity",
    "date":"2016-06-28 05:47:40",
    "value": 40, "unit": "\%"
},
{
    "property":"temperature",
    "date":"2016-06-28 05:47:15",
    "value": 75, "unit": "F"
} ,

{
    "property":"temperature",
    "date":"2016-06-28 05:47:25",
    "value": 70, "unit": "F"
} ,

{
    "property":"temperature",
    "date":"2016-06-28 05:47:35",
    "value": 72, "unit": "F"
},

{
    "property":"temperature",
    "date":"2016-06-28 05:47:45",
    "value": 75, "unit": "F"
} , 

];

///////////////////////////////////////////////
// d3 code
///////////////////////////////////////////////

    var canvas = d3.select('#disp')
      .append('svg')
      .attr('width', 800)
      .attr('height', 200);

    var x = d3.scale.linear().range([0, 800]);

    var y = d3.scale.linear()
      .domain([0, 100])
      .range([200, 0]);

    var line = d3.svg.line()
     // .interpolate("cardinal")
      .x(function(d) {
        return x(d.date);
      })
      .y(function(d) {
        return y(d.value);
      });

    var area = d3.svg.area()
      //.interpolate("cardinal")
      .x(line.x())
      .y1(line.y())
      .y0(y(0));

var parseDate = d3.time.format("%Y-%m-%d %H:%M:%S").parse;

    var lines = canvas.selectAll('.property');

    var lE;

///////////////////////////// init function
function init(dataset){
 dataset.forEach(function(d) { 
  if(typeof(d.date) === "string"){ d.date = parseDate(d.date); }
});

dataset = dataset.sort(sortByDateAscending);

x.domain(d3.extent(dataset, function (d) { return d.date; }));

lines = canvas.selectAll('.property')
      .data(dataset, function(d) {
        return d.property;
      });

lE = lines.enter()
      .append('g')
      .attr('class', 'property');
  
     lE.append("path")
      .attr('class', 'tline')
      .attr("d", function(d) {
        return line(dataset.filter(function(d){ return d.property == 'temperature'}));
     })
      .style("stroke", function(d) {
        return 'black';
      })

    lE.append("path")
      .attr('class', 'hline')
      .attr("d", function(d) {
        return line(dataset.filter(function(d){ return d.property == 'humidity'}));
      })
      .style("stroke", function(d) {
        return 'black';
      })

      lE.append("path")
        .attr("class", "tarea")
        .style('fill', 'red')
        .style('opacity', '0.3')
        .attr("d", function(d) {
          return area(dataset.filter(function(d){ return d.property == 'temperature'}));
        });
      
      lE.append("path")
        .attr("class", "harea")
        .style('fill', 'steelblue')
        .style('opacity', '0.9')
        .attr("d", function(d) {
          return area(dataset.filter(function(d){ return d.property == 'humidity'}));
        });

}

///////////////////////////// update function
function update(dataset){

 dataset.forEach(function(d) { 
  if(typeof(d.date) === "string"){ d.date = parseDate(d.date); }
});

dataset = dataset.sort(sortByDateAscending);

x.domain(d3.extent(dataset, function (d) { return d.date; }));

    lE.select(".tarea")
    // .transition().duration(500)
     .attr("d", function(d) {
          return area(dataset.filter(function(d){ return d.property == 'temperature'}));
        });

    lE.select(".harea")
    // .transition().duration(500)
     .attr("d", function(d) {
          return area(dataset.filter(function(d){ return d.property == 'humidity'}));
        });

    lE.select(".tline")
   //  .transition().duration(500)
     .attr("d", function(d) {
          return area(dataset.filter(function(d){ return d.property == 'temperature'}));
        });


    lE.select(".hline")
   //  .transition().duration(500)
     .attr("d", function(d) {
          return area(dataset.filter(function(d){ return d.property == 'humidity'}));
        });

 }

  function sortByDateAscending(a, b) {
    return Date.parse(a.date) - Date.parse(b.date);
};

////////////////////////////////////////////////////////
// main code
////////////////////////////////////////////////////////

var anim = false;
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)

init(tdata);	

function btnfunc(){
addObjs(tdata)
update(tdata);	
}

function rmvfunc(){
tdata.pop(); tdata.pop();
update(tdata);	
}

function shiftfunc(){
addObjs(tdata)
tdata.shift(); tdata.shift();
update(tdata);	
}

function animfunc() {
	anim = !anim; 
}

window.setInterval(function(){ if(anim){btnfunc();} }, 1000);

	
function addObjs(arr) {
var z = 1;
var h = JSON.parse(JSON.stringify(arr[arr.length-2])); 
var t = JSON.parse(JSON.stringify(arr[arr.length-1])); 

h.property = 'humidity'; h.unit = '\%';
t.property = 'temperature'; t.unit = 'F';

var time = dparse(t.date);
time+= z;
h.date = timeString(time.toString());
h.value = 15 + Math.floor(Math.random()*31);
time+= z;
t.date = timeString(time.toString());
t.value = 65 + Math.floor(Math.random()*28);

arr.push(h);
arr.push(t);

function timeString(ins) {
	return  ins.slice(0,4) + '-' + ins.slice(4,6) + '-' + ins.slice(6,8) + ' '
         +  ins.slice(8,10) + ':'  +  ins.slice(10,12) + ':'  +  ins.slice(12);
}
function dparse(date){
var d = date.substr(0,10).replace(/-/g , '');
var t = date.substr(11,18).replace(/:/g , '');
var i = parseInt(d+t);
return i;
}
	
}
.axis path,
.axis line {
  fill: none;
  shape-rendering: crispEdges;
}

.tline {
  fill: none;
  stroke-width: 1px;

}
.hline {
  fill: none;
  stroke-width: 1px;

}   
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="disp"></div> 
<button onclick="btnfunc()">add</button>
<button onclick="shiftfunc()">shift</button>
<button onclick="rmvfunc()">remove</button>
<button onclick="animfunc()">anim on/off</button>
<script src="data.js"></script>
<script src="graph2.js"></script>
<script src="main.js"></script>


来源:https://stackoverflow.com/questions/39174987/updating-area-chart

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!