Skip to content

Commit df3d8d2

Browse files
Histogram improvements (#26)
* feat: add Dandiset histogram; fix: allow all Dandisets for per asset histogram * feat: improve handling of 'undetermined' case; fix: bug with archive option disappearing
1 parent c29467e commit df3d8d2

File tree

2 files changed

+112
-35
lines changed

2 files changed

+112
-35
lines changed

index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ <h1>Dandiset Access Summaries</h1>
6565
</div>
6666
<div id="totals"></div>
6767
<div id="over_time_plot"></div>
68-
<div id="per_asset_histogram"></div>
68+
<div id="histogram"></div>
6969
<div id="geography_heatmap"></div>
7070
</body>
7171
</html>

plots.js

Lines changed: 111 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ window.addEventListener("load", () => {
3535

3636
// Reload plots with the current dandiset ID
3737
load_over_time_plot(selected_dandiset);
38-
load_per_asset_histogram(selected_dandiset);
38+
load_histogram(selected_dandiset);
3939
load_geographic_heatmap(selected_dandiset);
4040
});
4141
}
@@ -52,7 +52,7 @@ window.addEventListener("load", () => {
5252

5353
// Reload plots with the current dandiset ID
5454
load_over_time_plot(selected_dandiset);
55-
load_per_asset_histogram(selected_dandiset);
55+
load_histogram(selected_dandiset);
5656
load_geographic_heatmap(selected_dandiset);
5757
});
5858
}
@@ -70,7 +70,7 @@ window.addEventListener("load", () => {
7070
// Reload plots with the current dandiset ID
7171
update_totals(selected_dandiset);
7272
load_over_time_plot(selected_dandiset);
73-
load_per_asset_histogram(selected_dandiset);
73+
load_histogram(selected_dandiset);
7474
load_geographic_heatmap(selected_dandiset);
7575
});
7676
}
@@ -81,29 +81,29 @@ window.addEventListener("resize", resizePlots);
8181

8282
function resizePlots() {
8383
// Select the div elements
84-
const overTimePlot = document.getElementById("over_time_plot");
85-
const perAssetHistogram = document.getElementById("per_asset_histogram");
86-
const geographyHeatmap = document.getElementById("geography_heatmap");
84+
const over_time_plot = document.getElementById("over_time_plot");
85+
const histogram = document.getElementById("histogram");
86+
const geography_heatmap = document.getElementById("geography_heatmap");
8787

8888
const dandiset_selector = document.getElementById("dandiset_selector");
8989
const selected_dandiset = dandiset_selector.value;
9090

9191
// Update their sizes dynamically
92-
if (overTimePlot) {
93-
overTimePlot.style.width = "90vw";
94-
overTimePlot.style.height = "80vh";
95-
Plotly.relayout(overTimePlot, { width: overTimePlot.offsetWidth, height: overTimePlot.offsetHeight });
92+
if (over_time_plot) {
93+
over_time_plot.style.width = "90vw";
94+
over_time_plot.style.height = "80vh";
95+
Plotly.relayout(over_time_plot, { width: over_time_plot.offsetWidth, height: over_time_plot.offsetHeight });
9696
}
97-
if (selected_dandiset !== "archive" && perAssetHistogram) {
98-
perAssetHistogram.style.width = "90vw";
99-
perAssetHistogram.style.height = "80vh";
100-
Plotly.relayout(perAssetHistogram, { width: perAssetHistogram.offsetWidth, height: perAssetHistogram.offsetHeight });
97+
if (selected_dandiset !== "archive" && histogram) {
98+
histogram.style.width = "90vw";
99+
histogram.style.height = "80vh";
100+
Plotly.relayout(histogram, { width: histogram.offsetWidth, height: histogram.offsetHeight });
101101
}
102-
if (geographyHeatmap) {
103-
geographyHeatmap.style.width = "90vw";
104-
geographyHeatmap.style.height = "80vh";
105-
geographyHeatmap.style.margin = "auto";
106-
Plotly.relayout(geographyHeatmap, { width: geographyHeatmap.offsetWidth, height: geographyHeatmap.offsetHeight });
102+
if (geography_heatmap) {
103+
geography_heatmap.style.width = "90vw";
104+
geography_heatmap.style.height = "80vh";
105+
geography_heatmap.style.margin = "auto";
106+
Plotly.relayout(geography_heatmap, { width: geography_heatmap.offsetWidth, height: geography_heatmap.offsetHeight });
107107
}
108108
}
109109

@@ -183,15 +183,15 @@ fetch(ALL_DANDISET_TOTALS_URL)
183183
// Load the plot for the first ID by default
184184
update_totals("archive");
185185
load_over_time_plot("archive");
186-
load_per_asset_histogram("archive");
186+
load_histogram("archive");
187187
load_geographic_heatmap("archive");
188188

189189
// Update the plots when a new Dandiset ID is selected
190190
selector.addEventListener("change", (event) => {
191191
const target = event.target;
192192
update_totals(target.value);
193193
load_over_time_plot(target.value);
194-
load_per_asset_histogram(target.value);
194+
load_histogram(target.value);
195195
load_geographic_heatmap(target.value);
196196
});
197197
})
@@ -215,7 +215,8 @@ function update_totals(dandiset_id) {
215215
const human_readable_bytes_sent = format_bytes(totals.total_bytes_sent);
216216
//totals_element.innerText = `Totals: ${human_readable_bytes_sent} sent to ?(WIP)? unique requesters from
217217
// ${totals.number_of_unique_regions} regions of ${totals.number_of_unique_countries} countries.`;
218-
totals_element.innerHTML = `A total of ${human_readable_bytes_sent} was sent to ${totals.number_of_unique_regions} regions across ${totals.number_of_unique_countries} countries. <sup>*</sup>`;
218+
header = `A total of ${human_readable_bytes_sent} was sent to ${totals.number_of_unique_regions} regions across ${totals.number_of_unique_countries} countries. <sup>*</sup>`
219+
totals_element.innerHTML = dandiset_id != "undetermined" ? header : header + `<br>However, the activity could not be uniquely associated with a particular Dandiset.<br>This can occur if the same file exists within more than one Dandiset at a time.`
219220

220221
// Add the footnote
221222
const footnote = document.createElement("div");
@@ -331,21 +332,100 @@ function load_over_time_plot(dandiset_id) {
331332
});
332333
}
333334

334-
// Function to fetch and render histogram over asset IDs
335-
function load_per_asset_histogram(dandiset_id) {
336-
const plot_element_id = "per_asset_histogram";
337-
let by_asset_summary_tsv_url = "";
335+
// Function to fetch and render histogram over asset or Dandiset IDs
336+
function load_histogram(dandiset_id) {
337+
let by_asset_summary_tsv_url, dandiset_totals_json_url;
338338

339339
// Suppress div element content if 'archive' is selected
340-
if (dandiset_id === "archive") {
341-
const plot_element = document.getElementById(plot_element_id);
340+
if (dandiset_id === "undetermined") {
341+
const plot_element = document.getElementById("histogram");
342342
if (plot_element) {
343343
plot_element.innerText = "";
344344
}
345345
return "";
346+
} if (dandiset_id === "archive") {
347+
load_dandiset_histogram()
346348
} else {
347349
by_asset_summary_tsv_url = `${BASE_TSV_URL}/${dandiset_id}/by_asset.tsv`;
350+
load_per_asset_histogram(by_asset_summary_tsv_url);
348351
}
352+
}
353+
354+
function load_dandiset_histogram() {
355+
const plot_element_id = "histogram";
356+
357+
fetch(ALL_DANDISET_TOTALS_URL)
358+
.then((response) => {
359+
if (!response.ok) {
360+
throw new Error(`Failed to fetch JSON file: ${response.statusText}`);
361+
}
362+
return response.json();
363+
})
364+
.then((data) => {
365+
// Exclude 'archive' and cast IDs to strings
366+
const combined = Object.keys(data)
367+
.map(dandiset_id => ({
368+
dandiset_id: "Dandiset ID " + String(dandiset_id),
369+
bytes: data[dandiset_id].total_bytes_sent
370+
}))
371+
.sort((a, b) => b.bytes - a.bytes);
372+
373+
const sorted_dandiset_ids = combined.map(item => item.dandiset_id);
374+
const sorted_bytes_sent = combined.map(item => item.bytes);
375+
const human_readable_bytes_sent = sorted_bytes_sent.map(bytes => format_bytes(bytes));
376+
377+
const plot_data = [
378+
{
379+
type: "bar",
380+
x: sorted_dandiset_ids,
381+
y: sorted_bytes_sent,
382+
text: sorted_dandiset_ids.map((dandiset_id, index) => `${dandiset_id}<br>${human_readable_bytes_sent[index]}`),
383+
textposition: "none",
384+
hoverinfo: "text",
385+
}
386+
];
387+
388+
const layout = {
389+
bargap: 0,
390+
title: {
391+
text: `Bytes sent per Dandiset`,
392+
font: { size: 24 }
393+
},
394+
xaxis: {
395+
title: {
396+
text: "(hover over an entry for Dandiset IDs)",
397+
font: { size: 16 }
398+
},
399+
showticklabels: false,
400+
},
401+
yaxis: {
402+
title: {
403+
text: USE_LOG_SCALE ? "Bytes (log scale)" : "Bytes",
404+
font: { size: 16 }
405+
},
406+
type: USE_LOG_SCALE ? "log" : "linear",
407+
tickformat: USE_LOG_SCALE ? "" : "~s",
408+
ticksuffix: USE_LOG_SCALE ? "" : "B",
409+
tickvals: USE_LOG_SCALE ? [1000, 1000000, 1000000000, 1000000000000, 1000000000000000, 1000000000000000000] : null,
410+
ticktext: USE_LOG_SCALE ? ["KB", "MB", "GB", "TB"] : null
411+
},
412+
};
413+
414+
Plotly.newPlot(plot_element_id, plot_data, layout);
415+
})
416+
.catch((error) => {
417+
console.error("Error:", error);
418+
const plot_element = document.getElementById(plot_element_id);
419+
if (plot_element) {
420+
while (plot_element.firstChild) {
421+
plot_element.removeChild(plot_element.firstChild);
422+
}
423+
}
424+
});
425+
}
426+
427+
function load_per_asset_histogram(by_asset_summary_tsv_url) {
428+
const plot_element_id = "histogram";
349429

350430
fetch(by_asset_summary_tsv_url)
351431
.then((response) => {
@@ -363,12 +443,9 @@ function load_per_asset_histogram(dandiset_id) {
363443
const data = rows.slice(1).map((row) => row.split("\t"));
364444

365445
const asset_names = data.map((row) => {
366-
const filename = row[0].split("/").at(-1);
367-
const suffix = filename.split(".").at(-1);
368-
369-
if (suffix !== "nwb" && suffix !== "mp4" && suffix !== "avi") {
370-
throw new Error("Currently only supports NWB files.");
371-
}
446+
let suffix, filename;
447+
suffix = row[0].split(".").at(-1);
448+
filename = suffix === "nwb" ? row[0].split("/").at(-1) : row[0];
372449

373450
return filename;
374451
});

0 commit comments

Comments
 (0)