更新数组会在d3中引发错误

时间:2016-08-12 23:12:21

标签: javascript d3.js

我正在尝试模拟D3条形图的实时更新ajax,它包含在具有单个更新功能的模块中。

update()应按日期解析和排序数据并显示图表。

如果我显示图形,然后将数据数组分配给另一个数组,然后更新图形,它只能工作一次。

我有一个函数addObjs(dataset),可以将新对象添加到dataset。如果我在更新之间执行此操作,则会收到错误

  

“t.slice不是函数”

并且代码中断了。但是,如果我在重新分配数组后这样做,它可以工作,虽然只有一次。

我希望有一个我知道可以在window.setInterval()中使用的功能,但看起来我的D3代码处理更新数据的方式有问题。

///////////////////////////////////////////////////////////////////
// D3 graph module
///////////////////////////////////////////////////////////////////

var d3graph = (function () {
// init    
var w = 900;
var h = 300;

var margin = {top: 20, right: 20, bottom: 90, left: 40},
    width = w - margin.left - margin.right,
    height = h - margin.top - margin.bottom;

var y = d3.scale.linear().domain([0, 100]).range([ height, 0]);
var x = d3.scale.ordinal().rangeBands([0, width  - margin.left - margin.right], .25);

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

var svg = d3.select("body")
    .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 + ")");

var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom")  
    .tickSize(0)
    .tickFormat(d3.time.format('%H:%M:%S'));

var yAxis = d3.svg.axis()
    .scale(y)
    .orient('left')
    .tickPadding(8);

svg.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
    .call(xAxis)
    .selectAll("text")
    .style("text-anchor", "end")
    .attr("dx", "-4px")
    .attr("dy", "1px")
    .attr("transform", "rotate(-90)" );

svg.append('g')
  .attr('class', 'y axis')
  .call(yAxis);

var bars = svg.selectAll("rect");

// update function
var update = function(dataset) {
 
dataset.forEach(function(d) { d.date = parseDate(d.date); });
dataset = dataset.sort(sortByDateAscending);
x.domain(dataset.map(function(d) { return d.date; }));

bars = svg.selectAll("rect").data( dataset);
bars.enter().append("rect");

bars    
    .attr("x", function(d) { return x(d.date); })
    .attr("y", function(d) { return y(d.value); })   
    .attr("width", x.rangeBand())
    .attr("height", function(d) { return height - y(d.value); })
    .attr('fill',function(d){ return propClr(d.property); } )
    .style('opacity', function(d){ return rtnOpc(d.value, d.property) })
    .on("mouseover", mouseover)
    .on("mouseout", mouseout);

svg.selectAll("g.y.axis")
    .call(yAxis);

svg.selectAll("g.x.axis")
    .call(xAxis)
  .selectAll("text")
  .style("text-anchor", "end")
  .attr("dx", "-4px")
  .attr("dy", "1px")
  .attr("transform", "rotate(-90)" );


bars.exit().remove();

};

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

function mouseover(d) {
  var xPosition = parseFloat(d3.select(this).attr("x")); 
  var yPosition = parseFloat(d3.select(this).attr("y")); 

  d3.select("#tooltip")
    .style("left", xPosition + "px")
    .style("top", yPosition + "px")           
    .select("#value")
    .text(d.date +'\n'+d.value);

  d3.select("#tooltip").classed("hidden", false);

};

function mouseout(d) {
  d3.select("#tooltip").classed("hidden", true);        
};

function rtnOpc(innum, inprop) {
 var num = Math.abs(innum); 
if(inprop == 'temperature'){ 
 return num / 110;
}
else if(inprop == 'humidity'){ 
 return num / 60;
}
};

function propClr(inprop){
if(inprop == 'temperature'){
  return 'red';
}
else if(inprop == 'humidity'){
  return 'blue';
}
};
 
// return module fncs
return {
    update: update
  }
})();

///////////////////////////////////////////////////////////////////
// Data arrays
///////////////////////////////////////////////////////////////////
var tdata = [

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

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

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

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

];

//longer array
var tdata2 = [

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

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

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

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

,
{
    "property":"humidity",
    "date":"2016-06-28 05:48:10",
    "value": 40
},
{
    "property":"humidity",
    "date":"2016-06-28 05:48:20",
    "value": 35
},
{
    "property":"humidity",
    "date":"2016-06-28 05:48:30",
    "value": 26
} ,
{
    "property":"humidity",
    "date":"2016-06-28 05:48:40",
    "value": 20
},
{
    "property":"temperature",
    "date":"2016-06-28 05:48:15",
    "value": 75
} ,

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

{
    "property":"temperature",
    "date":"2016-06-28 05:48:35",
    "value": 82
},

{
    "property":"temperature",
    "date":"2016-06-28 05:48:45",
    "value": 85
} , 

];

///////////////////////////////////////////////////////////////////
// Ajax simulation
///////////////////////////////////////////////////////////////////

//first graph display
d3graph.update(tdata);

//method 1, reasigns array and updates
function method1(){
// reassign array	
tdata = tdata2;
//2nd graph display
d3graph.update(tdata);	
}

//same, but with addObjs() aworking after reassignment
function method1B(){
tdata = tdata2; //< this needs to be there?

addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
//2nd graph display
d3graph.update(tdata);	
}

// just add objects to tdata array
function method2(){
	
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
//2nd graph display
d3graph.update(tdata);	
}


//fnc to add objects to array
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';
t.property = 'temperature';

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;
}
	
}
#tooltip {
position: absolute; 
text-align: center; 
width: 90px;  
height: 98px;   
padding: 2px; 
font: 12px sans-serif;  
background: lightsteelblue; 
border: 0px;          
border-radius: 8px;
pointer-events: none;
}
      
#tooltip.hidden {
  display: none;
}
      
#tooltip p {
  margin: 0;
  font-family: sans-serif;
  font-size: 16px;
  line-height: 20px;
}

.axis text {
  font: 12px sans-serif;
}

.axis path,
.axis line {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="disp"></div> 
<button onclick="method1()">works once</button>
<button onclick="method1B()">works once</button>
<button onclick="method2()">doesn't work</button>
<script src="main.js"></script>

1 个答案:

答案 0 :(得分:1)

第一次使用d.date解析parseDate字符串时调用update,并将生成的日期对象分配回d.date。第二次输入update()时,此解析将失败,因为d.date不再是字符串,而是已经是日期对象。如果dataset是静态的并且在调用之间不会改变,则可以简单地将此初始解析移出函数。如果确实发生了变化,您可以执行类似

的操作
d.date = typeof d.date === "string" ? parseDate(d.date) : d.date;

确保只解析日期字符串

以下代码段将按预期工作:

///////////////////////////////////////////////////////////////////
// D3 graph module
///////////////////////////////////////////////////////////////////

var d3graph = (function () {
// init    
var w = 900;
var h = 300;

var margin = {top: 20, right: 20, bottom: 90, left: 40},
    width = w - margin.left - margin.right,
    height = h - margin.top - margin.bottom;

var y = d3.scale.linear().domain([0, 100]).range([ height, 0]);
var x = d3.scale.ordinal().rangeBands([0, width  - margin.left - margin.right], .25);

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

var svg = d3.select("body")
    .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 + ")");

var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom")  
    .tickSize(0)
    .tickFormat(d3.time.format('%H:%M:%S'));

var yAxis = d3.svg.axis()
    .scale(y)
    .orient('left')
    .tickPadding(8);

svg.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
    .call(xAxis)
    .selectAll("text")
    .style("text-anchor", "end")
    .attr("dx", "-4px")
    .attr("dy", "1px")
    .attr("transform", "rotate(-90)" );

svg.append('g')
  .attr('class', 'y axis')
  .call(yAxis);

var bars = svg.selectAll("rect");

// update function
var update = function(dataset) {
 
dataset.forEach(function(d) { 
  d.date = typeof d.date === "string" ? parseDate(d.date) : d.date;
});
dataset = dataset.sort(sortByDateAscending);
x.domain(dataset.map(function(d) { return d.date; }));

bars = svg.selectAll("rect").data( dataset);
bars.enter().append("rect");

bars    
    .attr("x", function(d) { return x(d.date); })
    .attr("y", function(d) { return y(d.value); })   
    .attr("width", x.rangeBand())
    .attr("height", function(d) { return height - y(d.value); })
    .attr('fill',function(d){ return propClr(d.property); } )
    .style('opacity', function(d){ return rtnOpc(d.value, d.property) })
    .on("mouseover", mouseover)
    .on("mouseout", mouseout);

svg.selectAll("g.y.axis")
    .call(yAxis);

svg.selectAll("g.x.axis")
    .call(xAxis)
  .selectAll("text")
  .style("text-anchor", "end")
  .attr("dx", "-4px")
  .attr("dy", "1px")
  .attr("transform", "rotate(-90)" );


bars.exit().remove();

};

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

function mouseover(d) {
  var xPosition = parseFloat(d3.select(this).attr("x")); 
  var yPosition = parseFloat(d3.select(this).attr("y")); 

  d3.select("#tooltip")
    .style("left", xPosition + "px")
    .style("top", yPosition + "px")           
    .select("#value")
    .text(d.date +'\n'+d.value);

  d3.select("#tooltip").classed("hidden", false);

};

function mouseout(d) {
  d3.select("#tooltip").classed("hidden", true);        
};

function rtnOpc(innum, inprop) {
 var num = Math.abs(innum); 
if(inprop == 'temperature'){ 
 return num / 110;
}
else if(inprop == 'humidity'){ 
 return num / 60;
}
};

function propClr(inprop){
if(inprop == 'temperature'){
  return 'red';
}
else if(inprop == 'humidity'){
  return 'blue';
}
};
 
// return module fncs
return {
    update: update
  }
})();

///////////////////////////////////////////////////////////////////
// Data arrays
///////////////////////////////////////////////////////////////////
var tdata = [

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

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

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

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

];

//longer array
var tdata2 = [

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

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

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

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

,
{
    "property":"humidity",
    "date":"2016-06-28 05:48:10",
    "value": 40
},
{
    "property":"humidity",
    "date":"2016-06-28 05:48:20",
    "value": 35
},
{
    "property":"humidity",
    "date":"2016-06-28 05:48:30",
    "value": 26
} ,
{
    "property":"humidity",
    "date":"2016-06-28 05:48:40",
    "value": 20
},
{
    "property":"temperature",
    "date":"2016-06-28 05:48:15",
    "value": 75
} ,

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

{
    "property":"temperature",
    "date":"2016-06-28 05:48:35",
    "value": 82
},

{
    "property":"temperature",
    "date":"2016-06-28 05:48:45",
    "value": 85
} , 

];

///////////////////////////////////////////////////////////////////
// Ajax simulation
///////////////////////////////////////////////////////////////////

//first graph display
d3graph.update(tdata);

//method 1, reasigns array and updates
function method1(){
// reassign array	
tdata = tdata2;
//2nd graph display
d3graph.update(tdata);	
}

//same, but with addObjs() aworking after reassignment
function method1B(){
tdata = tdata2; //< this needs to be there?

addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
//2nd graph display
d3graph.update(tdata);	
}

// just add objects to tdata array
function method2(){
	
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
addObjs(tdata)
//2nd graph display
d3graph.update(tdata);	
}


//fnc to add objects to array
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';
t.property = 'temperature';

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;
}
	
}
#tooltip {
position: absolute; 
text-align: center; 
width: 90px;  
height: 98px;   
padding: 2px; 
font: 12px sans-serif;  
background: lightsteelblue; 
border: 0px;          
border-radius: 8px;
pointer-events: none;
}
      
#tooltip.hidden {
  display: none;
}
      
#tooltip p {
  margin: 0;
  font-family: sans-serif;
  font-size: 16px;
  line-height: 20px;
}

.axis text {
  font: 12px sans-serif;
}

.axis path,
.axis line {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="disp"></div> 
<button onclick="method1()">works once</button>
<button onclick="method1B()">works once</button>
<button onclick="method2()">doesn't work</button>
<script src="main.js"></script>