Download assets/dataset_link.js from Tang-Lab/benchdash: direct link, hf CLI and curl.
- Browser
- Download file 12.9 kB
-
https://huggingface.co/spaces/Tang-Lab/benchdash/resolve/main/assets/dataset_link.js
- Command line
-
hf download hf://spaces/Tang-Lab/benchdash/assets/dataset_link.js
-
curl -L -o dataset_link.js https://huggingface.co/spaces/Tang-Lab/benchdash/resolve/main/assets/dataset_link.js
12.9 kB
| (function () { | |
| "use strict"; | |
| const CURSOR_COLOR = "#D55E00"; | |
| const PLAYBACK_DURATION_MS = 4200; | |
| let activeState = null; | |
| let scheduleGeneration = 0; | |
| function clamp(value, low, high) { | |
| return Math.max(low, Math.min(high, value)); | |
| } | |
| function traceRole(trace) { | |
| return trace && trace.meta && trace.meta.benchdash_role; | |
| } | |
| function traceIndex(graph, role) { | |
| return graph.data.findIndex((trace) => traceRole(trace) === role); | |
| } | |
| function graphDataset(graph) { | |
| return graph && graph.layout && graph.layout.meta && graph.layout.meta.benchdash_dataset; | |
| } | |
| function formatNumber(value, digits) { | |
| if (!Number.isFinite(Number(value))) return "—"; | |
| const number = Number(value); | |
| const magnitude = Math.abs(number); | |
| const precision = magnitude >= 10 ? 2 : digits; | |
| return number.toFixed(precision).replace(/^-/, "−"); | |
| } | |
| function formatSignedTime(value) { | |
| const rounded = Math.round(Number(value)); | |
| if (rounded === 0) return "0 ms"; | |
| return `${rounded < 0 ? "−" : "+"}${Math.abs(rounded)} ms`; | |
| } | |
| function makeElement(tag, className, text) { | |
| const node = document.createElement(tag); | |
| if (className) node.className = className; | |
| if (text !== undefined) node.textContent = text; | |
| return node; | |
| } | |
| function buildControls(state) { | |
| const lead = makeElement("div", "dataset-link-lead"); | |
| lead.append( | |
| makeElement("strong", "dataset-link-title", "Linked trial"), | |
| makeElement( | |
| "span", | |
| "dataset-link-hint", | |
| "Playback loops automatically. Hover to pause; scrub to inspect." | |
| ) | |
| ); | |
| const button = makeElement("button", "dataset-link-button", "Play"); | |
| button.type = "button"; | |
| button.setAttribute("aria-label", "Play linked neural activity and target trajectory"); | |
| const slider = makeElement("input", "dataset-link-slider"); | |
| slider.type = "range"; | |
| slider.min = "0"; | |
| slider.max = String(state.payload.time_ms.length - 1); | |
| slider.step = "1"; | |
| slider.value = String(state.payload.initial_index || 0); | |
| slider.setAttribute("aria-label", "Linked trial time bin"); | |
| const readout = makeElement("div", "dataset-link-readout"); | |
| const time = makeElement("span", "dataset-link-time"); | |
| const value = makeElement("span", "dataset-link-value"); | |
| readout.append(time, value); | |
| const transport = makeElement("div", "dataset-link-transport"); | |
| transport.append(button, slider, readout); | |
| state.controls.replaceChildren(lead, transport); | |
| state.button = button; | |
| state.slider = slider; | |
| state.timeReadout = time; | |
| state.valueReadout = value; | |
| } | |
| function setButton(state) { | |
| state.button.textContent = state.playing ? "Pause" : "Play"; | |
| state.button.setAttribute( | |
| "aria-label", | |
| state.playing | |
| ? "Pause linked neural activity and target trajectory" | |
| : "Play linked neural activity and target trajectory" | |
| ); | |
| } | |
| function positionPlayhead(state) { | |
| if (!state.neural.isConnected || !state.neural._fullLayout) return; | |
| const layout = state.neural._fullLayout; | |
| const axis = layout.xaxis; | |
| const size = layout._size; | |
| if (!axis || !size) return; | |
| const time = Number(state.payload.time_ms[state.index]); | |
| const left = size.l + axis.l2p(time); | |
| state.playhead.style.left = `${left}px`; | |
| state.playhead.style.top = `${size.t}px`; | |
| state.playhead.style.height = `${size.h}px`; | |
| } | |
| function updateReadout(state) { | |
| const index = state.index; | |
| const x = Number(state.payload.plot_x[index]); | |
| const y = Number(state.payload.plot_y[index]); | |
| const neuralTime = Number(state.payload.time_ms[index]); | |
| const targetLag = Number(state.payload.target_lag_ms || 0); | |
| state.timeReadout.textContent = `Neural time ${formatSignedTime(neuralTime)}`; | |
| if (targetLag !== 0) { | |
| state.valueReadout.textContent = ( | |
| `Target time ${formatSignedTime(neuralTime + targetLag)}` | |
| + ` · x ${formatNumber(x, 3)} · y ${formatNumber(y, 3)}` | |
| ); | |
| } else if (state.payload.value_kind === "force") { | |
| state.valueReadout.textContent = `Paired force ${formatNumber(y, 3)}`; | |
| } else { | |
| state.valueReadout.textContent = ( | |
| `Paired target x ${formatNumber(x, 3)} · y ${formatNumber(y, 3)}` | |
| ); | |
| } | |
| } | |
| function renderIndex(state, requestedIndex) { | |
| const lastIndex = state.payload.time_ms.length - 1; | |
| const index = clamp(Math.round(Number(requestedIndex)), 0, lastIndex); | |
| if (!Number.isFinite(index)) return; | |
| state.index = index; | |
| state.slider.value = String(index); | |
| positionPlayhead(state); | |
| updateReadout(state); | |
| if (state.renderFrame !== null) cancelAnimationFrame(state.renderFrame); | |
| state.renderFrame = requestAnimationFrame(() => { | |
| state.renderFrame = null; | |
| if (!state.target.isConnected || !state.target._fullLayout || !window.Plotly) return; | |
| const progressX = state.payload.plot_x.slice(0, index + 1); | |
| const progressY = state.payload.plot_y.slice(0, index + 1); | |
| window.Plotly.restyle( | |
| state.target, | |
| { | |
| x: [progressX, [state.payload.plot_x[index]]], | |
| y: [progressY, [state.payload.plot_y[index]]], | |
| }, | |
| [state.progressTrace, state.cursorTrace] | |
| ); | |
| }); | |
| } | |
| function pause(state) { | |
| state.playing = false; | |
| if (state.animationFrame !== null) cancelAnimationFrame(state.animationFrame); | |
| state.animationFrame = null; | |
| setButton(state); | |
| } | |
| function play(state) { | |
| pause(state); | |
| const finalIndex = state.payload.time_ms.length - 1; | |
| if (state.index >= finalIndex) renderIndex(state, 0); | |
| const startIndex = state.index; | |
| const stepDuration = PLAYBACK_DURATION_MS / Math.max(finalIndex, 1); | |
| const start = performance.now(); | |
| state.playing = true; | |
| setButton(state); | |
| const advance = (now) => { | |
| if (!state.playing || activeState !== state) return; | |
| const elapsedSteps = Math.floor((now - start) / stepDuration); | |
| const nextIndex = Math.min(finalIndex, startIndex + elapsedSteps); | |
| if (nextIndex !== state.index) renderIndex(state, nextIndex); | |
| if (nextIndex >= finalIndex) { | |
| state.animationFrame = requestAnimationFrame(() => { | |
| if (!state.playing || activeState !== state) return; | |
| renderIndex(state, 0); | |
| play(state); | |
| }); | |
| return; | |
| } | |
| state.animationFrame = requestAnimationFrame(advance); | |
| }; | |
| state.animationFrame = requestAnimationFrame(advance); | |
| } | |
| function eventIndex(state, event) { | |
| const point = event && event.points && event.points[0]; | |
| const custom = point && point.customdata; | |
| if (Array.isArray(custom) && Number.isFinite(Number(custom[1]))) { | |
| return Number(custom[1]); | |
| } | |
| const time = point && Number(point.x); | |
| if (!Number.isFinite(time)) return null; | |
| let bestIndex = 0; | |
| let bestDistance = Infinity; | |
| state.payload.time_ms.forEach((candidate, index) => { | |
| const distance = Math.abs(Number(candidate) - time); | |
| if (distance < bestDistance) { | |
| bestDistance = distance; | |
| bestIndex = index; | |
| } | |
| }); | |
| return bestIndex; | |
| } | |
| function bindInteractions(state) { | |
| const abort = state.abortController; | |
| const pauseForHover = (event, fromTarget) => { | |
| const point = event && event.points && event.points[0]; | |
| if (fromTarget && (!point || point.curveNumber !== state.exampleTrace)) return; | |
| const index = eventIndex(state, event); | |
| if (index === null) return; | |
| if (state.playing) state.resumeAfterHover = true; | |
| pause(state); | |
| renderIndex(state, index); | |
| }; | |
| const holdSelection = (event, fromTarget) => { | |
| const point = event && event.points && event.points[0]; | |
| if (fromTarget && (!point || point.curveNumber !== state.exampleTrace)) return; | |
| const index = eventIndex(state, event); | |
| if (index === null) return; | |
| state.resumeAfterHover = false; | |
| pause(state); | |
| renderIndex(state, index); | |
| }; | |
| const neuralHover = (event) => pauseForHover(event, false); | |
| const neuralClick = (event) => holdSelection(event, false); | |
| const targetHover = (event) => pauseForHover(event, true); | |
| const targetClick = (event) => holdSelection(event, true); | |
| const resumeAfterHover = () => { | |
| if (!state.resumeAfterHover || activeState !== state) return; | |
| state.resumeAfterHover = false; | |
| play(state); | |
| }; | |
| state.neural.on("plotly_hover", neuralHover); | |
| state.neural.on("plotly_click", neuralClick); | |
| state.target.on("plotly_hover", targetHover); | |
| state.target.on("plotly_click", targetClick); | |
| state.plotlyListeners = [ | |
| [state.neural, "plotly_hover", neuralHover], | |
| [state.neural, "plotly_click", neuralClick], | |
| [state.target, "plotly_hover", targetHover], | |
| [state.target, "plotly_click", targetClick], | |
| ]; | |
| state.neural.addEventListener("mouseleave", resumeAfterHover, { signal: abort.signal }); | |
| state.target.addEventListener("mouseleave", resumeAfterHover, { signal: abort.signal }); | |
| state.button.addEventListener("click", () => { | |
| state.resumeAfterHover = false; | |
| if (state.playing) pause(state); | |
| else play(state); | |
| }, { signal: abort.signal }); | |
| state.slider.addEventListener("input", () => { | |
| state.resumeAfterHover = false; | |
| pause(state); | |
| renderIndex(state, Number(state.slider.value)); | |
| }, { signal: abort.signal }); | |
| state.resizeObserver = new ResizeObserver(() => { | |
| requestAnimationFrame(() => positionPlayhead(state)); | |
| }); | |
| state.resizeObserver.observe(state.neural); | |
| } | |
| function maybeAutoplay(state) { | |
| state.autoplayTimer = window.setTimeout(() => { | |
| if (activeState === state) { | |
| renderIndex(state, 0); | |
| play(state); | |
| } | |
| }, 150); | |
| } | |
| function destroy(state) { | |
| if (!state) return; | |
| pause(state); | |
| if (state.renderFrame !== null) cancelAnimationFrame(state.renderFrame); | |
| if (state.autoplayTimer !== null) window.clearTimeout(state.autoplayTimer); | |
| if (state.resizeObserver) state.resizeObserver.disconnect(); | |
| if (state.abortController) state.abortController.abort(); | |
| (state.plotlyListeners || []).forEach(([graph, event, listener]) => { | |
| if (graph && typeof graph.removeListener === "function") { | |
| graph.removeListener(event, listener); | |
| } | |
| }); | |
| if (state.playhead) state.playhead.remove(); | |
| } | |
| function mount(payload) { | |
| const controls = document.getElementById("dataset-link-controls"); | |
| const neural = document.querySelector("#dataset-neural-example .js-plotly-plot"); | |
| const target = document.querySelector("#dataset-target-trajectory .js-plotly-plot"); | |
| if (!controls || !neural || !target || !neural._fullLayout || !target._fullLayout) { | |
| return false; | |
| } | |
| if (graphDataset(neural) !== payload.dataset || graphDataset(target) !== payload.dataset) { | |
| return false; | |
| } | |
| const exampleTrace = traceIndex(target, "example_trajectory"); | |
| const progressTrace = traceIndex(target, "linked_target_progress"); | |
| const cursorTrace = traceIndex(target, "linked_target_cursor"); | |
| if ([exampleTrace, progressTrace, cursorTrace].some((index) => index < 0)) return false; | |
| const state = { | |
| payload, | |
| controls, | |
| neural, | |
| target, | |
| exampleTrace, | |
| progressTrace, | |
| cursorTrace, | |
| index: payload.initial_index || 0, | |
| playing: false, | |
| resumeAfterHover: false, | |
| animationFrame: null, | |
| renderFrame: null, | |
| autoplayTimer: null, | |
| resizeObserver: null, | |
| abortController: new AbortController(), | |
| plotlyListeners: [], | |
| }; | |
| const playhead = makeElement("div", "dataset-linked-playhead"); | |
| playhead.style.backgroundColor = CURSOR_COLOR; | |
| playhead.setAttribute("aria-hidden", "true"); | |
| neural.style.position = "relative"; | |
| neural.appendChild(playhead); | |
| state.playhead = playhead; | |
| buildControls(state); | |
| bindInteractions(state); | |
| activeState = state; | |
| renderIndex(state, state.index); | |
| maybeAutoplay(state); | |
| return true; | |
| } | |
| function schedule(payload, activeTab) { | |
| scheduleGeneration += 1; | |
| const generation = scheduleGeneration; | |
| destroy(activeState); | |
| activeState = null; | |
| const controls = document.getElementById("dataset-link-controls"); | |
| if (!payload || !payload.enabled || activeTab !== "datasets") { | |
| if (controls) controls.replaceChildren(); | |
| return `${payload && payload.dataset ? payload.dataset : "none"}:inactive`; | |
| } | |
| let attempts = 0; | |
| const tryMount = () => { | |
| if (generation !== scheduleGeneration) return; | |
| if (mount(payload)) return; | |
| attempts += 1; | |
| if (attempts < 180) requestAnimationFrame(tryMount); | |
| }; | |
| requestAnimationFrame(tryMount); | |
| return `${payload.dataset}:scheduled`; | |
| } | |
| window.benchdashDatasetLink = { schedule }; | |
| })(); | |