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(){
|