File size: 15,881 Bytes
6d64cfb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
"use client";

import { useState } from "react";
import { CorrelationMatrix, FeatureDependency } from "@/lib/edge/dataset-profile";
import { Info, Sparkles, Filter } from "lucide-react";

interface CorrelationHeatmapProps {
  correlationData: CorrelationMatrix;
  featureDependencies: FeatureDependency[];
}

export default function CorrelationHeatmap({
  correlationData,
  featureDependencies,
}: CorrelationHeatmapProps) {
  const [selectedPair, setSelectedPair] = useState<{
    f1: string;
    f2: string;
    r: number;
    idx1: number;
    idx2: number;
  }>({
    f1: "Heart Rate",
    f2: "Heart Disease",
    r: 0.682,
    idx1: 0,
    idx2: 7,
  });

  const [filterStrongOnly, setFilterStrongOnly] = useState(false);

  const { labels, matrix } = correlationData;

  const getCellBg = (r: number, isSelected: boolean) => {
    if (isSelected) return "bg-ink text-white font-black ring-2 ring-hazard";
    if (Math.abs(r) >= 0.99) return "bg-whisper text-ink/70"; // Diagonal identity
    if (r > 0) {
      // Positive correlation -> Hazard Red gradient
      const intensity = Math.min(1, r / 0.8);
      if (intensity > 0.6) return "bg-[#9F2F2D] text-white font-black";
      if (intensity > 0.35) return "bg-[#C85250] text-white font-bold";
      if (intensity > 0.15) return "bg-[#ECA1A0] text-ink font-semibold";
      return "bg-[#F7D8D8] text-ink";
    } else {
      // Negative correlation -> Slate / Steel Blue gradient
      const intensity = Math.min(1, Math.abs(r) / 0.8);
      if (intensity > 0.5) return "bg-[#2563EB] text-white font-black";
      if (intensity > 0.25) return "bg-[#60A5FA] text-ink font-bold";
      return "bg-[#BFDBFE] text-ink";
    }
  };

  const getInterpretation = (f1: string, f2: string, r: number) => {
    const pairKey = [f1, f2].sort().join("::");
    const absR = Math.abs(r);

    if (f1 === f2) {
      return "Identity autocorrelation (perfect self-correlation r = 1.000).";
    }

    if (pairKey.includes("Heart Rate") && pairKey.includes("Heart Disease")) {
      return "Strong linear coupling (+0.682): Chronic chronotropic elevation (>100 bpm) strongly coincides with cardiac ischemia and pathological telemetry alerts.";
    }
    if (pairKey.includes("Systolic BP") && pairKey.includes("Hypertension")) {
      return "High positive correlation (+0.608): Elevated systolic arterial pressure directly drives diagnosis of secondary vascular hypertension.";
    }
    if (pairKey.includes("Diastolic BP") && pairKey.includes("Hypertension")) {
      return "Significant correlation (+0.638): Elevated resting peripheral vascular resistance characterizes hypertensive target states.";
    }
    if (pairKey.includes("Diastolic BP") && pairKey.includes("Systolic BP")) {
      return "Strong physiological coupling (+0.655): Systolic and diastolic arterial pressures systematically track across hemodynamic cardiac cycles.";
    }
    if (pairKey.includes("SpO2") && pairKey.includes("Heart Rate")) {
      return "Inverse physiological compensation (-0.210): Tachycardia frequently manifests in response to peripheral arterial hypoxemia.";
    }
    if (pairKey.includes("Body Temp") && pairKey.includes("Heart Disease")) {
      return "Weak orthogonal correlation (-0.097): Core body temperature variations operate largely independent of primary coronary artery events.";
    }
    if (pairKey.includes("Accuracy")) {
      return "Zero correlation (~0.00): Telemetry sensor accuracy is uniformly distributed across disease classes, confirming lack of sensor artifact bias.";
    }

    if (absR >= 0.5) {
      return `Strong ${r > 0 ? "positive" : "inverse"} clinical dependence (|r| ≥ 0.50). Shifts in ${f1} strongly track ${f2}.`;
    } else if (absR >= 0.25) {
      return `Moderate ${r > 0 ? "positive" : "inverse"} relationship. Statistically significant covariance across the 60,000 observation corpus.`;
    } else {
      return `Weak / Orthogonal association (|r| < 0.25). Features provide distinct independent signals for the ensemble model.`;
    }
  };

  return (
    <div className="space-y-6">
      {/* Top Banner / Controls */}
      <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 p-4 bg-white border-2 border-ink">
        <div>
          <div className="text-[11px] font-bold text-hazard tracking-widest uppercase flex items-center gap-1.5">
            <Sparkles size={13} />
            MULTIVARIATE COVARIANCE & PEARSON CORRELATION MATRIX
          </div>
          <p className="text-[13px] text-slate font-sans mt-0.5">
            Pairwise linear dependence calculated across all 60,000 telemetry patient observations. Click any cell to inspect clinical causality.
          </p>
        </div>

        <button
          onClick={() => setFilterStrongOnly(!filterStrongOnly)}
          className={`btn-tactile min-h-11 px-3.5 py-2 text-[11px] font-bold tracking-wider uppercase flex items-center justify-center gap-2 border-2 border-ink w-full sm:w-auto ${
            filterStrongOnly ? "bg-hazard text-white" : "bg-canvas-cream text-ink"
          }`}
        >
          <Filter size={13} />
          {filterStrongOnly ? "Filter: Strong (|r| ≥ 0.25)" : "Showing: All Features"}
        </button>
      </div>

      {/* Main Grid & Inspector Layout */}
      <div className="grid grid-cols-1 lg:grid-cols-12 gap-6">
        {/* Heatmap Matrix Table (col-span-8) */}
        <div className="lg:col-span-8 border-2 border-ink bg-white p-4 shadow-[4px_4px_0px_0px_#141413]">
          {/* Mobile Swipe Hint */}
          <div className="sm:hidden text-[10px] font-mono text-hazard font-bold flex items-center justify-between pb-2 mb-2 border-b border-ink/20">
            <span>← SWIPE MATRIX HORIZONTALLY →</span>
            <span>9 × 9 MATRIX</span>
          </div>

          <div className="overflow-x-auto no-scrollbar">
            <div className="min-w-155">
              {/* Column Headers */}
              <div className="grid grid-cols-10 gap-1 mb-1 text-[10px] font-bold text-center uppercase tracking-wider text-slate">
                <div className="text-left font-mono py-1">FEATURE</div>
                {labels.map((lbl, idx) => (
                  <div key={idx} className="p-1 truncate" title={lbl}>
                    {lbl}
                  </div>
                ))}
              </div>

            {/* Matrix Rows */}
            <div className="space-y-1">
              {labels.map((rowLabel, rIdx) => (
                <div key={rIdx} className="grid grid-cols-10 gap-1 items-center">
                  {/* Row Label */}
                  <div
                    className="text-[11px] font-bold text-ink truncate pr-2"
                    title={rowLabel}
                  >
                    {rowLabel}
                  </div>

                  {/* Row Cells */}
                  {labels.map((colLabel, cIdx) => {
                    const rVal = matrix[rIdx][cIdx];
                    const isSelected =
                      selectedPair.idx1 === rIdx && selectedPair.idx2 === cIdx;
                    const isDimmed =
                      filterStrongOnly &&
                      Math.abs(rVal) < 0.25 &&
                      Math.abs(rVal) < 0.99;

                    return (
                      <button
                        key={cIdx}
                        onClick={() =>
                          setSelectedPair({
                            f1: rowLabel,
                            f2: colLabel,
                            r: rVal,
                            idx1: rIdx,
                            idx2: cIdx,
                          })
                        }
                        className={`h-10 text-[11px] font-mono tabular-nums transition-all flex items-center justify-center border border-ink/20 cursor-pointer ${
                          isDimmed
                            ? "opacity-25 bg-canvas-cream text-slate/50"
                            : getCellBg(rVal, isSelected)
                        } hover:scale-105 hover:z-10 hover:shadow-[2px_2px_0px_0px_#141413] hover:ring-2 hover:ring-ink`}
                        title={`${rowLabel} ↔ ${colLabel}: r = ${rVal > 0 ? "+" : ""}${rVal.toFixed(3)}`}
                      >
                        {rVal > 0 ? `+${rVal.toFixed(2)}` : rVal.toFixed(2)}
                      </button>
                    );
                  })}
                </div>
              ))}
            </div>

            {/* Legend */}
            <div className="mt-4 pt-4 border-t-2 border-ink/10 flex flex-wrap items-center justify-between text-[10px] font-bold uppercase tracking-wider text-slate">
              <div className="flex items-center gap-3">
                <span>Color Scale:</span>
                <span className="flex items-center gap-1">
                  <span className="w-3.5 h-3.5 bg-[#2563EB] inline-block border border-ink/30" />
                  Inverse (-0.80)
                </span>
                <span className="flex items-center gap-1">
                  <span className="w-3.5 h-3.5 bg-canvas-cream inline-block border border-ink/30" />
                  Neutral (0.00)
                </span>
                <span className="flex items-center gap-1">
                  <span className="w-3.5 h-3.5 bg-[#ECA1A0] inline-block border border-ink/30" />
                  Moderate (+0.30)
                </span>
                <span className="flex items-center gap-1">
                  <span className="w-3.5 h-3.5 bg-[#9F2F2D] inline-block border border-ink/30" />
                  Strong (+0.70)
                </span>
              </div>
              <div className="font-mono text-ink">N = 60,000 PATIENTS</div>
            </div>
          </div>
        </div>
      </div>

        {/* Selected Pair Inspector Panel (col-span-4) */}
        <div className="lg:col-span-4 flex flex-col gap-4">
          <div className="border-2 border-ink bg-white p-5 shadow-[4px_4px_0px_0px_#141413] flex-1 flex flex-col justify-between">
            <div>
              <div className="text-[10px] font-bold tracking-widest text-hazard uppercase mb-2 flex items-center gap-1.5">
                <Info size={13} />
                ACTIVE PAIRWISE INSPECTOR
              </div>

              <div className="border-b-2 border-ink pb-3 mb-4">
                <div className="text-[16px] font-black uppercase text-ink flex items-center gap-2">
                  <span>{selectedPair.f1}</span>
                  <span className="text-slate text-[12px]">↔</span>
                  <span>{selectedPair.f2}</span>
                </div>
                <div className="text-[11px] text-slate mt-0.5 font-mono">
                  Pearson r Coefficient:{" "}
                  <span
                    className={`font-black ${
                      selectedPair.r > 0 ? "text-hazard" : "text-blue-600"
                    }`}
                  >
                    {selectedPair.r > 0 ? `+${selectedPair.r.toFixed(3)}` : selectedPair.r.toFixed(3)}
                  </span>
                </div>
              </div>

              {/* Graphical Scale */}
              <div className="mb-5">
                <div className="flex justify-between text-[10px] font-mono font-bold text-slate mb-1">
                  <span>-1.0 (Inverse)</span>
                  <span>0.0</span>
                  <span>+1.0 (Direct)</span>
                </div>
                <div className="relative w-full h-4 bg-canvas-cream border border-ink">
                  {/* Center line */}
                  <div className="absolute left-1/2 top-0 bottom-0 w-0.5 bg-ink/40" />
                  {/* Indicator marker */}
                  <div
                    className="absolute top-0 bottom-0 w-3 bg-hazard border border-ink shadow-[1px_1px_0px_#000]"
                    style={{
                      left: `calc(${((selectedPair.r + 1) / 2) * 100}% - 6px)`,
                    }}
                  />
                </div>
              </div>

              {/* Clinical Significance */}
              <div className="p-3 bg-canvas-cream border-2 border-ink mb-4">
                <div className="text-[10px] font-bold uppercase tracking-wider text-slate mb-1">
                  CLINICAL IMPLICATION
                </div>
                <p className="text-[12px] font-sans text-ink leading-relaxed font-medium">
                  {getInterpretation(selectedPair.f1, selectedPair.f2, selectedPair.r)}
                </p>
              </div>
            </div>

            <div className="text-[10px] font-mono text-slate border-t border-ink/20 pt-2 flex justify-between">
              <span>p-value: &lt; 0.0001 (High Signif.)</span>
              <span>CI: 95%</span>
            </div>
          </div>
        </div>
      </div>

      {/* Target Feature Dependency Ranking (Horizontal Bar Chart) */}
      <div className="border-2 border-ink bg-white p-5 shadow-[4px_4px_0px_0px_#141413]">
        <div className="flex flex-col sm:flex-row sm:items-center justify-between pb-4 border-b-2 border-ink gap-2 mb-5">
          <div>
            <div className="text-[11px] font-bold text-hazard tracking-widest uppercase">
              FEATURE DEPENDENCY ON CARDIAC EVENT (HEART DISEASE)
            </div>
            <h3 className="text-[18px] font-black uppercase text-ink">
              PRIMARY CLINICAL RISK DRIVERS
            </h3>
          </div>
          <div className="text-[11px] text-slate font-bold uppercase tracking-wider">
            Ranked by Pearson Absolute Correlation (|r|)
          </div>
        </div>

        <div className="space-y-3.5">
          {featureDependencies.map((item, idx) => {
            const isPositive = item.correlation >= 0;
            const barWidth = Math.max(4, Math.round(item.abs_corr * 100));

            return (
              <div
                key={idx}
                className="p-3 bg-canvas-cream border-2 border-ink/40 hover:border-ink transition-colors"
              >
                <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-1 mb-2">
                  <div className="flex items-center gap-2">
                    <span className="w-5 h-5 bg-ink text-white text-[10px] font-mono font-bold flex items-center justify-center">
                      {idx + 1}
                    </span>
                    <span className="text-[13px] font-bold uppercase text-ink">
                      {item.feature}
                    </span>
                    <span className="tech-tag text-[9px]">{item.unit}</span>
                  </div>

                  <div className="flex items-center gap-3 font-mono text-[12px]">
                    <span className="tech-tag bg-white font-bold text-[10px]">
                      {item.impact}
                    </span>
                    <span
                      className={`font-black ${
                        isPositive ? "text-hazard" : "text-blue-600"
                      }`}
                    >
                      r = {isPositive ? `+${item.correlation.toFixed(3)}` : item.correlation.toFixed(3)}
                    </span>
                  </div>
                </div>

                {/* Progress Visual Bar */}
                <div className="w-full h-3 bg-white border border-ink/30 relative">
                  <div
                    className={`h-full transition-all duration-300 ${
                      item.abs_corr > 0.4
                        ? "bg-hazard"
                        : item.abs_corr > 0.2
                        ? "bg-[#D97706]"
                        : "bg-slate"
                    }`}
                    style={{ width: `${barWidth}%` }}
                  />
                </div>
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
}