joelniklaus HF Staff commited on
Commit
7db0003
·
1 Parent(s): 23fd567

improved plots for mobile

Browse files
app/src/content/embeds/d3-benchmark-comparison.html CHANGED
@@ -45,6 +45,7 @@
45
  .d3-benchmark-comparison { position: relative; }
46
  .d3-benchmark-comparison .controls {
47
  display: flex;
 
48
  gap: 16px;
49
  align-items: flex-end;
50
  justify-content: center;
@@ -158,6 +159,28 @@
158
  margin-right: 6px;
159
  vertical-align: middle;
160
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  </style>
162
  <script>
163
  (() => {
@@ -376,7 +399,8 @@
376
  tipInner.innerHTML = html;
377
  const tipW = tip.offsetWidth || 180;
378
  const cW = container.clientWidth || 800;
379
- const px = (x + tipW + 20 > cW) ? x - tipW - 12 : x + 12;
 
380
  tip.style.transform = `translate(${px}px, ${Math.max(0, y - 20)}px)`;
381
  tip.style.opacity = '1';
382
  }
@@ -411,8 +435,8 @@
411
  // ─── BAR CHART ───
412
  function renderBar() {
413
  const width = container.clientWidth || 800;
 
414
  const hasBaselines = allData.some(r => isBaseline(r[RUN_COL]));
415
- const margin = { top: hasBaselines ? 20 : 12, right: 56, bottom: 32, left: 190 };
416
 
417
  const grouped = d3.group(allData, d => d[RUN_COL]);
418
  const finalData = [];
@@ -425,6 +449,17 @@
425
 
426
  const barData = finalData.filter(d => !isBaseline(d.rawName));
427
  const baselineData = finalData.filter(d => isBaseline(d.rawName));
 
 
 
 
 
 
 
 
 
 
 
428
 
429
  const barHeight = 28, barGap = 8;
430
  const height = margin.top + margin.bottom + barData.length * (barHeight + barGap);
@@ -446,9 +481,9 @@
446
  // X axis
447
  gRoot.selectAll('.axis-x').data([0]).join('g').attr('class', 'axes axis-x')
448
  .attr('transform', `translate(0,${innerHeight})`)
449
- .call(d3.axisBottom(x).ticks(5).tickFormat(d3.format('.2f')).tickSizeOuter(0))
450
  .call(g => {
451
- g.selectAll('text').attr('fill', 'var(--tick-color)').style('font-size', '11px');
452
  g.selectAll('path, line').attr('stroke', 'var(--axis-color)');
453
  });
454
 
@@ -456,7 +491,7 @@
456
  gRoot.selectAll('.axis-y').data([0]).join('g').attr('class', 'axes axis-y')
457
  .call(d3.axisLeft(y).tickSizeOuter(0))
458
  .call(g => {
459
- g.selectAll('text').attr('fill', 'var(--text-color)').style('font-size', '12px').style('font-weight', '500');
460
  g.selectAll('path, line').attr('stroke', 'var(--axis-color)');
461
  });
462
 
@@ -504,7 +539,7 @@
504
  gRoot.selectAll('text.value-label').data(barData, d => d.name).join(
505
  enter => enter.append('text').attr('class', 'value-label')
506
  .attr('x', d => x(d.value) + 5).attr('y', d => y(d.name) + y.bandwidth() / 2)
507
- .attr('dy', '0.35em').attr('fill', 'var(--text-color)').attr('font-size', 11)
508
  .text(d => d.value.toFixed(3)),
509
  update => update.transition().duration(300)
510
  .attr('x', d => x(d.value) + 5).attr('y', d => y(d.name) + y.bandwidth() / 2)
@@ -529,7 +564,7 @@
529
  enter => enter.append('text').attr('class', 'baseline-vlabel baseline')
530
  .attr('x', d => x(d.value)).attr('y', -4)
531
  .attr('text-anchor', 'middle').attr('fill', d => colorMap[d.rawName] || '#999')
532
- .attr('font-size', 11).attr('font-weight', 600)
533
  .text(d => `${d.name} (${d.value.toFixed(3)})`),
534
  update => update.transition().duration(300)
535
  .attr('x', d => x(d.value))
@@ -541,9 +576,10 @@
541
  // ─── LINE CHART ───
542
  function renderLine() {
543
  const width = container.clientWidth || 800;
 
544
  const hasBaselines = allData.some(r => isBaseline(r[RUN_COL]));
545
- const margin = { top: 16, right: 50, bottom: 48, left: 60 };
546
- const height = Math.max(300, Math.round(width / 2.5));
547
  svg.attr('width', width).attr('height', height);
548
  gRoot.attr('transform', `translate(${margin.left},${margin.top})`);
549
 
@@ -581,29 +617,34 @@
581
  // X axis
582
  gRoot.selectAll('.axis-x').data([0]).join('g').attr('class', 'axes axis-x')
583
  .attr('transform', `translate(0,${innerHeight})`)
584
- .call(d3.axisBottom(x).ticks(6).tickFormat(d => stepLabelShort(d)).tickSizeOuter(0))
 
 
 
 
 
585
  .call(g => {
586
- g.selectAll('text').attr('fill', 'var(--tick-color)').style('font-size', '10px');
587
  g.selectAll('path, line').attr('stroke', 'var(--axis-color)');
588
  });
589
 
590
  // Y axis
591
  gRoot.selectAll('.axis-y').data([0]).join('g').attr('class', 'axes axis-y')
592
- .call(d3.axisLeft(y).ticks(6).tickFormat(d3.format('.2f')).tickSizeOuter(0))
593
  .call(g => {
594
- g.selectAll('text').attr('fill', 'var(--tick-color)').style('font-size', '11px');
595
  g.selectAll('path, line').attr('stroke', 'var(--axis-color)');
596
  });
597
 
598
  // Axis labels
599
  gRoot.selectAll('.x-label').data([0]).join('text').attr('class', 'x-label')
600
- .attr('x', innerWidth / 2).attr('y', innerHeight + 38)
601
- .attr('text-anchor', 'middle').attr('fill', 'var(--text-color)').attr('font-size', 12)
602
  .text('Tokens (Steps)');
603
 
604
  gRoot.selectAll('.y-label').data([0]).join('text').attr('class', 'y-label')
605
- .attr('transform', 'rotate(-90)').attr('x', -innerHeight / 2).attr('y', -44)
606
- .attr('text-anchor', 'middle').attr('fill', 'var(--text-color)').attr('font-size', 12)
607
  .text(metricName(currentMetric));
608
 
609
  // Baseline horizontal reference lines
@@ -624,7 +665,7 @@
624
  .attr('x', 4).attr('y', d => y(d.finalValue) - 6)
625
  .attr('text-anchor', 'start')
626
  .attr('fill', d => colorMap[d.rawName] || '#999')
627
- .attr('font-size', 10).attr('font-weight', 600)
628
  .text(d => `${d.name} (${d.finalValue.toFixed(3)})`),
629
  update => update.transition().duration(300)
630
  .attr('x', 4).attr('y', d => y(d.finalValue) - 6)
 
45
  .d3-benchmark-comparison { position: relative; }
46
  .d3-benchmark-comparison .controls {
47
  display: flex;
48
+ flex-wrap: wrap;
49
  gap: 16px;
50
  align-items: flex-end;
51
  justify-content: center;
 
159
  margin-right: 6px;
160
  vertical-align: middle;
161
  }
162
+ @media (max-width: 640px) {
163
+ .d3-benchmark-comparison .controls {
164
+ flex-direction: column;
165
+ align-items: stretch;
166
+ gap: 10px;
167
+ }
168
+ .d3-benchmark-comparison .controls .control-group {
169
+ width: 100%;
170
+ }
171
+ .d3-benchmark-comparison .controls select {
172
+ width: 100%;
173
+ }
174
+ .d3-benchmark-comparison .legend .item {
175
+ white-space: normal;
176
+ align-items: flex-start;
177
+ line-height: 1.2;
178
+ }
179
+ .d3-benchmark-comparison .legend .swatch {
180
+ flex-shrink: 0;
181
+ margin-top: 1px;
182
+ }
183
+ }
184
  </style>
185
  <script>
186
  (() => {
 
399
  tipInner.innerHTML = html;
400
  const tipW = tip.offsetWidth || 180;
401
  const cW = container.clientWidth || 800;
402
+ const preferredX = (x + tipW + 20 > cW) ? x - tipW - 12 : x + 12;
403
+ const px = Math.max(0, Math.min(preferredX, Math.max(0, cW - tipW - 6)));
404
  tip.style.transform = `translate(${px}px, ${Math.max(0, y - 20)}px)`;
405
  tip.style.opacity = '1';
406
  }
 
435
  // ─── BAR CHART ───
436
  function renderBar() {
437
  const width = container.clientWidth || 800;
438
+ const isMobile = width < 640;
439
  const hasBaselines = allData.some(r => isBaseline(r[RUN_COL]));
 
440
 
441
  const grouped = d3.group(allData, d => d[RUN_COL]);
442
  const finalData = [];
 
449
 
450
  const barData = finalData.filter(d => !isBaseline(d.rawName));
451
  const baselineData = finalData.filter(d => isBaseline(d.rawName));
452
+ const maxLabelChars = d3.max(finalData, d => d.name.length) || 0;
453
+ const desiredLeft = Math.max(
454
+ isMobile ? 92 : 150,
455
+ Math.round(maxLabelChars * (isMobile ? 5.2 : 6.3))
456
+ );
457
+ const margin = {
458
+ top: hasBaselines ? 20 : 12,
459
+ right: isMobile ? 40 : 56,
460
+ bottom: isMobile ? 30 : 32,
461
+ left: Math.min(desiredLeft, isMobile ? 126 : 220),
462
+ };
463
 
464
  const barHeight = 28, barGap = 8;
465
  const height = margin.top + margin.bottom + barData.length * (barHeight + barGap);
 
481
  // X axis
482
  gRoot.selectAll('.axis-x').data([0]).join('g').attr('class', 'axes axis-x')
483
  .attr('transform', `translate(0,${innerHeight})`)
484
+ .call(d3.axisBottom(x).ticks(isMobile ? 4 : 5).tickFormat(d3.format('.2f')).tickSizeOuter(0))
485
  .call(g => {
486
+ g.selectAll('text').attr('fill', 'var(--tick-color)').style('font-size', isMobile ? '10px' : '11px');
487
  g.selectAll('path, line').attr('stroke', 'var(--axis-color)');
488
  });
489
 
 
491
  gRoot.selectAll('.axis-y').data([0]).join('g').attr('class', 'axes axis-y')
492
  .call(d3.axisLeft(y).tickSizeOuter(0))
493
  .call(g => {
494
+ g.selectAll('text').attr('fill', 'var(--text-color)').style('font-size', isMobile ? '11px' : '12px').style('font-weight', '500');
495
  g.selectAll('path, line').attr('stroke', 'var(--axis-color)');
496
  });
497
 
 
539
  gRoot.selectAll('text.value-label').data(barData, d => d.name).join(
540
  enter => enter.append('text').attr('class', 'value-label')
541
  .attr('x', d => x(d.value) + 5).attr('y', d => y(d.name) + y.bandwidth() / 2)
542
+ .attr('dy', '0.35em').attr('fill', 'var(--text-color)').attr('font-size', isMobile ? 10 : 11)
543
  .text(d => d.value.toFixed(3)),
544
  update => update.transition().duration(300)
545
  .attr('x', d => x(d.value) + 5).attr('y', d => y(d.name) + y.bandwidth() / 2)
 
564
  enter => enter.append('text').attr('class', 'baseline-vlabel baseline')
565
  .attr('x', d => x(d.value)).attr('y', -4)
566
  .attr('text-anchor', 'middle').attr('fill', d => colorMap[d.rawName] || '#999')
567
+ .attr('font-size', isMobile ? 10 : 11).attr('font-weight', 600)
568
  .text(d => `${d.name} (${d.value.toFixed(3)})`),
569
  update => update.transition().duration(300)
570
  .attr('x', d => x(d.value))
 
576
  // ─── LINE CHART ───
577
  function renderLine() {
578
  const width = container.clientWidth || 800;
579
+ const isMobile = width < 640;
580
  const hasBaselines = allData.some(r => isBaseline(r[RUN_COL]));
581
+ const margin = { top: 16, right: isMobile ? 18 : 50, bottom: isMobile ? 42 : 48, left: isMobile ? 46 : 60 };
582
+ const height = Math.max(isMobile ? 260 : 300, Math.round(width / (isMobile ? 1.95 : 2.5)));
583
  svg.attr('width', width).attr('height', height);
584
  gRoot.attr('transform', `translate(${margin.left},${margin.top})`);
585
 
 
617
  // X axis
618
  gRoot.selectAll('.axis-x').data([0]).join('g').attr('class', 'axes axis-x')
619
  .attr('transform', `translate(0,${innerHeight})`)
620
+ .call(
621
+ d3.axisBottom(x)
622
+ .ticks(isMobile ? 4 : 6)
623
+ .tickFormat(d => isMobile ? formatTokens(stepsToTokens(d)) : stepLabelShort(d))
624
+ .tickSizeOuter(0)
625
+ )
626
  .call(g => {
627
+ g.selectAll('text').attr('fill', 'var(--tick-color)').style('font-size', isMobile ? '9px' : '10px');
628
  g.selectAll('path, line').attr('stroke', 'var(--axis-color)');
629
  });
630
 
631
  // Y axis
632
  gRoot.selectAll('.axis-y').data([0]).join('g').attr('class', 'axes axis-y')
633
+ .call(d3.axisLeft(y).ticks(isMobile ? 5 : 6).tickFormat(d3.format('.2f')).tickSizeOuter(0))
634
  .call(g => {
635
+ g.selectAll('text').attr('fill', 'var(--tick-color)').style('font-size', isMobile ? '10px' : '11px');
636
  g.selectAll('path, line').attr('stroke', 'var(--axis-color)');
637
  });
638
 
639
  // Axis labels
640
  gRoot.selectAll('.x-label').data([0]).join('text').attr('class', 'x-label')
641
+ .attr('x', innerWidth / 2).attr('y', innerHeight + (isMobile ? 32 : 38))
642
+ .attr('text-anchor', 'middle').attr('fill', 'var(--text-color)').attr('font-size', isMobile ? 11 : 12)
643
  .text('Tokens (Steps)');
644
 
645
  gRoot.selectAll('.y-label').data([0]).join('text').attr('class', 'y-label')
646
+ .attr('transform', 'rotate(-90)').attr('x', -innerHeight / 2).attr('y', isMobile ? -34 : -44)
647
+ .attr('text-anchor', 'middle').attr('fill', 'var(--text-color)').attr('font-size', isMobile ? 11 : 12)
648
  .text(metricName(currentMetric));
649
 
650
  // Baseline horizontal reference lines
 
665
  .attr('x', 4).attr('y', d => y(d.finalValue) - 6)
666
  .attr('text-anchor', 'start')
667
  .attr('fill', d => colorMap[d.rawName] || '#999')
668
+ .attr('font-size', isMobile ? 9 : 10).attr('font-weight', 600)
669
  .text(d => `${d.name} (${d.finalValue.toFixed(3)})`),
670
  update => update.transition().duration(300)
671
  .attr('x', 4).attr('y', d => y(d.finalValue) - 6)
app/src/content/embeds/score-correlation.html CHANGED
@@ -1,6 +1,6 @@
1
  <div class="d3-score-correlation" style="width:100%;margin:10px 0;min-height:400px;"></div>
2
  <style>
3
- .d3-score-correlation { font-family: system-ui, -apple-system, sans-serif; }
4
  .d3-score-correlation .d3-tooltip {
5
  position: absolute; top: 0; left: 0;
6
  transform: translate(-9999px, -9999px);
@@ -29,6 +29,10 @@
29
  .d3-score-correlation .legend .swatch {
30
  width: 20px; height: 14px; border-radius: 3px; border: 1px solid var(--border-color);
31
  }
 
 
 
 
32
  </style>
33
  <script>
34
  (() => {
@@ -291,6 +295,7 @@
291
 
292
  const render = () => {
293
  const width = container.clientWidth || 900;
 
294
  const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
295
  const divColor = isDark ? 'rgba(255,255,255,0.22)' : 'rgba(0,0,0,0.18)';
296
  const textCol = isDark ? 'rgba(255,255,255,0.8)' : 'rgba(0,0,0,0.7)';
@@ -299,14 +304,14 @@
299
  const predLabels = PREDICTORS.map(p => p.label);
300
 
301
  // Layout
302
- const leftMargin = 140;
303
- const topMargin = 130; // extra room for two-tier header
304
- const rightMargin = 10;
305
  const bottomMargin = 10;
306
- const cellW = Math.max(30, Math.min(52, (width - leftMargin - rightMargin) / ALL_TARGETS.length));
307
- const cellH = Math.max(28, Math.min(42, cellW * 0.82));
308
- const plotW = cellW * ALL_TARGETS.length;
309
- const rowGap = 8; // gap between DCLM and EDU groups
310
  const plotH = cellH * predLabels.length + rowGap;
311
  const totalW = leftMargin + plotW + rightMargin;
312
  const totalH = topMargin + plotH + bottomMargin;
@@ -333,7 +338,6 @@
333
  // --- Group dividers (vertical) and header labels ---
334
  let colOffset = 0;
335
  const groupHeaderY = 18; // top-level group name
336
- const colLabelY = topMargin - 6; // individual column labels
337
 
338
  GROUPS.forEach((grp, gi) => {
339
  const groupStartX = colOffset * cellW;
@@ -349,25 +353,27 @@
349
  .attr('stroke-dasharray', gi === 1 ? 'none' : '4,3');
350
  }
351
 
352
- // Group header label (top tier)
353
- svg.append('text')
354
- .attr('x', leftMargin + groupStartX + groupW / 2)
355
- .attr('y', groupHeaderY)
356
- .attr('text-anchor', 'middle')
357
- .attr('font-size', '9.5px')
358
- .attr('font-weight', '700')
359
- .attr('letter-spacing', '0.5px')
360
- .attr('fill', mutedCol)
361
- .text(grp.name.toUpperCase());
362
-
363
- // Bracket line under group header
364
- const bracketY = groupHeaderY + 8;
365
- svg.append('line')
366
- .attr('x1', leftMargin + groupStartX + 4)
367
- .attr('x2', leftMargin + groupStartX + groupW - 4)
368
- .attr('y1', bracketY).attr('y2', bracketY)
369
- .attr('stroke', mutedCol)
370
- .attr('stroke-width', 0.8);
 
 
371
 
372
  colOffset += grp.targets.length;
373
  });
@@ -409,16 +415,16 @@
409
  .attr('y', (cellH - 1) / 2)
410
  .attr('text-anchor', 'middle')
411
  .attr('dominant-baseline', 'central')
412
- .attr('font-size', Math.max(9, Math.min(12, cellW * 0.24)) + 'px')
413
  .attr('font-weight', d => Math.abs(d.r) > 0.4 ? '700' : '500')
414
  .attr('fill', d => textFill(d.r))
415
  .text(d => d.r.toFixed(2));
416
 
417
  // Significance markers
418
  cells.append('text')
419
- .attr('x', cellW - 3).attr('y', 10)
420
  .attr('text-anchor', 'end')
421
- .attr('font-size', '11px')
422
  .attr('font-weight', '700')
423
  .attr('fill', d => Math.abs(d.r) > 0.45 ? 'rgba(255,255,255,0.8)' : mutedCol)
424
  .text(d => d.p < 0.001 ? '***' : d.p < 0.01 ? '**' : d.p < 0.05 ? '*' : '');
@@ -428,15 +434,21 @@
428
  PREDICTORS.forEach((pred, i) => {
429
  const labelG = gLabels.append('g')
430
  .style('cursor', 'help');
 
 
 
 
 
 
431
 
432
  labelG.append('text')
433
  .attr('x', 0).attr('y', rowY(i) + cellH / 2)
434
  .attr('text-anchor', 'end')
435
  .attr('dominant-baseline', 'central')
436
- .attr('font-size', '11px')
437
  .attr('fill', textCol)
438
  .attr('font-weight', '500')
439
- .text(pred.label);
440
 
441
  // Hit area
442
  labelG.append('rect')
@@ -470,9 +482,9 @@
470
 
471
  labelG.append('text')
472
  .attr('x', 0).attr('y', 0)
473
- .attr('transform', 'rotate(-55)')
474
  .attr('text-anchor', 'start')
475
- .attr('font-size', '10px')
476
  .attr('fill', textCol)
477
  .attr('font-weight', tgt.isAgg ? '700' : '400')
478
  .text(tgt.label);
@@ -501,51 +513,52 @@
501
  }
502
  });
503
 
504
- // --- Predictor group labels (vertical) ---
505
- const dclmCenterY = topMargin + (rowY(0) + rowY(DCLM_COUNT - 1) + cellH) / 2;
506
- const eduCenterY = topMargin + (rowY(DCLM_COUNT) + rowY(PREDICTORS.length - 1) + cellH) / 2;
507
- const groupLabelX = 14;
508
-
509
- const GROUP_DESC = {
510
- 'DCLM': 'DCLM score rates text quality on a 0–1 scale using a fastText classifier trained to distinguish curated, high-quality web data from random web crawls.',
511
- 'EDU': 'FineWeb-Edu score rates educational value on a 0–5 scale using a classifier trained on LLM-annotated web pages, where higher scores indicate more instructive content.',
512
- };
513
-
514
- [['DCLM', dclmCenterY], ['EDU', eduCenterY]].forEach(([text, cy]) => {
515
- const labelG = svg.append('g').style('cursor', 'help');
516
-
517
- labelG.append('text')
518
- .attr('x', groupLabelX).attr('y', cy)
519
- .attr('text-anchor', 'middle')
520
- .attr('dominant-baseline', 'central')
521
- .attr('font-size', '9px')
522
- .attr('font-weight', '700')
523
- .attr('letter-spacing', '1px')
524
- .attr('fill', isDark ? 'rgba(255,255,255,0.35)' : 'rgba(0,0,0,0.3)')
525
- .attr('transform', `rotate(-90, ${groupLabelX}, ${cy})`)
526
- .text(text);
527
-
528
- // Hit area for the rotated text
529
- const halfH = (DCLM_COUNT * cellH) / 2;
530
- labelG.append('rect')
531
- .attr('x', 0).attr('y', cy - halfH)
532
- .attr('width', 24).attr('height', halfH * 2)
533
- .attr('fill', 'transparent');
 
534
 
535
- labelG.on('mouseenter', function() {
536
- tip.innerHTML = `<div style="font-weight:700;font-size:13px;margin-bottom:4px;">${text} Score</div><div style="font-size:12px;color:var(--muted-color);line-height:1.45;">${GROUP_DESC[text]}</div>`;
537
- tip.style.opacity = '1';
538
- })
539
- .on('mousemove', function(ev) {
540
- const [mx, my] = d3.pointer(ev, container);
541
- const bw = tip.offsetWidth || 260;
542
- tip.style.transform = `translate(${Math.round(mx + 12)}px,${Math.round(my + 14)}px)`;
543
- })
544
- .on('mouseleave', function() {
545
- tip.style.opacity = '0';
546
- tip.style.transform = 'translate(-9999px,-9999px)';
547
  });
548
- });
549
 
550
  // --- Tooltip interactions ---
551
  cells.on('mouseenter', function(ev, d) {
@@ -603,7 +616,7 @@
603
  <span class="item"><span class="swatch" style="background:${sw(0)};"></span><span>ρ = 0</span></span>
604
  <span class="item"><span class="swatch" style="background:${sw(0.3)};"></span><span>ρ = +0.3</span></span>
605
  <span class="item"><span class="swatch" style="background:${sw(0.6)};"></span><span>ρ = +0.6</span></span>
606
- <span style="margin-left:12px;font-size:11px;color:var(--muted-color);">*** p&lt;0.001 &nbsp; ** p&lt;0.01 &nbsp; * p&lt;0.05</span>
607
  </div>`;
608
  container.appendChild(legend);
609
  }
 
1
  <div class="d3-score-correlation" style="width:100%;margin:10px 0;min-height:400px;"></div>
2
  <style>
3
+ .d3-score-correlation { font-family: system-ui, -apple-system, sans-serif; position: relative; overflow-x: hidden; }
4
  .d3-score-correlation .d3-tooltip {
5
  position: absolute; top: 0; left: 0;
6
  transform: translate(-9999px, -9999px);
 
29
  .d3-score-correlation .legend .swatch {
30
  width: 20px; height: 14px; border-radius: 3px; border: 1px solid var(--border-color);
31
  }
32
+ @media (max-width: 640px) {
33
+ .d3-score-correlation .legend .item { font-size: 10px; }
34
+ .d3-score-correlation .legend .swatch { width: 16px; height: 12px; }
35
+ }
36
  </style>
37
  <script>
38
  (() => {
 
295
 
296
  const render = () => {
297
  const width = container.clientWidth || 900;
298
+ const isMobile = width < 640;
299
  const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
300
  const divColor = isDark ? 'rgba(255,255,255,0.22)' : 'rgba(0,0,0,0.18)';
301
  const textCol = isDark ? 'rgba(255,255,255,0.8)' : 'rgba(0,0,0,0.7)';
 
304
  const predLabels = PREDICTORS.map(p => p.label);
305
 
306
  // Layout
307
+ const leftMargin = isMobile ? 126 : 140;
308
+ const topMargin = isMobile ? 90 : 130; // extra room for two-tier header on desktop
309
+ const rightMargin = isMobile ? 8 : 10;
310
  const bottomMargin = 10;
311
+ const plotW = Math.max(220, width - leftMargin - rightMargin);
312
+ const cellW = plotW / ALL_TARGETS.length;
313
+ const cellH = isMobile ? 26 : Math.max(28, Math.min(42, cellW * 0.82));
314
+ const rowGap = isMobile ? 6 : 8; // gap between DCLM and EDU groups
315
  const plotH = cellH * predLabels.length + rowGap;
316
  const totalW = leftMargin + plotW + rightMargin;
317
  const totalH = topMargin + plotH + bottomMargin;
 
338
  // --- Group dividers (vertical) and header labels ---
339
  let colOffset = 0;
340
  const groupHeaderY = 18; // top-level group name
 
341
 
342
  GROUPS.forEach((grp, gi) => {
343
  const groupStartX = colOffset * cellW;
 
353
  .attr('stroke-dasharray', gi === 1 ? 'none' : '4,3');
354
  }
355
 
356
+ if (!isMobile) {
357
+ // Group header label (top tier)
358
+ svg.append('text')
359
+ .attr('x', leftMargin + groupStartX + groupW / 2)
360
+ .attr('y', groupHeaderY)
361
+ .attr('text-anchor', 'middle')
362
+ .attr('font-size', '9.5px')
363
+ .attr('font-weight', '700')
364
+ .attr('letter-spacing', '0.5px')
365
+ .attr('fill', mutedCol)
366
+ .text(grp.name.toUpperCase());
367
+
368
+ // Bracket line under group header
369
+ const bracketY = groupHeaderY + 8;
370
+ svg.append('line')
371
+ .attr('x1', leftMargin + groupStartX + 4)
372
+ .attr('x2', leftMargin + groupStartX + groupW - 4)
373
+ .attr('y1', bracketY).attr('y2', bracketY)
374
+ .attr('stroke', mutedCol)
375
+ .attr('stroke-width', 0.8);
376
+ }
377
 
378
  colOffset += grp.targets.length;
379
  });
 
415
  .attr('y', (cellH - 1) / 2)
416
  .attr('text-anchor', 'middle')
417
  .attr('dominant-baseline', 'central')
418
+ .attr('font-size', Math.max(isMobile ? 7.5 : 9, Math.min(isMobile ? 10 : 12, cellW * 0.25)) + 'px')
419
  .attr('font-weight', d => Math.abs(d.r) > 0.4 ? '700' : '500')
420
  .attr('fill', d => textFill(d.r))
421
  .text(d => d.r.toFixed(2));
422
 
423
  // Significance markers
424
  cells.append('text')
425
+ .attr('x', cellW - 2).attr('y', isMobile ? 8 : 10)
426
  .attr('text-anchor', 'end')
427
+ .attr('font-size', isMobile ? '8px' : '11px')
428
  .attr('font-weight', '700')
429
  .attr('fill', d => Math.abs(d.r) > 0.45 ? 'rgba(255,255,255,0.8)' : mutedCol)
430
  .text(d => d.p < 0.001 ? '***' : d.p < 0.01 ? '**' : d.p < 0.05 ? '*' : '');
 
434
  PREDICTORS.forEach((pred, i) => {
435
  const labelG = gLabels.append('g')
436
  .style('cursor', 'help');
437
+ const labelText = isMobile
438
+ ? pred.label
439
+ .replace('Input ', 'In ')
440
+ .replace('Output ', 'Out ')
441
+ .replace('Improvement %', 'Δ%')
442
+ : pred.label;
443
 
444
  labelG.append('text')
445
  .attr('x', 0).attr('y', rowY(i) + cellH / 2)
446
  .attr('text-anchor', 'end')
447
  .attr('dominant-baseline', 'central')
448
+ .attr('font-size', isMobile ? '10px' : '11px')
449
  .attr('fill', textCol)
450
  .attr('font-weight', '500')
451
+ .text(labelText);
452
 
453
  // Hit area
454
  labelG.append('rect')
 
482
 
483
  labelG.append('text')
484
  .attr('x', 0).attr('y', 0)
485
+ .attr('transform', `rotate(${isMobile ? -62 : -55})`)
486
  .attr('text-anchor', 'start')
487
+ .attr('font-size', isMobile ? '8px' : '10px')
488
  .attr('fill', textCol)
489
  .attr('font-weight', tgt.isAgg ? '700' : '400')
490
  .text(tgt.label);
 
513
  }
514
  });
515
 
516
+ if (!isMobile) {
517
+ // --- Predictor group labels (vertical) ---
518
+ const dclmCenterY = topMargin + (rowY(0) + rowY(DCLM_COUNT - 1) + cellH) / 2;
519
+ const eduCenterY = topMargin + (rowY(DCLM_COUNT) + rowY(PREDICTORS.length - 1) + cellH) / 2;
520
+ const groupLabelX = 14;
521
+
522
+ const GROUP_DESC = {
523
+ 'DCLM': 'DCLM score rates text quality on a 0–1 scale using a fastText classifier trained to distinguish curated, high-quality web data from random web crawls.',
524
+ 'EDU': 'FineWeb-Edu score rates educational value on a 0–5 scale using a classifier trained on LLM-annotated web pages, where higher scores indicate more instructive content.',
525
+ };
526
+
527
+ [['DCLM', dclmCenterY], ['EDU', eduCenterY]].forEach(([text, cy]) => {
528
+ const labelG = svg.append('g').style('cursor', 'help');
529
+
530
+ labelG.append('text')
531
+ .attr('x', groupLabelX).attr('y', cy)
532
+ .attr('text-anchor', 'middle')
533
+ .attr('dominant-baseline', 'central')
534
+ .attr('font-size', '9px')
535
+ .attr('font-weight', '700')
536
+ .attr('letter-spacing', '1px')
537
+ .attr('fill', isDark ? 'rgba(255,255,255,0.35)' : 'rgba(0,0,0,0.3)')
538
+ .attr('transform', `rotate(-90, ${groupLabelX}, ${cy})`)
539
+ .text(text);
540
+
541
+ // Hit area for the rotated text
542
+ const halfH = (DCLM_COUNT * cellH) / 2;
543
+ labelG.append('rect')
544
+ .attr('x', 0).attr('y', cy - halfH)
545
+ .attr('width', 24).attr('height', halfH * 2)
546
+ .attr('fill', 'transparent');
547
 
548
+ labelG.on('mouseenter', function() {
549
+ tip.innerHTML = `<div style="font-weight:700;font-size:13px;margin-bottom:4px;">${text} Score</div><div style="font-size:12px;color:var(--muted-color);line-height:1.45;">${GROUP_DESC[text]}</div>`;
550
+ tip.style.opacity = '1';
551
+ })
552
+ .on('mousemove', function(ev) {
553
+ const [mx, my] = d3.pointer(ev, container);
554
+ tip.style.transform = `translate(${Math.round(mx + 12)}px,${Math.round(my + 14)}px)`;
555
+ })
556
+ .on('mouseleave', function() {
557
+ tip.style.opacity = '0';
558
+ tip.style.transform = 'translate(-9999px,-9999px)';
559
+ });
560
  });
561
+ }
562
 
563
  // --- Tooltip interactions ---
564
  cells.on('mouseenter', function(ev, d) {
 
616
  <span class="item"><span class="swatch" style="background:${sw(0)};"></span><span>ρ = 0</span></span>
617
  <span class="item"><span class="swatch" style="background:${sw(0.3)};"></span><span>ρ = +0.3</span></span>
618
  <span class="item"><span class="swatch" style="background:${sw(0.6)};"></span><span>ρ = +0.6</span></span>
619
+ <span style="display:block;width:100%;margin-top:4px;font-size:11px;color:var(--muted-color);">*** p&lt;0.001 &nbsp; ** p&lt;0.01 &nbsp; * p&lt;0.05</span>
620
  </div>`;
621
  container.appendChild(legend);
622
  }
app/src/content/embeds/score-shift.html CHANGED
@@ -172,6 +172,7 @@
172
 
173
  function render() {
174
  const width = container.clientWidth || 800;
 
175
  const height = Math.max(420, Math.round(width / 1.8));
176
  svg.attr('width', width).attr('height', height);
177
  const iw = width - margin.left - margin.right;
@@ -183,16 +184,16 @@
183
  const diffKey = currentMode === 'dclm' ? 'dclmDiff' : 'eduDiff';
184
 
185
  // Three column positions
186
- const colPad = Math.max(50, iw * 0.08);
187
  const colX = [
188
  margin.left + colPad,
189
  margin.left + iw / 2,
190
  width - margin.right - colPad
191
  ];
192
  const colLabels = [
193
- `Input ${mode.label}`,
194
- `Output ${mode.label}`,
195
- 'agg_score_macro'
196
  ];
197
 
198
  // Scales for each column (all vertical, higher = better at top)
@@ -228,7 +229,7 @@
228
 
229
  // Ticks
230
  const scale = scales[ci];
231
- const ticks = scale.ticks(6);
232
  const fmt = ci === 2 ? d3.format('.3f') : d3.format('.2f');
233
  ticks.forEach(t => {
234
  const y = scale(t);
@@ -239,7 +240,7 @@
239
  gGrid.append('text')
240
  .attr('x', cx - 8).attr('y', y)
241
  .attr('text-anchor', 'end').attr('dominant-baseline', 'central')
242
- .attr('fill', 'var(--tick-color)').attr('font-size', '12px')
243
  .text(fmt(t));
244
  });
245
 
@@ -247,7 +248,7 @@
247
  gGrid.append('text')
248
  .attr('x', cx).attr('y', margin.top - 12)
249
  .attr('text-anchor', 'middle').attr('fill', 'var(--text-color)')
250
- .attr('font-size', '14px').attr('font-weight', '700')
251
  .text(colLabels[ci]);
252
  });
253
 
 
172
 
173
  function render() {
174
  const width = container.clientWidth || 800;
175
+ const isMobile = width < 640;
176
  const height = Math.max(420, Math.round(width / 1.8));
177
  svg.attr('width', width).attr('height', height);
178
  const iw = width - margin.left - margin.right;
 
184
  const diffKey = currentMode === 'dclm' ? 'dclmDiff' : 'eduDiff';
185
 
186
  // Three column positions
187
+ const colPad = isMobile ? Math.max(18, iw * 0.04) : Math.max(50, iw * 0.08);
188
  const colX = [
189
  margin.left + colPad,
190
  margin.left + iw / 2,
191
  width - margin.right - colPad
192
  ];
193
  const colLabels = [
194
+ isMobile ? 'Input' : `Input ${mode.label}`,
195
+ isMobile ? 'Output' : `Output ${mode.label}`,
196
+ isMobile ? 'Macro' : 'agg_score_macro'
197
  ];
198
 
199
  // Scales for each column (all vertical, higher = better at top)
 
229
 
230
  // Ticks
231
  const scale = scales[ci];
232
+ const ticks = scale.ticks(isMobile ? 4 : 6);
233
  const fmt = ci === 2 ? d3.format('.3f') : d3.format('.2f');
234
  ticks.forEach(t => {
235
  const y = scale(t);
 
240
  gGrid.append('text')
241
  .attr('x', cx - 8).attr('y', y)
242
  .attr('text-anchor', 'end').attr('dominant-baseline', 'central')
243
+ .attr('fill', 'var(--tick-color)').attr('font-size', isMobile ? '10px' : '12px')
244
  .text(fmt(t));
245
  });
246
 
 
248
  gGrid.append('text')
249
  .attr('x', cx).attr('y', margin.top - 12)
250
  .attr('text-anchor', 'middle').attr('fill', 'var(--text-color)')
251
+ .attr('font-size', isMobile ? '11px' : '14px').attr('font-weight', '700')
252
  .text(colLabels[ci]);
253
  });
254