225 lines
6.0 KiB
JavaScript
225 lines
6.0 KiB
JavaScript
// https://github.com/fnando/sparkline/blob/main/src/sparkline.js
|
|
function getY(max, height, diff, value) {
|
|
if (max===0) return height;
|
|
const pre_parse = (height - (value * height / max) + diff);
|
|
// console.log(pre_parse);
|
|
return parseFloat((pre_parse).toFixed(2));
|
|
}
|
|
|
|
function removeChildren(svg) {
|
|
[...svg.querySelectorAll("*")].forEach(element => svg.removeChild(element));
|
|
}
|
|
|
|
function defaultFetch(entry) {
|
|
return entry.value;
|
|
}
|
|
|
|
function buildElement(tag, attrs) {
|
|
const element = document.createElementNS("http://www.w3.org/2000/svg", tag);
|
|
|
|
for (let name in attrs) {
|
|
element.setAttribute(name, attrs[name]);
|
|
}
|
|
|
|
return element;
|
|
}
|
|
|
|
export function sparkline(svg, entries, options) {
|
|
removeChildren(svg);
|
|
|
|
if (entries.length <= 1) {
|
|
return;
|
|
}
|
|
|
|
options = options || {};
|
|
|
|
if (typeof(entries[0]) === "number") {
|
|
entries = entries.map(entry => {
|
|
return {value: entry};
|
|
});
|
|
}
|
|
|
|
// This function will be called whenever the mouse moves
|
|
// over the SVG. You can use it to render something like a
|
|
// tooltip.
|
|
const onmousemove = options.onmousemove;
|
|
|
|
// This function will be called whenever the mouse leaves
|
|
// the SVG area. You can use it to hide the tooltip.
|
|
const onmouseout = options.onmouseout;
|
|
|
|
// Should we run in interactive mode? If yes, this will handle the
|
|
// cursor and spot position when moving the mouse.
|
|
const interactive = ("interactive" in options) ? options.interactive : !!onmousemove;
|
|
|
|
// Define how big should be the spot area.
|
|
const spotRadius = options.spotRadius || 2;
|
|
const spotDiameter = spotRadius * 2;
|
|
|
|
// Define how wide should be the cursor area.
|
|
const cursorWidth = options.cursorWidth || 2;
|
|
|
|
// Get the stroke width; this is used to compute the
|
|
// rendering offset.
|
|
const strokeWidth = parseFloat(svg.attributes["stroke-width"].value);
|
|
|
|
// By default, data must be formatted as an array of numbers or
|
|
// an array of objects with the value key (like `[{value: 1}]`).
|
|
// You can set a custom function to return data for a different
|
|
// data structure.
|
|
const fetch = options.fetch || defaultFetch;
|
|
|
|
// Retrieve only values, easing the find for the maximum value.
|
|
const values = entries.map(entry => fetch(entry));
|
|
|
|
// The rendering width will account for the spot size.
|
|
const width = parseFloat(svg.attributes.width.value) - spotDiameter * 2;
|
|
|
|
// Get the SVG element's full height.
|
|
// This is used
|
|
const fullHeight = parseFloat(svg.attributes.height.value);
|
|
|
|
// The rendering height accounts for stroke width and spot size.
|
|
const height = fullHeight - (strokeWidth * 2) - spotDiameter;
|
|
|
|
// The maximum value. This is used to calculate the Y coord of
|
|
// each sparkline datapoint.
|
|
const max = Math.max(...values);
|
|
|
|
// Some arbitrary value to remove the cursor and spot out of
|
|
// the viewing canvas.
|
|
const offscreen = -1000;
|
|
|
|
// Cache the last item index.
|
|
const lastItemIndex = values.length - 1;
|
|
|
|
// Calculate the X coord base step.
|
|
const offset = width / lastItemIndex;
|
|
|
|
// Hold all datapoints, which is whatever we got as the entry plus
|
|
// x/y coords and the index.
|
|
const datapoints = [];
|
|
|
|
// Hold the line coordinates.
|
|
const pathY = getY(max, height, strokeWidth + spotRadius, values[0]);
|
|
let pathCoords = `M${spotDiameter} ${pathY}`;
|
|
|
|
values.forEach((value, index) => {
|
|
const x = index * offset + spotDiameter;
|
|
const y = getY(max, height, strokeWidth + spotRadius, value);
|
|
|
|
datapoints.push(Object.assign({}, entries[index], {
|
|
index: index,
|
|
x: x,
|
|
y: y
|
|
}));
|
|
|
|
pathCoords += ` L ${x} ${y}`;
|
|
});
|
|
|
|
if(pathCoords.includes("NaN")) {
|
|
debugger;
|
|
}
|
|
|
|
const path = buildElement("path", {
|
|
class: "sparkline--line",
|
|
d: pathCoords,
|
|
fill: "none"
|
|
});
|
|
|
|
let fillCoords = `${pathCoords} V ${fullHeight} L ${spotDiameter} ${fullHeight} Z`;
|
|
|
|
if(fillCoords.includes("NaN")) {
|
|
debugger;
|
|
}
|
|
|
|
const fill = buildElement("path", {
|
|
class: "sparkline--fill",
|
|
d: fillCoords,
|
|
stroke: "none"
|
|
});
|
|
|
|
svg.appendChild(fill);
|
|
svg.appendChild(path);
|
|
|
|
if (!interactive) {
|
|
return;
|
|
}
|
|
|
|
// const cursor = buildElement("line", {
|
|
// class: "sparkline--cursor",
|
|
// x1: offscreen,
|
|
// x2: offscreen,
|
|
// y1: 0,
|
|
// y2: fullHeight,
|
|
// "stroke-width": cursorWidth
|
|
// });
|
|
|
|
// const spot = buildElement("circle", {
|
|
// class: "sparkline--spot",
|
|
// cx: offscreen,
|
|
// cy: offscreen,
|
|
// r: spotRadius
|
|
// });
|
|
|
|
// svg.appendChild(cursor);
|
|
// svg.appendChild(spot);
|
|
|
|
// const interactionLayer = buildElement("rect", {
|
|
// width: svg.attributes.width.value,
|
|
// height: svg.attributes.height.value,
|
|
// style: "fill: transparent; stroke: transparent",
|
|
// class: "sparkline--interaction-layer",
|
|
// });
|
|
// svg.appendChild(interactionLayer);
|
|
|
|
// interactionLayer.addEventListener("mouseout", event => {
|
|
// cursor.setAttribute("x1", offscreen);
|
|
// cursor.setAttribute("x2", offscreen);
|
|
|
|
// spot.setAttribute("cx", offscreen);
|
|
|
|
// if (onmouseout) {
|
|
// onmouseout(event);
|
|
// }
|
|
// });
|
|
|
|
// interactionLayer.addEventListener("mousemove", event => {
|
|
// const mouseX = event.offsetX;
|
|
|
|
// let nextDataPoint = datapoints.find(entry => {
|
|
// return entry.x >= mouseX;
|
|
// });
|
|
|
|
// if (!nextDataPoint) {
|
|
// nextDataPoint = datapoints[lastItemIndex];
|
|
// }
|
|
|
|
// let previousDataPoint = datapoints[datapoints.indexOf(nextDataPoint) - 1];
|
|
// let currentDataPoint;
|
|
// let halfway;
|
|
|
|
// if (previousDataPoint) {
|
|
// halfway = previousDataPoint.x + ((nextDataPoint.x - previousDataPoint.x) / 2);
|
|
// currentDataPoint = mouseX >= halfway ? nextDataPoint : previousDataPoint;
|
|
// } else {
|
|
// currentDataPoint = nextDataPoint;
|
|
// }
|
|
|
|
// const x = currentDataPoint.x;
|
|
// const y = currentDataPoint.y;
|
|
|
|
// spot.setAttribute("cx", x);
|
|
// spot.setAttribute("cy", y);
|
|
|
|
// cursor.setAttribute("x1", x);
|
|
// cursor.setAttribute("x2", x);
|
|
|
|
// if (onmousemove) {
|
|
// onmousemove(event, currentDataPoint);
|
|
// }
|
|
// });
|
|
}
|
|
|
|
export default sparkline;
|