Files
sinogram/public/ProjectionGraphComponent.js
2025-05-22 21:58:50 +10:00

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",
}),
]),
]),
]
);
},
};