0

I am trying to simulate the live-updating ajax for a D3 bar graph, which is contained in a module with a single update function.

update() is supposed to parse and sort the data by date and display the graph.

If I display the graph, then assign the data array to another array, then update the graph, it works but only once.

I have a function addObjs(dataset) that adds new objects to the dataset. If I do this in between updates I get an error

"t.slice is not a function"

and the code breaks. However, If I do this after reassigning the array it works, although just once as well.

I would like to have a function that I know will work in window.setInterval(), but it appears I have something wrong in the way my D3 code deals with updated data.

///////////////////////////////////////////////////////////////////
// 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>

4
  • t.slice does not appear in your code - the error should include a filename and line number ... exactly what code does the error point to? Commented Aug 12, 2016 at 23:19
  • t.slice is happening internally in d3. It points to the first line in my d3 update() function: dataset.forEach... Commented Aug 12, 2016 at 23:25
  • This means the argument to update isn't an array. What is the value of dataset when the error happens? Commented Aug 12, 2016 at 23:31
  • @Barmar console.log shows an array of objects Commented Aug 13, 2016 at 1:31

1 Answer 1

1

When calling update the first time you are parsing your d.date strings using parseDate and assign the resulting date object back to d.date. The second time update() is entered this parsing will fail, because d.date isn't a string anymore but already a date object. If dataset was static and will not change between calls you could simply move this initial parsing out of the function. If it actually does change, you can do something like

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

to ensure only date strings will get parsed.

The following snippet will work as expected:

///////////////////////////////////////////////////////////////////
// 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>

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.