thibaud frere commited on
Commit
b010293
·
1 Parent(s): 58a7faa

update charts

Browse files
app/src/components/HtmlEmbed.astro CHANGED
@@ -69,7 +69,7 @@ const mountId = `frag-${Math.random().toString(36).slice(2)}`;
69
  </script>
70
 
71
  <style is:global>
72
- .html-embed { margin: 0 0 var(--block-spacing-y); overflow: hidden; }
73
  .html-embed__title {
74
  text-align: left;
75
  font-weight: 600;
 
69
  </script>
70
 
71
  <style is:global>
72
+ .html-embed { margin: 0 0 var(--block-spacing-y); }
73
  .html-embed__title {
74
  text-align: left;
75
  font-weight: 600;
app/src/content/article.mdx CHANGED
@@ -340,10 +340,10 @@ If not specified otherwise, the “Baseline” in our intra dataset ablations re
340
 
341
  Compared against existing VLM training datasets, **FineVision** produces significantly higher benchmark ranks than the other options.
342
 
343
- Over the 10 different metrics, **FineVision** achieves a **45.68%** improvement over the Cauldron, a **13.04%** improvement over Cambrian, and a **46.83%** improvement over LLaVa.
344
 
345
  ---
346
- <HtmlEmbed src="against-baselines.html" desc="Average Rank of Models trained on different open source datasets." />
347
 
348
  ### How contaminated are the datasets?
349
 
 
340
 
341
  Compared against existing VLM training datasets, **FineVision** produces significantly higher benchmark ranks than the other options.
342
 
343
+ Over the 10 different metrics, **FineVision** achieves a **45.68%** improvement over the Cauldron, a **13.04%** improvement over Cambrian, and a **46.83%** improvement over LLaVa. <a href="#against-baselines">Fig1</a>
344
 
345
  ---
346
+ <HtmlEmbed id="against-baselines" src="against-baselines.html" desc="Average Rank of Models trained on different open source datasets." />
347
 
348
  ### How contaminated are the datasets?
349
 
app/src/content/embeds/against-baselines-deduplicated.html CHANGED
@@ -143,7 +143,7 @@
143
 
144
  const labelMetric = document.createElement('label');
145
  Object.assign(labelMetric.style, {
146
- fontSize: '12px', color: 'var(--muted-color)', display: 'flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap', padding: '6px 10px', marginLeft: 'auto'
147
  });
148
  labelMetric.textContent = 'Metric';
149
  const selectMetric = document.createElement('select');
@@ -158,7 +158,7 @@
158
  gap: '8px',
159
  alignItems: 'center',
160
  flexWrap: 'nowrap',
161
- fontSize: '11px',
162
  marginLeft: '8px'
163
  });
164
  controls.appendChild(legendInline);
@@ -272,6 +272,25 @@
272
  }
273
  }
274
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
275
  // Hover elements
276
  const hoverLine = gHover.append('line').attr('stroke-width', 1);
277
 
@@ -485,15 +504,15 @@
485
 
486
  // Create small SVG for marker shape
487
  const markerSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
488
- markerSvg.setAttribute('width', '16');
489
- markerSvg.setAttribute('height', '12');
490
  markerSvg.style.display = 'inline-block';
491
 
492
  const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
493
  g.setAttribute('transform', 'translate(8,6)');
494
 
495
  let shape;
496
- const size = 6;
497
  const halfSize = size / 2;
498
  switch(s.marker) {
499
  case 'circle':
@@ -546,17 +565,22 @@
546
  const sx = Math.max(steps[0], Math.min(steps[steps.length-1], Math.round(xScale.invert(mx)/1)*1));
547
  const nearest = steps.reduce((best, s)=> Math.abs(s - xScale.invert(mx)) < Math.abs(best - xScale.invert(mx)) ? s : best, steps[0]);
548
  const xpx = xScale(nearest);
549
- hoverLine.attr('x1', xpx).attr('x2', xpx).style('display', null).attr('stroke', 'rgba(0,0,0,0.25)');
550
- // Tooltip content
 
551
  let html = `<div><strong>${getMetricDisplayName(metricKey)}</strong></div><div><strong>step</strong> ${nearest}</div>`;
 
552
  series.forEach(s=>{
553
  const m = new Map(s.values.map(v=>[v.step, v]));
554
  const pt = m.get(nearest);
555
- if (pt && pt.value != null) {
556
- const formatVal = (vv) => (isRankStrict ? d3.format('d')(vv) : (+vv).toFixed(4));
557
- const errTxt = (pt.stderr != null && isFinite(pt.stderr) && pt.stderr > 0) ? ` ± ${formatVal(pt.stderr)}` : '';
558
- html += `<div><span style=\"display:inline-block;width:10px;height:10px;background:${s.color};border-radius:50%;margin-right:6px;\"></span><strong>${s.run}</strong> ${formatVal(pt.value)}${errTxt}</div>`;
559
- }
 
 
 
560
  });
561
  tipInner.innerHTML = html;
562
  const offsetX = 12, offsetY = 12;
@@ -581,7 +605,16 @@
581
  }));
582
  metricList = Array.from(new Set(rows.map(r=>r.metric))).sort();
583
  runList = Array.from(new Set(rows.map(r=>r.run))).sort();
584
- runOrder = runList;
 
 
 
 
 
 
 
 
 
585
  // Build dataByMetric
586
  metricList.forEach(m => {
587
  const map = {};
 
143
 
144
  const labelMetric = document.createElement('label');
145
  Object.assign(labelMetric.style, {
146
+ fontSize: '11px', color: 'var(--muted-color)', opacity: 0.8, display: 'flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap', padding: '6px 10px', marginLeft: 'auto'
147
  });
148
  labelMetric.textContent = 'Metric';
149
  const selectMetric = document.createElement('select');
 
158
  gap: '8px',
159
  alignItems: 'center',
160
  flexWrap: 'nowrap',
161
+ fontSize: '14px',
162
  marginLeft: '8px'
163
  });
164
  controls.appendChild(legendInline);
 
272
  }
273
  }
274
 
275
+ // Small SVG markup for marker shape (used in hover tooltip)
276
+ function shapeSvgMarkup(shape, color) {
277
+ const stroke = color;
278
+ switch (shape) {
279
+ case 'circle':
280
+ return `<svg width="14" height="12" viewBox="-6 -6 12 12" aria-hidden="true"><circle r="5" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
281
+ case 'square':
282
+ return `<svg width="14" height="12" viewBox="-6 -6 12 12" aria-hidden="true"><rect x="-5" y="-5" width="10" height="10" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
283
+ case 'triangle':
284
+ return `<svg width="14" height="12" viewBox="-6 -6 12 12" aria-hidden="true"><path d="M0,-6 L5,3 L-5,3 Z" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
285
+ case 'diamond':
286
+ return `<svg width="14" height="12" viewBox="-6 -6 12 12" aria-hidden="true"><path d="M0,-6 L6,0 L0,6 L-6,0 Z" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
287
+ case 'inverted-triangle':
288
+ return `<svg width="14" height="12" viewBox="-6 -6 12 12" aria-hidden="true"><path d="M0,6 L5,-3 L-5,-3 Z" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
289
+ default:
290
+ return `<svg width="14" height="12" viewBox="-6 -6 12 12" aria-hidden="true"><circle r="5" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
291
+ }
292
+ }
293
+
294
  // Hover elements
295
  const hoverLine = gHover.append('line').attr('stroke-width', 1);
296
 
 
504
 
505
  // Create small SVG for marker shape
506
  const markerSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
507
+ markerSvg.setAttribute('width', '18');
508
+ markerSvg.setAttribute('height', '14');
509
  markerSvg.style.display = 'inline-block';
510
 
511
  const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
512
  g.setAttribute('transform', 'translate(8,6)');
513
 
514
  let shape;
515
+ const size = 9;
516
  const halfSize = size / 2;
517
  switch(s.marker) {
518
  case 'circle':
 
565
  const sx = Math.max(steps[0], Math.min(steps[steps.length-1], Math.round(xScale.invert(mx)/1)*1));
566
  const nearest = steps.reduce((best, s)=> Math.abs(s - xScale.invert(mx)) < Math.abs(best - xScale.invert(mx)) ? s : best, steps[0]);
567
  const xpx = xScale(nearest);
568
+ // Theme-aware stroke already set in updateScales; don't override color here
569
+ hoverLine.attr('x1', xpx).attr('x2', xpx).style('display', null);
570
+ // Tooltip content trié par valeur au step hoveré
571
  let html = `<div><strong>${getMetricDisplayName(metricKey)}</strong></div><div><strong>step</strong> ${nearest}</div>`;
572
+ const items = [];
573
  series.forEach(s=>{
574
  const m = new Map(s.values.map(v=>[v.step, v]));
575
  const pt = m.get(nearest);
576
+ if (pt && pt.value != null) items.push({ s, pt });
577
+ });
578
+ // Tri: normal ascendant; rank strict descendant
579
+ const formatVal = (vv) => (isRankStrictFlag ? d3.format('d')(vv) : (+vv).toFixed(4));
580
+ items.sort((a,b)=> isRankStrictFlag ? (b.pt.value - a.pt.value) : (a.pt.value - b.pt.value));
581
+ items.forEach(({ s, pt }) => {
582
+ const errTxt = (pt.stderr != null && isFinite(pt.stderr) && pt.stderr > 0) ? ` ± ${formatVal(pt.stderr)}` : '';
583
+ html += `<div style="display:flex;align-items:center;gap:6px;">${shapeSvgMarkup(s.marker, s.color)}<strong>${s.run}</strong> ${formatVal(pt.value)}${errTxt}</div>`;
584
  });
585
  tipInner.innerHTML = html;
586
  const offsetX = 12, offsetY = 12;
 
605
  }));
606
  metricList = Array.from(new Set(rows.map(r=>r.metric))).sort();
607
  runList = Array.from(new Set(rows.map(r=>r.run))).sort();
608
+ // Prioritize 'finevisionDD' first, then 'FineVision' if present
609
+ {
610
+ const priorities = ['finevisiondd', 'finevision'];
611
+ const lowerMap = new Map(runList.map(r => [String(r).toLowerCase(), r]));
612
+ const selected = [];
613
+ priorities.forEach(p => { const found = lowerMap.get(p); if (found && !selected.includes(found)) selected.push(found); });
614
+ const selectedLower = new Set(selected.map(r => String(r).toLowerCase()));
615
+ const rest = runList.filter(r => !selectedLower.has(String(r).toLowerCase()));
616
+ runOrder = selected.concat(rest);
617
+ }
618
  // Build dataByMetric
619
  metricList.forEach(m => {
620
  const map = {};
app/src/content/embeds/against-baselines.html CHANGED
@@ -140,7 +140,7 @@
140
 
141
  const labelMetric = document.createElement('label');
142
  Object.assign(labelMetric.style, {
143
- fontSize: '12px', color: 'var(--muted-color)', display: 'flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap', padding: '6px 10px', marginLeft: 'auto'
144
  });
145
  labelMetric.textContent = 'Metric';
146
  const selectMetric = document.createElement('select');
@@ -155,7 +155,7 @@
155
  gap: '8px',
156
  alignItems: 'center',
157
  flexWrap: 'nowrap',
158
- fontSize: '11px',
159
  marginLeft: '8px'
160
  });
161
  controls.appendChild(legendInline);
@@ -270,6 +270,26 @@
270
  }
271
  }
272
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
  // Hover elements
274
  const hoverLine = gHover.append('line').attr('stroke-width', 1);
275
 
@@ -487,15 +507,15 @@
487
 
488
  // Create small SVG for marker shape
489
  const markerSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
490
- markerSvg.setAttribute('width', '16');
491
- markerSvg.setAttribute('height', '12');
492
  markerSvg.style.display = 'inline-block';
493
 
494
  const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
495
  g.setAttribute('transform', 'translate(8,6)');
496
 
497
  let shape;
498
- const size = 6;
499
  const halfSize = size / 2;
500
  switch(s.marker) {
501
  case 'circle':
@@ -548,17 +568,22 @@
548
  const sx = Math.max(steps[0], Math.min(steps[steps.length-1], Math.round(xScale.invert(mx)/1)*1));
549
  const nearest = steps.reduce((best, s)=> Math.abs(s - xScale.invert(mx)) < Math.abs(best - xScale.invert(mx)) ? s : best, steps[0]);
550
  const xpx = xScale(nearest);
551
- hoverLine.attr('x1', xpx).attr('x2', xpx).style('display', null).attr('stroke', 'rgba(0,0,0,0.25)');
552
- // Tooltip content
 
553
  let html = `<div><strong>${getMetricDisplayName(metricKey)}</strong></div><div><strong>step</strong> ${nearest}</div>`;
 
554
  series.forEach(s=>{
555
  const m = new Map(s.values.map(v=>[v.step, v]));
556
  const pt = m.get(nearest);
557
- if (pt && pt.value != null) {
558
- const formatVal = (vv) => (isRankStrict ? d3.format('d')(vv) : (+vv).toFixed(4));
559
- const errTxt = (pt.stderr != null && isFinite(pt.stderr) && pt.stderr > 0) ? ` ± ${formatVal(pt.stderr)}` : '';
560
- html += `<div><span style=\"display:inline-block;width:10px;height:10px;background:${s.color};border-radius:50%;margin-right:6px;\"></span><strong>${s.run}</strong> ${formatVal(pt.value)}${errTxt}</div>`;
561
- }
 
 
 
562
  });
563
  tipInner.innerHTML = html;
564
  const offsetX = 12, offsetY = 12;
@@ -583,7 +608,9 @@
583
  }));
584
  metricList = Array.from(new Set(rows.map(r=>r.metric))).sort();
585
  runList = Array.from(new Set(rows.map(r=>r.run))).sort();
586
- runOrder = runList;
 
 
587
  // Build dataByMetric
588
  metricList.forEach(m => {
589
  const map = {};
 
140
 
141
  const labelMetric = document.createElement('label');
142
  Object.assign(labelMetric.style, {
143
+ fontSize: '11px', color: 'var(--muted-color)', opacity: 0.8, display: 'flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap', padding: '6px 10px', marginLeft: 'auto'
144
  });
145
  labelMetric.textContent = 'Metric';
146
  const selectMetric = document.createElement('select');
 
155
  gap: '8px',
156
  alignItems: 'center',
157
  flexWrap: 'nowrap',
158
+ fontSize: '14px',
159
  marginLeft: '8px'
160
  });
161
  controls.appendChild(legendInline);
 
270
  }
271
  }
272
 
273
+ // Small SVG markup for marker shape (used in hover tooltip)
274
+ function shapeSvgMarkup(shape, color) {
275
+ const stroke = color;
276
+ // Use a normalized 12x12 box with centered origin for consistent shapes
277
+ switch (shape) {
278
+ case 'circle':
279
+ return `<svg width="14" height="12" viewBox="-6 -6 12 12" aria-hidden="true"><circle r="5" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
280
+ case 'square':
281
+ return `<svg width="14" height="12" viewBox="-6 -6 12 12" aria-hidden="true"><rect x="-5" y="-5" width="10" height="10" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
282
+ case 'triangle':
283
+ return `<svg width="14" height="12" viewBox="-6 -6 12 12" aria-hidden="true"><path d="M0,-6 L5,3 L-5,3 Z" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
284
+ case 'diamond':
285
+ return `<svg width="14" height="12" viewBox="-6 -6 12 12" aria-hidden="true"><path d="M0,-6 L6,0 L0,6 L-6,0 Z" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
286
+ case 'inverted-triangle':
287
+ return `<svg width="14" height="12" viewBox="-6 -6 12 12" aria-hidden="true"><path d="M0,6 L5,-3 L-5,-3 Z" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
288
+ default:
289
+ return `<svg width="14" height="12" viewBox="-6 -6 12 12" aria-hidden="true"><circle r="5" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
290
+ }
291
+ }
292
+
293
  // Hover elements
294
  const hoverLine = gHover.append('line').attr('stroke-width', 1);
295
 
 
507
 
508
  // Create small SVG for marker shape
509
  const markerSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
510
+ markerSvg.setAttribute('width', '18');
511
+ markerSvg.setAttribute('height', '14');
512
  markerSvg.style.display = 'inline-block';
513
 
514
  const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
515
  g.setAttribute('transform', 'translate(8,6)');
516
 
517
  let shape;
518
+ const size = 9;
519
  const halfSize = size / 2;
520
  switch(s.marker) {
521
  case 'circle':
 
568
  const sx = Math.max(steps[0], Math.min(steps[steps.length-1], Math.round(xScale.invert(mx)/1)*1));
569
  const nearest = steps.reduce((best, s)=> Math.abs(s - xScale.invert(mx)) < Math.abs(best - xScale.invert(mx)) ? s : best, steps[0]);
570
  const xpx = xScale(nearest);
571
+ // Use theme-aware stroke set in updateScales (don't override color here)
572
+ hoverLine.attr('x1', xpx).attr('x2', xpx).style('display', null);
573
+ // Tooltip content (sorted by value at hovered step)
574
  let html = `<div><strong>${getMetricDisplayName(metricKey)}</strong></div><div><strong>step</strong> ${nearest}</div>`;
575
+ const items = [];
576
  series.forEach(s=>{
577
  const m = new Map(s.values.map(v=>[v.step, v]));
578
  const pt = m.get(nearest);
579
+ if (pt && pt.value != null) items.push({ s, pt });
580
+ });
581
+ // Inverser l'ordre: métriques normales ascendant; métriques de rang descendant
582
+ items.sort((a,b)=> isRankStrict ? (b.pt.value - a.pt.value) : (a.pt.value - b.pt.value));
583
+ const formatVal = (vv) => (isRankStrict ? d3.format('d')(vv) : (+vv).toFixed(4));
584
+ items.forEach(({ s, pt }) => {
585
+ const errTxt = (pt.stderr != null && isFinite(pt.stderr) && pt.stderr > 0) ? ` ± ${formatVal(pt.stderr)}` : '';
586
+ html += `<div style="display:flex;align-items:center;gap:6px;">${shapeSvgMarkup(s.marker, s.color)}<strong>${s.run}</strong> ${formatVal(pt.value)}${errTxt}</div>`;
587
  });
588
  tipInner.innerHTML = html;
589
  const offsetX = 12, offsetY = 12;
 
608
  }));
609
  metricList = Array.from(new Set(rows.map(r=>r.metric))).sort();
610
  runList = Array.from(new Set(rows.map(r=>r.run))).sort();
611
+ // Prioriser FineVision en tête d'affichage (légende, couleurs, etc.)
612
+ const prioritizeRun = (list, name) => (list.includes(name) ? [name, ...list.filter(r => r !== name)] : list);
613
+ runOrder = prioritizeRun(runList, 'FineVision');
614
  // Build dataByMetric
615
  metricList.forEach(m => {
616
  const map = {};
app/src/content/embeds/all-ratings.html CHANGED
@@ -140,7 +140,7 @@
140
 
141
  const labelMetric = document.createElement('label');
142
  Object.assign(labelMetric.style, {
143
- fontSize: '12px', color: 'var(--muted-color)', display: 'flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap', padding: '6px 10px', marginLeft: 'auto'
144
  });
145
  labelMetric.textContent = 'Metric';
146
  const selectMetric = document.createElement('select');
@@ -155,7 +155,7 @@
155
  gap: '8px',
156
  alignItems: 'center',
157
  flexWrap: 'nowrap',
158
- fontSize: '11px',
159
  marginLeft: '8px'
160
  });
161
  controls.appendChild(legendInline);
@@ -269,6 +269,25 @@
269
  }
270
  }
271
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
  // Hover elements
273
  const hoverLine = gHover.append('line').attr('stroke-width', 1);
274
 
@@ -474,59 +493,16 @@
474
  .style('cursor', 'crosshair');
475
  });
476
 
477
- // Inline legend content with marker shapes
478
  legendInline.innerHTML = '';
479
  series.forEach(s => {
480
  const legendItem = document.createElement('span');
481
  legendItem.style.cssText = 'display:inline-flex;align-items:center;gap:6px;white-space:nowrap;';
482
-
483
- // Create small SVG for marker shape
484
- const markerSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
485
- markerSvg.setAttribute('width', '16');
486
- markerSvg.setAttribute('height', '12');
487
  markerSvg.style.display = 'inline-block';
488
-
489
- const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
490
- g.setAttribute('transform', 'translate(8,6)');
491
-
492
- let shape;
493
- const size = 6;
494
- const halfSize = size / 2;
495
- switch(s.marker) {
496
- case 'circle':
497
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
498
- shape.setAttribute('r', halfSize);
499
- break;
500
- case 'square':
501
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
502
- shape.setAttribute('x', -halfSize);
503
- shape.setAttribute('y', -halfSize);
504
- shape.setAttribute('width', size);
505
- shape.setAttribute('height', size);
506
- break;
507
- case 'triangle':
508
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'path');
509
- shape.setAttribute('d', `M0,${-halfSize * 1.2} L${halfSize * 1.1},${halfSize * 0.6} L${-halfSize * 1.1},${halfSize * 0.6} Z`);
510
- break;
511
- case 'diamond':
512
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'path');
513
- shape.setAttribute('d', `M0,${-halfSize * 1.2} L${halfSize * 1.1},0 L0,${halfSize * 1.2} L${-halfSize * 1.1},0 Z`);
514
- break;
515
- case 'inverted-triangle':
516
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'path');
517
- shape.setAttribute('d', `M0,${halfSize * 1.2} L${halfSize * 1.1},${-halfSize * 0.6} L${-halfSize * 1.1},${-halfSize * 0.6} Z`);
518
- break;
519
- default:
520
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
521
- shape.setAttribute('r', halfSize);
522
- }
523
- shape.setAttribute('fill', s.color);
524
- shape.setAttribute('stroke', s.color);
525
- shape.setAttribute('stroke-width', '1');
526
-
527
- g.appendChild(shape);
528
- markerSvg.appendChild(g);
529
-
530
  const label = document.createElement('span');
531
  label.textContent = s.run;
532
 
@@ -543,17 +519,22 @@
543
  const sx = Math.max(steps[0], Math.min(steps[steps.length-1], Math.round(xScale.invert(mx)/1)*1));
544
  const nearest = steps.reduce((best, s)=> Math.abs(s - xScale.invert(mx)) < Math.abs(best - xScale.invert(mx)) ? s : best, steps[0]);
545
  const xpx = xScale(nearest);
546
- hoverLine.attr('x1', xpx).attr('x2', xpx).style('display', null).attr('stroke', 'rgba(0,0,0,0.25)');
547
- // Tooltip content
 
548
  let html = `<div><strong>${getMetricDisplayName(metricKey)}</strong></div><div><strong>step</strong> ${nearest}</div>`;
 
549
  series.forEach(s=>{
550
  const m = new Map(s.values.map(v=>[v.step, v]));
551
  const pt = m.get(nearest);
552
- if (pt && pt.value != null) {
553
- const formatVal = (vv) => (isRankStrict ? d3.format('d')(vv) : (+vv).toFixed(4));
554
- const errTxt = (pt.stderr != null && isFinite(pt.stderr) && pt.stderr > 0) ? ` ± ${formatVal(pt.stderr)}` : '';
555
- html += `<div><span style=\"display:inline-block;width:10px;height:10px;background:${s.color};border-radius:50%;margin-right:6px;\"></span><strong>${s.run}</strong> ${formatVal(pt.value)}${errTxt}</div>`;
556
- }
 
 
 
557
  });
558
  tipInner.innerHTML = html;
559
  const offsetX = 12, offsetY = 12;
@@ -578,7 +559,8 @@
578
  }));
579
  metricList = Array.from(new Set(rows.map(r=>r.metric))).sort();
580
  runList = Array.from(new Set(rows.map(r=>r.run))).sort();
581
- runOrder = runList;
 
582
  // Build dataByMetric
583
  metricList.forEach(m => {
584
  const map = {};
 
140
 
141
  const labelMetric = document.createElement('label');
142
  Object.assign(labelMetric.style, {
143
+ fontSize: '11px', color: 'var(--muted-color)', opacity: 0.8, display: 'flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap', padding: '6px 10px', marginLeft: 'auto'
144
  });
145
  labelMetric.textContent = 'Metric';
146
  const selectMetric = document.createElement('select');
 
155
  gap: '8px',
156
  alignItems: 'center',
157
  flexWrap: 'nowrap',
158
+ fontSize: '14px',
159
  marginLeft: '8px'
160
  });
161
  controls.appendChild(legendInline);
 
269
  }
270
  }
271
 
272
+ // Small SVG markup for marker shape (used in hover tooltip and legend)
273
+ function shapeSvgMarkup(shape, color) {
274
+ const stroke = color;
275
+ switch (shape) {
276
+ case 'circle':
277
+ return `<svg width="18" height="14" viewBox="-6 -6 12 12" aria-hidden="true"><circle r="5" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
278
+ case 'square':
279
+ return `<svg width="18" height="14" viewBox="-6 -6 12 12" aria-hidden="true"><rect x="-5" y="-5" width="10" height="10" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
280
+ case 'triangle':
281
+ return `<svg width="18" height="14" viewBox="-6 -6 12 12" aria-hidden="true"><path d="M0,-6 L5,3 L-5,3 Z" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
282
+ case 'diamond':
283
+ return `<svg width="18" height="14" viewBox="-6 -6 12 12" aria-hidden="true"><path d="M0,-6 L6,0 L0,6 L-6,0 Z" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
284
+ case 'inverted-triangle':
285
+ return `<svg width="18" height="14" viewBox="-6 -6 12 12" aria-hidden="true"><path d="M0,6 L5,-3 L-5,-3 Z" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
286
+ default:
287
+ return `<svg width="18" height="14" viewBox="-6 -6 12 12" aria-hidden="true"><circle r="5" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
288
+ }
289
+ }
290
+
291
  // Hover elements
292
  const hoverLine = gHover.append('line').attr('stroke-width', 1);
293
 
 
493
  .style('cursor', 'crosshair');
494
  });
495
 
496
+ // Inline legend content with marker shapes (size 9 => SVG ~18x14)
497
  legendInline.innerHTML = '';
498
  series.forEach(s => {
499
  const legendItem = document.createElement('span');
500
  legendItem.style.cssText = 'display:inline-flex;align-items:center;gap:6px;white-space:nowrap;';
501
+
502
+ const markerSvg = document.createElement('span');
503
+ markerSvg.innerHTML = shapeSvgMarkup(s.marker, s.color);
 
 
504
  markerSvg.style.display = 'inline-block';
505
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
506
  const label = document.createElement('span');
507
  label.textContent = s.run;
508
 
 
519
  const sx = Math.max(steps[0], Math.min(steps[steps.length-1], Math.round(xScale.invert(mx)/1)*1));
520
  const nearest = steps.reduce((best, s)=> Math.abs(s - xScale.invert(mx)) < Math.abs(best - xScale.invert(mx)) ? s : best, steps[0]);
521
  const xpx = xScale(nearest);
522
+ // Keep theme-aware stroke from updateScales; do not override here
523
+ hoverLine.attr('x1', xpx).attr('x2', xpx).style('display', null);
524
+ // Tooltip content with sorted runs
525
  let html = `<div><strong>${getMetricDisplayName(metricKey)}</strong></div><div><strong>step</strong> ${nearest}</div>`;
526
+ const entries = [];
527
  series.forEach(s=>{
528
  const m = new Map(s.values.map(v=>[v.step, v]));
529
  const pt = m.get(nearest);
530
+ if (pt && pt.value != null) entries.push({ s, pt });
531
+ });
532
+ const isRankStrict = isRankStrictFlag;
533
+ const formatVal = (vv) => (isRankStrict ? d3.format('d')(vv) : (+vv).toFixed(4));
534
+ entries.sort((a,b)=> isRankStrict ? (b.pt.value - a.pt.value) : (a.pt.value - b.pt.value));
535
+ entries.forEach(({ s, pt }) => {
536
+ const errTxt = (pt.stderr != null && isFinite(pt.stderr) && pt.stderr > 0) ? ` ± ${formatVal(pt.stderr)}` : '';
537
+ html += `<div style="display:flex;align-items:center;gap:6px;">${shapeSvgMarkup(s.marker, s.color)}<strong>${s.run}</strong> ${formatVal(pt.value)}${errTxt}</div>`;
538
  });
539
  tipInner.innerHTML = html;
540
  const offsetX = 12, offsetY = 12;
 
559
  }));
560
  metricList = Array.from(new Set(rows.map(r=>r.metric))).sort();
561
  runList = Array.from(new Set(rows.map(r=>r.run))).sort();
562
+ const prioritizeRun = (list, name) => (list.includes(name) ? [name, ...list.filter(r => r !== name)] : list);
563
+ runOrder = prioritizeRun(runList, 'FineVision');
564
  // Build dataByMetric
565
  metricList.forEach(m => {
566
  const map = {};
app/src/content/embeds/filters-quad.html CHANGED
@@ -15,7 +15,7 @@
15
  @media (max-width: 980px) { .filters-quad__grid { grid-template-columns: 1fr; } }
16
 
17
  .filters-quad__controls { display:flex; align-items:center; justify-content:center; gap:12px; margin: 6px 0 12px 0; flex-wrap:wrap; }
18
- .filters-quad__controls label { font-size:14px; color: var(--text-color); font-weight:600; display:flex; align-items:center; gap:8px; }
19
  .filters-quad__controls select {
20
  font-size: 14px; padding: 8px 32px 8px 12px; border: 1px solid var(--border-color); border-radius: 10px;
21
  background-color: var(--surface-bg); color: var(--text-color);
@@ -28,7 +28,7 @@
28
  .filters-quad__controls select:hover { border-color: var(--primary-color); }
29
  .filters-quad__controls select:focus { border-color: var(--primary-color); box-shadow: 0 0 0 3px rgba(232,137,171,.25); outline: none; }
30
 
31
- .filters-quad__legend { display:flex; align-items:center; justify-content:center; gap:12px; flex-wrap:wrap; font-size:12px; color: var(--text-color); margin: 2px 0 10px 0; }
32
  .filters-quad__legend .item { display:inline-flex; align-items:center; gap:6px; white-space:nowrap; }
33
  .filters-quad__legend .swatch { width:10px; height:10px; border-radius:50%; display:inline-block; }
34
 
@@ -112,7 +112,7 @@
112
  const primary = getComputedStyle(document.documentElement).getPropertyValue('--primary-color').trim() || '#E889AB';
113
  const pool = [primary, '#4EA5B7', '#E38A42', '#CEC0FA', ...(d3.schemeTableau10||[])];
114
  const markerShapes = ['circle', 'square', 'triangle', 'diamond', 'inverted-triangle'];
115
- const markerSize = 8;
116
  function drawMarker(selection, shape, size) {
117
  const s = size / 2;
118
  switch (shape) {
@@ -163,8 +163,8 @@
163
  gAxes.selectAll('*').remove();
164
  let xAxis = d3.axisBottom(xScale).tickSizeOuter(0); xAxis = xAxis.ticks(8);
165
  const yAxis = d3.axisLeft(yScale).tickValues(yTicks).tickSizeOuter(0).tickFormat(isRankStrictFlag ? d3.format('d') : d3.format('.2f'));
166
- gAxes.append('g').attr('transform', `translate(0,${innerHeight})`).call(xAxis).call(g=>{ g.selectAll('path, line').attr('stroke',axisColor); g.selectAll('text').attr('fill',tickColor).style('font-size','11px'); });
167
- gAxes.append('g').call(yAxis).call(g=>{ g.selectAll('path, line').attr('stroke',axisColor); g.selectAll('text').attr('fill',tickColor).style('font-size','11px'); });
168
 
169
  // Legend box (top-right)
170
  // Per-cell legend hidden; global legend is used
@@ -228,10 +228,23 @@
228
  // Hover
229
  gHover.selectAll('*').remove();
230
  const overlay = gHover.append('rect').attr('fill','transparent').style('cursor','crosshair').attr('x',0).attr('y',0).attr('width', innerWidth).attr('height', innerHeight);
231
- const hoverLine = gHover.append('line').attr('stroke','rgba(0,0,0,0.25)').attr('stroke-width',1).attr('y1',0).attr('y2',innerHeight).style('display','none');
 
232
  const stepSet = new Set(); series.forEach(s=>s.values.forEach(v=>stepSet.add(v.step))); const steps = Array.from(stepSet).sort((a,b)=>a-b);
233
  function onMove(ev){ const [mx,my]=d3.pointer(ev, overlay.node()); const nearest = steps.reduce((best,s)=> Math.abs(s - xScale.invert(mx)) < Math.abs(best - xScale.invert(mx)) ? s : best, steps[0]); const xpx = xScale(nearest); hoverLine.attr('x1',xpx).attr('x2',xpx).style('display',null);
234
- let html = `<div><strong>${titleText}</strong></div><div><strong>step</strong> ${nearest}</div>`; series.forEach(s=>{ const m = new Map(s.values.map(v=>[v.step, v])); const pt = m.get(nearest); if (pt && pt.value!=null){ const fmt = (vv)=> (isRankStrictFlag? d3.format('d')(vv) : (+vv).toFixed(4)); const err = (pt.stderr!=null && isFinite(pt.stderr) && pt.stderr>0) ? ` ± ${fmt(pt.stderr)}` : ''; html+=`<div><span style=\"display:inline-block;width:10px;height:10px;background:${s.color};border-radius:50%;margin-right:6px;\"></span><strong>${s.run}</strong> ${fmt(pt.value)}${err}</div>`; }});
 
 
 
 
 
 
 
 
 
 
 
 
235
  tipInner.innerHTML = html; const offsetX=12, offsetY=12; tip.style.opacity='1'; tip.style.transform=`translate(${Math.round(mx+offsetX+margin.left)}px, ${Math.round(my+offsetY+margin.top)}px)`; }
236
  function onLeave(){ tip.style.opacity='0'; tip.style.transform='translate(-9999px, -9999px)'; hoverLine.style('display','none'); }
237
  overlay.on('mousemove', onMove).on('mouseleave', onLeave);
@@ -328,18 +341,19 @@
328
  if (r.ok && window.d3 && window.d3.csvParse) {
329
  const txt = await r.text();
330
  const rows = window.d3.csvParse(txt);
331
- const runList = Array.from(new Set(rows.map(row => String(row.run||'').trim()).filter(Boolean)));
 
332
  const primary = getComputedStyle(document.documentElement).getPropertyValue('--primary-color').trim() || '#E889AB';
333
  const pool = [primary, '#4EA5B7', '#E38A42', '#CEC0FA', ...((window.d3 && window.d3.schemeTableau10) ? window.d3.schemeTableau10 : ['#4e79a7','#f28e2b','#e15759','#76b7b2','#59a14f','#edc948','#b07aa1','#ff9da7','#9c755f','#bab0ab'])];
334
  const markerShapes = ['circle', 'square', 'triangle', 'diamond', 'inverted-triangle'];
335
  const shapeSVG = (shape, color) => {
336
- const size = 12; const s = size/2; const stroke = color;
337
- if (shape === 'circle') return `<svg width="12" height="12" viewBox="-6 -6 12 12" aria-hidden="true"><circle r="5" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
338
- if (shape === 'square') return `<svg width="12" height="12" viewBox="-6 -6 12 12" aria-hidden="true"><rect x="-5" y="-5" width="10" height="10" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
339
- if (shape === 'triangle') return `<svg width="12" height="12" viewBox="-6 -6 12 12" aria-hidden="true"><path d="M0,-6 L5,3 L-5,3 Z" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
340
- if (shape === 'diamond') return `<svg width="12" height="12" viewBox="-6 -6 12 12" aria-hidden="true"><path d="M0,-6 L6,0 L0,6 L-6,0 Z" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
341
- if (shape === 'inverted-triangle') return `<svg width="12" height="12" viewBox="-6 -6 12 12" aria-hidden="true"><path d="M0,6 L5,-3 L-5,-3 Z" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
342
- return `<svg width="12" height="12" viewBox="-6 -6 12 12" aria-hidden="true"><circle r="5" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
343
  };
344
  legendHost.innerHTML = runList.map((name, i)=> {
345
  const color = pool[i % pool.length];
 
15
  @media (max-width: 980px) { .filters-quad__grid { grid-template-columns: 1fr; } }
16
 
17
  .filters-quad__controls { display:flex; align-items:center; justify-content:center; gap:12px; margin: 6px 0 12px 0; flex-wrap:wrap; }
18
+ .filters-quad__controls label { font-size:11px; color: var(--muted-color); opacity: .8; display:flex; align-items:center; gap:8px; }
19
  .filters-quad__controls select {
20
  font-size: 14px; padding: 8px 32px 8px 12px; border: 1px solid var(--border-color); border-radius: 10px;
21
  background-color: var(--surface-bg); color: var(--text-color);
 
28
  .filters-quad__controls select:hover { border-color: var(--primary-color); }
29
  .filters-quad__controls select:focus { border-color: var(--primary-color); box-shadow: 0 0 0 3px rgba(232,137,171,.25); outline: none; }
30
 
31
+ .filters-quad__legend { display:flex; align-items:center; justify-content:center; gap:12px; flex-wrap:wrap; font-size:14px; color: var(--text-color); margin: 2px 0 10px 0; }
32
  .filters-quad__legend .item { display:inline-flex; align-items:center; gap:6px; white-space:nowrap; }
33
  .filters-quad__legend .swatch { width:10px; height:10px; border-radius:50%; display:inline-block; }
34
 
 
112
  const primary = getComputedStyle(document.documentElement).getPropertyValue('--primary-color').trim() || '#E889AB';
113
  const pool = [primary, '#4EA5B7', '#E38A42', '#CEC0FA', ...(d3.schemeTableau10||[])];
114
  const markerShapes = ['circle', 'square', 'triangle', 'diamond', 'inverted-triangle'];
115
+ const markerSize = 9;
116
  function drawMarker(selection, shape, size) {
117
  const s = size / 2;
118
  switch (shape) {
 
163
  gAxes.selectAll('*').remove();
164
  let xAxis = d3.axisBottom(xScale).tickSizeOuter(0); xAxis = xAxis.ticks(8);
165
  const yAxis = d3.axisLeft(yScale).tickValues(yTicks).tickSizeOuter(0).tickFormat(isRankStrictFlag ? d3.format('d') : d3.format('.2f'));
166
+ gAxes.append('g').attr('transform', `translate(0,${innerHeight})`).call(xAxis).call(g=>{ g.selectAll('path, line').attr('stroke',axisColor); g.selectAll('text').attr('fill',tickColor).style('font-size','12px'); });
167
+ gAxes.append('g').call(yAxis).call(g=>{ g.selectAll('path, line').attr('stroke',axisColor); g.selectAll('text').attr('fill',tickColor).style('font-size','12px'); });
168
 
169
  // Legend box (top-right)
170
  // Per-cell legend hidden; global legend is used
 
228
  // Hover
229
  gHover.selectAll('*').remove();
230
  const overlay = gHover.append('rect').attr('fill','transparent').style('cursor','crosshair').attr('x',0).attr('y',0).attr('width', innerWidth).attr('height', innerHeight);
231
+ const axisColor = document.documentElement.getAttribute('data-theme') === 'dark' ? 'rgba(255,255,255,0.25)' : 'rgba(0,0,0,0.25)';
232
+ const hoverLine = gHover.append('line').attr('stroke',axisColor).attr('stroke-width',1).attr('y1',0).attr('y2',innerHeight).style('display','none');
233
  const stepSet = new Set(); series.forEach(s=>s.values.forEach(v=>stepSet.add(v.step))); const steps = Array.from(stepSet).sort((a,b)=>a-b);
234
  function onMove(ev){ const [mx,my]=d3.pointer(ev, overlay.node()); const nearest = steps.reduce((best,s)=> Math.abs(s - xScale.invert(mx)) < Math.abs(best - xScale.invert(mx)) ? s : best, steps[0]); const xpx = xScale(nearest); hoverLine.attr('x1',xpx).attr('x2',xpx).style('display',null);
235
+ let html = `<div><strong>${titleText}</strong></div><div><strong>step</strong> ${nearest}</div>`;
236
+ const withPt = series.map(s=>{ const m=new Map(s.values.map(v=>[v.step,v])); const pt=m.get(nearest); return {s, pt}; }).filter(d=>d.pt && d.pt.value!=null);
237
+ withPt.sort((a,b)=> isRankStrictFlag ? (b.pt.value - a.pt.value) : (a.pt.value - b.pt.value));
238
+ const shapeSvg = (shape, color) => {
239
+ const size=9, half=size/2, cx=9, cy=7, sw='1';
240
+ if(shape==='circle') return `<svg width="18" height="14" viewBox="0 0 18 14" aria-hidden="true"><g transform="translate(${cx},${cy})"><circle r="${half}" fill="${color}" stroke="${color}" stroke-width="${sw}"/></g></svg>`;
241
+ if(shape==='square') return `<svg width="18" height="14" viewBox="0 0 18 14" aria-hidden="true"><g transform="translate(${cx},${cy})"><rect x="${-half}" y="${-half}" width="${size}" height="${size}" fill="${color}" stroke="${color}" stroke-width="${sw}"/></g></svg>`;
242
+ if(shape==='triangle') return `<svg width="18" height="14" viewBox="0 0 18 14" aria-hidden="true"><g transform="translate(${cx},${cy})"><path d="M0,${-half*1.2} L${half*1.1},${half*0.6} L${-half*1.1},${half*0.6} Z" fill="${color}" stroke="${color}" stroke-width="${sw}"/></g></svg>`;
243
+ if(shape==='diamond') return `<svg width="18" height="14" viewBox="0 0 18 14" aria-hidden="true"><g transform="translate(${cx},${cy})"><path d="M0,${-half*1.2} L${half*1.1},0 L0,${half*1.2} L${-half*1.1},0 Z" fill="${color}" stroke="${color}" stroke-width="${sw}"/></g></svg>`;
244
+ if(shape==='inverted-triangle') return `<svg width="18" height="14" viewBox="0 0 18 14" aria-hidden="true"><g transform="translate(${cx},${cy})"><path d="M0,${half*1.2} L${half*1.1},${-half*0.6} L${-half*1.1},${-half*0.6} Z" fill="${color}" stroke="${color}" stroke-width="${sw}"/></g></svg>`;
245
+ return `<svg width="18" height="14" viewBox="0 0 18 14" aria-hidden="true"><g transform="translate(${cx},${cy})"><circle r="${half}" fill="${color}" stroke="${color}" stroke-width="${sw}"/></g></svg>`;
246
+ };
247
+ withPt.forEach(({s,pt})=>{ const fmt=(vv)=> (isRankStrictFlag? d3.format('d')(vv) : (+vv).toFixed(4)); const err=(pt.stderr!=null && isFinite(pt.stderr) && pt.stderr>0)? ` ± ${fmt(pt.stderr)}` : ''; html+=`<div style="display:flex;align-items:center;gap:6px;white-space:nowrap;">${shapeSvg(s.marker, s.color)}<strong>${s.run}</strong> ${fmt(pt.value)}${err}</div>`; });
248
  tipInner.innerHTML = html; const offsetX=12, offsetY=12; tip.style.opacity='1'; tip.style.transform=`translate(${Math.round(mx+offsetX+margin.left)}px, ${Math.round(my+offsetY+margin.top)}px)`; }
249
  function onLeave(){ tip.style.opacity='0'; tip.style.transform='translate(-9999px, -9999px)'; hoverLine.style('display','none'); }
250
  overlay.on('mousemove', onMove).on('mouseleave', onLeave);
 
341
  if (r.ok && window.d3 && window.d3.csvParse) {
342
  const txt = await r.text();
343
  const rows = window.d3.csvParse(txt);
344
+ let runList = Array.from(new Set(rows.map(row => String(row.run||'').trim()).filter(Boolean)));
345
+ if (runList.includes('FineVision')) { runList = ['FineVision', ...runList.filter(r => r !== 'FineVision')]; }
346
  const primary = getComputedStyle(document.documentElement).getPropertyValue('--primary-color').trim() || '#E889AB';
347
  const pool = [primary, '#4EA5B7', '#E38A42', '#CEC0FA', ...((window.d3 && window.d3.schemeTableau10) ? window.d3.schemeTableau10 : ['#4e79a7','#f28e2b','#e15759','#76b7b2','#59a14f','#edc948','#b07aa1','#ff9da7','#9c755f','#bab0ab'])];
348
  const markerShapes = ['circle', 'square', 'triangle', 'diamond', 'inverted-triangle'];
349
  const shapeSVG = (shape, color) => {
350
+ const size = 9; const s = size/2; const stroke = color;
351
+ if (shape === 'circle') return `<svg width="18" height="14" viewBox="0 0 18 14" aria-hidden="true"><g transform="translate(9,7)"><circle r="${s}" fill="${color}" stroke="${stroke}" stroke-width="1" /></g></svg>`;
352
+ if (shape === 'square') return `<svg width="18" height="14" viewBox="0 0 18 14" aria-hidden="true"><g transform="translate(9,7)"><rect x="${-s}" y="${-s}" width="${size}" height="${size}" fill="${color}" stroke="${stroke}" stroke-width="1" /></g></svg>`;
353
+ if (shape === 'triangle') return `<svg width="18" height="14" viewBox="0 0 18 14" aria-hidden="true"><g transform="translate(9,7)"><path d="M0,${-s*1.2} L${s*1.1},${s*0.6} L${-s*1.1},${s*0.6} Z" fill="${color}" stroke="${stroke}" stroke-width="1" /></g></svg>`;
354
+ if (shape === 'diamond') return `<svg width="18" height="14" viewBox="0 0 18 14" aria-hidden="true"><g transform="translate(9,7)"><path d="M0,${-s*1.2} L${s*1.1},0 L0,${s*1.2} L${-s*1.1},0 Z" fill="${color}" stroke="${stroke}" stroke-width="1" /></g></svg>`;
355
+ if (shape === 'inverted-triangle') return `<svg width="18" height="14" viewBox="0 0 18 14" aria-hidden="true"><g transform="translate(9,7)"><path d="M0,${s*1.2} L${s*1.1},${-s*0.6} L${-s*1.1},${-s*0.6} Z" fill="${color}" stroke="${stroke}" stroke-width="1" /></g></svg>`;
356
+ return `<svg width="18" height="14" viewBox="0 0 18 14" aria-hidden="true"><g transform="translate(9,7)"><circle r="${s}" fill="${color}" stroke="${stroke}" stroke-width="1" /></g></svg>`;
357
  };
358
  legendHost.innerHTML = runList.map((name, i)=> {
359
  const color = pool[i % pool.length];
app/src/content/embeds/formatting-filters.html CHANGED
@@ -140,7 +140,7 @@
140
 
141
  const labelMetric = document.createElement('label');
142
  Object.assign(labelMetric.style, {
143
- fontSize: '12px', color: 'var(--muted-color)', display: 'flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap', padding: '6px 10px', marginLeft: 'auto'
144
  });
145
  labelMetric.textContent = 'Metric';
146
  const selectMetric = document.createElement('select');
@@ -155,7 +155,7 @@
155
  gap: '8px',
156
  alignItems: 'center',
157
  flexWrap: 'nowrap',
158
- fontSize: '11px',
159
  marginLeft: '8px'
160
  });
161
  controls.appendChild(legendInline);
@@ -269,6 +269,25 @@
269
  }
270
  }
271
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
  // Hover elements
273
  const hoverLine = gHover.append('line').attr('stroke-width', 1);
274
 
@@ -474,63 +493,19 @@
474
  .style('cursor', 'crosshair');
475
  });
476
 
477
- // Inline legend content with marker shapes
478
  legendInline.innerHTML = '';
479
  series.forEach(s => {
480
  const legendItem = document.createElement('span');
481
  legendItem.style.cssText = 'display:inline-flex;align-items:center;gap:6px;white-space:nowrap;';
482
-
483
- // Create small SVG for marker shape
484
- const markerSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
485
- markerSvg.setAttribute('width', '16');
486
- markerSvg.setAttribute('height', '12');
487
- markerSvg.style.display = 'inline-block';
488
-
489
- const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
490
- g.setAttribute('transform', 'translate(8,6)');
491
-
492
- let shape;
493
- const size = 6;
494
- const halfSize = size / 2;
495
- switch(s.marker) {
496
- case 'circle':
497
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
498
- shape.setAttribute('r', halfSize);
499
- break;
500
- case 'square':
501
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
502
- shape.setAttribute('x', -halfSize);
503
- shape.setAttribute('y', -halfSize);
504
- shape.setAttribute('width', size);
505
- shape.setAttribute('height', size);
506
- break;
507
- case 'triangle':
508
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'path');
509
- shape.setAttribute('d', `M0,${-halfSize * 1.2} L${halfSize * 1.1},${halfSize * 0.6} L${-halfSize * 1.1},${halfSize * 0.6} Z`);
510
- break;
511
- case 'diamond':
512
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'path');
513
- shape.setAttribute('d', `M0,${-halfSize * 1.2} L${halfSize * 1.1},0 L0,${halfSize * 1.2} L${-halfSize * 1.1},0 Z`);
514
- break;
515
- case 'inverted-triangle':
516
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'path');
517
- shape.setAttribute('d', `M0,${halfSize * 1.2} L${halfSize * 1.1},${-halfSize * 0.6} L${-halfSize * 1.1},${-halfSize * 0.6} Z`);
518
- break;
519
- default:
520
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
521
- shape.setAttribute('r', halfSize);
522
- }
523
- shape.setAttribute('fill', s.color);
524
- shape.setAttribute('stroke', s.color);
525
- shape.setAttribute('stroke-width', '1');
526
-
527
- g.appendChild(shape);
528
- markerSvg.appendChild(g);
529
-
530
  const label = document.createElement('span');
531
  label.textContent = s.run;
532
 
533
- legendItem.appendChild(markerSvg);
534
  legendItem.appendChild(label);
535
  legendInline.appendChild(legendItem);
536
  });
@@ -543,17 +518,21 @@
543
  const sx = Math.max(steps[0], Math.min(steps[steps.length-1], Math.round(xScale.invert(mx)/1)*1));
544
  const nearest = steps.reduce((best, s)=> Math.abs(s - xScale.invert(mx)) < Math.abs(best - xScale.invert(mx)) ? s : best, steps[0]);
545
  const xpx = xScale(nearest);
546
- hoverLine.attr('x1', xpx).attr('x2', xpx).style('display', null).attr('stroke', 'rgba(0,0,0,0.25)');
547
- // Tooltip content
548
  let html = `<div><strong>${getMetricDisplayName(metricKey)}</strong></div><div><strong>step</strong> ${nearest}</div>`;
 
549
  series.forEach(s=>{
550
  const m = new Map(s.values.map(v=>[v.step, v]));
551
  const pt = m.get(nearest);
552
- if (pt && pt.value != null) {
553
- const formatVal = (vv) => (isRankStrict ? d3.format('d')(vv) : (+vv).toFixed(4));
554
- const errTxt = (pt.stderr != null && isFinite(pt.stderr) && pt.stderr > 0) ? ` ± ${formatVal(pt.stderr)}` : '';
555
- html += `<div><span style=\"display:inline-block;width:10px;height:10px;background:${s.color};border-radius:50%;margin-right:6px;\"></span><strong>${s.run}</strong> ${formatVal(pt.value)}${errTxt}</div>`;
556
- }
 
 
 
557
  });
558
  tipInner.innerHTML = html;
559
  const offsetX = 12, offsetY = 12;
@@ -578,7 +557,8 @@
578
  }));
579
  metricList = Array.from(new Set(rows.map(r=>r.metric))).sort();
580
  runList = Array.from(new Set(rows.map(r=>r.run))).sort();
581
- runOrder = runList;
 
582
  // Build dataByMetric
583
  metricList.forEach(m => {
584
  const map = {};
 
140
 
141
  const labelMetric = document.createElement('label');
142
  Object.assign(labelMetric.style, {
143
+ fontSize: '11px', color: 'var(--muted-color)', opacity: 0.8, display: 'flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap', padding: '6px 10px', marginLeft: 'auto'
144
  });
145
  labelMetric.textContent = 'Metric';
146
  const selectMetric = document.createElement('select');
 
155
  gap: '8px',
156
  alignItems: 'center',
157
  flexWrap: 'nowrap',
158
+ fontSize: '14px',
159
  marginLeft: '8px'
160
  });
161
  controls.appendChild(legendInline);
 
269
  }
270
  }
271
 
272
+ // Small SVG markup for marker shape (used in hover tooltip and legend)
273
+ function shapeSvgMarkup(shape, color) {
274
+ const stroke = color;
275
+ switch (shape) {
276
+ case 'circle':
277
+ return `<svg width="18" height="14" viewBox="-6 -6 12 12" aria-hidden="true"><circle r="5" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
278
+ case 'square':
279
+ return `<svg width="18" height="14" viewBox="-6 -6 12 12" aria-hidden="true"><rect x="-5" y="-5" width="10" height="10" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
280
+ case 'triangle':
281
+ return `<svg width="18" height="14" viewBox="-6 -6 12 12" aria-hidden="true"><path d="M0,-6 L5,3 L-5,3 Z" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
282
+ case 'diamond':
283
+ return `<svg width="18" height="14" viewBox="-6 -6 12 12" aria-hidden="true"><path d="M0,-6 L6,0 L0,6 L-6,0 Z" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
284
+ case 'inverted-triangle':
285
+ return `<svg width="18" height="14" viewBox="-6 -6 12 12" aria-hidden="true"><path d="M0,6 L5,-3 L-5,-3 Z" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
286
+ default:
287
+ return `<svg width="18" height="14" viewBox="-6 -6 12 12" aria-hidden="true"><circle r="5" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
288
+ }
289
+ }
290
+
291
  // Hover elements
292
  const hoverLine = gHover.append('line').attr('stroke-width', 1);
293
 
 
493
  .style('cursor', 'crosshair');
494
  });
495
 
496
+ // Inline legend content with marker shapes (size 9 => SVG ~18x14)
497
  legendInline.innerHTML = '';
498
  series.forEach(s => {
499
  const legendItem = document.createElement('span');
500
  legendItem.style.cssText = 'display:inline-flex;align-items:center;gap:6px;white-space:nowrap;';
501
+ const markerSpan = document.createElement('span');
502
+ markerSpan.innerHTML = shapeSvgMarkup(s.marker, s.color);
503
+ markerSpan.style.display = 'inline-block';
504
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
505
  const label = document.createElement('span');
506
  label.textContent = s.run;
507
 
508
+ legendItem.appendChild(markerSpan);
509
  legendItem.appendChild(label);
510
  legendInline.appendChild(legendItem);
511
  });
 
518
  const sx = Math.max(steps[0], Math.min(steps[steps.length-1], Math.round(xScale.invert(mx)/1)*1));
519
  const nearest = steps.reduce((best, s)=> Math.abs(s - xScale.invert(mx)) < Math.abs(best - xScale.invert(mx)) ? s : best, steps[0]);
520
  const xpx = xScale(nearest);
521
+ hoverLine.attr('x1', xpx).attr('x2', xpx).style('display', null);
522
+ // Tooltip content (sorted)
523
  let html = `<div><strong>${getMetricDisplayName(metricKey)}</strong></div><div><strong>step</strong> ${nearest}</div>`;
524
+ const items = [];
525
  series.forEach(s=>{
526
  const m = new Map(s.values.map(v=>[v.step, v]));
527
  const pt = m.get(nearest);
528
+ if (pt && pt.value != null) items.push({ s, pt });
529
+ });
530
+ const isRankStrict = isRankStrictFlag;
531
+ const formatVal = (vv) => (isRankStrict ? d3.format('d')(vv) : (+vv).toFixed(4));
532
+ items.sort((a,b)=> isRankStrict ? (b.pt.value - a.pt.value) : (a.pt.value - b.pt.value));
533
+ items.forEach(({ s, pt }) => {
534
+ const errTxt = (pt.stderr != null && isFinite(pt.stderr) && pt.stderr > 0) ? ` ± ${formatVal(pt.stderr)}` : '';
535
+ html += `<div style=\"display:flex;align-items:center;gap:6px;\">${shapeSvgMarkup(s.marker, s.color)}<strong>${s.run}</strong> ${formatVal(pt.value)}${errTxt}</div>`;
536
  });
537
  tipInner.innerHTML = html;
538
  const offsetX = 12, offsetY = 12;
 
557
  }));
558
  metricList = Array.from(new Set(rows.map(r=>r.metric))).sort();
559
  runList = Array.from(new Set(rows.map(r=>r.run))).sort();
560
+ const prioritizeRun = (list, name) => (list.includes(name) ? [name, ...list.filter(r => r !== name)] : list);
561
+ runOrder = prioritizeRun(runList, 'FineVision');
562
  // Build dataByMetric
563
  metricList.forEach(m => {
564
  const map = {};
app/src/content/embeds/image-correspondence-filters.html CHANGED
@@ -140,7 +140,7 @@
140
 
141
  const labelMetric = document.createElement('label');
142
  Object.assign(labelMetric.style, {
143
- fontSize: '12px', color: 'var(--muted-color)', display: 'flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap', padding: '6px 10px', marginLeft: 'auto'
144
  });
145
  labelMetric.textContent = 'Metric';
146
  const selectMetric = document.createElement('select');
@@ -155,7 +155,7 @@
155
  gap: '8px',
156
  alignItems: 'center',
157
  flexWrap: 'nowrap',
158
- fontSize: '11px',
159
  marginLeft: '8px'
160
  });
161
  controls.appendChild(legendInline);
@@ -269,6 +269,25 @@
269
  }
270
  }
271
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
  // Hover elements
273
  const hoverLine = gHover.append('line').attr('stroke-width', 1);
274
 
@@ -474,63 +493,19 @@
474
  .style('cursor', 'crosshair');
475
  });
476
 
477
- // Inline legend content with marker shapes
478
  legendInline.innerHTML = '';
479
  series.forEach(s => {
480
  const legendItem = document.createElement('span');
481
  legendItem.style.cssText = 'display:inline-flex;align-items:center;gap:6px;white-space:nowrap;';
482
-
483
- // Create small SVG for marker shape
484
- const markerSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
485
- markerSvg.setAttribute('width', '16');
486
- markerSvg.setAttribute('height', '12');
487
- markerSvg.style.display = 'inline-block';
488
-
489
- const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
490
- g.setAttribute('transform', 'translate(8,6)');
491
-
492
- let shape;
493
- const size = 6;
494
- const halfSize = size / 2;
495
- switch(s.marker) {
496
- case 'circle':
497
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
498
- shape.setAttribute('r', halfSize);
499
- break;
500
- case 'square':
501
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
502
- shape.setAttribute('x', -halfSize);
503
- shape.setAttribute('y', -halfSize);
504
- shape.setAttribute('width', size);
505
- shape.setAttribute('height', size);
506
- break;
507
- case 'triangle':
508
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'path');
509
- shape.setAttribute('d', `M0,${-halfSize * 1.2} L${halfSize * 1.1},${halfSize * 0.6} L${-halfSize * 1.1},${halfSize * 0.6} Z`);
510
- break;
511
- case 'diamond':
512
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'path');
513
- shape.setAttribute('d', `M0,${-halfSize * 1.2} L${halfSize * 1.1},0 L0,${halfSize * 1.2} L${-halfSize * 1.1},0 Z`);
514
- break;
515
- case 'inverted-triangle':
516
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'path');
517
- shape.setAttribute('d', `M0,${halfSize * 1.2} L${halfSize * 1.1},${-halfSize * 0.6} L${-halfSize * 1.1},${-halfSize * 0.6} Z`);
518
- break;
519
- default:
520
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
521
- shape.setAttribute('r', halfSize);
522
- }
523
- shape.setAttribute('fill', s.color);
524
- shape.setAttribute('stroke', s.color);
525
- shape.setAttribute('stroke-width', '1');
526
-
527
- g.appendChild(shape);
528
- markerSvg.appendChild(g);
529
-
530
  const label = document.createElement('span');
531
  label.textContent = s.run;
532
 
533
- legendItem.appendChild(markerSvg);
534
  legendItem.appendChild(label);
535
  legendInline.appendChild(legendItem);
536
  });
@@ -543,17 +518,21 @@
543
  const sx = Math.max(steps[0], Math.min(steps[steps.length-1], Math.round(xScale.invert(mx)/1)*1));
544
  const nearest = steps.reduce((best, s)=> Math.abs(s - xScale.invert(mx)) < Math.abs(best - xScale.invert(mx)) ? s : best, steps[0]);
545
  const xpx = xScale(nearest);
546
- hoverLine.attr('x1', xpx).attr('x2', xpx).style('display', null).attr('stroke', 'rgba(0,0,0,0.25)');
547
- // Tooltip content
548
  let html = `<div><strong>${getMetricDisplayName(metricKey)}</strong></div><div><strong>step</strong> ${nearest}</div>`;
 
549
  series.forEach(s=>{
550
  const m = new Map(s.values.map(v=>[v.step, v]));
551
  const pt = m.get(nearest);
552
- if (pt && pt.value != null) {
553
- const formatVal = (vv) => (isRankStrict ? d3.format('d')(vv) : (+vv).toFixed(4));
554
- const errTxt = (pt.stderr != null && isFinite(pt.stderr) && pt.stderr > 0) ? ` ± ${formatVal(pt.stderr)}` : '';
555
- html += `<div><span style=\"display:inline-block;width:10px;height:10px;background:${s.color};border-radius:50%;margin-right:6px;\"></span><strong>${s.run}</strong> ${formatVal(pt.value)}${errTxt}</div>`;
556
- }
 
 
 
557
  });
558
  tipInner.innerHTML = html;
559
  const offsetX = 12, offsetY = 12;
@@ -578,7 +557,8 @@
578
  }));
579
  metricList = Array.from(new Set(rows.map(r=>r.metric))).sort();
580
  runList = Array.from(new Set(rows.map(r=>r.run))).sort();
581
- runOrder = runList;
 
582
  // Build dataByMetric
583
  metricList.forEach(m => {
584
  const map = {};
 
140
 
141
  const labelMetric = document.createElement('label');
142
  Object.assign(labelMetric.style, {
143
+ fontSize: '11px', color: 'var(--muted-color)', opacity: 0.8, display: 'flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap', padding: '6px 10px', marginLeft: 'auto'
144
  });
145
  labelMetric.textContent = 'Metric';
146
  const selectMetric = document.createElement('select');
 
155
  gap: '8px',
156
  alignItems: 'center',
157
  flexWrap: 'nowrap',
158
+ fontSize: '14px',
159
  marginLeft: '8px'
160
  });
161
  controls.appendChild(legendInline);
 
269
  }
270
  }
271
 
272
+ // Small SVG markup for marker shape (used in hover tooltip and legend)
273
+ function shapeSvgMarkup(shape, color) {
274
+ const stroke = color;
275
+ switch (shape) {
276
+ case 'circle':
277
+ return `<svg width="18" height="14" viewBox="-6 -6 12 12" aria-hidden="true"><circle r="5" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
278
+ case 'square':
279
+ return `<svg width="18" height="14" viewBox="-6 -6 12 12" aria-hidden="true"><rect x="-5" y="-5" width="10" height="10" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
280
+ case 'triangle':
281
+ return `<svg width="18" height="14" viewBox="-6 -6 12 12" aria-hidden="true"><path d="M0,-6 L5,3 L-5,3 Z" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
282
+ case 'diamond':
283
+ return `<svg width="18" height="14" viewBox="-6 -6 12 12" aria-hidden="true"><path d="M0,-6 L6,0 L0,6 L-6,0 Z" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
284
+ case 'inverted-triangle':
285
+ return `<svg width="18" height="14" viewBox="-6 -6 12 12" aria-hidden="true"><path d="M0,6 L5,-3 L-5,-3 Z" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
286
+ default:
287
+ return `<svg width="18" height="14" viewBox="-6 -6 12 12" aria-hidden="true"><circle r="5" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
288
+ }
289
+ }
290
+
291
  // Hover elements
292
  const hoverLine = gHover.append('line').attr('stroke-width', 1);
293
 
 
493
  .style('cursor', 'crosshair');
494
  });
495
 
496
+ // Inline legend content with marker shapes (size 9 => SVG ~18x14)
497
  legendInline.innerHTML = '';
498
  series.forEach(s => {
499
  const legendItem = document.createElement('span');
500
  legendItem.style.cssText = 'display:inline-flex;align-items:center;gap:6px;white-space:nowrap;';
501
+ const markerSpan = document.createElement('span');
502
+ markerSpan.innerHTML = shapeSvgMarkup(s.marker, s.color);
503
+ markerSpan.style.display = 'inline-block';
504
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
505
  const label = document.createElement('span');
506
  label.textContent = s.run;
507
 
508
+ legendItem.appendChild(markerSpan);
509
  legendItem.appendChild(label);
510
  legendInline.appendChild(legendItem);
511
  });
 
518
  const sx = Math.max(steps[0], Math.min(steps[steps.length-1], Math.round(xScale.invert(mx)/1)*1));
519
  const nearest = steps.reduce((best, s)=> Math.abs(s - xScale.invert(mx)) < Math.abs(best - xScale.invert(mx)) ? s : best, steps[0]);
520
  const xpx = xScale(nearest);
521
+ hoverLine.attr('x1', xpx).attr('x2', xpx).style('display', null);
522
+ // Tooltip content sorted
523
  let html = `<div><strong>${getMetricDisplayName(metricKey)}</strong></div><div><strong>step</strong> ${nearest}</div>`;
524
+ const items = [];
525
  series.forEach(s=>{
526
  const m = new Map(s.values.map(v=>[v.step, v]));
527
  const pt = m.get(nearest);
528
+ if (pt && pt.value != null) items.push({ s, pt });
529
+ });
530
+ const isRankStrict = isRankStrictFlag;
531
+ const formatVal = (vv) => (isRankStrict ? d3.format('d')(vv) : (+vv).toFixed(4));
532
+ items.sort((a,b)=> isRankStrict ? (b.pt.value - a.pt.value) : (a.pt.value - b.pt.value));
533
+ items.forEach(({ s, pt }) => {
534
+ const errTxt = (pt.stderr != null && isFinite(pt.stderr) && pt.stderr > 0) ? ` ± ${formatVal(pt.stderr)}` : '';
535
+ html += `<div style=\"display:flex;align-items:center;gap:6px;\">${shapeSvgMarkup(s.marker, s.color)}<strong>${s.run}</strong> ${formatVal(pt.value)}${errTxt}</div>`;
536
  });
537
  tipInner.innerHTML = html;
538
  const offsetX = 12, offsetY = 12;
 
557
  }));
558
  metricList = Array.from(new Set(rows.map(r=>r.metric))).sort();
559
  runList = Array.from(new Set(rows.map(r=>r.run))).sort();
560
+ const prioritizeRun = (list, name) => (list.includes(name) ? [name, ...list.filter(r => r !== name)] : list);
561
+ runOrder = prioritizeRun(runList, 'FineVision');
562
  // Build dataByMetric
563
  metricList.forEach(m => {
564
  const map = {};
app/src/content/embeds/internal-deduplication.html CHANGED
@@ -140,7 +140,7 @@
140
 
141
  const labelMetric = document.createElement('label');
142
  Object.assign(labelMetric.style, {
143
- fontSize: '12px', color: 'var(--muted-color)', display: 'flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap', padding: '6px 10px', marginLeft: 'auto'
144
  });
145
  labelMetric.textContent = 'Metric';
146
  const selectMetric = document.createElement('select');
@@ -155,7 +155,7 @@
155
  gap: '8px',
156
  alignItems: 'center',
157
  flexWrap: 'nowrap',
158
- fontSize: '11px',
159
  marginLeft: '8px'
160
  });
161
  controls.appendChild(legendInline);
@@ -269,6 +269,25 @@
269
  }
270
  }
271
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
  // Hover elements
273
  const hoverLine = gHover.append('line').attr('stroke-width', 1);
274
 
@@ -482,15 +501,15 @@
482
 
483
  // Create small SVG for marker shape
484
  const markerSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
485
- markerSvg.setAttribute('width', '16');
486
- markerSvg.setAttribute('height', '12');
487
  markerSvg.style.display = 'inline-block';
488
 
489
  const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
490
  g.setAttribute('transform', 'translate(8,6)');
491
 
492
  let shape;
493
- const size = 6;
494
  const halfSize = size / 2;
495
  switch(s.marker) {
496
  case 'circle':
@@ -543,17 +562,20 @@
543
  const sx = Math.max(steps[0], Math.min(steps[steps.length-1], Math.round(xScale.invert(mx)/1)*1));
544
  const nearest = steps.reduce((best, s)=> Math.abs(s - xScale.invert(mx)) < Math.abs(best - xScale.invert(mx)) ? s : best, steps[0]);
545
  const xpx = xScale(nearest);
546
- hoverLine.attr('x1', xpx).attr('x2', xpx).style('display', null).attr('stroke', 'rgba(0,0,0,0.25)');
547
- // Tooltip content
548
  let html = `<div><strong>${getMetricDisplayName(metricKey)}</strong></div><div><strong>step</strong> ${nearest}</div>`;
 
549
  series.forEach(s=>{
550
  const m = new Map(s.values.map(v=>[v.step, v]));
551
  const pt = m.get(nearest);
552
- if (pt && pt.value != null) {
553
- const formatVal = (vv) => (isRankStrict ? d3.format('d')(vv) : (+vv).toFixed(4));
554
- const errTxt = (pt.stderr != null && isFinite(pt.stderr) && pt.stderr > 0) ? ` ± ${formatVal(pt.stderr)}` : '';
555
- html += `<div><span style=\"display:inline-block;width:10px;height:10px;background:${s.color};border-radius:50%;margin-right:6px;\"></span><strong>${s.run}</strong> ${formatVal(pt.value)}${errTxt}</div>`;
556
- }
 
 
557
  });
558
  tipInner.innerHTML = html;
559
  const offsetX = 12, offsetY = 12;
@@ -578,7 +600,8 @@
578
  }));
579
  metricList = Array.from(new Set(rows.map(r=>r.metric))).sort();
580
  runList = Array.from(new Set(rows.map(r=>r.run))).sort();
581
- runOrder = runList;
 
582
  // Build dataByMetric
583
  metricList.forEach(m => {
584
  const map = {};
 
140
 
141
  const labelMetric = document.createElement('label');
142
  Object.assign(labelMetric.style, {
143
+ fontSize: '11px', color: 'var(--muted-color)', opacity: 0.8, display: 'flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap', padding: '6px 10px', marginLeft: 'auto'
144
  });
145
  labelMetric.textContent = 'Metric';
146
  const selectMetric = document.createElement('select');
 
155
  gap: '8px',
156
  alignItems: 'center',
157
  flexWrap: 'nowrap',
158
+ fontSize: '14px',
159
  marginLeft: '8px'
160
  });
161
  controls.appendChild(legendInline);
 
269
  }
270
  }
271
 
272
+ // Small SVG markup for marker shape (used in tooltips/legend)
273
+ function shapeSvgMarkup(shape, color) {
274
+ const stroke = color;
275
+ switch (shape) {
276
+ case 'circle':
277
+ return `<svg width="18" height="14" viewBox="-6 -6 12 12" aria-hidden="true"><circle r="5" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
278
+ case 'square':
279
+ return `<svg width="18" height="14" viewBox="-6 -6 12 12" aria-hidden="true"><rect x="-5" y="-5" width="10" height="10" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
280
+ case 'triangle':
281
+ return `<svg width="18" height="14" viewBox="-6 -6 12 12" aria-hidden="true"><path d="M0,-6 L5,3 L-5,3 Z" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
282
+ case 'diamond':
283
+ return `<svg width="18" height="14" viewBox="-6 -6 12 12" aria-hidden="true"><path d="M0,-6 L6,0 L0,6 L-6,0 Z" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
284
+ case 'inverted-triangle':
285
+ return `<svg width="18" height="14" viewBox="-6 -6 12 12" aria-hidden="true"><path d="M0,6 L5,-3 L-5,-3 Z" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
286
+ default:
287
+ return `<svg width="18" height="14" viewBox="-6 -6 12 12" aria-hidden="true"><circle r="5" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
288
+ }
289
+ }
290
+
291
  // Hover elements
292
  const hoverLine = gHover.append('line').attr('stroke-width', 1);
293
 
 
501
 
502
  // Create small SVG for marker shape
503
  const markerSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
504
+ markerSvg.setAttribute('width', '18');
505
+ markerSvg.setAttribute('height', '14');
506
  markerSvg.style.display = 'inline-block';
507
 
508
  const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
509
  g.setAttribute('transform', 'translate(8,6)');
510
 
511
  let shape;
512
+ const size = 9;
513
  const halfSize = size / 2;
514
  switch(s.marker) {
515
  case 'circle':
 
562
  const sx = Math.max(steps[0], Math.min(steps[steps.length-1], Math.round(xScale.invert(mx)/1)*1));
563
  const nearest = steps.reduce((best, s)=> Math.abs(s - xScale.invert(mx)) < Math.abs(best - xScale.invert(mx)) ? s : best, steps[0]);
564
  const xpx = xScale(nearest);
565
+ hoverLine.attr('x1', xpx).attr('x2', xpx).style('display', null);
566
+ // Tooltip content trié
567
  let html = `<div><strong>${getMetricDisplayName(metricKey)}</strong></div><div><strong>step</strong> ${nearest}</div>`;
568
+ const items = [];
569
  series.forEach(s=>{
570
  const m = new Map(s.values.map(v=>[v.step, v]));
571
  const pt = m.get(nearest);
572
+ if (pt && pt.value != null) items.push({ s, pt });
573
+ });
574
+ const formatVal = (vv) => (isRankStrictFlag ? d3.format('d')(vv) : (+vv).toFixed(4));
575
+ items.sort((a,b)=> isRankStrictFlag ? (b.pt.value - a.pt.value) : (a.pt.value - b.pt.value));
576
+ items.forEach(({ s, pt }) => {
577
+ const errTxt = (pt.stderr != null && isFinite(pt.stderr) && pt.stderr > 0) ? ` ± ${formatVal(pt.stderr)}` : '';
578
+ html += `<div style=\"display:flex;align-items:center;gap:6px;\">${shapeSvgMarkup(s.marker, s.color)}<strong>${s.run}</strong> ${formatVal(pt.value)}${errTxt}</div>`;
579
  });
580
  tipInner.innerHTML = html;
581
  const offsetX = 12, offsetY = 12;
 
600
  }));
601
  metricList = Array.from(new Set(rows.map(r=>r.metric))).sort();
602
  runList = Array.from(new Set(rows.map(r=>r.run))).sort();
603
+ const prioritizeRun = (list, name) => (list.includes(name) ? [name, ...list.filter(r => r !== name)] : list);
604
+ runOrder = prioritizeRun(runList, 'FineVision');
605
  // Build dataByMetric
606
  metricList.forEach(m => {
607
  const map = {};
app/src/content/embeds/relevance-filters.html CHANGED
@@ -140,7 +140,7 @@
140
 
141
  const labelMetric = document.createElement('label');
142
  Object.assign(labelMetric.style, {
143
- fontSize: '12px', color: 'var(--muted-color)', display: 'flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap', padding: '6px 10px', marginLeft: 'auto'
144
  });
145
  labelMetric.textContent = 'Metric';
146
  const selectMetric = document.createElement('select');
@@ -155,7 +155,7 @@
155
  gap: '8px',
156
  alignItems: 'center',
157
  flexWrap: 'nowrap',
158
- fontSize: '11px',
159
  marginLeft: '8px'
160
  });
161
  controls.appendChild(legendInline);
@@ -269,6 +269,25 @@
269
  }
270
  }
271
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
  // Hover elements
273
  const hoverLine = gHover.append('line').attr('stroke-width', 1);
274
 
@@ -474,63 +493,19 @@
474
  .style('cursor', 'crosshair');
475
  });
476
 
477
- // Inline legend content with marker shapes
478
  legendInline.innerHTML = '';
479
  series.forEach(s => {
480
  const legendItem = document.createElement('span');
481
  legendItem.style.cssText = 'display:inline-flex;align-items:center;gap:6px;white-space:nowrap;';
482
-
483
- // Create small SVG for marker shape
484
- const markerSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
485
- markerSvg.setAttribute('width', '16');
486
- markerSvg.setAttribute('height', '12');
487
- markerSvg.style.display = 'inline-block';
488
-
489
- const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
490
- g.setAttribute('transform', 'translate(8,6)');
491
-
492
- let shape;
493
- const size = 6;
494
- const halfSize = size / 2;
495
- switch(s.marker) {
496
- case 'circle':
497
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
498
- shape.setAttribute('r', halfSize);
499
- break;
500
- case 'square':
501
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
502
- shape.setAttribute('x', -halfSize);
503
- shape.setAttribute('y', -halfSize);
504
- shape.setAttribute('width', size);
505
- shape.setAttribute('height', size);
506
- break;
507
- case 'triangle':
508
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'path');
509
- shape.setAttribute('d', `M0,${-halfSize * 1.2} L${halfSize * 1.1},${halfSize * 0.6} L${-halfSize * 1.1},${halfSize * 0.6} Z`);
510
- break;
511
- case 'diamond':
512
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'path');
513
- shape.setAttribute('d', `M0,${-halfSize * 1.2} L${halfSize * 1.1},0 L0,${halfSize * 1.2} L${-halfSize * 1.1},0 Z`);
514
- break;
515
- case 'inverted-triangle':
516
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'path');
517
- shape.setAttribute('d', `M0,${halfSize * 1.2} L${halfSize * 1.1},${-halfSize * 0.6} L${-halfSize * 1.1},${-halfSize * 0.6} Z`);
518
- break;
519
- default:
520
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
521
- shape.setAttribute('r', halfSize);
522
- }
523
- shape.setAttribute('fill', s.color);
524
- shape.setAttribute('stroke', s.color);
525
- shape.setAttribute('stroke-width', '1');
526
-
527
- g.appendChild(shape);
528
- markerSvg.appendChild(g);
529
-
530
  const label = document.createElement('span');
531
  label.textContent = s.run;
532
 
533
- legendItem.appendChild(markerSvg);
534
  legendItem.appendChild(label);
535
  legendInline.appendChild(legendItem);
536
  });
@@ -543,17 +518,21 @@
543
  const sx = Math.max(steps[0], Math.min(steps[steps.length-1], Math.round(xScale.invert(mx)/1)*1));
544
  const nearest = steps.reduce((best, s)=> Math.abs(s - xScale.invert(mx)) < Math.abs(best - xScale.invert(mx)) ? s : best, steps[0]);
545
  const xpx = xScale(nearest);
546
- hoverLine.attr('x1', xpx).attr('x2', xpx).style('display', null).attr('stroke', 'rgba(0,0,0,0.25)');
547
- // Tooltip content
548
  let html = `<div><strong>${getMetricDisplayName(metricKey)}</strong></div><div><strong>step</strong> ${nearest}</div>`;
 
549
  series.forEach(s=>{
550
  const m = new Map(s.values.map(v=>[v.step, v]));
551
  const pt = m.get(nearest);
552
- if (pt && pt.value != null) {
553
- const formatVal = (vv) => (isRankStrict ? d3.format('d')(vv) : (+vv).toFixed(4));
554
- const errTxt = (pt.stderr != null && isFinite(pt.stderr) && pt.stderr > 0) ? ` ± ${formatVal(pt.stderr)}` : '';
555
- html += `<div><span style=\"display:inline-block;width:10px;height:10px;background:${s.color};border-radius:50%;margin-right:6px;\"></span><strong>${s.run}</strong> ${formatVal(pt.value)}${errTxt}</div>`;
556
- }
 
 
 
557
  });
558
  tipInner.innerHTML = html;
559
  const offsetX = 12, offsetY = 12;
@@ -578,7 +557,8 @@
578
  }));
579
  metricList = Array.from(new Set(rows.map(r=>r.metric))).sort();
580
  runList = Array.from(new Set(rows.map(r=>r.run))).sort();
581
- runOrder = runList;
 
582
  // Build dataByMetric
583
  metricList.forEach(m => {
584
  const map = {};
 
140
 
141
  const labelMetric = document.createElement('label');
142
  Object.assign(labelMetric.style, {
143
+ fontSize: '11px', color: 'var(--muted-color)', opacity: 0.8, display: 'flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap', padding: '6px 10px', marginLeft: 'auto'
144
  });
145
  labelMetric.textContent = 'Metric';
146
  const selectMetric = document.createElement('select');
 
155
  gap: '8px',
156
  alignItems: 'center',
157
  flexWrap: 'nowrap',
158
+ fontSize: '14px',
159
  marginLeft: '8px'
160
  });
161
  controls.appendChild(legendInline);
 
269
  }
270
  }
271
 
272
+ // Small SVG markup for marker shape (used in hover tooltip and legend)
273
+ function shapeSvgMarkup(shape, color) {
274
+ const stroke = color;
275
+ switch (shape) {
276
+ case 'circle':
277
+ return `<svg width=\"18\" height=\"14\" viewBox=\"-6 -6 12 12\" aria-hidden=\"true\"><circle r=\"5\" fill=\"${color}\" stroke=\"${stroke}\" stroke-width=\"1\" /></svg>`;
278
+ case 'square':
279
+ return `<svg width=\"18\" height=\"14\" viewBox=\"-6 -6 12 12\" aria-hidden=\"true\"><rect x=\"-5\" y=\"-5\" width=\"10\" height=\"10\" fill=\"${color}\" stroke=\"${stroke}\" stroke-width=\"1\" /></svg>`;
280
+ case 'triangle':
281
+ return `<svg width=\"18\" height=\"14\" viewBox=\"-6 -6 12 12\" aria-hidden=\"true\"><path d=\"M0,-6 L5,3 L-5,3 Z\" fill=\"${color}\" stroke=\"${stroke}\" stroke-width=\"1\" /></svg>`;
282
+ case 'diamond':
283
+ return `<svg width=\"18\" height=\"14\" viewBox=\"-6 -6 12 12\" aria-hidden=\"true\"><path d=\"M0,-6 L6,0 L0,6 L-6,0 Z\" fill=\"${color}\" stroke=\"${stroke}\" stroke-width=\"1\" /></svg>`;
284
+ case 'inverted-triangle':
285
+ return `<svg width=\"18\" height=\"14\" viewBox=\"-6 -6 12 12\" aria-hidden=\"true\"><path d=\"M0,6 L5,-3 L-5,-3 Z\" fill=\"${color}\" stroke=\"${stroke}\" stroke-width=\"1\" /></svg>`;
286
+ default:
287
+ return `<svg width=\"18\" height=\"14\" viewBox=\"-6 -6 12 12\" aria-hidden=\"true\"><circle r=\"5\" fill=\"${color}\" stroke=\"${stroke}\" stroke-width=\"1\" /></svg>`;
288
+ }
289
+ }
290
+
291
  // Hover elements
292
  const hoverLine = gHover.append('line').attr('stroke-width', 1);
293
 
 
493
  .style('cursor', 'crosshair');
494
  });
495
 
496
+ // Inline legend content with marker shapes (size 9 => SVG 18x14)
497
  legendInline.innerHTML = '';
498
  series.forEach(s => {
499
  const legendItem = document.createElement('span');
500
  legendItem.style.cssText = 'display:inline-flex;align-items:center;gap:6px;white-space:nowrap;';
501
+ const markerSpan = document.createElement('span');
502
+ markerSpan.innerHTML = shapeSvgMarkup(s.marker, s.color);
503
+ markerSpan.style.display = 'inline-block';
504
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
505
  const label = document.createElement('span');
506
  label.textContent = s.run;
507
 
508
+ legendItem.appendChild(markerSpan);
509
  legendItem.appendChild(label);
510
  legendInline.appendChild(legendItem);
511
  });
 
518
  const sx = Math.max(steps[0], Math.min(steps[steps.length-1], Math.round(xScale.invert(mx)/1)*1));
519
  const nearest = steps.reduce((best, s)=> Math.abs(s - xScale.invert(mx)) < Math.abs(best - xScale.invert(mx)) ? s : best, steps[0]);
520
  const xpx = xScale(nearest);
521
+ hoverLine.attr('x1', xpx).attr('x2', xpx).style('display', null);
522
+ // Tooltip content sorted
523
  let html = `<div><strong>${getMetricDisplayName(metricKey)}</strong></div><div><strong>step</strong> ${nearest}</div>`;
524
+ const items = [];
525
  series.forEach(s=>{
526
  const m = new Map(s.values.map(v=>[v.step, v]));
527
  const pt = m.get(nearest);
528
+ if (pt && pt.value != null) items.push({ s, pt });
529
+ });
530
+ const isRankStrict = isRankStrictFlag;
531
+ const formatVal = (vv) => (isRankStrict ? d3.format('d')(vv) : (+vv).toFixed(4));
532
+ items.sort((a,b)=> isRankStrict ? (b.pt.value - a.pt.value) : (a.pt.value - b.pt.value));
533
+ items.forEach(({ s, pt }) => {
534
+ const errTxt = (pt.stderr != null && isFinite(pt.stderr) && pt.stderr > 0) ? ` ± ${formatVal(pt.stderr)}` : '';
535
+ html += `<div style=\"display:flex;align-items:center;gap:6px;\">${shapeSvgMarkup(s.marker, s.color)}<strong>${s.run}</strong> ${formatVal(pt.value)}${errTxt}</div>`;
536
  });
537
  tipInner.innerHTML = html;
538
  const offsetX = 12, offsetY = 12;
 
557
  }));
558
  metricList = Array.from(new Set(rows.map(r=>r.metric))).sort();
559
  runList = Array.from(new Set(rows.map(r=>r.run))).sort();
560
+ const prioritizeRun = (list, name) => (list.includes(name) ? [name, ...list.filter(r => r !== name)] : list);
561
+ runOrder = prioritizeRun(runList, 'FineVision');
562
  // Build dataByMetric
563
  metricList.forEach(m => {
564
  const map = {};
app/src/content/embeds/remove-ch.html CHANGED
@@ -140,7 +140,7 @@
140
 
141
  const labelMetric = document.createElement('label');
142
  Object.assign(labelMetric.style, {
143
- fontSize: '12px', color: 'var(--muted-color)', display: 'flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap', padding: '6px 10px', marginLeft: 'auto'
144
  });
145
  labelMetric.textContent = 'Metric';
146
  const selectMetric = document.createElement('select');
@@ -155,7 +155,7 @@
155
  gap: '8px',
156
  alignItems: 'center',
157
  flexWrap: 'nowrap',
158
- fontSize: '11px',
159
  marginLeft: '8px'
160
  });
161
  controls.appendChild(legendInline);
@@ -171,7 +171,7 @@
171
 
172
  // Academic marker shapes
173
  const markerShapes = ['circle', 'square', 'triangle', 'diamond', 'inverted-triangle'];
174
- const markerSize = 8;
175
 
176
  // Groups
177
  const gRoot = svg.append('g');
@@ -269,6 +269,28 @@
269
  }
270
  }
271
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
  // Hover elements
273
  const hoverLine = gHover.append('line').attr('stroke-width', 1);
274
 
@@ -482,15 +504,15 @@
482
 
483
  // Create small SVG for marker shape
484
  const markerSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
485
- markerSvg.setAttribute('width', '16');
486
- markerSvg.setAttribute('height', '12');
487
  markerSvg.style.display = 'inline-block';
488
 
489
  const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
490
- g.setAttribute('transform', 'translate(8,6)');
491
 
492
  let shape;
493
- const size = 6;
494
  const halfSize = size / 2;
495
  switch(s.marker) {
496
  case 'circle':
@@ -543,17 +565,27 @@
543
  const sx = Math.max(steps[0], Math.min(steps[steps.length-1], Math.round(xScale.invert(mx)/1)*1));
544
  const nearest = steps.reduce((best, s)=> Math.abs(s - xScale.invert(mx)) < Math.abs(best - xScale.invert(mx)) ? s : best, steps[0]);
545
  const xpx = xScale(nearest);
546
- hoverLine.attr('x1', xpx).attr('x2', xpx).style('display', null).attr('stroke', 'rgba(0,0,0,0.25)');
 
 
547
  // Tooltip content
548
  let html = `<div><strong>${getMetricDisplayName(metricKey)}</strong></div><div><strong>step</strong> ${nearest}</div>`;
549
- series.forEach(s=>{
550
- const m = new Map(s.values.map(v=>[v.step, v]));
551
- const pt = m.get(nearest);
552
- if (pt && pt.value != null) {
553
- const formatVal = (vv) => (isRankStrict ? d3.format('d')(vv) : (+vv).toFixed(4));
554
- const errTxt = (pt.stderr != null && isFinite(pt.stderr) && pt.stderr > 0) ? ` ± ${formatVal(pt.stderr)}` : '';
555
- html += `<div><span style=\"display:inline-block;width:10px;height:10px;background:${s.color};border-radius:50%;margin-right:6px;\"></span><strong>${s.run}</strong> ${formatVal(pt.value)}${errTxt}</div>`;
556
- }
 
 
 
 
 
 
 
 
557
  });
558
  tipInner.innerHTML = html;
559
  const offsetX = 12, offsetY = 12;
@@ -578,7 +610,9 @@
578
  }));
579
  metricList = Array.from(new Set(rows.map(r=>r.metric))).sort();
580
  runList = Array.from(new Set(rows.map(r=>r.run))).sort();
581
- runOrder = runList;
 
 
582
  // Build dataByMetric
583
  metricList.forEach(m => {
584
  const map = {};
 
140
 
141
  const labelMetric = document.createElement('label');
142
  Object.assign(labelMetric.style, {
143
+ fontSize: '11px', color: 'var(--muted-color)', opacity: 0.8, display: 'flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap', padding: '6px 10px', marginLeft: 'auto'
144
  });
145
  labelMetric.textContent = 'Metric';
146
  const selectMetric = document.createElement('select');
 
155
  gap: '8px',
156
  alignItems: 'center',
157
  flexWrap: 'nowrap',
158
+ fontSize: '14px',
159
  marginLeft: '8px'
160
  });
161
  controls.appendChild(legendInline);
 
171
 
172
  // Academic marker shapes
173
  const markerShapes = ['circle', 'square', 'triangle', 'diamond', 'inverted-triangle'];
174
+ const markerSize = 9;
175
 
176
  // Groups
177
  const gRoot = svg.append('g');
 
269
  }
270
  }
271
 
272
+ // Small SVG markup for marker shape (used in tooltip)
273
+ function shapeSvgMarkup(shape, color) {
274
+ const size = 9; // visual marker size
275
+ const half = size / 2;
276
+ const cx = 9, cy = 7; // center for 18x14 viewport
277
+ const stroke = color;
278
+ switch (shape) {
279
+ case 'circle':
280
+ return `<svg width="18" height="14" viewBox="0 0 18 14" aria-hidden="true"><g transform="translate(${cx},${cy})"><circle r="${half}" fill="${color}" stroke="${stroke}" stroke-width="1"/></g></svg>`;
281
+ case 'square':
282
+ return `<svg width="18" height="14" viewBox="0 0 18 14" aria-hidden="true"><g transform="translate(${cx},${cy})"><rect x="${-half}" y="${-half}" width="${size}" height="${size}" fill="${color}" stroke="${stroke}" stroke-width="1"/></g></svg>`;
283
+ case 'triangle':
284
+ return `<svg width="18" height="14" viewBox="0 0 18 14" aria-hidden="true"><g transform="translate(${cx},${cy})"><path d="M0,${-half * 1.2} L${half * 1.1},${half * 0.6} L${-half * 1.1},${half * 0.6} Z" fill="${color}" stroke="${stroke}" stroke-width="1"/></g></svg>`;
285
+ case 'diamond':
286
+ return `<svg width="18" height="14" viewBox="0 0 18 14" aria-hidden="true"><g transform="translate(${cx},${cy})"><path d="M0,${-half * 1.2} L${half * 1.1},0 L0,${half * 1.2} L${-half * 1.1},0 Z" fill="${color}" stroke="${stroke}" stroke-width="1"/></g></svg>`;
287
+ case 'inverted-triangle':
288
+ return `<svg width="18" height="14" viewBox="0 0 18 14" aria-hidden="true"><g transform="translate(${cx},${cy})"><path d="M0,${half * 1.2} L${half * 1.1},${-half * 0.6} L${-half * 1.1},${-half * 0.6} Z" fill="${color}" stroke="${stroke}" stroke-width="1"/></g></svg>`;
289
+ default:
290
+ return `<svg width="18" height="14" viewBox="0 0 18 14" aria-hidden="true"><g transform="translate(${cx},${cy})"><circle r="${half}" fill="${color}" stroke="${stroke}" stroke-width="1"/></g></svg>`;
291
+ }
292
+ }
293
+
294
  // Hover elements
295
  const hoverLine = gHover.append('line').attr('stroke-width', 1);
296
 
 
504
 
505
  // Create small SVG for marker shape
506
  const markerSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
507
+ markerSvg.setAttribute('width', '18');
508
+ markerSvg.setAttribute('height', '14');
509
  markerSvg.style.display = 'inline-block';
510
 
511
  const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
512
+ g.setAttribute('transform', 'translate(9,7)');
513
 
514
  let shape;
515
+ const size = 9;
516
  const halfSize = size / 2;
517
  switch(s.marker) {
518
  case 'circle':
 
565
  const sx = Math.max(steps[0], Math.min(steps[steps.length-1], Math.round(xScale.invert(mx)/1)*1));
566
  const nearest = steps.reduce((best, s)=> Math.abs(s - xScale.invert(mx)) < Math.abs(best - xScale.invert(mx)) ? s : best, steps[0]);
567
  const xpx = xScale(nearest);
568
+ const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
569
+ const axisColor = isDark ? 'rgba(255,255,255,0.25)' : 'rgba(0,0,0,0.25)';
570
+ hoverLine.attr('x1', xpx).attr('x2', xpx).style('display', null).attr('stroke', axisColor);
571
  // Tooltip content
572
  let html = `<div><strong>${getMetricDisplayName(metricKey)}</strong></div><div><strong>step</strong> ${nearest}</div>`;
573
+ const seriesWithPoint = series
574
+ .map(s => {
575
+ const m = new Map(s.values.map(v=>[v.step, v]));
576
+ const pt = m.get(nearest);
577
+ return { s, pt };
578
+ })
579
+ .filter(d => d.pt && d.pt.value != null);
580
+ // Sort runs at hovered step
581
+ seriesWithPoint.sort((a,b) => {
582
+ if (isRankStrict) return (b.pt.value - a.pt.value); // rank: descendant
583
+ return (a.pt.value - b.pt.value); // normal: ascendant
584
+ });
585
+ seriesWithPoint.forEach(({s, pt}) => {
586
+ const formatVal = (vv) => (isRankStrict ? d3.format('d')(vv) : (+vv).toFixed(4));
587
+ const errTxt = (pt.stderr != null && isFinite(pt.stderr) && pt.stderr > 0) ? ` ± ${formatVal(pt.stderr)}` : '';
588
+ html += `<div style=\"display:flex;align-items:center;gap:6px;white-space:nowrap;\">${shapeSvgMarkup(s.marker, s.color)}<strong>${s.run}</strong> ${formatVal(pt.value)}${errTxt}</div>`;
589
  });
590
  tipInner.innerHTML = html;
591
  const offsetX = 12, offsetY = 12;
 
610
  }));
611
  metricList = Array.from(new Set(rows.map(r=>r.metric))).sort();
612
  runList = Array.from(new Set(rows.map(r=>r.run))).sort();
613
+ // Prioritize "FineVision" first in legend/order
614
+ const hasFV = runList.includes('FineVision');
615
+ runOrder = hasFV ? ['FineVision', ...runList.filter(r => r !== 'FineVision')] : runList;
616
  // Build dataByMetric
617
  metricList.forEach(m => {
618
  const map = {};
app/src/content/embeds/s25-ratings.html CHANGED
@@ -140,7 +140,7 @@
140
 
141
  const labelMetric = document.createElement('label');
142
  Object.assign(labelMetric.style, {
143
- fontSize: '12px', color: 'var(--muted-color)', display: 'flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap', padding: '6px 10px', marginLeft: 'auto'
144
  });
145
  labelMetric.textContent = 'Metric';
146
  const selectMetric = document.createElement('select');
@@ -155,7 +155,7 @@
155
  gap: '8px',
156
  alignItems: 'center',
157
  flexWrap: 'nowrap',
158
- fontSize: '11px',
159
  marginLeft: '8px'
160
  });
161
  controls.appendChild(legendInline);
@@ -269,6 +269,25 @@
269
  }
270
  }
271
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
  // Hover elements
273
  const hoverLine = gHover.append('line').attr('stroke-width', 1);
274
 
@@ -474,7 +493,7 @@
474
  .style('cursor', 'crosshair');
475
  });
476
 
477
- // Inline legend content with marker shapes
478
  legendInline.innerHTML = '';
479
  series.forEach(s => {
480
  const legendItem = document.createElement('span');
@@ -482,15 +501,15 @@
482
 
483
  // Create small SVG for marker shape
484
  const markerSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
485
- markerSvg.setAttribute('width', '16');
486
- markerSvg.setAttribute('height', '12');
487
  markerSvg.style.display = 'inline-block';
488
 
489
  const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
490
  g.setAttribute('transform', 'translate(8,6)');
491
 
492
  let shape;
493
- const size = 6;
494
  const halfSize = size / 2;
495
  switch(s.marker) {
496
  case 'circle':
@@ -543,17 +562,20 @@
543
  const sx = Math.max(steps[0], Math.min(steps[steps.length-1], Math.round(xScale.invert(mx)/1)*1));
544
  const nearest = steps.reduce((best, s)=> Math.abs(s - xScale.invert(mx)) < Math.abs(best - xScale.invert(mx)) ? s : best, steps[0]);
545
  const xpx = xScale(nearest);
546
- hoverLine.attr('x1', xpx).attr('x2', xpx).style('display', null).attr('stroke', 'rgba(0,0,0,0.25)');
547
- // Tooltip content
548
  let html = `<div><strong>${getMetricDisplayName(metricKey)}</strong></div><div><strong>step</strong> ${nearest}</div>`;
 
549
  series.forEach(s=>{
550
  const m = new Map(s.values.map(v=>[v.step, v]));
551
  const pt = m.get(nearest);
552
- if (pt && pt.value != null) {
553
- const formatVal = (vv) => (isRankStrict ? d3.format('d')(vv) : (+vv).toFixed(4));
554
- const errTxt = (pt.stderr != null && isFinite(pt.stderr) && pt.stderr > 0) ? ` ± ${formatVal(pt.stderr)}` : '';
555
- html += `<div><span style=\"display:inline-block;width:10px;height:10px;background:${s.color};border-radius:50%;margin-right:6px;\"></span><strong>${s.run}</strong> ${formatVal(pt.value)}${errTxt}</div>`;
556
- }
 
 
557
  });
558
  tipInner.innerHTML = html;
559
  const offsetX = 12, offsetY = 12;
@@ -578,7 +600,8 @@
578
  }));
579
  metricList = Array.from(new Set(rows.map(r=>r.metric))).sort();
580
  runList = Array.from(new Set(rows.map(r=>r.run))).sort();
581
- runOrder = runList;
 
582
  // Build dataByMetric
583
  metricList.forEach(m => {
584
  const map = {};
 
140
 
141
  const labelMetric = document.createElement('label');
142
  Object.assign(labelMetric.style, {
143
+ fontSize: '11px', color: 'var(--muted-color)', opacity: 0.8, display: 'flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap', padding: '6px 10px', marginLeft: 'auto'
144
  });
145
  labelMetric.textContent = 'Metric';
146
  const selectMetric = document.createElement('select');
 
155
  gap: '8px',
156
  alignItems: 'center',
157
  flexWrap: 'nowrap',
158
+ fontSize: '14px',
159
  marginLeft: '8px'
160
  });
161
  controls.appendChild(legendInline);
 
269
  }
270
  }
271
 
272
+ // Small SVG markup for marker shape (used in tooltip/legend)
273
+ function shapeSvgMarkup(shape, color) {
274
+ const stroke = color;
275
+ switch (shape) {
276
+ case 'circle':
277
+ return `<svg width="18" height="14" viewBox="-6 -6 12 12" aria-hidden="true"><circle r="5" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
278
+ case 'square':
279
+ return `<svg width="18" height="14" viewBox="-6 -6 12 12" aria-hidden="true"><rect x="-5" y="-5" width="10" height="10" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
280
+ case 'triangle':
281
+ return `<svg width="18" height="14" viewBox="-6 -6 12 12" aria-hidden="true"><path d="M0,-6 L5,3 L-5,3 Z" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
282
+ case 'diamond':
283
+ return `<svg width="18" height="14" viewBox="-6 -6 12 12" aria-hidden="true"><path d="M0,-6 L6,0 L0,6 L-6,0 Z" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
284
+ case 'inverted-triangle':
285
+ return `<svg width="18" height="14" viewBox="-6 -6 12 12" aria-hidden="true"><path d="M0,6 L5,-3 L-5,-3 Z" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
286
+ default:
287
+ return `<svg width="18" height="14" viewBox="-6 -6 12 12" aria-hidden="true"><circle r="5" fill="${color}" stroke="${stroke}" stroke-width="1" /></svg>`;
288
+ }
289
+ }
290
+
291
  // Hover elements
292
  const hoverLine = gHover.append('line').attr('stroke-width', 1);
293
 
 
493
  .style('cursor', 'crosshair');
494
  });
495
 
496
+ // Inline legend content with marker shapes (size 9 => SVG 18x14)
497
  legendInline.innerHTML = '';
498
  series.forEach(s => {
499
  const legendItem = document.createElement('span');
 
501
 
502
  // Create small SVG for marker shape
503
  const markerSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
504
+ markerSvg.setAttribute('width', '18');
505
+ markerSvg.setAttribute('height', '14');
506
  markerSvg.style.display = 'inline-block';
507
 
508
  const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
509
  g.setAttribute('transform', 'translate(8,6)');
510
 
511
  let shape;
512
+ const size = 9;
513
  const halfSize = size / 2;
514
  switch(s.marker) {
515
  case 'circle':
 
562
  const sx = Math.max(steps[0], Math.min(steps[steps.length-1], Math.round(xScale.invert(mx)/1)*1));
563
  const nearest = steps.reduce((best, s)=> Math.abs(s - xScale.invert(mx)) < Math.abs(best - xScale.invert(mx)) ? s : best, steps[0]);
564
  const xpx = xScale(nearest);
565
+ hoverLine.attr('x1', xpx).attr('x2', xpx).style('display', null);
566
+ // Tooltip content (sorted by value at hovered step)
567
  let html = `<div><strong>${getMetricDisplayName(metricKey)}</strong></div><div><strong>step</strong> ${nearest}</div>`;
568
+ const items = [];
569
  series.forEach(s=>{
570
  const m = new Map(s.values.map(v=>[v.step, v]));
571
  const pt = m.get(nearest);
572
+ if (pt && pt.value != null) items.push({ s, pt });
573
+ });
574
+ const formatVal = (vv) => (isRankStrictFlag ? d3.format('d')(vv) : (+vv).toFixed(4));
575
+ items.sort((a,b)=> isRankStrictFlag ? (b.pt.value - a.pt.value) : (a.pt.value - b.pt.value));
576
+ items.forEach(({ s, pt }) => {
577
+ const errTxt = (pt.stderr != null && isFinite(pt.stderr) && pt.stderr > 0) ? ` ± ${formatVal(pt.stderr)}` : '';
578
+ html += `<div style=\"display:flex;align-items:center;gap:6px;\">${shapeSvgMarkup(s.marker, s.color)}<strong>${s.run}</strong> ${formatVal(pt.value)}${errTxt}</div>`;
579
  });
580
  tipInner.innerHTML = html;
581
  const offsetX = 12, offsetY = 12;
 
600
  }));
601
  metricList = Array.from(new Set(rows.map(r=>r.metric))).sort();
602
  runList = Array.from(new Set(rows.map(r=>r.run))).sort();
603
+ const prioritizeRun = (list, name) => (list.includes(name) ? [name, ...list.filter(r => r !== name)] : list);
604
+ runOrder = prioritizeRun(runList, 'FineVision');
605
  // Build dataByMetric
606
  metricList.forEach(m => {
607
  const map = {};
app/src/content/embeds/ss-vs-s1.html CHANGED
@@ -140,7 +140,7 @@
140
 
141
  const labelMetric = document.createElement('label');
142
  Object.assign(labelMetric.style, {
143
- fontSize: '12px', color: 'var(--muted-color)', display: 'flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap', padding: '6px 10px', marginLeft: 'auto'
144
  });
145
  labelMetric.textContent = 'Metric';
146
  const selectMetric = document.createElement('select');
@@ -155,7 +155,7 @@
155
  gap: '8px',
156
  alignItems: 'center',
157
  flexWrap: 'nowrap',
158
- fontSize: '11px',
159
  marginLeft: '8px'
160
  });
161
  controls.appendChild(legendInline);
@@ -269,6 +269,25 @@
269
  }
270
  }
271
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
  // Hover elements
273
  const hoverLine = gHover.append('line').attr('stroke-width', 1);
274
 
@@ -474,63 +493,19 @@
474
  .style('cursor', 'crosshair');
475
  });
476
 
477
- // Inline legend content with marker shapes
478
  legendInline.innerHTML = '';
479
  series.forEach(s => {
480
  const legendItem = document.createElement('span');
481
  legendItem.style.cssText = 'display:inline-flex;align-items:center;gap:6px;white-space:nowrap;';
482
-
483
- // Create small SVG for marker shape
484
- const markerSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
485
- markerSvg.setAttribute('width', '16');
486
- markerSvg.setAttribute('height', '12');
487
- markerSvg.style.display = 'inline-block';
488
-
489
- const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
490
- g.setAttribute('transform', 'translate(8,6)');
491
-
492
- let shape;
493
- const size = 6;
494
- const halfSize = size / 2;
495
- switch(s.marker) {
496
- case 'circle':
497
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
498
- shape.setAttribute('r', halfSize);
499
- break;
500
- case 'square':
501
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
502
- shape.setAttribute('x', -halfSize);
503
- shape.setAttribute('y', -halfSize);
504
- shape.setAttribute('width', size);
505
- shape.setAttribute('height', size);
506
- break;
507
- case 'triangle':
508
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'path');
509
- shape.setAttribute('d', `M0,${-halfSize * 1.2} L${halfSize * 1.1},${halfSize * 0.6} L${-halfSize * 1.1},${halfSize * 0.6} Z`);
510
- break;
511
- case 'diamond':
512
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'path');
513
- shape.setAttribute('d', `M0,${-halfSize * 1.2} L${halfSize * 1.1},0 L0,${halfSize * 1.2} L${-halfSize * 1.1},0 Z`);
514
- break;
515
- case 'inverted-triangle':
516
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'path');
517
- shape.setAttribute('d', `M0,${halfSize * 1.2} L${halfSize * 1.1},${-halfSize * 0.6} L${-halfSize * 1.1},${-halfSize * 0.6} Z`);
518
- break;
519
- default:
520
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
521
- shape.setAttribute('r', halfSize);
522
- }
523
- shape.setAttribute('fill', s.color);
524
- shape.setAttribute('stroke', s.color);
525
- shape.setAttribute('stroke-width', '1');
526
-
527
- g.appendChild(shape);
528
- markerSvg.appendChild(g);
529
-
530
  const label = document.createElement('span');
531
  label.textContent = s.run;
532
 
533
- legendItem.appendChild(markerSvg);
534
  legendItem.appendChild(label);
535
  legendInline.appendChild(legendItem);
536
  });
@@ -543,17 +518,21 @@
543
  const sx = Math.max(steps[0], Math.min(steps[steps.length-1], Math.round(xScale.invert(mx)/1)*1));
544
  const nearest = steps.reduce((best, s)=> Math.abs(s - xScale.invert(mx)) < Math.abs(best - xScale.invert(mx)) ? s : best, steps[0]);
545
  const xpx = xScale(nearest);
546
- hoverLine.attr('x1', xpx).attr('x2', xpx).style('display', null).attr('stroke', 'rgba(0,0,0,0.25)');
547
- // Tooltip content
548
  let html = `<div><strong>${getMetricDisplayName(metricKey)}</strong></div><div><strong>step</strong> ${nearest}</div>`;
 
549
  series.forEach(s=>{
550
  const m = new Map(s.values.map(v=>[v.step, v]));
551
  const pt = m.get(nearest);
552
- if (pt && pt.value != null) {
553
- const formatVal = (vv) => (isRankStrict ? d3.format('d')(vv) : (+vv).toFixed(4));
554
- const errTxt = (pt.stderr != null && isFinite(pt.stderr) && pt.stderr > 0) ? ` ± ${formatVal(pt.stderr)}` : '';
555
- html += `<div><span style=\"display:inline-block;width:10px;height:10px;background:${s.color};border-radius:50%;margin-right:6px;\"></span><strong>${s.run}</strong> ${formatVal(pt.value)}${errTxt}</div>`;
556
- }
 
 
 
557
  });
558
  tipInner.innerHTML = html;
559
  const offsetX = 12, offsetY = 12;
@@ -578,7 +557,8 @@
578
  }));
579
  metricList = Array.from(new Set(rows.map(r=>r.metric))).sort();
580
  runList = Array.from(new Set(rows.map(r=>r.run))).sort();
581
- runOrder = runList;
 
582
  // Build dataByMetric
583
  metricList.forEach(m => {
584
  const map = {};
 
140
 
141
  const labelMetric = document.createElement('label');
142
  Object.assign(labelMetric.style, {
143
+ fontSize: '11px', color: 'var(--muted-color)', opacity: 0.8, display: 'flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap', padding: '6px 10px', marginLeft: 'auto'
144
  });
145
  labelMetric.textContent = 'Metric';
146
  const selectMetric = document.createElement('select');
 
155
  gap: '8px',
156
  alignItems: 'center',
157
  flexWrap: 'nowrap',
158
+ fontSize: '14px',
159
  marginLeft: '8px'
160
  });
161
  controls.appendChild(legendInline);
 
269
  }
270
  }
271
 
272
+ // Small SVG markup for marker shape (used in hover tooltip and legend)
273
+ function shapeSvgMarkup(shape, color) {
274
+ const stroke = color;
275
+ switch (shape) {
276
+ case 'circle':
277
+ return `<svg width=\"18\" height=\"14\" viewBox=\"-6 -6 12 12\" aria-hidden=\"true\"><circle r=\"5\" fill=\"${color}\" stroke=\"${stroke}\" stroke-width=\"1\" /></svg>`;
278
+ case 'square':
279
+ return `<svg width=\"18\" height=\"14\" viewBox=\"-6 -6 12 12\" aria-hidden=\"true\"><rect x=\"-5\" y=\"-5\" width=\"10\" height=\"10\" fill=\"${color}\" stroke=\"${stroke}\" stroke-width=\"1\" /></svg>`;
280
+ case 'triangle':
281
+ return `<svg width=\"18\" height=\"14\" viewBox=\"-6 -6 12 12\" aria-hidden=\"true\"><path d=\"M0,-6 L5,3 L-5,3 Z\" fill=\"${color}\" stroke=\"${stroke}\" stroke-width=\"1\" /></svg>`;
282
+ case 'diamond':
283
+ return `<svg width=\"18\" height=\"14\" viewBox=\"-6 -6 12 12\" aria-hidden=\"true\"><path d=\"M0,-6 L6,0 L0,6 L-6,0 Z\" fill=\"${color}\" stroke=\"${stroke}\" stroke-width=\"1\" /></svg>`;
284
+ case 'inverted-triangle':
285
+ return `<svg width=\"18\" height=\"14\" viewBox=\"-6 -6 12 12\" aria-hidden=\"true\"><path d=\"M0,6 L5,-3 L-5,-3 Z\" fill=\"${color}\" stroke=\"${stroke}\" stroke-width=\"1\" /></svg>`;
286
+ default:
287
+ return `<svg width=\"18\" height=\"14\" viewBox=\"-6 -6 12 12\" aria-hidden=\"true\"><circle r=\"5\" fill=\"${color}\" stroke=\"${stroke}\" stroke-width=\"1\" /></svg>`;
288
+ }
289
+ }
290
+
291
  // Hover elements
292
  const hoverLine = gHover.append('line').attr('stroke-width', 1);
293
 
 
493
  .style('cursor', 'crosshair');
494
  });
495
 
496
+ // Inline legend content with marker shapes (size 9 => SVG 18x14)
497
  legendInline.innerHTML = '';
498
  series.forEach(s => {
499
  const legendItem = document.createElement('span');
500
  legendItem.style.cssText = 'display:inline-flex;align-items:center;gap:6px;white-space:nowrap;';
501
+ const markerSpan = document.createElement('span');
502
+ markerSpan.innerHTML = shapeSvgMarkup(s.marker, s.color);
503
+ markerSpan.style.display = 'inline-block';
504
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
505
  const label = document.createElement('span');
506
  label.textContent = s.run;
507
 
508
+ legendItem.appendChild(markerSpan);
509
  legendItem.appendChild(label);
510
  legendInline.appendChild(legendItem);
511
  });
 
518
  const sx = Math.max(steps[0], Math.min(steps[steps.length-1], Math.round(xScale.invert(mx)/1)*1));
519
  const nearest = steps.reduce((best, s)=> Math.abs(s - xScale.invert(mx)) < Math.abs(best - xScale.invert(mx)) ? s : best, steps[0]);
520
  const xpx = xScale(nearest);
521
+ hoverLine.attr('x1', xpx).attr('x2', xpx).style('display', null);
522
+ // Tooltip content sorted
523
  let html = `<div><strong>${getMetricDisplayName(metricKey)}</strong></div><div><strong>step</strong> ${nearest}</div>`;
524
+ const items = [];
525
  series.forEach(s=>{
526
  const m = new Map(s.values.map(v=>[v.step, v]));
527
  const pt = m.get(nearest);
528
+ if (pt && pt.value != null) items.push({ s, pt });
529
+ });
530
+ const isRankStrict = isRankStrictFlag;
531
+ const formatVal = (vv) => (isRankStrict ? d3.format('d')(vv) : (+vv).toFixed(4));
532
+ items.sort((a,b)=> isRankStrict ? (b.pt.value - a.pt.value) : (a.pt.value - b.pt.value));
533
+ items.forEach(({ s, pt }) => {
534
+ const errTxt = (pt.stderr != null && isFinite(pt.stderr) && pt.stderr > 0) ? ` ± ${formatVal(pt.stderr)}` : '';
535
+ html += `<div style=\"display:flex;align-items:center;gap:6px;\">${shapeSvgMarkup(s.marker, s.color)}<strong>${s.run}</strong> ${formatVal(pt.value)}${errTxt}</div>`;
536
  });
537
  tipInner.innerHTML = html;
538
  const offsetX = 12, offsetY = 12;
 
557
  }));
558
  metricList = Array.from(new Set(rows.map(r=>r.metric))).sort();
559
  runList = Array.from(new Set(rows.map(r=>r.run))).sort();
560
+ const prioritizeRun = (list, name) => (list.includes(name) ? [name, ...list.filter(r => r !== name)] : list);
561
+ runOrder = prioritizeRun(runList, 'FineVision');
562
  // Build dataByMetric
563
  metricList.forEach(m => {
564
  const map = {};
app/src/content/embeds/visual-dependency-filters.html CHANGED
@@ -140,7 +140,7 @@
140
 
141
  const labelMetric = document.createElement('label');
142
  Object.assign(labelMetric.style, {
143
- fontSize: '12px', color: 'var(--muted-color)', display: 'flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap', padding: '6px 10px', marginLeft: 'auto'
144
  });
145
  labelMetric.textContent = 'Metric';
146
  const selectMetric = document.createElement('select');
@@ -155,7 +155,7 @@
155
  gap: '8px',
156
  alignItems: 'center',
157
  flexWrap: 'nowrap',
158
- fontSize: '11px',
159
  marginLeft: '8px'
160
  });
161
  controls.appendChild(legendInline);
@@ -269,6 +269,25 @@
269
  }
270
  }
271
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
  // Hover elements
273
  const hoverLine = gHover.append('line').attr('stroke-width', 1);
274
 
@@ -474,63 +493,19 @@
474
  .style('cursor', 'crosshair');
475
  });
476
 
477
- // Inline legend content with marker shapes
478
  legendInline.innerHTML = '';
479
  series.forEach(s => {
480
  const legendItem = document.createElement('span');
481
  legendItem.style.cssText = 'display:inline-flex;align-items:center;gap:6px;white-space:nowrap;';
482
-
483
- // Create small SVG for marker shape
484
- const markerSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
485
- markerSvg.setAttribute('width', '16');
486
- markerSvg.setAttribute('height', '12');
487
- markerSvg.style.display = 'inline-block';
488
-
489
- const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
490
- g.setAttribute('transform', 'translate(8,6)');
491
-
492
- let shape;
493
- const size = 6;
494
- const halfSize = size / 2;
495
- switch(s.marker) {
496
- case 'circle':
497
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
498
- shape.setAttribute('r', halfSize);
499
- break;
500
- case 'square':
501
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
502
- shape.setAttribute('x', -halfSize);
503
- shape.setAttribute('y', -halfSize);
504
- shape.setAttribute('width', size);
505
- shape.setAttribute('height', size);
506
- break;
507
- case 'triangle':
508
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'path');
509
- shape.setAttribute('d', `M0,${-halfSize * 1.2} L${halfSize * 1.1},${halfSize * 0.6} L${-halfSize * 1.1},${halfSize * 0.6} Z`);
510
- break;
511
- case 'diamond':
512
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'path');
513
- shape.setAttribute('d', `M0,${-halfSize * 1.2} L${halfSize * 1.1},0 L0,${halfSize * 1.2} L${-halfSize * 1.1},0 Z`);
514
- break;
515
- case 'inverted-triangle':
516
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'path');
517
- shape.setAttribute('d', `M0,${halfSize * 1.2} L${halfSize * 1.1},${-halfSize * 0.6} L${-halfSize * 1.1},${-halfSize * 0.6} Z`);
518
- break;
519
- default:
520
- shape = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
521
- shape.setAttribute('r', halfSize);
522
- }
523
- shape.setAttribute('fill', s.color);
524
- shape.setAttribute('stroke', s.color);
525
- shape.setAttribute('stroke-width', '1');
526
-
527
- g.appendChild(shape);
528
- markerSvg.appendChild(g);
529
-
530
  const label = document.createElement('span');
531
  label.textContent = s.run;
532
 
533
- legendItem.appendChild(markerSvg);
534
  legendItem.appendChild(label);
535
  legendInline.appendChild(legendItem);
536
  });
@@ -543,17 +518,21 @@
543
  const sx = Math.max(steps[0], Math.min(steps[steps.length-1], Math.round(xScale.invert(mx)/1)*1));
544
  const nearest = steps.reduce((best, s)=> Math.abs(s - xScale.invert(mx)) < Math.abs(best - xScale.invert(mx)) ? s : best, steps[0]);
545
  const xpx = xScale(nearest);
546
- hoverLine.attr('x1', xpx).attr('x2', xpx).style('display', null).attr('stroke', 'rgba(0,0,0,0.25)');
547
- // Tooltip content
548
  let html = `<div><strong>${getMetricDisplayName(metricKey)}</strong></div><div><strong>step</strong> ${nearest}</div>`;
 
549
  series.forEach(s=>{
550
  const m = new Map(s.values.map(v=>[v.step, v]));
551
  const pt = m.get(nearest);
552
- if (pt && pt.value != null) {
553
- const formatVal = (vv) => (isRankStrict ? d3.format('d')(vv) : (+vv).toFixed(4));
554
- const errTxt = (pt.stderr != null && isFinite(pt.stderr) && pt.stderr > 0) ? ` ± ${formatVal(pt.stderr)}` : '';
555
- html += `<div><span style=\"display:inline-block;width:10px;height:10px;background:${s.color};border-radius:50%;margin-right:6px;\"></span><strong>${s.run}</strong> ${formatVal(pt.value)}${errTxt}</div>`;
556
- }
 
 
 
557
  });
558
  tipInner.innerHTML = html;
559
  const offsetX = 12, offsetY = 12;
@@ -578,7 +557,8 @@
578
  }));
579
  metricList = Array.from(new Set(rows.map(r=>r.metric))).sort();
580
  runList = Array.from(new Set(rows.map(r=>r.run))).sort();
581
- runOrder = runList;
 
582
  // Build dataByMetric
583
  metricList.forEach(m => {
584
  const map = {};
 
140
 
141
  const labelMetric = document.createElement('label');
142
  Object.assign(labelMetric.style, {
143
+ fontSize: '11px', color: 'var(--muted-color)', opacity: 0.8, display: 'flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap', padding: '6px 10px', marginLeft: 'auto'
144
  });
145
  labelMetric.textContent = 'Metric';
146
  const selectMetric = document.createElement('select');
 
155
  gap: '8px',
156
  alignItems: 'center',
157
  flexWrap: 'nowrap',
158
+ fontSize: '14px',
159
  marginLeft: '8px'
160
  });
161
  controls.appendChild(legendInline);
 
269
  }
270
  }
271
 
272
+ // Small SVG markup for marker shape (used in hover tooltip and legend)
273
+ function shapeSvgMarkup(shape, color) {
274
+ const stroke = color;
275
+ switch (shape) {
276
+ case 'circle':
277
+ return `<svg width=\"18\" height=\"14\" viewBox=\"-6 -6 12 12\" aria-hidden=\"true\"><circle r=\"5\" fill=\"${color}\" stroke=\"${stroke}\" stroke-width=\"1\" /></svg>`;
278
+ case 'square':
279
+ return `<svg width=\"18\" height=\"14\" viewBox=\"-6 -6 12 12\" aria-hidden=\"true\"><rect x=\"-5\" y=\"-5\" width=\"10\" height=\"10\" fill=\"${color}\" stroke=\"${stroke}\" stroke-width=\"1\" /></svg>`;
280
+ case 'triangle':
281
+ return `<svg width=\"18\" height=\"14\" viewBox=\"-6 -6 12 12\" aria-hidden=\"true\"><path d=\"M0,-6 L5,3 L-5,3 Z\" fill=\"${color}\" stroke=\"${stroke}\" stroke-width=\"1\" /></svg>`;
282
+ case 'diamond':
283
+ return `<svg width=\"18\" height=\"14\" viewBox=\"-6 -6 12 12\" aria-hidden=\"true\"><path d=\"M0,-6 L6,0 L0,6 L-6,0 Z\" fill=\"${color}\" stroke=\"${stroke}\" stroke-width=\"1\" /></svg>`;
284
+ case 'inverted-triangle':
285
+ return `<svg width=\"18\" height=\"14\" viewBox=\"-6 -6 12 12\" aria-hidden=\"true\"><path d=\"M0,6 L5,-3 L-5,-3 Z\" fill=\"${color}\" stroke=\"${stroke}\" stroke-width=\"1\" /></svg>`;
286
+ default:
287
+ return `<svg width=\"18\" height=\"14\" viewBox=\"-6 -6 12 12\" aria-hidden=\"true\"><circle r=\"5\" fill=\"${color}\" stroke=\"${stroke}\" stroke-width=\"1\" /></svg>`;
288
+ }
289
+ }
290
+
291
  // Hover elements
292
  const hoverLine = gHover.append('line').attr('stroke-width', 1);
293
 
 
493
  .style('cursor', 'crosshair');
494
  });
495
 
496
+ // Inline legend content with marker shapes (size 9 => SVG 18x14)
497
  legendInline.innerHTML = '';
498
  series.forEach(s => {
499
  const legendItem = document.createElement('span');
500
  legendItem.style.cssText = 'display:inline-flex;align-items:center;gap:6px;white-space:nowrap;';
501
+ const markerSpan = document.createElement('span');
502
+ markerSpan.innerHTML = shapeSvgMarkup(s.marker, s.color);
503
+ markerSpan.style.display = 'inline-block';
504
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
505
  const label = document.createElement('span');
506
  label.textContent = s.run;
507
 
508
+ legendItem.appendChild(markerSpan);
509
  legendItem.appendChild(label);
510
  legendInline.appendChild(legendItem);
511
  });
 
518
  const sx = Math.max(steps[0], Math.min(steps[steps.length-1], Math.round(xScale.invert(mx)/1)*1));
519
  const nearest = steps.reduce((best, s)=> Math.abs(s - xScale.invert(mx)) < Math.abs(best - xScale.invert(mx)) ? s : best, steps[0]);
520
  const xpx = xScale(nearest);
521
+ hoverLine.attr('x1', xpx).attr('x2', xpx).style('display', null);
522
+ // Tooltip content sorted
523
  let html = `<div><strong>${getMetricDisplayName(metricKey)}</strong></div><div><strong>step</strong> ${nearest}</div>`;
524
+ const items = [];
525
  series.forEach(s=>{
526
  const m = new Map(s.values.map(v=>[v.step, v]));
527
  const pt = m.get(nearest);
528
+ if (pt && pt.value != null) items.push({ s, pt });
529
+ });
530
+ const isRankStrict = isRankStrictFlag;
531
+ const formatVal = (vv) => (isRankStrict ? d3.format('d')(vv) : (+vv).toFixed(4));
532
+ items.sort((a,b)=> isRankStrict ? (b.pt.value - a.pt.value) : (a.pt.value - b.pt.value));
533
+ items.forEach(({ s, pt }) => {
534
+ const errTxt = (pt.stderr != null && isFinite(pt.stderr) && pt.stderr > 0) ? ` ± ${formatVal(pt.stderr)}` : '';
535
+ html += `<div style=\"display:flex;align-items:center;gap:6px;\">${shapeSvgMarkup(s.marker, s.color)}<strong>${s.run}</strong> ${formatVal(pt.value)}${errTxt}</div>`;
536
  });
537
  tipInner.innerHTML = html;
538
  const offsetX = 12, offsetY = 12;
 
557
  }));
558
  metricList = Array.from(new Set(rows.map(r=>r.metric))).sort();
559
  runList = Array.from(new Set(rows.map(r=>r.run))).sort();
560
+ const prioritizeRun = (list, name) => (list.includes(name) ? [name, ...list.filter(r => r !== name)] : list);
561
+ runOrder = prioritizeRun(runList, 'FineVision');
562
  // Build dataByMetric
563
  metricList.forEach(m => {
564
  const map = {};