1window.util = (function () { 2 var params = (function(){ 3 var rv = {} 4 5 rv.get = key => { 6 var url = new URL(window.location) 7 var searchParams = new URLSearchParams(url.search) 8 9 var str = searchParams.get(key) 10 return str && decodeURIComponent(str) 11 } 12 13 rv.getAll = () => { 14 var url = new URL(window.location) 15 var searchParams = new URLSearchParams(url.search) 16 17 var values = {} 18 for (const [key, value] of searchParams.entries()) { 19 values[key] = decodeURIComponent(value) 20 } 21 return values 22 } 23 24 rv.set = (key, value) => { 25 var url = new URL(window.location) 26 var searchParams = new URLSearchParams(url.search) 27 28 if (value === null) { 29 searchParams.delete(key) 30 } else { 31 searchParams.set(key, encodeURIComponent(value)) 32 } 33 34 url.search = searchParams.toString() 35 history.replaceState(null, '', url) 36 } 37 38 return rv 39 })() 40 41 async function getFile(path) { 42 // Cache storage 43 // var __datacache = window.__datacache = window.__datacache || {} // TODO: re-enable caching 44 45 // If path starts with /, treat as relative to static_js directory 46 // if (path.startsWith('/')) { 47 // path = 'https://goodfire-clt-graph-viz.s3.us-east-1.amazonaws.com' + path 48 // } 49 50 // // Return cached result if available 51 // if (!__datacache[path]) __datacache[path] = __fetch() 52 // return __datacache[path] 53 return __fetch() 54 55 async function __fetch() { 56 var res = await fetch(path) // , {cache: 'force-cache'}) // TODO: re-enable caching 57 if (res.status == 500) { 58 var resText = await res.text() 59 console.log(resText, res) 60 throw '500 error' 61 } 62 63 var type = path.replaceAll('..', '').split('.').at(-1) 64 if (type == 'csv') { 65 return d3.csvParse(await res.text()) 66 } else if (type == 'npy') { 67 return npyjs.parse(await res.arrayBuffer()) 68 } else if (type == 'json') { 69 return await res.json() 70 } else if (type == 'jsonl') { 71 var text = await res.text() 72 return text.split(/\r?\n/).filter(d => d).map(line => JSON.parse(line)) 73 } else { 74 return await res.text() 75 } 76 } 77 } 78 79 80 function addAxisLabel(c, xText, yText, title='', xOffset=0, yOffset=0, titleOffset=0){ 81 c.svg.select('.x').append('g') 82 .translate([c.width/2, xOffset + 25]) 83 .append('text.axis-label') 84 .text(xText) 85 .at({textAnchor: 'middle', fill: '#000'}) 86 87 c.svg.select('.y') 88 .append('g') 89 .translate([yOffset -30, c.height/2]) 90 .append('text.axis-label') 91 .text(yText) 92 .at({textAnchor: 'middle', fill: '#000', transform: 'rotate(-90)'}) 93 94 c.svg 95 .append('g.axis').at({fontFamily: 'sans-serif'}) 96 .translate([c.width/2, titleOffset -10]) 97 .append('text.axis-label.axis-title') 98 .text(title) 99 .at({textAnchor: 'middle', fill: '#000'}) 100 } 101 102 function ggPlot(c){ 103 c.svg.append('rect.bg-rect') 104 .at({width: c.width, height: c.height, fill: c.isBlack ? '#000' : '#EAECED'}).lower() 105 c.svg.selectAll('.domain').remove() 106 107 c.svg.selectAll('.x text').at({y: 4}) 108 c.svg.selectAll('.x .tick') 109 .selectAppend('path').at({d: 'M 0 0 V -' + c.height, stroke: c.isBlack ? '#444' : '#fff', strokeWidth: 1}) 110 111 c.svg.selectAll('.y text').at({x: -3}) 112 c.svg.selectAll('.y .tick') 113 .selectAppend('path').at({d: 'M 0 0 H ' + c.width, stroke: c.isBlack? '#444' : '#fff', strokeWidth: 1}) 114 115 ggPlotUpdate(c) 116 } 117 118 function ggPlotUpdate(c){ 119 c.svg.selectAll('.tick').selectAll('line').remove() 120 121 c.svg.selectAll('.x text').at({y: 4}) 122 c.svg.selectAll('.x .tick') 123 .selectAppend('path').at({d: 'M 0 0 V -' + c.height, stroke: c.isBlack ? '#444' : '#fff', strokeWidth: 1}) 124 125 c.svg.selectAll('.y text').at({x: -3}) 126 c.svg.selectAll('.y .tick') 127 .selectAppend('path').at({d: 'M 0 0 H ' + c.width, stroke: c.isBlack? '#444' : '#fff', strokeWidth: 1}) 128 } 129 130 function initRenderAll(fnLabels){ 131 var rv = {}
132 fnLabels.forEach(label => { 133 rv[label] = (ev) => Object.values(rv[label].fns).forEach(d => d(ev)) 134 rv[label].fns = [] 135 }) 136 137 return rv 138 } 139 140 function attachRenderAllHistory(renderAll, skipKeys=['hoverId', 'hoverIdx']) { 141 // Add state pushing to each render function 142 Object.keys(renderAll).forEach(key => { 143 renderAll[key].fns.push(() => { 144 if (skipKeys.includes(key)) return 145 var simpleVisState = {...visState} 146 skipKeys.forEach(key => delete simpleVisState[key]) 147 148 var url = new URL(window.location) 149 if (visState[key] == url.searchParams.get(key)) return 150 url.searchParams.set(key, simpleVisState[key]) 151 history.pushState(simpleVisState, '', url) 152 }) 153 }) 154 155 // Handle back/forward navigation 156 d3.select(window).on('popstate.updateState', ev => { 157 if (!ev.state) return 158 ev.preventDefault() 159 Object.keys(renderAll).forEach(key => { 160 if (skipKeys.includes(key)) return 161 if (visState[key] == ev.state[key]) return 162 visState[key] = ev.state[key] 163 renderAll[key]() 164 }) 165 }) 166 } 167 168 function throttle(fn, delay){ 169 var lastCall = 0 170 return (...args) => { 171 if (Date.now() - lastCall < delay) return 172 lastCall = Date.now() 173 fn(...args) 174 } 175 } 176 177 function debounce(fn, delay) { 178 var timeout 179 return (...args) => { 180 clearTimeout(timeout) 181 timeout = setTimeout(() => fn(...args), delay) 182 } 183 } 184 185 function throttleDebounce(fn, delay) { 186 var lastCall = 0 187 var timeoutId 188 189 return function (...args) { 190 clearTimeout(timeoutId) 191 var remainingDelay = delay - (Date.now() - lastCall) 192 if (remainingDelay <= 0) { 193 lastCall = Date.now() 194 fn.apply(this, args) 195 } else { 196 timeoutId = setTimeout(() => { 197 lastCall = Date.now() 198 fn.apply(this, args) 199 }, remainingDelay) 200 } 201 } 202 } 203 204 function sleep(ms) { 205 return new Promise(resolve => setTimeout(resolve, ms)) 206 } 207 208 function cache(fn){ 209 var cache = {} 210 return function(...args){ 211 var key = JSON.stringify(args) 212 if (!(key in cache)) cache[key] = fn.apply(this, args) 213 return cache[key] 214 } 215 } 216 var featureExamplesTooltipSel 217 var featureExamples 218 var featureQueue = []
219 function attachFeatureExamplesTooltip(sel, getFeatureParams, getNearby){ 220 if (!featureExamplesTooltipSel){ 221 featureExamplesTooltipSel = d3.select('body') 222 .selectAppend('div.tooltip.feature-examples-tooltip.tooltip-hidden') 223 .on('mouseover', mousemove) 224 .on('mousemove', mousemove) 225 .on('mouseleave', mouseout) 226 227 // Add touch event handler to body 228 d3.select('body').on('click.feature-tooltip', ev => { 229 // Don't trigger if touch is on tooltip or tooltipped element 230 if (ev.target.closest('.feature-examples-tooltip') || ev.target.closest('.feature-examples-tooltipped')) return 231 mouseout() 232 }) 233 234 d3.select(window).on('scroll.feature-examples-tooltip', () => { 235 if (featureExamplesTooltipSel.isFading || featureExamplesTooltipSel.isFaded) return 236 mouseout() 237 }) 238 239 featureExamplesTooltipSel.append('div.feature-nav') 240 featureExamples = window.initFeatureExamples({ 241 containerSel: featureExamplesTooltipSel.append('div'), 242 hideStaleOutputs: true, 243 }) 244 245 if (window.__feature_tooltip_queue_timer) __feature_tooltip_queue_timer.stop() 246 window.__feature_tooltip_queue_timer = d3.timer(() => { 247 if (!featureQueue.length) return 248 var feature = featureQueue.pop() 249 featureExamples.loadFeature(feature.scan, feature.featureIndex) 250 }, 250) 251 } 252 253 sel 254 .on('mousemove.feature-examples-tooltip', mousemove) 255 .on('mouseleave.feature-examples-tooltip', mouseout) 256 .on('mouseenter.feature-examples-tooltip', function(ev, d){ 257 setTimeout(mousemove, 0) 258 259 // skip moving if we're just bouncing in and out of the current feature 260 if (featureExamplesTooltipSel.cur == d && !featureExamplesTooltipSel.classed('tooltip-hidden')) return 261 featureExamplesTooltipSel.cur = d 262 263 featureExamplesTooltipSel.node().scrollTop = -200 264 265 featureExamplesTooltipSel.isFaded = false 266 featureExamplesTooltipSel.classed('tooltip-hidden', 0) 267 268 // requires either featureIndex or featureIndices 269 var {scan, featureIndex, featureIndices}
269 = getFeatureParams(d) 270 featureIndices = featureIndices ?? [featureIndex] 271 featureIndex = featureIndex ?? featureIndices[0] 272 273 var buttonSel = featureExamplesTooltipSel.select('.feature-nav').html('') 274 .appendMany('div.button', featureIndices) 275 .text((_, i) => 'Feature ' + (i + 1)) 276 .classed('active', idx => idx == featureIndex) 277 .on('click', (ev, idx) => { 278 featureExamples.renderFeature(scan, idx) 279 buttonSel.classed('active', idx2 => idx2 == idx) 280 }) 281 282 featureExamples.renderFeature(scan, featureIndex) 283 284 d3.selectAll('.feature-examples-tooltipped').classed('feature-examples-tooltipped', 0) 285 d3.select(this).classed('feature-examples-tooltipped', 1) 286 287 var snBB = this.getBoundingClientRect() 288 var ttBB = featureExamplesTooltipSel.node().getBoundingClientRect() 289 var left = d3.clamp(20, (ev.clientX-ttBB.width/2), window.innerWidth - ttBB.width - 20) 290 var top = snBB.top > innerHeight - snBB.bottom ? 291 snBB.top - ttBB.height - 10 : 292 snBB.bottom + 10 293 featureExamplesTooltipSel.st({left, top, pointerEvents: 'all'}) 294 295 getNearby?.(d).forEach(e => featureQueue.push(e)) 296 }) 297 298 function mousemove(){ 299 if (window.__ttfade) window.__ttfade.stop() 300 featureExamplesTooltipSel.isFading = false 301 featureExamplesTooltipSel.isFaded = false 302 } 303 304 function mouseout(){ 305 if (featureExamplesTooltipSel.isFading) return 306 307 if (window.__ttfade) window.__ttfade.stop() 308 featureExamplesTooltipSel.isFading = true 309 window.__ttfade = d3.timeout(() => { 310 featureExamplesTooltipSel.classed('tooltip-hidden', 1).st({pointerEvents: 'none'}) 311 d3.selectAll('.feature-examples-tooltipped').classed('feature-examples-tooltipped', 0) 312 featureExamplesTooltipSel.isFading = false 313 featureExamplesTooltipSel.isFaded = true 314 }, 250) 315 } 316 } 317 318 async function initGraphSelect(sel, cgSlug){ 319 var {graphs} = await util.getFile('/data/graph-metadata.json') 320 321 graphs = graphs.map(g => { 322 g.slug = g.scan + '/' + g.slug 323 return g 324 }) 325 326 var selectSel = sel.html('').append('select.graph-prompt-select') 327 .on('change', function() { 328 cgSlug = this.value 329 // visState.clickedId = undefined 330 util.params.set('slug', this.value) 331 render() 332 }) 333 334 var cgSel = sel.append('div.cg-container') 335 336 selectSel.appendMany('option', graphs) 337 .text(d => { 338 var scanName = util.nameToPrettyPrint[d.scan] || d.scan 339 var prefix = d.title_prefix ? d.title_prefix + ' ' : '' 340 return prefix + scanName + ' â ' + d.prompt 341 }) 342 .attr('value', d => d.slug) 343 .property('selected', d => d.slug === cgSlug) 344 345 function render() { 346 initCg(cgSel.html(''), cgSlug, { 347 isModal: true, 348 // clickedId: visState.clickedId, 349 // clickedIdCb: id => util.params.set('clickedId', id) 350 }) 351 352 var m = graphs.find(g => g.slug == cgSlug) 353 if (!m) return 354 selectSel.at({title: m.prompt}) 355 } 356 render() 357 } 358 359 function attachCgLinkEvents(sel, cgSlug, figmaSlug){ 360 sel 361 .on('mouseover', () => util.getFile(`/graph_data/${cgSlug}.json`)) 362 .on('click', (ev) => { 363 ev.preventDefault() 364 365 if (window.innerWidth < 900 || window.innerHeight < 500) { 366 return window.open(`./static_js/attribution_graphs/index.html?slug=${cgSlug}`, '_blank') 367 } 368 369 d3.select('body').classed('modal-open', true) 370 var contentSel = d3.select('modal').classed('is-active', 1) 371 .select('.modal-content').html('') 372 373 util.initGraphSelect(contentSel, cgSlug) 374 375 util.params.set('slug', cgSlug) 376 if (figmaSlug) history.replaceState(null, '', '#' + figmaSlug) 377 }) 378 } 379 380 // TODO: tidy 381 function ppToken(d){ 382 return d 383 } 384 385 function ppClerp(d){ 386 return d 387 } 388 389 390 var scanSlugToName = { // no need to add ours here - just used for hiding layer count 391 'h35': 'jackl-circuits-runs-1-4-sofa-v3_0', 392 '18l': 'jackl-circuits-runs-1-1-druid-cp_0', 393 'moc': 'jackl-circuits-runs-12-19-valet-m_0' 394 } 395 396 var nameToPrettyPrint = { // TODO: add ours to this 397 'jackl-circuits-runs-1-4-sofa-v3_0': 'Haiku',
398 'jackl-circuits-runs-1-1-druid-cp_0': '18L', 399 'jackl-circuits-runs-12-19-valet-m_0': 'Model Organism', 400 'jackl-circuits-runs-1-12-rune-cp3_0': '18L PLTs', 401 } 402 403 404 return { 405 scanSlugToName, 406 nameToPrettyPrint, 407 params, 408 getFile, 409 addAxisLabel, 410 ggPlot, 411 ggPlotUpdate, 412 initRenderAll, 413 attachRenderAllHistory, 414 throttle, 415 debounce, 416 throttleDebounce, 417 sleep, 418 cache, 419 initGraphSelect, 420 attachCgLinkEvents, 421 ppToken, 422 ppClerp, 423 attachFeatureExamplesTooltip, 424 } 425})() 426 427window.init?.()
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.