我正在尝试模拟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>
答案 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>