<!-- Author: Bo Ericsson, bo@boe.net -->
<!-- Inspiration from numerous examples by Mike Bostock, http:
<!-- and example by Andy Aiken, http:
function realTimeChartMulti() {
var version = "0.1.0",
datum, data,
maxSeconds = 300, pixelsPerSecond = 10,
svgWidth = 700, svgHeight = 300,
margin = { top: 20, bottom: 20, left: 100, right: 30, topNav: 10, bottomNav: 20 },
dimension = { chartTitle: 20, xAxis: 20, yAxis: 20, xTitle: 20, yTitle: 20, navChart: 70 },
maxY = 100, minY = 0,
chartTitle, yTitle, xTitle,
drawXAxis = true, drawYAxis = true, drawNavChart = true,
border,
selection,
barId = 0,
yDomain = [],
debug = false,
barWidth = 5,
halted = false,
x, y,
xNav, yNav,
width, height,
widthNav, heightNav,
xAxisG, yAxisG,
xAxis, yAxis,
svg;
var chart = function(s) {
selection = s;
if (selection == undefined) {
console.error("selection is undefined");
return;
};
chartTitle = chartTitle || "";
xTitle = xTitle || "";
yTitle = yTitle || "";
var chartTitleDim = chartTitle == "" ? 0 : dimension.chartTitle,
xTitleDim = xTitle == "" ? 0 : dimension.xTitle,
yTitleDim = yTitle == "" ? 0 : dimension.yTitle,
xAxisDim = !drawXAxis ? 0 : dimension.xAxis,
yAxisDim = !drawYAxis ? 0 : dimension.yAxis,
navChartDim = !drawNavChart ? 0 : dimension.navChart;
var marginTop = margin.top + chartTitleDim;
height = svgHeight - marginTop - margin.bottom - chartTitleDim - xTitleDim - xAxisDim - navChartDim + 30;
heightNav = navChartDim - margin.topNav - margin.bottomNav;
var marginTopNav = svgHeight - margin.bottom - heightNav - margin.topNav;
width = svgWidth - margin.left - margin.right;
widthNav = width;
svg = selection.append("svg")
.attr("width", svgWidth)
.attr("height", svgHeight)
.style("border", function(d) {
if (border) return "1px solid lightgray";
else return null;
});
var main = svg.append("g")
.attr("transform", "translate (" + margin.left + "," + marginTop + ")");
main.append("defs").append("clipPath")
.attr("id", "myClip")
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", width)
.attr("height", height);
main.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", width)
.attr("height", height)
.style("fill", "#f5f5f5");
var barG = main.append("g")
.attr("class", "barGroup")
.attr("transform", "translate(0, 0)")
.attr("clip-path", "url(#myClip")
.append("g");
xAxisG = main.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")");
yAxisG = main.append("g")
.attr("class", "y axis");
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;
});
yAxisG.append("text")
.attr("class", "title")
.attr("transform", "rotate(-90)")
.attr("x", - height / 2)
.attr("y", -margin.left + 15)
.attr("dy", ".71em")
.text(function(d) {
var text = yTitle == undefined ? "" : yTitle;
return text;
});
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;
});
x = d3.time.scale().range([0, width]);
y = d3.scale.ordinal().domain(yDomain).rangeRoundPoints([height, 0], 1)
xAxis = d3.svg.axis().orient("bottom");
yAxis = d3.svg.axis().orient("left");
var nav = svg.append("g")
.attr("transform", "translate (" + margin.left + "," + marginTopNav + ")");
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)");
var navG = nav.append("g")
.attr("class", "nav");
var xAxisGNav = nav.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + heightNav + ")");
xNav = d3.time.scale().range([0, widthNav]);
yNav = d3.scale.ordinal().domain(yDomain).rangeRoundPoints([heightNav, 0], 1)
var xAxisNav = d3.svg.axis().orient("bottom");
var ts = new Date().getTime();
var endTime = new Date(ts);
var startTime = new Date(endTime.getTime() - maxSeconds * 1000);
var interval = endTime.getTime() - startTime.getTime();
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();
x.domain([startTimeViewport, endTimeViewport]);
xNav.domain([startTime, endTime]);
xAxis.scale(x)(xAxisG);
yAxis.scale(y)(yAxisG);
xAxisNav.scale(xNav)(xAxisGNav);
var viewport = d3.svg.brush()
.x(xNav)
.extent([startTimeViewport, endTimeViewport])
.on("brush", function () {
var extent = viewport.extent();
startTimeViewport = extent[0];
endTimeViewport = extent[1];
intervalViewport = endTimeViewport.getTime() - startTimeViewport.getTime();
offsetViewport = startTimeViewport.getTime() - startTime.getTime();
if (intervalViewport == 0) {
intervalViewport = maxSeconds * 1000;
offsetViewport = 0;
}
x.domain(viewport.empty() ? xNav.domain() : extent);
xAxis.scale(x)(xAxisG);
refresh();
});
var viewportG = nav.append("g")
.attr("class", "viewport")
.call(viewport)
.selectAll("rect")
.attr("height", heightNav);
data = [];
refresh();
function refresh() {
data = data.filter(function(d) {
if (d.time.getTime() > startTime.getTime()) return true;
})
var categoryCount = yDomain.length;
if (debug) console.log("yDomain", yDomain)
var updateSel = barG.selectAll(".bar")
.data(data);
updateSel.exit().remove();
updateSel.enter()
.append(function(d) {
if (debug) { console.log("d", JSON.stringify(d)); }
if (d.type == undefined) console.error(JSON.stringify(d))
var type = d.type || "circle";
var node = document.createElementNS("http://www.w3.org/2000/svg", type);
return node;
})
.attr("class", "bar")
.attr("id", function() {
return "bar-" + barId++;
});
updateSel
.attr("x", function(d) {
var retVal = null;
switch (getTagName(this)) {
case "rect":
var size = d.size || 6;
retVal = Math.round(x(d.time) - size / 2);
break;
default:
}
return retVal;
})
.attr("y", function(d) {
var retVal = null;
switch (getTagName(this)) {
case "rect":
var size = d.size || 6;
retVal = y(d.category) - size / 2;
break;
default:
}
return retVal;
})
.attr("cx", function(d) {
var retVal = null;
switch (getTagName(this)) {
case "circle":
retVal = Math.round(x(d.time));
break;
default:
}
return retVal;
})
.attr("cy", function(d) {
var retVal = null;
switch (getTagName(this)) {
case "circle":
retVal = y(d.category);
break;
default:
}
return retVal;
})
.attr("r", function(d) {
var retVal = null;
switch (getTagName(this)) {
case "circle":
retVal = d.size / 2;
break;
default:
}
return retVal;
})
.attr("width", function(d) {
var retVal = null;
switch (getTagName(this)) {
case "rect":
retVal = d.size;
break;
default:
}
return retVal;
})
.attr("height", function(d) {
var retVal = null;
switch (getTagName(this)) {
case "rect":
retVal = d.size;
break;
default:
}
return retVal;
})
.style("fill", function(d) { return d.color || "black"; })
.style("fill-opacity", function(d) { return d.opacity || 1; });
var updateSelNav = navG.selectAll("circle")
.data(data);
updateSelNav.exit().remove();
updateSelNav.enter().append("circle")
.attr("r", 1)
.attr("fill", "black")
updateSelNav
.attr("cx", function(d) {
return Math.round(xNav(d.time));
})
.attr("cy", function(d) {
return yNav(d.category);
})
}
function getTagName(that) {
var tagName = d3.select(that).node().tagName;
return (tagName);
}
setInterval(function() {
if (halted) return;
var extent = viewport.empty() ? xNav.domain() : viewport.extent();
var interval = extent[1].getTime() - extent[0].getTime();
var offset = extent[0].getTime() - xNav.domain()[0].getTime();
endTime = new Date();
startTime = new Date(endTime.getTime() - maxSeconds * 1000);
startTimeViewport = new Date(startTime.getTime() + offset);
endTimeViewport = new Date(startTimeViewport.getTime() + interval);
viewport.extent([startTimeViewport, endTimeViewport])
x.domain([startTimeViewport, endTimeViewport]);
xNav.domain([startTime, endTime]);
xAxis.scale(x)(xAxisG);
xAxisNav.scale(xNav)(xAxisGNav);
refresh();
}, 200)
return chart;
}
chart.datum = function(_) {
if (arguments.length == 0) return datum;
datum = _;
data.push(datum);
return chart;
}
chart.width = function(_) {
if (arguments.length == 0) return svgWidth;
svgWidth = _;
return chart;
}
chart.height = function(_) {
if (arguments.length == 0) return svgHeight;
svgHeight = _;
return chart;
}
chart.border = function(_) {
if (arguments.length == 0) return border;
border = _;
return chart;
}
chart.title = function(_) {
if (arguments.length == 0) return chartTitle;
chartTitle = _;
return chart;
}
chart.xTitle = function(_) {
if (arguments.length == 0) return xTitle;
xTitle = _;
return chart;
}
chart.yTitle = function(_) {
if (arguments.length == 0) return yTitle;
yTitle = _;
return chart;
}
chart.yDomain = function(_) {
if (arguments.length == 0) return yDomain;
yDomain = _;
if (svg) {
y = d3.scale.ordinal().domain(yDomain).rangeRoundPoints([height, 0], 1);
yAxis.scale(y)(yAxisG);
yNav = d3.scale.ordinal().domain(yDomain).rangeRoundPoints([heightNav, 0], 1);
}
return chart;
}
chart.debug = function(_) {
if (arguments.length == 0) return debug;
debug = _;
return chart;
}
chart.halt = function(_) {
if (arguments.length == 0) return halted;
halted = _;
return chart;
}
chart.version = version;
return chart;
}