Spaces:
Running
Running
thibaud frere
commited on
Commit
·
b010293
1
Parent(s):
58a7faa
update charts
Browse files- app/src/components/HtmlEmbed.astro +1 -1
- app/src/content/article.mdx +2 -2
- app/src/content/embeds/against-baselines-deduplicated.html +46 -13
- app/src/content/embeds/against-baselines.html +40 -13
- app/src/content/embeds/all-ratings.html +40 -58
- app/src/content/embeds/filters-quad.html +29 -15
- app/src/content/embeds/formatting-filters.html +40 -60
- app/src/content/embeds/image-correspondence-filters.html +40 -60
- app/src/content/embeds/internal-deduplication.html +36 -13
- app/src/content/embeds/relevance-filters.html +40 -60
- app/src/content/embeds/remove-ch.html +51 -17
- app/src/content/embeds/s25-ratings.html +37 -14
- app/src/content/embeds/ss-vs-s1.html +40 -60
- app/src/content/embeds/visual-dependency-filters.html +40 -60
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);
|
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: '
|
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: '
|
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', '
|
489 |
-
markerSvg.setAttribute('height', '
|
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 =
|
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 |
-
|
550 |
-
|
|
|
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 |
-
|
557 |
-
|
558 |
-
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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: '
|
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: '
|
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', '
|
491 |
-
markerSvg.setAttribute('height', '
|
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 =
|
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 |
-
|
552 |
-
|
|
|
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 |
-
|
559 |
-
|
560 |
-
|
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 |
-
|
|
|
|
|
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: '
|
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: '
|
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 |
-
|
484 |
-
|
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 |
-
|
547 |
-
|
|
|
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 |
-
|
554 |
-
|
555 |
-
|
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 |
-
|
|
|
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:
|
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:
|
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 =
|
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','
|
167 |
-
gAxes.append('g').call(yAxis).call(g=>{ g.selectAll('path, line').attr('stroke',axisColor); g.selectAll('text').attr('fill',tickColor).style('font-size','
|
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
|
|
|
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>`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
|
|
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 =
|
337 |
-
if (shape === 'circle') return `<svg width="
|
338 |
-
if (shape === 'square') return `<svg width="
|
339 |
-
if (shape === 'triangle') return `<svg width="
|
340 |
-
if (shape === 'diamond') return `<svg width="
|
341 |
-
if (shape === 'inverted-triangle') return `<svg width="
|
342 |
-
return `<svg width="
|
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: '
|
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: '
|
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 |
-
|
484 |
-
|
485 |
-
|
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(
|
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)
|
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 |
-
|
554 |
-
|
555 |
-
|
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 |
-
|
|
|
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: '
|
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: '
|
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 |
-
|
484 |
-
|
485 |
-
|
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(
|
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)
|
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 |
-
|
554 |
-
|
555 |
-
|
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 |
-
|
|
|
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: '
|
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: '
|
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', '
|
486 |
-
markerSvg.setAttribute('height', '
|
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 =
|
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)
|
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 |
-
|
554 |
-
|
555 |
-
|
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 |
-
|
|
|
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: '
|
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: '
|
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 |
-
|
484 |
-
|
485 |
-
|
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(
|
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)
|
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 |
-
|
554 |
-
|
555 |
-
|
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 |
-
|
|
|
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: '
|
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: '
|
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 =
|
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', '
|
486 |
-
markerSvg.setAttribute('height', '
|
487 |
markerSvg.style.display = 'inline-block';
|
488 |
|
489 |
const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
490 |
-
g.setAttribute('transform', 'translate(
|
491 |
|
492 |
let shape;
|
493 |
-
const size =
|
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 |
-
|
|
|
|
|
547 |
// Tooltip content
|
548 |
let html = `<div><strong>${getMetricDisplayName(metricKey)}</strong></div><div><strong>step</strong> ${nearest}</div>`;
|
549 |
-
series
|
550 |
-
|
551 |
-
|
552 |
-
|
553 |
-
|
554 |
-
|
555 |
-
|
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 |
-
|
|
|
|
|
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: '
|
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: '
|
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', '
|
486 |
-
markerSvg.setAttribute('height', '
|
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 =
|
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)
|
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 |
-
|
554 |
-
|
555 |
-
|
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 |
-
|
|
|
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: '
|
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: '
|
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 |
-
|
484 |
-
|
485 |
-
|
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(
|
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)
|
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 |
-
|
554 |
-
|
555 |
-
|
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 |
-
|
|
|
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: '
|
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: '
|
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 |
-
|
484 |
-
|
485 |
-
|
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(
|
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)
|
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 |
-
|
554 |
-
|
555 |
-
|
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 |
-
|
|
|
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 = {};
|