|
// This DataObjStub is a mock object exposing a getNextDomain |
|
// and getPreviousDomain method. When calling those methods, |
|
// a dataset with x ranging from 0 to 29 is created with random |
|
// y values between 0 and 1. |
|
// In an application, this should be replaced by a layer that |
|
// is querying a data backend. |
|
function DataObjStub() { |
|
var firstDomainIndex = 0; |
|
var lastDomainIndex = 3; |
|
var nrOfDomainsShown = 5; |
|
|
|
var getDummyDomain = function() { |
|
var data = []; |
|
for (var i=0; i<10; i++) { |
|
var y = Math.random(); |
|
data.push({"x": i, "y": y}); |
|
} |
|
return data; |
|
} |
|
|
|
this.getNextDomain = function() { |
|
var data = getDummyDomain(); |
|
return data; |
|
} |
|
|
|
this.getPreviousDomain = function() { |
|
var data = getDummyDomain(); |
|
return data; |
|
} |
|
} |
|
|
|
function lineChart() { |
|
// Display options |
|
// width of the overall chart area |
|
var w = 500; |
|
// height of the overall chart area |
|
var h = 200; |
|
// Distance between first domain and left svg border |
|
leftPadding = 50; |
|
// width of one domain svg |
|
var domainWidth = 60; |
|
// height of one domain svg |
|
var domainHeight = 190; |
|
// spacing between two domains |
|
var domainSpacing = 10; |
|
|
|
// Other options |
|
var domains = []; |
|
var nrOfDomains = 5; |
|
var dataObj = new DataObjStub(); |
|
var nrOfPrefetchedDomains = 1; // will fetch data for additional domains at each end of the chart |
|
var totalNrOfDomains = nrOfDomains + (2 * nrOfPrefetchedDomains); |
|
|
|
// Add main svg element |
|
var mainSvg = d3.select("#linechart") |
|
.append("svg") |
|
.attr("width", w) |
|
.attr("height", h) |
|
.attr("id", "linecontainer"); |
|
|
|
// Add clipPath |
|
var devs = mainSvg.append("defs"); |
|
devs.append("clipPath") |
|
.attr("id", "lineclip") |
|
.append("rect") |
|
.attr("x", leftPadding) |
|
.attr("y", 0) |
|
.attr("width", w-leftPadding) |
|
.attr("height", h); |
|
|
|
// Create domain objects |
|
for (var i=0; i<totalNrOfDomains; i++) { |
|
var domainData = dataObj.getNextDomain(); |
|
var type = "visible"; |
|
if (i<nrOfPrefetchedDomains || i > totalNrOfDomains - nrOfPrefetchedDomains - 1) { |
|
type = "prefetched"; |
|
} |
|
var start = leftPadding + (i-nrOfPrefetchedDomains) * (domainWidth + domainSpacing); |
|
var domain = {"data": domainData, "type": type, "x": start}; |
|
domains.push(domain); |
|
} |
|
|
|
// Create overall y-scale |
|
var allYValues = getAllYValues(); |
|
var yScale = d3.scale.linear().domain(d3.extent(allYValues)).range([domainHeight, 0]); |
|
|
|
// Create domains svg g elements |
|
for (var i=0; i<totalNrOfDomains; i++) { |
|
domain = domains[i]; |
|
domain = createDomain(domain, mainSvg, yScale); // adds svg with line chart |
|
domains[i] = domain; |
|
} |
|
|
|
// Create y axis |
|
drawYAxis(); |
|
|
|
/* -------------------------- |
|
* LineChart exposed functions |
|
* -------------------------- |
|
*/ |
|
// this method will add a domain to the left of the chart |
|
this.previous = function() { |
|
var new_data = dataObj.getPreviousDomain(); |
|
domain = {"data": new_data, "x": leftPadding - ((nrOfPrefetchedDomains + 1) * (domainWidth + domainSpacing)), "type": "prefetched"}; |
|
domain = createDomain(domain, mainSvg, yScale); |
|
domains.unshift(domain); |
|
// One domain becomes visible |
|
domains[nrOfPrefetchedDomains].type = "visible"; |
|
// One domain becomes invisible |
|
domains[totalNrOfDomains - nrOfPrefetchedDomains].type = "prefetched"; |
|
moveDomainsToRight(); |
|
dropLastDomain(); |
|
rescaleDomains(); |
|
} |
|
|
|
// this method will add a domain to the right of the chart |
|
this.next = function() { |
|
var new_data = dataObj.getNextDomain(); |
|
var x = leftPadding + (nrOfDomains + nrOfPrefetchedDomains) * (domainWidth + domainSpacing); |
|
domain = {"data": new_data, "x": x, "type": "prefetched"}; |
|
domain = createDomain(domain, mainSvg, yScale); |
|
domains.push(domain); |
|
// This domain becomes visible |
|
domains[totalNrOfDomains - nrOfPrefetchedDomains].type = "visible"; |
|
// This domain becomes invisible |
|
domains[nrOfPrefetchedDomains].type = "prefetched"; |
|
moveDomainsToLeft(); |
|
dropFirstDomain(); |
|
rescaleDomains(); |
|
} |
|
|
|
/* -------------------------- |
|
* LineChart helper functions |
|
* -------------------------- |
|
*/ |
|
// Fetch all y values from all visible domains. This |
|
// is used to calculate an overall y scale |
|
function getAllYValues() { |
|
var values = []; |
|
for (var i=0; i<domains.length; i++) { |
|
var d = domains[i]; |
|
if (d.type == "visible") { |
|
for (var k=0; k<d.data.length; k++) { |
|
values.push(d.data[k].y); |
|
} |
|
} |
|
} |
|
return values; |
|
} |
|
|
|
// Get data from the dataObj, create scales and |
|
// define a line function that will draw the data. |
|
// Mind you that the domain element given to this |
|
// function should already contain a `data` and a |
|
// `x` property. |
|
function createDomain(domain, mainSvg, yScale) { |
|
var x_domain = d3.extent(domain.data, function(d) {return d.x}); |
|
var x = d3.scale.linear().domain(x_domain).range([domain.x, (domain.x + domainWidth)]); |
|
domain.xscale = x; |
|
domain.yscale = yScale; |
|
domain = drawLine(domain); |
|
return domain; |
|
} |
|
|
|
// The actual drawing function |
|
function drawLine(domain) { |
|
lineFunction = createLineFunction(domain.xscale, domain.yscale); |
|
|
|
var line = mainSvg.append("g") |
|
.attr("clip-path", "url(#lineclip)"); |
|
var path = line.append("path") |
|
.attr("class", "domain") |
|
.attr("d", lineFunction(domain.data)) |
|
.attr("stroke", "black") |
|
.attr("stroke-width", 2) |
|
.attr("fill", "none"); |
|
if (domain.type == "prefetched") { |
|
line.attr("visibility", "hidden"); |
|
} else { |
|
line.attr("visibility", "visible") |
|
} |
|
domain.g = line; |
|
return domain; |
|
} |
|
|
|
// This function will create a line function which |
|
// is used to draw the lines based on their domain data |
|
function createLineFunction(xscale, yscale) { |
|
lineFunction = d3.svg.line() |
|
.x(function(d) {return xscale(d["x"])}) |
|
.y(function(d) {return yscale(d["y"])}) |
|
.interpolate("linear"); |
|
return lineFunction; |
|
} |
|
|
|
// Draw the y axis |
|
function drawYAxis() { |
|
var yAxis = d3.svg.axis().scale(yScale).orient("left").ticks(5); |
|
try { |
|
var g = mainSvg.selectAll(".axis"); |
|
g.remove(); |
|
} |
|
catch(err) { |
|
console.log(err); |
|
} |
|
|
|
mainSvg.append("g") |
|
.attr("class", "axis") |
|
.attr("transform", "translate(" + (leftPadding - 10) + ", 0)") |
|
.call(yAxis); |
|
} |
|
|
|
// Function for redrawing (used by rescale) |
|
function reDrawLine(domain) { |
|
// create a new line function that will be used by the g element |
|
lineFunction = createLineFunction(domain.xscale, domain.yscale); |
|
// animate the line to the new position |
|
domain.g.selectAll("path").transition().attr("d", lineFunction(domain.data)); |
|
// Toggle visibility |
|
if (domain.type == "prefetched") { |
|
domain.g.attr("visibility", "hidden"); |
|
} else { |
|
domain.g.attr("visibility", "visible"); |
|
} |
|
} |
|
|
|
// General function to move all lines in the chart area |
|
function moveDomains(xDiff) { |
|
for (var i=0; i<domains.length; i++) { |
|
// calculate new x position for the line |
|
var new_x = domains[i].x + xDiff; |
|
// find x_domain |
|
var x_domain = d3.extent(domains[i].data, function(d) {return d.x}); |
|
// create a new x scale |
|
var new_xscale = d3.scale.linear().domain(x_domain).range([new_x, (new_x + domainWidth)]); |
|
domains[i].x = new_x; |
|
domains[i].xscale = new_xscale; |
|
reDrawLine(domains[i]); |
|
} |
|
} |
|
|
|
// Shift all domains to the right by one domain |
|
function moveDomainsToRight() { |
|
var xdiff = domainWidth + domainSpacing; |
|
moveDomains(xdiff); |
|
} |
|
|
|
// Shift all domains to the left by one domain |
|
function moveDomainsToLeft() { |
|
var xdiff = -(domainWidth + domainSpacing); |
|
moveDomains(xdiff); |
|
} |
|
|
|
// after adding a new domain, the existing domains should be rescaled |
|
// and the y axis should be redrawn |
|
function rescaleDomains() { |
|
var allYValues = getAllYValues(); |
|
yScale = d3.scale.linear().domain(d3.extent(allYValues)).range([domainHeight, 0]); |
|
for (var i=0; i<domains.length; i++) { |
|
var domain = domains[i]; |
|
domain.yscale = yScale; |
|
reDrawLine(domain); |
|
domains[i] = domain; |
|
} |
|
drawYAxis(); |
|
} |
|
|
|
function dropLastDomain() { |
|
var g = domains[domains.length - 1].g; |
|
g.remove(); |
|
domains.pop(); |
|
} |
|
|
|
function dropFirstDomain() { |
|
var g = domains[0].g; |
|
g.remove(); |
|
domains.shift(); |
|
} |
|
|
|
} |
|
|
|
var lineChart = new lineChart(); |
|
|
|
/* ----------------------- |
|
* Bind UI controls |
|
*/ |
|
$("#previous").click(function() { |
|
lineChart.previous(); |
|
}); |
|
|
|
$("#next").click(function() { |
|
lineChart.next(); |
|
}); |