Spaces:
Running
Running
thibaud frere
commited on
Commit
·
cb92d91
1
Parent(s):
95e6b80
update
Browse files
app/src/content/embeds/d3-line.html
CHANGED
|
@@ -119,19 +119,18 @@
|
|
| 119 |
display: 'flex',
|
| 120 |
gap: '16px',
|
| 121 |
alignItems: 'center',
|
| 122 |
-
justifyContent: '
|
| 123 |
width: '100%'
|
| 124 |
});
|
| 125 |
|
| 126 |
const labelMetric = document.createElement('label');
|
| 127 |
Object.assign(labelMetric.style, {
|
| 128 |
-
fontSize: '12px', color: 'var(--muted-color)', display: 'flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap', padding: '6px 10px'
|
| 129 |
});
|
| 130 |
labelMetric.textContent = 'Metric';
|
| 131 |
const selectMetric = document.createElement('select');
|
| 132 |
Object.assign(selectMetric.style, { fontSize: '12px' });
|
| 133 |
labelMetric.appendChild(selectMetric);
|
| 134 |
-
controls.appendChild(labelMetric);
|
| 135 |
|
| 136 |
// Inline legend on the right of the select
|
| 137 |
const legendInline = document.createElement('div');
|
|
@@ -141,9 +140,11 @@
|
|
| 141 |
gap: '8px',
|
| 142 |
alignItems: 'center',
|
| 143 |
flexWrap: 'nowrap',
|
| 144 |
-
fontSize: '11px'
|
|
|
|
| 145 |
});
|
| 146 |
controls.appendChild(legendInline);
|
|
|
|
| 147 |
|
| 148 |
// Create SVG
|
| 149 |
const svg = d3.select(container).append('svg')
|
|
@@ -224,11 +225,20 @@
|
|
| 224 |
xScale.range([0, innerWidth]);
|
| 225 |
yScale.range([innerHeight, 0]);
|
| 226 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
// Grid (horizontal)
|
| 228 |
gGrid.selectAll('*').remove();
|
| 229 |
-
const yTicks = yScale.ticks(6);
|
| 230 |
gGrid.selectAll('line')
|
| 231 |
-
.data(
|
| 232 |
.join('line')
|
| 233 |
.attr('x1', 0)
|
| 234 |
.attr('x2', innerWidth)
|
|
@@ -241,7 +251,7 @@
|
|
| 241 |
// Axes
|
| 242 |
gAxes.selectAll('*').remove();
|
| 243 |
const xAxis = d3.axisBottom(xScale).ticks(8).tickSizeOuter(0);
|
| 244 |
-
const yAxis = d3.axisLeft(yScale).
|
| 245 |
gAxes.append('g')
|
| 246 |
.attr('transform', `translate(0,${innerHeight})`)
|
| 247 |
.call(xAxis)
|
|
@@ -268,7 +278,7 @@
|
|
| 268 |
gAxes.append('text')
|
| 269 |
.attr('class', 'axis-label axis-label--y')
|
| 270 |
.attr('text-anchor', 'middle')
|
| 271 |
-
.attr('transform', `translate(${-
|
| 272 |
.style('font-size', '12px')
|
| 273 |
.style('fill', tickColor)
|
| 274 |
.text('Value');
|
|
|
|
| 119 |
display: 'flex',
|
| 120 |
gap: '16px',
|
| 121 |
alignItems: 'center',
|
| 122 |
+
justifyContent: 'space-between',
|
| 123 |
width: '100%'
|
| 124 |
});
|
| 125 |
|
| 126 |
const labelMetric = document.createElement('label');
|
| 127 |
Object.assign(labelMetric.style, {
|
| 128 |
+
fontSize: '12px', color: 'var(--muted-color)', display: 'flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap', padding: '6px 10px', marginLeft: 'auto'
|
| 129 |
});
|
| 130 |
labelMetric.textContent = 'Metric';
|
| 131 |
const selectMetric = document.createElement('select');
|
| 132 |
Object.assign(selectMetric.style, { fontSize: '12px' });
|
| 133 |
labelMetric.appendChild(selectMetric);
|
|
|
|
| 134 |
|
| 135 |
// Inline legend on the right of the select
|
| 136 |
const legendInline = document.createElement('div');
|
|
|
|
| 140 |
gap: '8px',
|
| 141 |
alignItems: 'center',
|
| 142 |
flexWrap: 'nowrap',
|
| 143 |
+
fontSize: '11px',
|
| 144 |
+
marginLeft: '8px'
|
| 145 |
});
|
| 146 |
controls.appendChild(legendInline);
|
| 147 |
+
controls.appendChild(labelMetric);
|
| 148 |
|
| 149 |
// Create SVG
|
| 150 |
const svg = d3.select(container).append('svg')
|
|
|
|
| 225 |
xScale.range([0, innerWidth]);
|
| 226 |
yScale.range([innerHeight, 0]);
|
| 227 |
|
| 228 |
+
// Compute integer ticks for Y
|
| 229 |
+
const yDomain = yScale.domain();
|
| 230 |
+
const yMin = Math.min(yDomain[0], yDomain[1]);
|
| 231 |
+
const yMax = Math.max(yDomain[0], yDomain[1]);
|
| 232 |
+
let yStep = Math.max(1, Math.round((yMax - yMin) / 6));
|
| 233 |
+
if (!isFinite(yStep) || yStep <= 0) yStep = 1;
|
| 234 |
+
let yIntTicks = [];
|
| 235 |
+
for (let v = Math.ceil(yMin); v <= Math.floor(yMax); v += yStep) { yIntTicks.push(v); }
|
| 236 |
+
if (yIntTicks.length === 0) { yIntTicks = [Math.round(yMin), Math.round(yMax)]; }
|
| 237 |
+
|
| 238 |
// Grid (horizontal)
|
| 239 |
gGrid.selectAll('*').remove();
|
|
|
|
| 240 |
gGrid.selectAll('line')
|
| 241 |
+
.data(yIntTicks)
|
| 242 |
.join('line')
|
| 243 |
.attr('x1', 0)
|
| 244 |
.attr('x2', innerWidth)
|
|
|
|
| 251 |
// Axes
|
| 252 |
gAxes.selectAll('*').remove();
|
| 253 |
const xAxis = d3.axisBottom(xScale).ticks(8).tickSizeOuter(0);
|
| 254 |
+
const yAxis = d3.axisLeft(yScale).tickValues(yIntTicks).tickSizeOuter(0).tickFormat(d3.format('d'));
|
| 255 |
gAxes.append('g')
|
| 256 |
.attr('transform', `translate(0,${innerHeight})`)
|
| 257 |
.call(xAxis)
|
|
|
|
| 278 |
gAxes.append('text')
|
| 279 |
.attr('class', 'axis-label axis-label--y')
|
| 280 |
.attr('text-anchor', 'middle')
|
| 281 |
+
.attr('transform', `translate(${-44},${innerHeight/2}) rotate(-90)`)
|
| 282 |
.style('font-size', '12px')
|
| 283 |
.style('fill', tickColor)
|
| 284 |
.text('Value');
|
app/src/content/embeds/d3-pie.html
CHANGED
|
@@ -77,20 +77,20 @@
|
|
| 77 |
}));
|
| 78 |
|
| 79 |
// Layout
|
| 80 |
-
let width=800, height=
|
| 81 |
const CAPTION_GAP = 28; // espace entre titre et donut (augmenté)
|
| 82 |
const LEGEND_GAP = 8; // espace entre donut et légende (réduit)
|
| 83 |
const updateSize = () => {
|
| 84 |
width = container.clientWidth || 800;
|
| 85 |
-
height = Math.max(
|
| 86 |
svg.attr('width', width).attr('height', height);
|
| 87 |
gRoot.attr('transform', `translate(${margin.left},${margin.top})`);
|
| 88 |
return { innerWidth: width - margin.left - margin.right, innerHeight: height - margin.top - margin.bottom };
|
| 89 |
};
|
| 90 |
|
| 91 |
-
function renderLegend(categories, colorOf, innerWidth,
|
| 92 |
const legendHeight = 60;
|
| 93 |
-
gLegend.attr('x', 0).attr('y',
|
| 94 |
const root = gLegend.selectAll('div').data([0]).join('xhtml:div');
|
| 95 |
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>`);
|
| 96 |
}
|
|
@@ -116,7 +116,7 @@
|
|
| 116 |
return pool[idx % pool.length] || primary;
|
| 117 |
};
|
| 118 |
|
| 119 |
-
|
| 120 |
|
| 121 |
// Clear plots
|
| 122 |
gPlots.selectAll('*').remove();
|
|
@@ -133,6 +133,11 @@
|
|
| 133 |
const arc = d3.arc().innerRadius(innerR).outerRadius(radius).cornerRadius(3);
|
| 134 |
const arcLabel = d3.arc().innerRadius((innerR + radius) / 2).outerRadius((innerR + radius) / 2);
|
| 135 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
const captions = new Map(METRICS.map(m => [m.key, `${m.title}`]));
|
| 137 |
|
| 138 |
METRICS.forEach((metric, idx) => {
|
|
|
|
| 77 |
}));
|
| 78 |
|
| 79 |
// Layout
|
| 80 |
+
let width=800, height=450; const margin = { top: 0, right: 24, bottom: 32, left: 24 };
|
| 81 |
const CAPTION_GAP = 28; // espace entre titre et donut (augmenté)
|
| 82 |
const LEGEND_GAP = 8; // espace entre donut et légende (réduit)
|
| 83 |
const updateSize = () => {
|
| 84 |
width = container.clientWidth || 800;
|
| 85 |
+
height = Math.max(260, Math.round(width/3.4));
|
| 86 |
svg.attr('width', width).attr('height', height);
|
| 87 |
gRoot.attr('transform', `translate(${margin.left},${margin.top})`);
|
| 88 |
return { innerWidth: width - margin.left - margin.right, innerHeight: height - margin.top - margin.bottom };
|
| 89 |
};
|
| 90 |
|
| 91 |
+
function renderLegend(categories, colorOf, innerWidth, legendY){
|
| 92 |
const legendHeight = 60;
|
| 93 |
+
gLegend.attr('x', 0).attr('y', legendY).attr('width', innerWidth).attr('height', legendHeight);
|
| 94 |
const root = gLegend.selectAll('div').data([0]).join('xhtml:div');
|
| 95 |
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>`);
|
| 96 |
}
|
|
|
|
| 116 |
return pool[idx % pool.length] || primary;
|
| 117 |
};
|
| 118 |
|
| 119 |
+
// Legend will be positioned after radius is known
|
| 120 |
|
| 121 |
// Clear plots
|
| 122 |
gPlots.selectAll('*').remove();
|
|
|
|
| 133 |
const arc = d3.arc().innerRadius(innerR).outerRadius(radius).cornerRadius(3);
|
| 134 |
const arcLabel = d3.arc().innerRadius((innerR + radius) / 2).outerRadius((innerR + radius) / 2);
|
| 135 |
|
| 136 |
+
// Now that radius is known, position legend just beneath donuts
|
| 137 |
+
const tempCy = innerHeight / 2;
|
| 138 |
+
const legendY = tempCy + radius + (CAPTION_GAP * 2);
|
| 139 |
+
renderLegend(categories, colorOf, innerWidth, legendY);
|
| 140 |
+
|
| 141 |
const captions = new Map(METRICS.map(m => [m.key, `${m.title}`]));
|
| 142 |
|
| 143 |
METRICS.forEach((metric, idx) => {
|