PageSourceSearch

https://embed-goodfire-attr-graph.netlify.app/util.js

js embed-goodfire-attr-graph.netlify.app collected 2026-10-03 10:41:42 UTC 13,422 bytes, 427 lines download raw bytes

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.