ESPUI/data/js/graph.js

298 lines
7.7 KiB
JavaScript

function lineGraph(parent, xAccessor, yAccessor) {
// Constant size definitions TODO: this could well be improved and calculated...
const width = 620;
const height = 420;
const gutter = 40;
const pixelsPerTick = 30;
/**
* Creates an object that contatins transform functions that:
* transforms numeric data into coordinate space, linearly
* transforms coordinates into numeric data, linearly
*/
function numericTransformer(dataMin, dataMax, pxMin, pxMax) {
var dataDiff = dataMax - dataMin,
pxDiff = pxMax - pxMin,
dataRatio = pxDiff / dataDiff,
coordRatio = dataDiff / pxDiff;
return {
// transforms a data point to a coordinate point
toCoord: function(data) {
return (data - dataMin) * dataRatio + pxMin;
},
// transforms a coord point to a data point
toData: function(coord) {
return (coord - pxMin) * coordRatio + dataMin;
}
};
}
/**
* Renders an axis.
* orientation = 'x' or 'y'
* transform = a function for transforming px into data for labeling/creating tick marks
*/
function axisRenderer(orientation, transform) {
var axisGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
var axisPath = document.createElementNS(
"http://www.w3.org/2000/svg",
"path"
);
axisGroup.setAttribute("class", orientation + "-axis");
var xMin = gutter;
var xMax = width - gutter;
var yMin = height - gutter;
var yMax = gutter;
if (orientation === "x") {
axisPath.setAttribute(
"d",
"M " + xMin + " " + yMin + " L " + xMax + " " + yMin
);
// generate labels
for (var i = xMin; i <= xMax; i++) {
if ((i - xMin) % (pixelsPerTick*3) === 0 && i !== xMin) {
var text = document.createElementNS(
"http://www.w3.org/2000/svg",
"text"
);
// primitive formatting
text.innerHTML = new Date(Math.floor(transform(i))).toLocaleTimeString();
text.setAttribute("x", i);
text.setAttribute("y", yMin);
// offset the text by 1 em
text.setAttribute("dy", "1em");
axisGroup.appendChild(text);
}
}
} else {
axisPath.setAttribute(
"d",
"M " + xMin + " " + yMin + " L " + xMin + " " + yMax
);
// generate labels
for (var i = yMax; i <= yMin; i++) {
if ((i - yMin) % pixelsPerTick === 0 && i !== yMin) {
var tickGroup = document.createElementNS(
"http://www.w3.org/2000/svg",
"g"
);
var gridLine = document.createElementNS(
"http://www.w3.org/2000/svg",
"path"
);
text = document.createElementNS("http://www.w3.org/2000/svg", "text");
// primitive formatting
text.innerHTML = Math.floor(transform(i));
text.setAttribute("x", xMin);
text.setAttribute("y", i);
// offset the text labels to align with grid line and keeping it to the left of the y-axis
text.setAttribute("dx", "-.5em");
text.setAttribute("dy", ".3em");
gridLine.setAttribute(
"d",
"M " + xMin + " " + i + " L " + xMax + " " + i
);
tickGroup.appendChild(gridLine);
tickGroup.appendChild(text);
axisGroup.appendChild(tickGroup);
}
}
}
axisGroup.appendChild(axisPath);
parent.appendChild(axisGroup);
}
/**
* Renders a line
*/
function lineRenderer(xAccessor, yAccessor, xTransform, yTransform) {
var line = document.createElementNS("http://www.w3.org/2000/svg", "path");
xAccessor.reset();
yAccessor.reset();
if (!xAccessor.hasNext() || !yAccessor.hasNext()) {
return;
}
var pathString =
"M " + xTransform(xAccessor.next()) + " " + yTransform(yAccessor.next());
while (xAccessor.hasNext() && yAccessor.hasNext()) {
pathString +=
" L " +
xTransform(xAccessor.next()) +
" " +
yTransform(yAccessor.next());
}
line.setAttribute("class", "series");
line.setAttribute("d", pathString);
parent.appendChild(line);
}
/**
* Renders data point circles + text labels
*/
function pointRenderer(xAccessor, yAccessor, xTransform, yTransform) {
var pointGroup = document.createElementNS(
"http://www.w3.org/2000/svg",
"g"
);
pointGroup.setAttribute("class", "data-points");
xAccessor.reset();
yAccessor.reset();
if (!xAccessor.hasNext() || !yAccessor.hasNext()) {
return;
}
while (xAccessor.hasNext() && yAccessor.hasNext()) {
var xDataValue = xAccessor.next();
var x = xTransform(xDataValue);
var yDataValue = yAccessor.next();
var y = yTransform(yDataValue);
var circle = document.createElementNS(
"http://www.w3.org/2000/svg",
"circle"
);
circle.setAttribute("cx", x);
circle.setAttribute("cy", y);
circle.setAttribute("r", "4");
var text = document.createElementNS("http://www.w3.org/2000/svg", "text");
// primitive formatting
text.innerHTML = Math.floor(xDataValue) + " / " + Math.floor(yDataValue);
text.setAttribute("x", x);
text.setAttribute("y", y);
text.setAttribute("dx", "1em");
text.setAttribute("dy", "-.7em");
pointGroup.appendChild(circle);
pointGroup.appendChild(text);
}
parent.appendChild(pointGroup);
}
// perform the rendering
xTransform = numericTransformer(
xAccessor.min(),
xAccessor.max(),
0 + gutter,
width - gutter
);
// NOTE: for y... have to reverse coordinate space
yTransform = numericTransformer(
yAccessor.min(),
yAccessor.max(),
height - gutter,
0 + gutter
);
axisRenderer("x", xTransform.toData);
axisRenderer("y", yTransform.toData);
lineRenderer(xAccessor, yAccessor, xTransform.toCoord, yTransform.toCoord);
pointRenderer(xAccessor, yAccessor, xTransform.toCoord, yTransform.toCoord);
}
// Final render function
function renderGraphSvg(dataArray, renderId) {
var figure = document.getElementById(renderId);
while (figure.hasChildNodes()) {
figure.removeChild(figure.lastChild);
}
//console.log(dataArray);
var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("viewBox", "0 0 640 440");
svg.setAttribute("preserveAspectRatio", "xMidYMid meet");
lineGraph(
svg,
// time accessor
(function(data, min, max) {
var i = 0;
return {
hasNext: function() {
return i < data.length;
},
next: function() {
return data[i++].x;
},
reset: function() {
i = 0;
},
min: function() {
return min;
},
max: function() {
return max;
}
};
})(
dataArray,
Math.min.apply(
Math,
dataArray.map(function(o) {
return o.x;
})
),
Math.max.apply(
Math,
dataArray.map(function(o) {
return o.x;
})
)
),
// value accessor
(function(data, min, max) {
var i = 0;
return {
hasNext: function() {
return i < data.length;
},
next: function() {
return data[i++].y;
},
reset: function() {
i = 0;
},
min: function() {
return min;
},
max: function() {
return max;
}
};
})(
dataArray,
Math.min.apply(
Math,
dataArray.map(function(o) {
return o.y;
})
),
Math.max.apply(
Math,
dataArray.map(function(o) {
return o.y;
})
)
)
);
figure.appendChild(svg);
}