PageSourceSearch

https://embed-goodfire-attr-graph.netlify.app/attribution_graph/init-cg-feature-scatter.js

js embed-goodfire-attr-graph.netlify.app collected 2026-10-03 10:41:50 UTC 3,082 bytes, 85 lines download raw bytes

1// possible improvements:
2// - persist to url in diff view
3// - support multiple scatter plots
4// - color by x
5// - facet by x
6// - brush to filter
7// - scatter plot of links
8
9window.initCgFeatureScatter = function({visState, renderAll, data, cgSel}){
10  var nodes = data.nodes//.filter(d => !d.isLogit)
11
12  var numericCols = Object.entries(nodes[0])
13    .filter(([k, v]) => typeof v != 'object' && typeof v != 'function' && !utilCg.keysToSkip.has(k) && isFinite(v))
14    .map(([k]) => k)
15
16  var xKey = util.params.get('feature_scatter_x') || 'ctx_idx'
17  var yKey = util.params.get('feature_scatter_y') || 'target_influence'
18  function addSelect(isX){
19    var options = isX ? ['Distribution'].concat(numericCols) : numericCols
20    selectSel.append('select').st({marginRight: 10})
21      .on('change', function(){
22        isX ? xKey = options[this.selectedIndex] : yKey = options[this.selectedIndex]
23        isX ? util.params.set('feature_scatter_x', xKey) : util.params.set('feature_scatter_y', yKey)
24        renderScales()
25      })
26      .appendMany('option', options)
27      .text(d => d)
28      .at({value: d => d})
29      .filter(d => isX && d == xKey || !isX && d == yKey).at({selected: 'selected'})
30  }
31
32  var sel = cgSel.select('.feature-scatter').html('')
33  var selectSel = sel.append('div.select-container').st({marginLeft: 35})
34  var chartSel = sel.append('div.chart-container')
35  addSelect(1)
36  addSelect(0)
37
38  function renderScales(){
39    var c = d3.conventions({
40      sel: chartSel.html('').append('div'),
41      margin: {left: 35, bottom: 30, top: 2, right: 6},
42    })
43
44    if (xKey == 'Distribution'){
45      d3.sort(d3.sort(nodes, d => +d[yKey]), d => d.feature_type)
46        .forEach((d, i) => d.Distribution = i/nodes.length)
47    }
48
49    c.x.domain(d3.extent(nodes, d => +d[xKey])).nice()
50    c.y.domain(d3.extent(nodes, d => +d[yKey])).nice()
51
52    c.yAxis.ticks(3)
53    c.xAxis.ticks(5)
54    c.drawAxis()
55    util.ggPlot(c)
56    util.addAxisLabel(c, xKey + ' →', yKey + ' →', '', 0, 5)
57
58    var nodeSel = c.svg.appendMany('text.node', nodes)
59      .translate(d => [c.x(d[xKey]) ?? -2, c.y(d[yKey]) ?? c.height + 2])
60      .text(d => utilCg.featureTypeToText(d.feature_type))
61      .at({
62        fontSize: 7,
63        stroke: '#000',
64        strokeWidth: .2,
65        textAnchor: 'middle',
66        dominantBaseline: 'central',
67        fill: 'rgba(0,0,0,.1)'
68      })
69      .call(utilCg.addFeatureTooltip)
70      .call(utilCg.addFeatureEvents(visState, renderAll))
71
72    // TODO: add hover circle?
73    utilCg.updateFeatureStyles(nodeSel)
74    renderAll.hoveredId.fns['featureScatter'] = () => utilCg.updateFeatureStyles(nodeSel)
75    renderAll.clickedId.fns['featureScatter'] = () => utilCg.updateFeatureStyles(nodeSel)
76    renderAll.pinnedIds.fns['featureScatter'] = () => utilCg.updateFeatureStyles(nodeSel)
77    renderAll.hiddenIds.fns['featureScatter'] = () => utilCg.updateFeatureStyles(nodeSel)
78  }
79
80  // TODO: awkward, maybe gridsnap/widget inits need to be restructured?
81  if (!sel.datum().resizeFn) renderScales()
82  sel.datum().resizeFn = renderScales
83}
84
85window.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.