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