Spaces:
Running
Running
Deploy sha:591a9f67fc1c
Browse files- _version.json +2 -2
- analysis/mimic_iv_ecg/report.html +202 -140
_version.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
{
|
| 2 |
"tag": "",
|
| 3 |
-
"sha": "
|
| 4 |
-
"date": "2026-03-
|
| 5 |
"deploy": "space"
|
| 6 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"tag": "",
|
| 3 |
+
"sha": "591a9f67fc1c",
|
| 4 |
+
"date": "2026-03-31T09:21:23Z",
|
| 5 |
"deploy": "space"
|
| 6 |
}
|
analysis/mimic_iv_ecg/report.html
CHANGED
|
@@ -129,9 +129,10 @@
|
|
| 129 |
</div>
|
| 130 |
<div class="legend" style="margin-top:14px;">
|
| 131 |
<div class="legend-item"><span class="legend-swatch" style="background:#1a2233;border-radius:50%;"></span> Root / Directory</div>
|
| 132 |
-
<div class="legend-item"><span class="legend-swatch" style="background:#4f8ef7;"></span> CSV Metadata File</div>
|
| 133 |
<div class="legend-item"><span class="legend-swatch" style="background:#10b981;"></span> WFDB Waveform File (.hea / .dat)</div>
|
| 134 |
<div class="legend-item"><span class="legend-swatch" style="background:#94a3b8;"></span> Other File</div>
|
|
|
|
| 135 |
</div>
|
| 136 |
</div>
|
| 137 |
</section>
|
|
@@ -5788,17 +5789,82 @@ sections.forEach(s => observer.observe(s));
|
|
| 5788 |
|
| 5789 |
// ββ Dataset Structure Tree ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 5790 |
(function() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5791 |
const treeData = {
|
| 5792 |
name: "mimic-iv-ecg-v1.0/", type: "root", desc: "Root dataset directory (800,035 studies)",
|
| 5793 |
children: [
|
| 5794 |
-
{
|
| 5795 |
-
|
| 5796 |
-
|
| 5797 |
-
|
| 5798 |
-
|
| 5799 |
-
|
| 5800 |
-
{
|
| 5801 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5802 |
{
|
| 5803 |
name: "files/", type: "dir", desc: "Waveform data organised in a 4-level hierarchy",
|
| 5804 |
_collapsed: false,
|
|
@@ -5806,207 +5872,203 @@ sections.forEach(s => observer.observe(s));
|
|
| 5806 |
name: "pXXXX/", type: "dir", desc: "1,000 patient-group folders (p1000βp1999)",
|
| 5807 |
_collapsed: false,
|
| 5808 |
children: [{
|
| 5809 |
-
name: "pNNNNNNNN/", type: "dir",
|
|
|
|
| 5810 |
_collapsed: false,
|
| 5811 |
children: [{
|
| 5812 |
-
name: "sNNNNNNNN/", type: "dir",
|
|
|
|
| 5813 |
_collapsed: false,
|
| 5814 |
children: [
|
| 5815 |
{ name: "NNNNNNNN.hea", type: "hea", desc: "WFDB header β 12 leads, 500 Hz, 10 s (plain text, ~600 B)" },
|
| 5816 |
-
{ name: "NNNNNNNN.dat", type: "dat", desc: "WFDB signal β 16-bit signed binary, 5,000 samples/lead (~120 KB)" }
|
| 5817 |
-
]
|
| 5818 |
-
}]
|
| 5819 |
-
}]
|
| 5820 |
-
}]
|
| 5821 |
-
}
|
| 5822 |
-
]
|
| 5823 |
};
|
| 5824 |
|
| 5825 |
-
|
| 5826 |
-
|
| 5827 |
-
|
| 5828 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5829 |
return "#94a3b8";
|
| 5830 |
};
|
| 5831 |
-
const nodeShape = n => (n.data.type === "root" || n.data.type === "dir") ? "diamond" : "rect";
|
| 5832 |
|
| 5833 |
-
|
| 5834 |
-
const
|
| 5835 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5836 |
|
| 5837 |
const svg = d3.select("#ds-tree-svg")
|
| 5838 |
.attr("width", baseWidth + margin.left + margin.right)
|
| 5839 |
-
.attr("height", svgHeight
|
| 5840 |
.style("font", "13px 'Segoe UI', system-ui, sans-serif");
|
| 5841 |
|
| 5842 |
const g = svg.append("g").attr("transform", `translate(${margin.left},${margin.top})`);
|
| 5843 |
-
|
| 5844 |
-
// link path generator
|
| 5845 |
const linkPath = d3.linkHorizontal().x(d => d.y).y(d => d.x);
|
| 5846 |
|
| 5847 |
-
//
|
| 5848 |
function buildHierarchy(data) {
|
| 5849 |
-
|
| 5850 |
-
root.x0 = 0; root.y0 = 0;
|
| 5851 |
-
return root;
|
| 5852 |
}
|
| 5853 |
|
| 5854 |
-
//
|
| 5855 |
-
function toggle(d) {
|
| 5856 |
-
if (!d.data.children) return;
|
| 5857 |
-
if (d.children) {
|
| 5858 |
-
d.data._collapsed = true;
|
| 5859 |
-
} else {
|
| 5860 |
-
d.data._collapsed = false;
|
| 5861 |
-
}
|
| 5862 |
-
}
|
| 5863 |
-
|
| 5864 |
-
function setAllCollapsed(node, collapsed) {
|
| 5865 |
-
if (node.children) node.data.children = node.children.map(c => c.data);
|
| 5866 |
-
if (collapsed) node.data._collapsed = true;
|
| 5867 |
-
else node.data._collapsed = false;
|
| 5868 |
-
(node.children || node._children || []).forEach(c => setAllCollapsed(c, collapsed));
|
| 5869 |
-
}
|
| 5870 |
-
|
| 5871 |
-
let root = buildHierarchy(treeData);
|
| 5872 |
-
|
| 5873 |
function update() {
|
| 5874 |
-
root = buildHierarchy(treeData);
|
| 5875 |
|
| 5876 |
-
|
|
|
|
| 5877 |
treeLayout(root);
|
| 5878 |
|
| 5879 |
-
// centre vertically
|
| 5880 |
const nodes = root.descendants();
|
| 5881 |
-
const minX
|
| 5882 |
-
const maxX
|
| 5883 |
-
svgHeight = Math.max(
|
| 5884 |
svg.attr("height", svgHeight);
|
| 5885 |
const offsetY = margin.top - minX;
|
| 5886 |
|
| 5887 |
g.selectAll("*").remove();
|
| 5888 |
|
| 5889 |
-
// Links
|
| 5890 |
-
g.selectAll(".
|
| 5891 |
.data(root.links())
|
| 5892 |
.join("path")
|
| 5893 |
-
.attr("
|
| 5894 |
-
.attr("
|
| 5895 |
-
.attr("stroke",
|
| 5896 |
-
.attr("stroke-
|
| 5897 |
.attr("d", d => linkPath({
|
| 5898 |
-
source: {x: d.source.x + offsetY, y: d.source.y},
|
| 5899 |
-
target: {x: d.target.x + offsetY, y: d.target.y}
|
| 5900 |
}));
|
| 5901 |
|
| 5902 |
-
//
|
| 5903 |
-
const node = g.selectAll(".
|
| 5904 |
.data(nodes)
|
| 5905 |
.join("g")
|
| 5906 |
-
.attr("class","node")
|
| 5907 |
.attr("transform", d => `translate(${d.y},${d.x + offsetY})`)
|
| 5908 |
.style("cursor", d => d.data.children ? "pointer" : "default")
|
| 5909 |
.on("click", (event, d) => {
|
| 5910 |
if (!d.data.children) return;
|
| 5911 |
-
|
| 5912 |
update();
|
| 5913 |
})
|
| 5914 |
.on("mouseenter", (event, d) => {
|
| 5915 |
-
const
|
| 5916 |
-
|
| 5917 |
-
d.data.type === "hea" ? "π" :
|
| 5918 |
-
d.data.type === "dat" ? "πΎ" : "π";
|
| 5919 |
-
showTip(`<b>${icon} ${d.data.name}</b><br>${d.data.desc}`, event);
|
| 5920 |
})
|
| 5921 |
-
.on("mousemove",
|
| 5922 |
-
|
| 5923 |
-
})
|
| 5924 |
-
.on("mouseleave", () => hideTip());
|
| 5925 |
|
| 5926 |
-
//
|
| 5927 |
node.each(function(d) {
|
| 5928 |
-
const
|
| 5929 |
-
|
| 5930 |
-
|
| 5931 |
-
|
| 5932 |
-
|
| 5933 |
-
|
| 5934 |
-
.attr("stroke",
|
| 5935 |
-
.attr("stroke-width", 1.5);
|
| 5936 |
} else {
|
| 5937 |
-
|
| 5938 |
-
.attr("
|
| 5939 |
-
.attr("width", 14).attr("height", 14)
|
| 5940 |
-
.attr("rx", 3)
|
| 5941 |
-
.attr("fill", color)
|
| 5942 |
-
.attr("stroke", "#fff")
|
| 5943 |
-
.attr("stroke-width", 1.5);
|
| 5944 |
}
|
| 5945 |
});
|
| 5946 |
|
| 5947 |
-
//
|
| 5948 |
-
node.filter(d => d.data.children)
|
| 5949 |
.append("text")
|
| 5950 |
-
.attr("dy",
|
| 5951 |
-
.attr("
|
| 5952 |
-
.attr("
|
| 5953 |
-
.attr("font-size", "11px")
|
| 5954 |
-
.attr("font-weight", "700")
|
| 5955 |
-
.attr("pointer-events", "none")
|
| 5956 |
.text(d => d.children ? "β" : "+");
|
| 5957 |
|
| 5958 |
-
// Labels
|
| 5959 |
-
node.
|
| 5960 |
-
|
| 5961 |
-
|
| 5962 |
-
|
| 5963 |
-
|
| 5964 |
-
|
| 5965 |
-
|
| 5966 |
-
|
| 5967 |
-
.
|
| 5968 |
-
.attr("stroke","#f4f6f9")
|
| 5969 |
-
.attr("stroke-width",3)
|
| 5970 |
-
.attr("stroke-linejoin","round");
|
| 5971 |
|
| 5972 |
-
|
| 5973 |
-
|
| 5974 |
-
|
| 5975 |
-
|
| 5976 |
-
|
| 5977 |
-
|
| 5978 |
-
|
| 5979 |
-
|
| 5980 |
-
|
| 5981 |
-
.
|
| 5982 |
-
|
| 5983 |
-
|
| 5984 |
-
|
| 5985 |
-
|
| 5986 |
-
|
| 5987 |
-
|
| 5988 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5989 |
|
| 5990 |
-
// update container height
|
| 5991 |
document.getElementById("ds-tree-container").style.minHeight = svgHeight + "px";
|
| 5992 |
}
|
| 5993 |
|
| 5994 |
update();
|
| 5995 |
|
|
|
|
| 5996 |
document.getElementById("tree-expand-all").addEventListener("click", () => {
|
|
|
|
| 5997 |
function expandAll(n) {
|
| 5998 |
-
n.
|
| 5999 |
-
|
|
|
|
| 6000 |
}
|
| 6001 |
-
expandAll(
|
| 6002 |
update();
|
| 6003 |
});
|
|
|
|
| 6004 |
document.getElementById("tree-collapse-all").addEventListener("click", () => {
|
| 6005 |
function collapseAll(n, depth) {
|
| 6006 |
-
if (depth > 0) n.
|
| 6007 |
-
if (n.
|
| 6008 |
}
|
| 6009 |
-
collapseAll(
|
| 6010 |
update();
|
| 6011 |
});
|
| 6012 |
})();
|
|
|
|
| 129 |
</div>
|
| 130 |
<div class="legend" style="margin-top:14px;">
|
| 131 |
<div class="legend-item"><span class="legend-swatch" style="background:#1a2233;border-radius:50%;"></span> Root / Directory</div>
|
| 132 |
+
<div class="legend-item"><span class="legend-swatch" style="background:#4f8ef7;"></span> CSV Metadata File <em style="font-size:0.72rem;color:#888">(click to expand columns βΎ)</em></div>
|
| 133 |
<div class="legend-item"><span class="legend-swatch" style="background:#10b981;"></span> WFDB Waveform File (.hea / .dat)</div>
|
| 134 |
<div class="legend-item"><span class="legend-swatch" style="background:#94a3b8;"></span> Other File</div>
|
| 135 |
+
<div class="legend-item"><span class="legend-swatch" style="background:#cbd5e1;border-radius:50%;width:10px;height:10px;"></span> Column name <em style="font-size:0.72rem;color:#888">(monospace, hover for type)</em></div>
|
| 136 |
</div>
|
| 137 |
</div>
|
| 138 |
</section>
|
|
|
|
| 5789 |
|
| 5790 |
// ββ Dataset Structure Tree ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 5791 |
(function() {
|
| 5792 |
+
// Column metadata: name β description shown in tooltip
|
| 5793 |
+
const COL_META = {
|
| 5794 |
+
"subject_id": "Patient identifier (integer)",
|
| 5795 |
+
"study_id": "ECG study identifier β primary key (integer)",
|
| 5796 |
+
"file_name": "WFDB record filename without extension (string)",
|
| 5797 |
+
"ecg_time": "Acquisition datetime β date-shifted for de-identification",
|
| 5798 |
+
"path": "Relative path to WFDB record within files/",
|
| 5799 |
+
"cart_id": "ECG cart / device identifier (string)",
|
| 5800 |
+
"report_0 β¦ report_17": "18 machine-generated diagnostic phrase fields (string, often blank)",
|
| 5801 |
+
"bandwidth": "ECG device bandwidth setting (string)",
|
| 5802 |
+
"filtering": "ECG device filter setting (string)",
|
| 5803 |
+
"rr_interval": "RR interval β ms (float)",
|
| 5804 |
+
"p_onset": "P-wave onset β ms (float)",
|
| 5805 |
+
"p_end": "P-wave end β ms (float)",
|
| 5806 |
+
"qrs_onset": "QRS complex onset β ms (float)",
|
| 5807 |
+
"qrs_end": "QRS complex end β ms (float)",
|
| 5808 |
+
"t_end": "T-wave end β ms (float)",
|
| 5809 |
+
"p_axis": "P-wave electrical axis β degrees (float)",
|
| 5810 |
+
"qrs_axis": "QRS electrical axis β degrees (float)",
|
| 5811 |
+
"t_axis": "T-wave electrical axis β degrees (float)",
|
| 5812 |
+
"waveform_path": "Path used to link waveform to clinical note",
|
| 5813 |
+
"note_id": "MIMIC-IV clinical note identifier (integer)",
|
| 5814 |
+
"note_seq": "Note sequence number within admission (integer)",
|
| 5815 |
+
"charttime": "Datetime of linked clinical note",
|
| 5816 |
+
"variable": "Column / variable name (string)",
|
| 5817 |
+
"label": "Short human-readable label (string)",
|
| 5818 |
+
"description": "Full description of the variable (string)",
|
| 5819 |
+
"unit": "Measurement unit (string)",
|
| 5820 |
+
};
|
| 5821 |
+
const col = name => ({ name, type: "column", desc: COL_META[name] || "" });
|
| 5822 |
+
|
| 5823 |
+
// Shared measurement columns (machine_measurements + original)
|
| 5824 |
+
const measCols = [
|
| 5825 |
+
"subject_id","study_id","cart_id","ecg_time",
|
| 5826 |
+
"report_0 β¦ report_17",
|
| 5827 |
+
"bandwidth","filtering",
|
| 5828 |
+
"rr_interval","p_onset","p_end","qrs_onset","qrs_end","t_end",
|
| 5829 |
+
"p_axis","qrs_axis","t_axis",
|
| 5830 |
+
].map(col);
|
| 5831 |
+
|
| 5832 |
const treeData = {
|
| 5833 |
name: "mimic-iv-ecg-v1.0/", type: "root", desc: "Root dataset directory (800,035 studies)",
|
| 5834 |
children: [
|
| 5835 |
+
{
|
| 5836 |
+
name: "record_list.csv", type: "csv",
|
| 5837 |
+
desc: "800,035 rows β all ECG record paths + subject/study IDs + ecg_time",
|
| 5838 |
+
_collapsed: true,
|
| 5839 |
+
children: ["subject_id","study_id","file_name","ecg_time","path"].map(col),
|
| 5840 |
+
},
|
| 5841 |
+
{
|
| 5842 |
+
name: "machine_measurements.csv", type: "csv",
|
| 5843 |
+
desc: "789,481 rows Γ 33 cols β automated interval measurements + text report phrases",
|
| 5844 |
+
_collapsed: true,
|
| 5845 |
+
children: measCols,
|
| 5846 |
+
},
|
| 5847 |
+
{
|
| 5848 |
+
name: "machine_measurements_original.csv", type: "csv",
|
| 5849 |
+
desc: "789,481 rows β pre-processed raw measurement values (same 33 columns)",
|
| 5850 |
+
_collapsed: true,
|
| 5851 |
+
children: measCols,
|
| 5852 |
+
},
|
| 5853 |
+
{
|
| 5854 |
+
name: "machine_measurements_data_dictionary.csv", type: "csv",
|
| 5855 |
+
desc: "18 rows β variable descriptions and units",
|
| 5856 |
+
_collapsed: true,
|
| 5857 |
+
children: ["variable","label","description","unit"].map(col),
|
| 5858 |
+
},
|
| 5859 |
+
{
|
| 5860 |
+
name: "waveform_note_links.csv", type: "csv",
|
| 5861 |
+
desc: "609,272 rows β links ECG studies to MIMIC-IV clinical notes",
|
| 5862 |
+
_collapsed: true,
|
| 5863 |
+
children: ["subject_id","study_id","waveform_path","note_id","note_seq","charttime"].map(col),
|
| 5864 |
+
},
|
| 5865 |
+
{ name: "RECORDS", type: "file", desc: "1000 patient-group directory names (p1000βp1999)" },
|
| 5866 |
+
{ name: "SHA256SUMS.txt", type: "file", desc: "File integrity checksums (SHA-256)" },
|
| 5867 |
+
{ name: "LICENSE.txt", type: "file", desc: "ODC Open Database License (ODbL)" },
|
| 5868 |
{
|
| 5869 |
name: "files/", type: "dir", desc: "Waveform data organised in a 4-level hierarchy",
|
| 5870 |
_collapsed: false,
|
|
|
|
| 5872 |
name: "pXXXX/", type: "dir", desc: "1,000 patient-group folders (p1000βp1999)",
|
| 5873 |
_collapsed: false,
|
| 5874 |
children: [{
|
| 5875 |
+
name: "pNNNNNNNN/", type: "dir",
|
| 5876 |
+
desc: "Individual patient directory (subject_id β 160,862 unique patients)",
|
| 5877 |
_collapsed: false,
|
| 5878 |
children: [{
|
| 5879 |
+
name: "sNNNNNNNN/", type: "dir",
|
| 5880 |
+
desc: "Study directory (study_id β one per ECG acquisition)",
|
| 5881 |
_collapsed: false,
|
| 5882 |
children: [
|
| 5883 |
{ name: "NNNNNNNN.hea", type: "hea", desc: "WFDB header β 12 leads, 500 Hz, 10 s (plain text, ~600 B)" },
|
| 5884 |
+
{ name: "NNNNNNNN.dat", type: "dat", desc: "WFDB signal β 16-bit signed binary, 5,000 samples/lead (~120 KB)" },
|
| 5885 |
+
],
|
| 5886 |
+
}],
|
| 5887 |
+
}],
|
| 5888 |
+
}],
|
| 5889 |
+
},
|
| 5890 |
+
],
|
| 5891 |
};
|
| 5892 |
|
| 5893 |
+
// ββ Helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 5894 |
+
const isDir = d => d.data.type === "root" || d.data.type === "dir";
|
| 5895 |
+
const isCSV = d => d.data.type === "csv";
|
| 5896 |
+
const isCol = d => d.data.type === "column";
|
| 5897 |
+
|
| 5898 |
+
const nodeColor = d => {
|
| 5899 |
+
if (isDir(d)) return "#1a2233";
|
| 5900 |
+
if (isCSV(d)) return "#4f8ef7";
|
| 5901 |
+
if (d.data.type === "hea" || d.data.type === "dat") return "#10b981";
|
| 5902 |
+
if (isCol(d)) return "#94a3b8";
|
| 5903 |
return "#94a3b8";
|
| 5904 |
};
|
|
|
|
| 5905 |
|
| 5906 |
+
// Approximate pixel width of a label string
|
| 5907 |
+
const labelPx = (s, mono) => (s || "").length * (mono ? 7.2 : 7.5);
|
| 5908 |
+
|
| 5909 |
+
// ββ SVG setup ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 5910 |
+
const margin = { top: 24, right: 360, bottom: 24, left: 160 };
|
| 5911 |
+
const baseWidth = 1100;
|
| 5912 |
+
let svgHeight = 600;
|
| 5913 |
|
| 5914 |
const svg = d3.select("#ds-tree-svg")
|
| 5915 |
.attr("width", baseWidth + margin.left + margin.right)
|
| 5916 |
+
.attr("height", svgHeight)
|
| 5917 |
.style("font", "13px 'Segoe UI', system-ui, sans-serif");
|
| 5918 |
|
| 5919 |
const g = svg.append("g").attr("transform", `translate(${margin.left},${margin.top})`);
|
|
|
|
|
|
|
| 5920 |
const linkPath = d3.linkHorizontal().x(d => d.y).y(d => d.x);
|
| 5921 |
|
| 5922 |
+
// ββ Hierarchy builder ββββββββββββββββββββββββββββββββββββββββββββββββββββββοΏ½οΏ½οΏ½ββ
|
| 5923 |
function buildHierarchy(data) {
|
| 5924 |
+
return d3.hierarchy(data, d => d._collapsed ? null : d.children);
|
|
|
|
|
|
|
| 5925 |
}
|
| 5926 |
|
| 5927 |
+
// ββ Render ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5928 |
function update() {
|
| 5929 |
+
const root = buildHierarchy(treeData);
|
| 5930 |
|
| 5931 |
+
// Use tighter row spacing for column nodes; standard for others
|
| 5932 |
+
const treeLayout = d3.tree().nodeSize([26, 220]);
|
| 5933 |
treeLayout(root);
|
| 5934 |
|
|
|
|
| 5935 |
const nodes = root.descendants();
|
| 5936 |
+
const minX = d3.min(nodes, d => d.x);
|
| 5937 |
+
const maxX = d3.max(nodes, d => d.x);
|
| 5938 |
+
svgHeight = Math.max(420, maxX - minX + margin.top + margin.bottom + 40);
|
| 5939 |
svg.attr("height", svgHeight);
|
| 5940 |
const offsetY = margin.top - minX;
|
| 5941 |
|
| 5942 |
g.selectAll("*").remove();
|
| 5943 |
|
| 5944 |
+
// ββ Links ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 5945 |
+
g.selectAll(".lnk")
|
| 5946 |
.data(root.links())
|
| 5947 |
.join("path")
|
| 5948 |
+
.attr("fill", "none")
|
| 5949 |
+
.attr("stroke", d => isCol(d.target) ? "#dde3ef" : "#c8d0e0")
|
| 5950 |
+
.attr("stroke-width", d => isCol(d.target) ? 1 : 1.5)
|
| 5951 |
+
.attr("stroke-dasharray", d => isCol(d.target) ? "3,3" : null)
|
| 5952 |
.attr("d", d => linkPath({
|
| 5953 |
+
source: { x: d.source.x + offsetY, y: d.source.y },
|
| 5954 |
+
target: { x: d.target.x + offsetY, y: d.target.y },
|
| 5955 |
}));
|
| 5956 |
|
| 5957 |
+
// ββ Node groups ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 5958 |
+
const node = g.selectAll(".nd")
|
| 5959 |
.data(nodes)
|
| 5960 |
.join("g")
|
|
|
|
| 5961 |
.attr("transform", d => `translate(${d.y},${d.x + offsetY})`)
|
| 5962 |
.style("cursor", d => d.data.children ? "pointer" : "default")
|
| 5963 |
.on("click", (event, d) => {
|
| 5964 |
if (!d.data.children) return;
|
| 5965 |
+
d.data._collapsed = !d.data._collapsed;
|
| 5966 |
update();
|
| 5967 |
})
|
| 5968 |
.on("mouseenter", (event, d) => {
|
| 5969 |
+
const icons = { root:"π", dir:"π", csv:"π", hea:"π", dat:"πΎ", column:"⬑", file:"π" };
|
| 5970 |
+
showTip(`<b>${icons[d.data.type]||"π"} ${d.data.name}</b><br>${d.data.desc}`, event);
|
|
|
|
|
|
|
|
|
|
| 5971 |
})
|
| 5972 |
+
.on("mousemove", event => tip.style("left",(event.pageX+14)+"px").style("top",(event.pageY-28)+"px"))
|
| 5973 |
+
.on("mouseleave", () => hideTip());
|
|
|
|
|
|
|
| 5974 |
|
| 5975 |
+
// ββ Node shapes ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 5976 |
node.each(function(d) {
|
| 5977 |
+
const s = d3.select(this), c = nodeColor(d);
|
| 5978 |
+
if (isCol(d)) {
|
| 5979 |
+
// Small filled circle for column nodes
|
| 5980 |
+
s.append("circle").attr("r", 4).attr("fill", c).attr("stroke","#fff").attr("stroke-width",1);
|
| 5981 |
+
} else if (isDir(d)) {
|
| 5982 |
+
s.append("polygon").attr("points","-9,0 0,-9 9,0 0,9")
|
| 5983 |
+
.attr("fill",c).attr("stroke","#fff").attr("stroke-width",1.5);
|
|
|
|
| 5984 |
} else {
|
| 5985 |
+
s.append("rect").attr("x",-7).attr("y",-7).attr("width",14).attr("height",14)
|
| 5986 |
+
.attr("rx",3).attr("fill",c).attr("stroke","#fff").attr("stroke-width",1.5);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5987 |
}
|
| 5988 |
});
|
| 5989 |
|
| 5990 |
+
// ββ Expand/collapse +/β badge (non-column expandable nodes only) βββββββββββ
|
| 5991 |
+
node.filter(d => d.data.children && !isCol(d))
|
| 5992 |
.append("text")
|
| 5993 |
+
.attr("dy","0.35em").attr("text-anchor","middle")
|
| 5994 |
+
.attr("fill","#fff").attr("font-size","11px").attr("font-weight","700")
|
| 5995 |
+
.attr("pointer-events","none")
|
|
|
|
|
|
|
|
|
|
| 5996 |
.text(d => d.children ? "β" : "+");
|
| 5997 |
|
| 5998 |
+
// ββ Labels βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 5999 |
+
node.each(function(d) {
|
| 6000 |
+
const s = d3.select(this);
|
| 6001 |
+
const col_ = isCol(d);
|
| 6002 |
+
const xOff = col_ ? 8 : isDir(d) ? 14 : 12;
|
| 6003 |
+
const fSize = col_ ? "11px" : "12.5px";
|
| 6004 |
+
const fFam = col_ ? "'Courier New', Courier, monospace" : null;
|
| 6005 |
+
const fColor = col_ ? "#475569" : isDir(d) ? "#1a2233" : "#334";
|
| 6006 |
+
const fWeight= (!col_ && isDir(d)) ? "700" : "400";
|
| 6007 |
+
const name = d.data.name;
|
|
|
|
|
|
|
|
|
|
| 6008 |
|
| 6009 |
+
// Readability halo
|
| 6010 |
+
s.append("text")
|
| 6011 |
+
.attr("dy","0.35em").attr("x",xOff).attr("text-anchor","start")
|
| 6012 |
+
.attr("stroke","#f4f6f9").attr("stroke-width", col_ ? 2 : 3)
|
| 6013 |
+
.attr("stroke-linejoin","round").attr("fill","none")
|
| 6014 |
+
.attr("font-size",fSize).attr("font-family",fFam||null)
|
| 6015 |
+
.text(name);
|
| 6016 |
+
|
| 6017 |
+
// Name label
|
| 6018 |
+
s.append("text")
|
| 6019 |
+
.attr("dy","0.35em").attr("x",xOff).attr("text-anchor","start")
|
| 6020 |
+
.attr("fill",fColor).attr("font-weight",fWeight)
|
| 6021 |
+
.attr("font-size",fSize).attr("font-family",fFam||null)
|
| 6022 |
+
.text(name);
|
| 6023 |
+
|
| 6024 |
+
if (col_) return; // column nodes: name only, no extra labels
|
| 6025 |
+
|
| 6026 |
+
const nameW = labelPx(name, false);
|
| 6027 |
+
|
| 6028 |
+
// Description (grey, smaller)
|
| 6029 |
+
s.append("text")
|
| 6030 |
+
.attr("dy","0.35em").attr("x",xOff).attr("dx", nameW + 6)
|
| 6031 |
+
.attr("text-anchor","start").attr("fill","#888").attr("font-size","11px")
|
| 6032 |
+
.text("β " + d.data.desc)
|
| 6033 |
+
.clone(true).lower()
|
| 6034 |
+
.attr("stroke","#f4f6f9").attr("stroke-width",3)
|
| 6035 |
+
.attr("stroke-linejoin","round").attr("fill","none");
|
| 6036 |
+
|
| 6037 |
+
// "βΎ N cols" badge on collapsed CSV nodes
|
| 6038 |
+
if (isCSV(d) && d.data._collapsed) {
|
| 6039 |
+
const n = d.data.children.length;
|
| 6040 |
+
const descW = labelPx("β " + d.data.desc, false);
|
| 6041 |
+
s.append("text")
|
| 6042 |
+
.attr("dy","0.35em").attr("x",xOff).attr("dx", nameW + 6 + descW + 10)
|
| 6043 |
+
.attr("text-anchor","start")
|
| 6044 |
+
.attr("fill","#4f8ef7").attr("font-size","10.5px").attr("font-weight","600")
|
| 6045 |
+
.text(`[βΎ ${n} cols]`);
|
| 6046 |
+
}
|
| 6047 |
+
});
|
| 6048 |
|
|
|
|
| 6049 |
document.getElementById("ds-tree-container").style.minHeight = svgHeight + "px";
|
| 6050 |
}
|
| 6051 |
|
| 6052 |
update();
|
| 6053 |
|
| 6054 |
+
// ββ Toolbar buttons βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 6055 |
document.getElementById("tree-expand-all").addEventListener("click", () => {
|
| 6056 |
+
// Expand dirs/files but NOT CSV column children (too noisy)
|
| 6057 |
function expandAll(n) {
|
| 6058 |
+
if (n.type === "csv") return; // leave CSV column children collapsed
|
| 6059 |
+
n._collapsed = false;
|
| 6060 |
+
if (n.children) n.children.forEach(expandAll);
|
| 6061 |
}
|
| 6062 |
+
expandAll(treeData);
|
| 6063 |
update();
|
| 6064 |
});
|
| 6065 |
+
|
| 6066 |
document.getElementById("tree-collapse-all").addEventListener("click", () => {
|
| 6067 |
function collapseAll(n, depth) {
|
| 6068 |
+
if (depth > 0) n._collapsed = true;
|
| 6069 |
+
if (n.children) n.children.forEach(c => collapseAll(c, depth + 1));
|
| 6070 |
}
|
| 6071 |
+
collapseAll(treeData, 0);
|
| 6072 |
update();
|
| 6073 |
});
|
| 6074 |
})();
|