xreal-webxr/tools/sparkline.js

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;