Spaces:
Sleeping
Sleeping
File size: 5,415 Bytes
c993983 3be71f7 fb19a97 c993983 3be71f7 c993983 3be71f7 c993983 3be71f7 c993983 3be71f7 c993983 3be71f7 c993983 3be71f7 c993983 fb19a97 c993983 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | /** HPIChart — GCC with diamond markers for selected heat pumps. */
import Plot from 'react-plotly.js';
import { useAnalysisStore } from '../../store/analysisStore';
import { useUIStore } from '../../store/uiStore';
import ChartHelpButton from '../ui/ChartHelpButton';
const HP_COLORS = [
'#00CC96', '#AB63FA', '#FFA15A', '#19D3F3',
'#FF6692', '#B6E880', '#FF97FF', '#FECB52',
];
export default function HPIChart() {
const hpiResult = useAnalysisStore((s) => s.hpiResult);
const pinchResult = useAnalysisStore((s) => s.pinchResult);
const selectedHPTypes = useAnalysisStore((s) => s.selectedHPTypes);
const theme = useUIStore((s) => s.theme);
const isDark = theme === 'dark';
if (!hpiResult || !pinchResult) return null;
const gccH = hpiResult.gcc_data.H;
const gccT = hpiResult.gcc_data.T;
// GCC segment traces
const traces: any[] = [];
const heatCascade = pinchResult.heat_cascade;
for (let i = 0; i < gccH.length - 1; i++) {
let color = 'gray';
if (i < heatCascade.length) {
const dh = heatCascade[i].deltaH ?? 0;
if (dh > 0) color = 'red';
else if (dh < 0) color = 'blue';
}
traces.push({
x: [gccH[i], gccH[i + 1]],
y: [gccT[i], gccT[i + 1]],
mode: 'lines+markers' as const,
line: { color, width: 2 },
marker: { size: 5 },
showlegend: false,
hovertemplate: `T: %{y:.1f}°C<br>H: %{x:.1f} kW<extra></extra>`,
});
}
// Diamond markers for selected HP types
const feasible = hpiResult.integrations.filter((r) => r.feasible);
const allHPNames = feasible.map((r) => r.hp_type);
const displayed = selectedHPTypes.length > 0
? feasible.filter((r) => selectedHPTypes.includes(r.hp_type))
: feasible.slice(0, 1); // default: first
displayed.forEach((hp) => {
const globalIdx = allHPNames.indexOf(hp.hp_type);
const color = HP_COLORS[globalIdx % HP_COLORS.length];
const hpName = hp.hp_type;
if (hp.source_points.H.length > 0) {
traces.push({
x: hp.source_points.H,
y: hp.source_points.T,
mode: 'markers' as const,
marker: {
size: 12,
color,
symbol: 'diamond',
line: { width: 2, color },
},
name: `${hpName} - Source`,
legendgroup: hpName,
hovertemplate: `<b>${hpName} - Source</b><br>T: %{y:.1f}°C<br>Q: %{x:.1f} kW<br>COP: ${hp.COP?.toFixed(2)}<extra></extra>`,
});
}
if (hp.sink_points.H.length > 0) {
traces.push({
x: hp.sink_points.H,
y: hp.sink_points.T,
mode: 'markers' as const,
marker: {
size: 12,
color,
symbol: 'diamond-open',
line: { width: 2, color },
},
name: `${hpName} - Sink`,
legendgroup: hpName,
hovertemplate: `<b>${hpName} - Sink</b><br>T: %{y:.1f}°C<br>Q: %{x:.1f} kW<br>COP: ${hp.COP?.toFixed(2)}<extra></extra>`,
});
}
});
const layout: any = {
title: { text: 'Heat Pump Integration - Grand Composite Curve', font: { size: 14, color: isDark ? '#F8FAFC' : '#1A1C1E' } },
xaxis: {
title: 'Net ΔH (kW)',
gridcolor: isDark ? '#334155' : '#E2E8F0',
tickfont: { color: isDark ? '#94A3B8' : '#5F6368' },
titlefont: { color: isDark ? '#F8FAFC' : '#1A1C1E' }
},
yaxis: {
title: 'Shifted Temperature (°C)',
rangemode: 'tozero',
gridcolor: isDark ? '#334155' : '#E2E8F0',
tickfont: { color: isDark ? '#94A3B8' : '#5F6368' },
titlefont: { color: isDark ? '#F8FAFC' : '#1A1C1E' }
},
height: 400,
paper_bgcolor: 'rgba(0,0,0,0)',
plot_bgcolor: 'rgba(0,0,0,0)',
margin: { l: 220, r: 20, t: 40, b: 50 },
hovermode: 'closest' as const,
showlegend: true,
legend: {
x: -0.15, y: 1.0,
xanchor: 'right', yanchor: 'top',
font: { size: 10, color: isDark ? '#F8FAFC' : '#1A1C1E' },
itemsizing: 'constant'
},
shapes: [
{
type: 'line',
x0: 0, x1: 1, xref: 'paper',
y0: pinchResult.pinch_temperature,
y1: pinchResult.pinch_temperature,
line: { dash: 'dash', color: 'gray', width: 1 },
},
{
type: 'line',
x0: 0, x1: 0,
y0: 0, y1: 1, yref: 'paper',
line: { color: isDark ? '#94A3B8' : 'black', width: 1 },
opacity: 0.3,
},
],
annotations: [
{
x: 1, xref: 'paper', xanchor: 'right',
y: pinchResult.pinch_temperature,
text: `Pinch: ${pinchResult.pinch_temperature.toFixed(1)}°C`,
showarrow: false,
font: { size: 11, color: isDark ? '#60A5FA' : 'gray' },
},
],
};
return (
<div className="pa-chart" style={{ position: 'relative' }}>
<ChartHelpButton
title="Heat Pump Integration"
description={
<>
<p>Visualizes how a heat pump bridges the temperature gap to lift heat from <strong>below the pinch</strong> (source) to <strong>above the pinch</strong> (sink).</p>
<p>The solid diamonds represent the heat pump source, and hollow diamonds represent the sink.</p>
<p>Heat pumps reduce both external heating and cooling demands simultaneously.</p>
</>
}
/>
<Plot
data={traces}
layout={layout}
config={{ responsive: true }}
style={{ width: '100%' }}
/>
</div>
);
}
|