161 lines
4.8 KiB
JavaScript
161 lines
4.8 KiB
JavaScript
export const ProjectionGraphComponent = {
|
|
view: function (vnode) {
|
|
const {
|
|
data,
|
|
title,
|
|
width = 300,
|
|
height = 150,
|
|
color = "steelblue",
|
|
} = vnode.attrs;
|
|
|
|
if (!data || data.length === 0) {
|
|
return m(
|
|
"div.projection-graph-container",
|
|
{
|
|
style: {
|
|
width: `${width}px`,
|
|
height: `${height}px`,
|
|
border: "1px solid #ccc",
|
|
display: "flex",
|
|
"align-items": "center",
|
|
"justify-content": "center",
|
|
"background-color": "#f9f9f9",
|
|
"margin-bottom": "10px",
|
|
},
|
|
},
|
|
m("p.text-gray-500", title ? `${title}: No data` : "No data available")
|
|
);
|
|
}
|
|
|
|
const padding = { top: 20, right: 20, bottom: 30, left: 40 };
|
|
const chartWidth = width - padding.left - padding.right;
|
|
const chartHeight = height - padding.top - padding.bottom;
|
|
|
|
const maxVal = Math.max(...data, 0); // Ensure maxVal is at least 0
|
|
const minVal = Math.min(...data, 0); // Ensure minVal is at most 0
|
|
|
|
// Adjust scale if all values are 0 or very close to 0
|
|
let yMax = maxVal;
|
|
let yMin = minVal;
|
|
|
|
if (maxVal === 0 && minVal === 0) {
|
|
yMax = 1; // Avoid division by zero if all data points are 0
|
|
yMin = -1;
|
|
} else if (maxVal === minVal) {
|
|
// All values are the same but not zero
|
|
yMax = maxVal + Math.abs(maxVal * 0.1) + 1; // Add some padding
|
|
yMin = minVal - Math.abs(minVal * 0.1) - 1;
|
|
}
|
|
|
|
const xScale = (index) => (index / (data.length - 1)) * chartWidth;
|
|
const yScale = (value) =>
|
|
chartHeight - ((value - yMin) / (yMax - yMin)) * chartHeight;
|
|
|
|
const linePath = data
|
|
.map((d, i) => `${i === 0 ? "M" : "L"}${xScale(i)},${yScale(d)}`)
|
|
.join(" ");
|
|
|
|
// Generate Y-axis ticks (e.g., 3 ticks: min, mid, max)
|
|
const yTicks = [];
|
|
if (yMin !== yMax) {
|
|
yTicks.push({ value: yMin, y: yScale(yMin) });
|
|
yTicks.push({ value: (yMin + yMax) / 2, y: yScale((yMin + yMax) / 2) });
|
|
yTicks.push({ value: yMax, y: yScale(yMax) });
|
|
} else {
|
|
// Handle case where all values are the same
|
|
yTicks.push({ value: yMin - 1, y: yScale(yMin - 1) });
|
|
yTicks.push({ value: yMin, y: yScale(yMin) });
|
|
yTicks.push({ value: yMin + 1, y: yScale(yMin + 1) });
|
|
}
|
|
|
|
return m(
|
|
"div.projection-graph-container",
|
|
{
|
|
style: {
|
|
"margin-bottom": "15px",
|
|
padding: "10px",
|
|
border: "1px solid #e2e8f0",
|
|
"border-radius": "0.375rem",
|
|
"background-color": "#fff",
|
|
},
|
|
},
|
|
[
|
|
m(
|
|
"h4.text-md.font-semibold.text-gray-700.mb-2.text-center",
|
|
title || "Projection Data"
|
|
),
|
|
m("svg", { width: width, height: height }, [
|
|
m("g", { transform: `translate(${padding.left}, ${padding.top})` }, [
|
|
// Y-axis
|
|
m("line", { x1: 0, y1: 0, x2: 0, y2: chartHeight, stroke: "#ccc" }),
|
|
yTicks.map((tick) =>
|
|
m("g", { transform: `translate(0, ${tick.y})` }, [
|
|
m("line", { x1: -5, y1: 0, x2: 0, y2: 0, stroke: "#ccc" }),
|
|
m(
|
|
"text",
|
|
{
|
|
x: -10,
|
|
y: 0,
|
|
"font-size": "10px",
|
|
"text-anchor": "end",
|
|
dy: ".32em",
|
|
},
|
|
parseFloat(tick.value).toFixed(1)
|
|
),
|
|
])
|
|
),
|
|
|
|
// X-axis
|
|
m("line", {
|
|
x1: 0,
|
|
y1: chartHeight,
|
|
x2: chartWidth,
|
|
y2: chartHeight,
|
|
stroke: "#ccc",
|
|
}),
|
|
// X-axis labels (optional, e.g., start, mid, end)
|
|
m(
|
|
"text",
|
|
{
|
|
x: 0,
|
|
y: chartHeight + 15,
|
|
"font-size": "10px",
|
|
"text-anchor": "start",
|
|
},
|
|
"0"
|
|
),
|
|
m(
|
|
"text",
|
|
{
|
|
x: chartWidth / 2,
|
|
y: chartHeight + 15,
|
|
"font-size": "10px",
|
|
"text-anchor": "middle",
|
|
},
|
|
`${Math.floor(data.length / 2)}`
|
|
),
|
|
m(
|
|
"text",
|
|
{
|
|
x: chartWidth,
|
|
y: chartHeight + 15,
|
|
"font-size": "10px",
|
|
"text-anchor": "end",
|
|
},
|
|
`${data.length - 1}`
|
|
),
|
|
|
|
// Data line
|
|
m("path", {
|
|
d: linePath,
|
|
stroke: color,
|
|
"stroke-width": 2,
|
|
fill: "none",
|
|
}),
|
|
]),
|
|
]),
|
|
]
|
|
);
|
|
},
|
|
};
|