|
function LineChart(parameters) { |
|
// Svg parameters |
|
var w = 900; |
|
var h = 350; |
|
var pad = 40; |
|
var left_pad = 150; |
|
|
|
// Chart elements. Defined here so they can be accessed later in other functions. |
|
var data; // will contain the DataObj element containing data |
|
var x; // the x scale |
|
var y; // the y scale |
|
var xmin; // the minimum value of the x domain |
|
var xmax; // the maximum value of the x domain |
|
var xAxis; // d3.svg.axis for x |
|
var yAxis; // d3.svg.axis for y |
|
var xaxis_el; // the x axis element on the screen |
|
var yaxis_el; // the y axis element on the screen |
|
var xlab; // label for the x axis |
|
var ylab; // label for the y axis |
|
var xlab_el; // the element on the screen containing the x axis label |
|
var ylab_el; // the element on the screen containing the y axis label |
|
var lineFunction; // the function to draw the line |
|
var line; // the line element |
|
|
|
// Add main svg element |
|
var svg = d3.select("#linechart").append("svg").attr("width", w).attr("height", h); |
|
|
|
// Add clipPath for both the line element and the x axis element |
|
var devs = svg.append("defs"); |
|
devs.append("clipPath") |
|
.attr("id", "lineclip") |
|
.append("rect") |
|
.attr("x", left_pad) |
|
.attr("y", pad) |
|
.attr("width", w - left_pad - pad) |
|
.attr("height", h - 3*pad); |
|
|
|
devs.append("clipPath") |
|
.attr("id", "axisclip") |
|
.append("rect") |
|
.attr("x", left_pad - 15) |
|
.attr("y", h - pad - 10) |
|
.attr("width", w - left_pad - pad + 30) |
|
.attr("height", 30); |
|
// note that the margins defined for the x axis clip Path are arbitrary |
|
|
|
// ------------------------ |
|
// calculateChartParameters will calculate all required parameters |
|
// that are needed to draw and redraw the chart. |
|
// ------------------------ |
|
var calculateChartParameters = function (data, xdomain) { |
|
|
|
// Find data metrics |
|
if (xdomain.length == 2) { |
|
xmin = xdomain[0]; |
|
xmax = xdomain[1]; |
|
} else { |
|
var xmin_xmax = d3.extent(data, function(d) {return d.x}); |
|
xmin = xmin_xmax[0]; |
|
xmax = xmin_xmax[1]; |
|
} |
|
var ymin_ymax = d3.extent(data, function(d) {return d.y}); |
|
ymin = ymin_ymax[0]; |
|
ymax = ymin_ymax[1]; |
|
|
|
// Define x and y scale and domain |
|
x = d3.scale.linear().domain([xmin,xmax]).range([left_pad, w - pad]); |
|
y = d3.scale.linear().domain([ymin,ymax]).range([h - 2*pad, pad]); |
|
|
|
// Define axes |
|
xAxis = d3.svg.axis().scale(x).orient("bottom").ticks(5); |
|
yAxis = d3.svg.axis().scale(y).orient("left").ticks(5); |
|
|
|
// The accessor function for the line |
|
lineFunction = d3.svg.line() |
|
.x(function(d) {return x(d["x"])}) |
|
.y(function(d) {return y(d["y"])}) |
|
.interpolate("linear"); |
|
} |
|
|
|
|
|
// ------------------------ |
|
// init function: this function will draw the initial chart. |
|
// It is this function that will first populate the Chart element |
|
// variables that are declared above. |
|
// ------------------------ |
|
_init = function () { |
|
// User defined parameters, with default values |
|
data = new DataObj(parameters.data.sort(compareByX)) |
|
xlab = parameters.xlab || "no xlabel given"; |
|
ylab = parameters.ylab || "no ylabel given"; |
|
view = parameters.view || []; |
|
|
|
data.setXDomain(view); |
|
calculateChartParameters(data.domain_data, view); |
|
|
|
// define xLabel |
|
xlab_el = svg.append("text") |
|
.attr("class", "axis-label") |
|
.attr("x", w / 2) |
|
.attr("y", h - 6) |
|
.text(xlab); |
|
|
|
// define yLabel |
|
ylab_el = svg.append("text") |
|
.attr("class", "axis-label") |
|
.attr("transform", "translate(" + left_pad / 3 + ", " + h / 2 + ") rotate(-90)") |
|
.text(ylab); |
|
|
|
// Draw xAxis |
|
xaxis_el = svg.append("g") |
|
.attr("clip-path", "url(#axisclip)") |
|
.append("g") |
|
.attr("class", "axis") |
|
.attr("transform", "translate(0, " + (h - pad) + ")") |
|
.call(xAxis); |
|
|
|
// Draw yAxis |
|
yaxis_el = svg.append("g") |
|
.attr("class", "axis") |
|
.attr("transform", "translate(" + (left_pad - pad) + ", 0)") |
|
.call(yAxis); |
|
|
|
// Add the line to the chart |
|
line = svg.append("g") |
|
.attr("clip-path", "url(#lineclip)") |
|
.append("path") |
|
.attr("d", lineFunction(data.domain_data)) |
|
.attr("stroke", "black") |
|
.attr("stroke-width", 2) |
|
.attr("fill", "none"); |
|
} |
|
|
|
this.updateXDomain = function (xdomain) { |
|
data.setXDomain(xdomain); |
|
calculateChartParameters(data.domain_data, xdomain); |
|
xaxis_el.call(xAxis); |
|
yaxis_el.call(yAxis); |
|
line.transition().duration(200).attr("d", lineFunction(data.domain_data)); |
|
} |
|
|
|
this.shiftBy = function (shift) { |
|
data.addShiftPoints(shift); |
|
var rangeDiff = x(0) - x(0 + shift); |
|
if (shift > 0) { |
|
tmpx = d3.scale.linear().domain([xmin,xmax + shift]).range([left_pad, w - pad - rangeDiff]); |
|
} else { |
|
tmpx = d3.scale.linear().domain([xmin + shift,xmax]).range([left_pad - rangeDiff, w - pad]); |
|
} |
|
tmpxAxis = d3.svg.axis().scale(tmpx).orient("bottom").ticks(5); |
|
xaxis_el.transition().duration(350).attr("transform", "translate(" + rangeDiff + ", " + (h - pad) + ")").call(tmpxAxis); |
|
line.attr("d", lineFunction(data.domain_data)).attr("transform", null) |
|
.transition().duration(350).attr("transform", "translate(" + rangeDiff + ")").each("end", function() { |
|
data.removeShiftPoints(shift); |
|
calculateChartParameters(data.domain_data, [xmin + shift, xmax + shift]); |
|
yaxis_el.call(yAxis); |
|
xaxis_el.attr("transform", "translate(0, " + (h - pad) + ")").call(xAxis); |
|
line.attr("transform", null).attr("d", lineFunction(data.domain_data)); |
|
}); |
|
} |
|
|
|
// Run init when constructing a new LineChart object |
|
_init(); |
|
|
|
/* ---------------- |
|
* Helper functions |
|
* ---------------- |
|
*/ |
|
function compareByX(a,b) { |
|
if (a.x < b.x) |
|
return -1; |
|
if (a.x > b.x) |
|
return 1; |
|
return 0; |
|
} |
|
|
|
} |
|
|
|
function DataObj(data) { |
|
this.data = data; |
|
this.setXDomain = function(xdomain) { |
|
if (xdomain.length == 0) { |
|
this.minX = this.data[0].x; |
|
this.maxX = this.data[this.data.length - 1].x; |
|
} else { |
|
this.minX = xdomain[0]; |
|
this.maxX = xdomain[1]; |
|
} |
|
this.start_index = this.getStartIndex(this.data, this.minX); |
|
this.stop_index = this.getStopIndex(this.data, this.maxX); |
|
this.domain_data = []; |
|
for (var i=this.start_index; i<= this.stop_index; i++) { |
|
this.domain_data.push(this.data[i]); |
|
} |
|
return this.domain_data; |
|
} |
|
|
|
this.getStartIndex = function(data, minXValue) { |
|
for (var i=0; i < data.length; i++) { |
|
var x = data[i].x; |
|
if (x >= minXValue) { |
|
return i; |
|
} |
|
} |
|
return data.length - 1; |
|
} |
|
|
|
this.getStopIndex = function(data, maxXValue) { |
|
|
|
for (var i=0; i < data.length; i++) { |
|
index = data.length - 1 - i; |
|
var x = data[index].x; |
|
if (x <= maxXValue) { |
|
return index; |
|
} |
|
} |
|
return 0; |
|
} |
|
|
|
this.addShiftPoints = function(shift) { |
|
if (shift < 0) { |
|
var new_minX = this.minX + shift; |
|
var new_start_index = this.getStartIndex(this.data, new_minX); |
|
for (var i=this.start_index - 1; i >= new_start_index; i=i-1) { |
|
this.domain_data.unshift(this.data[i]); |
|
} |
|
this.minX = new_minX; |
|
this.start_index = new_start_index; |
|
} |
|
if (shift > 0) { |
|
var new_maxX = this.maxX + shift; |
|
var new_stop_index = this.getStopIndex(this.data, new_maxX); |
|
for (var i=this.stop_index + 1; i <= new_stop_index; i++) { |
|
this.domain_data.push(this.data[i]); |
|
} |
|
this.maxX = new_maxX; |
|
this.stop_index = new_stop_index; |
|
} |
|
return this.domain_data; |
|
} |
|
|
|
this.removeShiftPoints = function(shift) { |
|
if (shift < 0) { |
|
var new_maxX = this.maxX + shift; |
|
var new_stop_index = this.getStopIndex(this.data, new_maxX); |
|
var diff = this.stop_index - new_stop_index; |
|
for (var i=0; i<diff; i++) { |
|
this.domain_data.pop(); |
|
} |
|
this.maxX = new_maxX; |
|
this.stop_index = new_stop_index; |
|
} |
|
if (shift > 0) { |
|
var new_minX = this.minX + shift; |
|
var new_start_index = this.getStartIndex(this.data, new_minX); |
|
var diff = new_start_index - this.start_index; |
|
for (var i=0; i<diff; i++) { |
|
this.domain_data.shift(); |
|
} |
|
this.minX = new_minX; |
|
this.start_index = new_start_index; |
|
} |
|
return this.domain_data; |
|
} |
|
} |