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-start; margin:12px 0; }
5
- .image-comparison .controls label { font-size:12px; color: var(--muted-color); display:flex; align-items:center; gap:8px; }
6
  .image-comparison .controls select {
7
- font-size: 12px;
8
- padding: 8px 28px 8px 10px;
9
  border: 1px solid var(--border-color);
10
- border-radius: 8px;
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
- @media (max-width: 980px) { .image-comparison .grid { grid-template-columns: repeat(2, 1fr); } }
 
 
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, height=450; const margin = { top: 0, right: 24, bottom: 32, left: 24 };
93
- const CAPTION_GAP = 28; // espace entre titre et donut (augmenté)
94
- const LEGEND_GAP = 8; // espace entre donut et légende (réduit)
 
 
 
95
  const updateSize = () => {
96
  width = container.clientWidth || 800;
97
- height = Math.max(260, Math.round(width/3.4));
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, innerHeight: height - margin.top - margin.bottom };
101
  };
102
 
103
  function renderLegend(categories, colorOf, innerWidth, legendY){
104
- const legendHeight = 60;
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, innerHeight } = updateSize();
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
- const gapX = 20;
124
- const cols = 4;
125
- const pieAreaWidth = innerWidth;
126
- const cellWidth = (pieAreaWidth - gapX * (cols - 1)) / cols;
127
- const cellHeight = innerHeight - 6; // room for captions
128
- const radius = Math.max(30, Math.min(cellWidth, cellHeight) * 0.42);
129
- const innerR = Math.round(radius * 0.28); // donut, trou modéré
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- // Now that radius is known, position legend just beneath donuts
136
- const tempCy = innerHeight / 2;
137
- const legendY = tempCy + radius + (CAPTION_GAP * 2);
 
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 col = idx; // 0..3
150
- const cx = col * (cellWidth + gapX) + cellWidth / 2;
151
- const cy = innerHeight / 2;
 
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(){