viewof master_gate = html`
<div style="background: rgba(253, 224, 197, 0.3); padding: 15px; border-radius: 6px; margin-bottom: 20px; font-size: 0.85em;">
<p style="margin-top: 0; font-weight: bold; color: #1A1F4D;">Alur Kerja Penapisan Sekuensial (Workstation Real-Time):</p>
<p style="font-size:0.9em; margin-bottom:10px; color:#666;">Langkah 1: Isolasi populasi pada plot utama CD45. Langkah 2: Amati distribusi ukuran fisiknya (FSC) pada plot sekunder.</p>
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
<button id="btn-all" style="padding: 6px 12px; font-weight: bold; border-radius: 4px; border: 1px solid #ccc; cursor: pointer;">Semua Event Tanpa Gate</button>
<button id="btn-mblasts" style="background: #E74C3C; color: white; padding: 6px 12px; font-weight: bold; border-radius: 4px; border: none; cursor: pointer; box-shadow: 0 0 8px rgba(231,76,60,0.5);">🚨 Gate D: Mieloblast (CD45-Dim)</button>
</div>
</div>
`
// Menangani status tombol reaktif
update_master = {
const container = html`<div></div>`;
d3.select("#btn-all").on("click", () => { viewof master_gate.value = "all"; viewof master_gate.dispatchEvent(new Event("input")); });
d3.select("#btn-mblasts").on("click", () => { viewof master_gate.value = "blasts"; viewof master_gate.dispatchEvent(new Event("input")); });
return container;
}
current_master = master_gate || "all"
// 2. GENERASI PROFIL DATA SEL (Disinkronkan di kedua plot)
synchronized_data = {
const randomNormal = d3.randomNormal;
const list = [];
// 1. Limfosit (1.500 sel): CD45-Bright / Low-SSC || Ukuran Kecil (Low-FSC)
const lymphCD45 = randomNormal.source(d3.randomLcg(501))(85, 4);
const lymphSSC = randomNormal.source(d3.randomLcg(502))(15, 3);
const lymphFSC = randomNormal.source(d3.randomLcg(503))(25, 4);
for(let i=0; i<1500; i++) {
list.push({cd45: lymphCD45(), ssc: lymphSSC(), fsc: lymphFSC(), type: "lymphocytes"});
}
// 2. Granulosit (3.500 sel): CD45-Moderate / High-SSC || Ukuran Sedang-Besar (Mid-FSC)
const granCD45 = randomNormal.source(d3.randomLcg(504))(65, 5);
const granSSC = randomNormal.source(d3.randomLcg(505))(75, 6);
const granFSC = randomNormal.source(d3.randomLcg(506))(55, 6);
for(let i=0; i<3500; i++) {
list.push({cd45: granCD45(), ssc: granSSC(), fsc: granFSC(), type: "granulocytes"});
}
// 3. Mieloblast AML (5.000 sel): CD45-Dim / Low-SSC || Sel Fisik Besar (High-FSC)
const blastCD45 = randomNormal.source(d3.randomLcg(507))(35, 4);
const blastSSC = randomNormal.source(d3.randomLcg(508))(22, 4);
const blastFSC = randomNormal.source(d3.randomLcg(509))(75, 5);
for(let i=0; i<5000; i++) {
list.push({cd45: blastCD45(), ssc: blastSSC(), fsc: blastFSC(), type: "myeloblasts"});
}
return list;
}
// 3. KOTAK INTERPRETASI WORKSTATION
workstation_box = {
if (current_master === "blasts") {
return `🚨 <b>Gating Sekuensial Aktif: Mengisolasi Gate D</b><br>
<span style="color: #E74C3C; font-weight: bold;">Populasi Terisolasi: Mieloblast (50.0% dari total sumsum)</span><br>
<span style="font-size: 0.9em; color: #1A1F4D;">Lihat plot sebelah kanan! Dengan menyaring semua kecuali Gate D, operator mengonfirmasi bahwa sel-sel <b>CD45-dim</b> ini secara fisik adalah <b>sel besar (High FSC)</b> namun memiliki <b>granularitas internal yang rendah (Low SSC)</b>. Footprint fisik yang bersih ini sangat cocok dengan morfologi leukemia mieloid akut klasik.</span>`;
}
return `📋 <b>Mode Skrining: Semua Event Sumsum Tulang Ditampilkan Tanpa Gating</b><br>
<span style="color: #1A1F4D; font-weight: bold;">Total Kejadian: 10.000 sel.</span><br>
<span style="font-size: 0.9em; color: #666;">Perhatikan bagaimana awan mieloblast merah relatif sejajar dengan limfosit pada sumbu vertikal plot kanan karena keduanya memiliki granularitas rendah. Klik <b>Gate D: Mieloblast</b> untuk melihat perangkat lunak menyaring mereka secara berurutan!</span>`;
}
html`
<div style="padding: 12px; border-left: 5px solid #E74C3C; background: #FDE0C5; font-size: 0.85em; border-radius: 0 4px 4px 0; margin-bottom: 20px; min-height: 80px; color: #1A1F4D;">
${workstation_box}
</div>
`html`
<div style="display: flex; gap: 20px; flex-wrap: wrap; justify-content: center; background: white; padding: 10px; border-radius: 8px;">
<div style="flex: 1; min-width: 280px; text-align: center;">
<p style="font-size: 0.7em; font-weight: bold; color: #555; margin-bottom: 5px; text-transform: uppercase;">Skrining Utama: Penanda Molekuler</p>
${Plot.plot({
width: 560,
height: 480,
grid: true,
x: { domain: [0, 100], label: "CD45 [Dim ──► Bright]" },
y: { domain: [0, 100], label: "Side Scatter (SSC)" },
marks: [
Plot.dot(synchronized_data, {
x: "cd45", y: "ssc", r: 1.1,
fill: d => d.type === "lymphocytes" ? "#5B3A8C" : (d.type === "granulocytes" ? "#C34A5E" : "#E74C3C"),
fillOpacity: d => current_master === "all" ? 0.4 : (d.type === "myeloblasts" ? 0.8 : 0.02)
}),
current_master === "blasts" ? Plot.rectY([0], {x1: 22, x2: 48, y1: 8, y2: 36, stroke: "#E74C3C", strokeWidth: 2, strokeDasharray: "3 3"}) : null
]
})}
</div>
<div style="flex: 1; min-width: 280px; text-align: center;">
<p style="font-size: 0.7em; font-weight: bold; color: #555; margin-bottom: 5px; text-transform: uppercase;">Skrining Sekunder: Struktur Fisik</p>
${Plot.plot({
width: 560,
height: 480,
grid: true,
x: { domain: [0, 100], label: "Forward Scatter (FSC) [Kecil ──► Besar]" },
y: { domain: [0, 100], label: null },
marks: [
Plot.dot(synchronized_data, {
x: "fsc", y: "ssc", r: 1.1,
fill: d => d.type === "lymphocytes" ? "#5B3A8C" : (d.type === "granulocytes" ? "#C34A5E" : "#E74C3C"),
fillOpacity: d => current_master === "all" ? 0.4 : (d.type === "myeloblasts" ? 0.8 : 0.0)
})
]
})}
</div>
</div>
`






