Spaces:
Running
Running
thibaud frere
commited on
Commit
·
14a3a46
1
Parent(s):
f679fa9
add responsiveness on pie and comparison
Browse files
app/src/content/article.mdx
CHANGED
|
@@ -359,7 +359,7 @@ Below is an example of a correctly identified Duplicate ("Photo"), a false-posit
|
|
| 359 |
We open-source the deduplication pipeline here as well as the precomputed test-set embedding’s here.
|
| 360 |
<br/>
|
| 361 |
<Wide>
|
| 362 |
-
<HtmlEmbed src="comparison.html" desc="Examples of the Deduplication Pipeline."/>
|
| 363 |
</Wide>
|
| 364 |
|
| 365 |
| Name | Samples | Contamination Rate | Performance Drop |
|
|
|
|
| 359 |
We open-source the deduplication pipeline here as well as the precomputed test-set embedding’s here.
|
| 360 |
<br/>
|
| 361 |
<Wide>
|
| 362 |
+
<HtmlEmbed src="comparison.html" align="center" desc="Examples of the Deduplication Pipeline."/>
|
| 363 |
</Wide>
|
| 364 |
|
| 365 |
| Name | Samples | Contamination Rate | Performance Drop |
|
app/src/content/embeds/comparison.html
CHANGED
|
@@ -1,13 +1,13 @@
|
|
| 1 |
<div class="image-comparison" style="width:100%;margin:10px 0;"></div>
|
| 2 |
<style>
|
| 3 |
.image-comparison { position: relative; }
|
| 4 |
-
.image-comparison .controls { display:flex; align-items:center; gap:16px; justify-content:flex-
|
| 5 |
-
.image-comparison .controls label { font-size:
|
| 6 |
.image-comparison .controls select {
|
| 7 |
-
font-size:
|
| 8 |
-
padding: 8px
|
| 9 |
border: 1px solid var(--border-color);
|
| 10 |
-
border-radius:
|
| 11 |
background-color: var(--surface-bg);
|
| 12 |
color: var(--text-color);
|
| 13 |
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%230f1115' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
|
|
@@ -16,6 +16,7 @@
|
|
| 16 |
background-size: 12px;
|
| 17 |
-webkit-appearance: none; appearance: none; cursor: pointer;
|
| 18 |
transition: border-color .15s ease, box-shadow .15s ease;
|
|
|
|
| 19 |
}
|
| 20 |
[data-theme="dark"] .image-comparison .controls select {
|
| 21 |
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
|
|
@@ -23,8 +24,15 @@
|
|
| 23 |
.image-comparison .controls select:hover { border-color: var(--primary-color); }
|
| 24 |
.image-comparison .controls select:focus { border-color: var(--primary-color); box-shadow: 0 0 0 3px rgba(232,137,171,.25); outline: none; }
|
| 25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
.image-comparison .grid { display:grid; grid-template-columns: repeat(4, 1fr); gap: 12px; width:100%; align-items: start; }
|
| 27 |
-
|
|
|
|
|
|
|
| 28 |
|
| 29 |
.image-comparison .card { position: relative; border:1px solid var(--border-color); border-radius:10px; overflow:hidden; background: var(--surface-bg); display:flex; flex-direction:column; }
|
| 30 |
.image-comparison .card .media { position: relative; width:100%; height: 200px; background: var(--surface-2, var(--surface-bg)); display:block; }
|
|
|
|
| 1 |
<div class="image-comparison" style="width:100%;margin:10px 0;"></div>
|
| 2 |
<style>
|
| 3 |
.image-comparison { position: relative; }
|
| 4 |
+
.image-comparison .controls { display:flex; align-items:center; gap:16px; justify-content:center; flex-wrap:wrap; margin:14px 0; }
|
| 5 |
+
.image-comparison .controls label { font-size:14px; color: var(--text-color); display:flex; align-items:center; justify-content:center; gap:10px; font-weight:600; }
|
| 6 |
.image-comparison .controls select {
|
| 7 |
+
font-size: 14px;
|
| 8 |
+
padding: 8px 32px 8px 12px;
|
| 9 |
border: 1px solid var(--border-color);
|
| 10 |
+
border-radius: 10px;
|
| 11 |
background-color: var(--surface-bg);
|
| 12 |
color: var(--text-color);
|
| 13 |
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%230f1115' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
|
|
|
|
| 16 |
background-size: 12px;
|
| 17 |
-webkit-appearance: none; appearance: none; cursor: pointer;
|
| 18 |
transition: border-color .15s ease, box-shadow .15s ease;
|
| 19 |
+
box-shadow: 0 1px 2px rgba(0,0,0,.04);
|
| 20 |
}
|
| 21 |
[data-theme="dark"] .image-comparison .controls select {
|
| 22 |
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
|
|
|
|
| 24 |
.image-comparison .controls select:hover { border-color: var(--primary-color); }
|
| 25 |
.image-comparison .controls select:focus { border-color: var(--primary-color); box-shadow: 0 0 0 3px rgba(232,137,171,.25); outline: none; }
|
| 26 |
|
| 27 |
+
/* Responsive: empiler label et select proprement sur petits écrans */
|
| 28 |
+
@media (max-width: 520px) {
|
| 29 |
+
.image-comparison .controls label { flex-direction: column; gap: 6px; }
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
.image-comparison .grid { display:grid; grid-template-columns: repeat(4, 1fr); gap: 12px; width:100%; align-items: start; }
|
| 33 |
+
/* Large → 4 colonnes; Medium → 2 colonnes; Mobile → 1 colonne */
|
| 34 |
+
@media (max-width: 1100px) { .image-comparison .grid { grid-template-columns: repeat(2, 1fr); } }
|
| 35 |
+
@media (max-width: 680px) { .image-comparison .grid { grid-template-columns: 1fr; } }
|
| 36 |
|
| 37 |
.image-comparison .card { position: relative; border:1px solid var(--border-color); border-radius:10px; overflow:hidden; background: var(--surface-bg); display:flex; flex-direction:column; }
|
| 38 |
.image-comparison .card .media { position: relative; width:100%; height: 200px; background: var(--surface-2, var(--surface-bg)); display:block; }
|
app/src/content/embeds/d3-pie.html
CHANGED
|
@@ -51,7 +51,7 @@
|
|
| 51 |
} else { tipInner = tip.querySelector('.d3-tooltip__inner') || tip; }
|
| 52 |
|
| 53 |
// SVG scaffolding
|
| 54 |
-
const svg = d3.select(container).append('svg').attr('width','100%').style('display','block');
|
| 55 |
const gRoot = svg.append('g');
|
| 56 |
const gLegend = gRoot.append('foreignObject').attr('class','legend');
|
| 57 |
const gPlots = gRoot.append('g').attr('class','plots');
|
|
@@ -89,52 +89,91 @@
|
|
| 89 |
}));
|
| 90 |
|
| 91 |
// Layout
|
| 92 |
-
let width=800
|
| 93 |
-
const CAPTION_GAP =
|
| 94 |
-
const
|
|
|
|
|
|
|
|
|
|
| 95 |
const updateSize = () => {
|
| 96 |
width = container.clientWidth || 800;
|
| 97 |
-
|
| 98 |
-
svg.attr('width', width).attr('height', height);
|
| 99 |
gRoot.attr('transform', `translate(${margin.left},${margin.top})`);
|
| 100 |
-
return { innerWidth: width - margin.left - margin.right
|
| 101 |
};
|
| 102 |
|
| 103 |
function renderLegend(categories, colorOf, innerWidth, legendY){
|
| 104 |
-
const legendHeight =
|
| 105 |
gLegend.attr('x', 0).attr('y', legendY).attr('width', innerWidth).attr('height', legendHeight);
|
| 106 |
const root = gLegend.selectAll('div').data([0]).join('xhtml:div');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
root.html(`<div class="items">${categories.map(c => `<div class="item"><span class="swatch" style="background:${colorOf(c)}"></span><span style="font-weight:500">${c}</span></div>`).join('')}</div>`);
|
| 108 |
}
|
| 109 |
|
| 110 |
function drawPies(rows){
|
| 111 |
-
const { innerWidth
|
| 112 |
|
| 113 |
// Catégories (triées) + échelle de couleurs harmonisée avec banner.html
|
| 114 |
const categories = Array.from(new Set(rows.map(r => r.eagle_cathegory || 'Unknown'))).sort();
|
| 115 |
const color = d3.scaleOrdinal().domain(categories).range(d3.schemeTableau10);
|
| 116 |
const colorOf = (cat) => color(cat || 'Unknown');
|
| 117 |
|
| 118 |
-
// Legend will be positioned after radius is known
|
| 119 |
-
|
| 120 |
// Clear plots
|
| 121 |
gPlots.selectAll('*').remove();
|
| 122 |
|
| 123 |
-
|
| 124 |
-
const
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
|
| 131 |
const pie = d3.pie().sort(null).value(d => d.value).padAngle(0.02);
|
| 132 |
const arc = d3.arc().innerRadius(innerR).outerRadius(radius).cornerRadius(3);
|
| 133 |
const arcLabel = d3.arc().innerRadius((innerR + radius) / 2).outerRadius((innerR + radius) / 2);
|
| 134 |
|
| 135 |
-
//
|
| 136 |
-
const
|
| 137 |
-
|
|
|
|
| 138 |
renderLegend(categories, colorOf, innerWidth, legendY);
|
| 139 |
|
| 140 |
const captions = new Map(METRICS.map(m => [m.key, `${m.title}`]));
|
|
@@ -146,9 +185,10 @@
|
|
| 146 |
const values = categories.map(c => ({ category: c, value: totals.get(c) || 0 }));
|
| 147 |
const totalSum = d3.sum(values, d => d.value);
|
| 148 |
|
| 149 |
-
const
|
| 150 |
-
const
|
| 151 |
-
const
|
|
|
|
| 152 |
|
| 153 |
const gCell = gPlots.append('g').attr('transform', `translate(${cx},${cy})`);
|
| 154 |
|
|
@@ -187,9 +227,13 @@
|
|
| 187 |
gCell.append('text')
|
| 188 |
.attr('class','caption')
|
| 189 |
.attr('text-anchor','middle')
|
| 190 |
-
.attr('y', -(radius + CAPTION_GAP))
|
| 191 |
.text(captions.get(metric.key));
|
| 192 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
}
|
| 194 |
|
| 195 |
async function init(){
|
|
|
|
| 51 |
} else { tipInner = tip.querySelector('.d3-tooltip__inner') || tip; }
|
| 52 |
|
| 53 |
// SVG scaffolding
|
| 54 |
+
const svg = d3.select(container).append('svg').attr('width','100%').style('display','block').attr('preserveAspectRatio','xMidYMin meet');
|
| 55 |
const gRoot = svg.append('g');
|
| 56 |
const gLegend = gRoot.append('foreignObject').attr('class','legend');
|
| 57 |
const gPlots = gRoot.append('g').attr('class','plots');
|
|
|
|
| 89 |
}));
|
| 90 |
|
| 91 |
// Layout
|
| 92 |
+
let width=800; const margin = { top: 8, right: 24, bottom: 0, left: 24 };
|
| 93 |
+
const CAPTION_GAP = 24; // espace entre titre et donut
|
| 94 |
+
const GAP_X = 20; // espace entre colonnes
|
| 95 |
+
const GAP_Y = 12; // espace entre lignes
|
| 96 |
+
const LEGEND_HEIGHT = 44; // hauteur de la légende
|
| 97 |
+
const TOP_OFFSET = 4; // décalage vertical supplémentaire pour aérer le haut
|
| 98 |
const updateSize = () => {
|
| 99 |
width = container.clientWidth || 800;
|
| 100 |
+
svg.attr('width', width);
|
|
|
|
| 101 |
gRoot.attr('transform', `translate(${margin.left},${margin.top})`);
|
| 102 |
+
return { innerWidth: width - margin.left - margin.right };
|
| 103 |
};
|
| 104 |
|
| 105 |
function renderLegend(categories, colorOf, innerWidth, legendY){
|
| 106 |
+
const legendHeight = LEGEND_HEIGHT;
|
| 107 |
gLegend.attr('x', 0).attr('y', legendY).attr('width', innerWidth).attr('height', legendHeight);
|
| 108 |
const root = gLegend.selectAll('div').data([0]).join('xhtml:div');
|
| 109 |
+
root
|
| 110 |
+
.style('height', legendHeight + 'px')
|
| 111 |
+
.style('display', 'flex')
|
| 112 |
+
.style('align-items', 'center')
|
| 113 |
+
.style('justify-content', 'center');
|
| 114 |
root.html(`<div class="items">${categories.map(c => `<div class="item"><span class="swatch" style="background:${colorOf(c)}"></span><span style="font-weight:500">${c}</span></div>`).join('')}</div>`);
|
| 115 |
}
|
| 116 |
|
| 117 |
function drawPies(rows){
|
| 118 |
+
const { innerWidth } = updateSize();
|
| 119 |
|
| 120 |
// Catégories (triées) + échelle de couleurs harmonisée avec banner.html
|
| 121 |
const categories = Array.from(new Set(rows.map(r => r.eagle_cathegory || 'Unknown'))).sort();
|
| 122 |
const color = d3.scaleOrdinal().domain(categories).range(d3.schemeTableau10);
|
| 123 |
const colorOf = (cat) => color(cat || 'Unknown');
|
| 124 |
|
|
|
|
|
|
|
| 125 |
// Clear plots
|
| 126 |
gPlots.selectAll('*').remove();
|
| 127 |
|
| 128 |
+
// Colonnes responsives: tenter 4 colonnes si possible, sinon descendre à 3/2/1
|
| 129 |
+
const selectCols = () => {
|
| 130 |
+
const MIN_RADIUS = 80; // garantir lisibilité
|
| 131 |
+
const allowed = [4, 2, 1]; // seulement 4 / 2 / 1 colonnes
|
| 132 |
+
// 1) essayer avec contrainte de rayon minimal
|
| 133 |
+
for (const c of allowed) {
|
| 134 |
+
const cw = (innerWidth - GAP_X * (c - 1)) / c;
|
| 135 |
+
const r = Math.max(30, Math.min(cw * 0.42, 120));
|
| 136 |
+
const gw = c * (r * 2) + (c - 1) * GAP_X;
|
| 137 |
+
if (gw <= innerWidth && r >= MIN_RADIUS) {
|
| 138 |
+
return { c, r };
|
| 139 |
+
}
|
| 140 |
+
}
|
| 141 |
+
// 2) sinon, première config qui tient (même si plus petit rayon)
|
| 142 |
+
for (const c of allowed) {
|
| 143 |
+
const cw = (innerWidth - GAP_X * (c - 1)) / c;
|
| 144 |
+
const r = Math.max(30, Math.min(cw * 0.42, 120));
|
| 145 |
+
const gw = c * (r * 2) + (c - 1) * GAP_X;
|
| 146 |
+
if (gw <= innerWidth) {
|
| 147 |
+
return { c, r };
|
| 148 |
+
}
|
| 149 |
+
}
|
| 150 |
+
// 3) fallback très petit écran
|
| 151 |
+
const r1 = Math.max(30, Math.min(innerWidth * 0.42, 120));
|
| 152 |
+
return { c: 1, r: r1 };
|
| 153 |
+
};
|
| 154 |
+
const { c: cols, r: radius } = selectCols();
|
| 155 |
+
const rowsCount = Math.ceil(METRICS.length / cols);
|
| 156 |
+
const innerR = Math.round(radius * 0.28);
|
| 157 |
+
// Calculer un espacement effectif pour occuper toute la largeur disponible
|
| 158 |
+
const baseGap = GAP_X;
|
| 159 |
+
const effectiveGapX = cols > 1
|
| 160 |
+
? Math.max(baseGap, Math.floor((innerWidth - cols * (radius * 2)) / (cols - 1)))
|
| 161 |
+
: 0;
|
| 162 |
+
// largeur réelle de la grille avec l'espacement effectif
|
| 163 |
+
const gridWidth = cols * (radius * 2) + (cols - 1) * effectiveGapX;
|
| 164 |
+
const xOffset = Math.max(0, Math.floor((innerWidth - gridWidth) / 2));
|
| 165 |
+
gPlots.attr('transform', `translate(${xOffset},${TOP_OFFSET})`);
|
| 166 |
+
const perRowHeight = Math.ceil(radius * 2 + CAPTION_GAP + 20); // donut + caption + marge
|
| 167 |
+
const plotsHeight = rowsCount * perRowHeight + (rowsCount - 1) * GAP_Y;
|
| 168 |
|
| 169 |
const pie = d3.pie().sort(null).value(d => d.value).padAngle(0.02);
|
| 170 |
const arc = d3.arc().innerRadius(innerR).outerRadius(radius).cornerRadius(3);
|
| 171 |
const arcLabel = d3.arc().innerRadius((innerR + radius) / 2).outerRadius((innerR + radius) / 2);
|
| 172 |
|
| 173 |
+
// Positionner la légende sous les graphiques
|
| 174 |
+
const legendY = TOP_OFFSET + plotsHeight + 4;
|
| 175 |
+
// Légende centrée globalement (pleine largeur du conteneur)
|
| 176 |
+
gLegend.attr('x', 0).attr('width', innerWidth);
|
| 177 |
renderLegend(categories, colorOf, innerWidth, legendY);
|
| 178 |
|
| 179 |
const captions = new Map(METRICS.map(m => [m.key, `${m.title}`]));
|
|
|
|
| 185 |
const values = categories.map(c => ({ category: c, value: totals.get(c) || 0 }));
|
| 186 |
const totalSum = d3.sum(values, d => d.value);
|
| 187 |
|
| 188 |
+
const rowIdx = Math.floor(idx / cols);
|
| 189 |
+
const colIdx = idx % cols;
|
| 190 |
+
const cx = colIdx * ((radius * 2) + effectiveGapX) + radius;
|
| 191 |
+
const cy = TOP_OFFSET + rowIdx * (perRowHeight + GAP_Y) + CAPTION_GAP + radius;
|
| 192 |
|
| 193 |
const gCell = gPlots.append('g').attr('transform', `translate(${cx},${cy})`);
|
| 194 |
|
|
|
|
| 227 |
gCell.append('text')
|
| 228 |
.attr('class','caption')
|
| 229 |
.attr('text-anchor','middle')
|
| 230 |
+
.attr('y', -(radius + (CAPTION_GAP - 6)))
|
| 231 |
.text(captions.get(metric.key));
|
| 232 |
});
|
| 233 |
+
|
| 234 |
+
// Définir la hauteur totale du SVG après avoir placé les éléments
|
| 235 |
+
const totalHeight = Math.ceil(margin.top + TOP_OFFSET + plotsHeight + 4 + LEGEND_HEIGHT + margin.bottom);
|
| 236 |
+
svg.attr('height', totalHeight);
|
| 237 |
}
|
| 238 |
|
| 239 |
async function init(){
|