[BUG]: Bar traces draw in O(n²) time since v4.0.0 (getZeroBarDir runs once per bar)
Los mantenedores suelen responder en 2 días
@KoolADE85 ya está trabajando en esto.
Desde el 2/11/2026.
Evaluación
- Dificultad
- 3/5
- Tiempo estimado
- 1-2 días
- Aptitud para principiantes
- 74/100
- Tipo de issue
- Error
- Claridad
- Bien especificado
- Estado de actividad
- Activo
- Stack tecnológico
- javascript
Línea de trabajo
Read src/traces/bar/plot.js, focusing on appendBarText() and getZeroBarDir(). Run the provided 60,000-bar reproduction and compare all-positive data with mixed-sign data. Done means the all-positive case no longer scales quadratically, including traces without text or with textposition: 'none'.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Description
Since v4.0.0, drawing a bar trace takes O(n²) time whenever all bar values have the same sign, which is the common case for counts, totals, and revenue. 60k bars take ~3.7 s in v4.x versus ~0.3 s in v3.7.0, and 100k bars freeze the page for close to a minute (measured in an app that embeds plotly.js 4.1.2).
The cause is in src/traces/bar/plot.js. Since #7872, appendBarText() calls getZeroBarDir(cd, isHorizontal, xa, ya) for every bar. getZeroBarDir loops over all of cd and only returns early once it has seen both a positive and a negative value:
function appendBarText(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, r, overhead, opts, makeOnCompleteCallback) {
...
var zeroBarDir = getZeroBarDir(cd, isHorizontal, xa, ya); // O(n), runs once per bar
var text = getText(fullLayout, cd, i, xa, ya);
textPosition = getTextPosition(trace, i);
...
if (!text || textPosition === 'none' || ...) {
bar.select('text').remove();
return;
}
function getZeroBarDir(cd, isHorizontal, xa, ya) {
var hasPositive = false;
var hasNegative = false;
for (var i = 0; i < cd.length; i++) {
var s = cd[i].s;
if (s > 0) {
hasPositive = true;
} else if (s < 0) {
hasNegative = true;
}
if (hasPositive && hasNegative) {
return 0;
}
}
...
}
The call runs before the !text || textPosition === 'none' early return. So traces without any text, and traces with textposition: 'none', pay the quadratic cost too. Mixed-sign data exits the loop almost immediately, which is why it stays fast.
A Chrome CPU profile of a 30k-bar newPlot attributes 2.6 s of the 3.4 s long task to this function.
Screenshots/Video
N/A (performance). Plotly.newPlot timings for a single bar trace in headless Chromium 156 on an Apple M4 (macOS). Each cell is one run in a fresh page:
| plotly.js | bars | all positive (ms) | mixed sign (ms) | all positive, textposition: 'none' (ms) |
|---|---|---|---|---|
| 3.7.0 | 10,000 | 93 | 86 | 76 |
| 3.7.0 | 30,000 | 175 | 179 | 172 |
| 3.7.0 | 60,000 | 299 | 304 | 301 |
| 4.0.0 | 10,000 | 184 | 83 | 184 |
| 4.0.0 | 30,000 | 1,052 | 191 | 1,134 |
| 4.0.0 | 60,000 | 3,716 | 350 | 3,698 |
| 4.2.0 | 10,000 | 189 | 105 | 186 |
| 4.2.0 | 30,000 | 1,043 | 195 | 1,048 |
| 4.2.0 | 60,000 | 3,736 | 386 | 3,701 |
Doubling the bar count roughly quadruples the time on 4.x, while 3.7.0 and mixed-sign data scale linearly.
Steps to reproduce
Open this page in Chrome and look at the console. Switching the version in the script URL to 3.7.0, or making some y values negative, makes it fast again.
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/plotly.min.js"></script>
</head>
<body>
<div id="plot" style="width:900px;height:450px"></div>
<script>
const n = 60000;
const x = Array.from({ length: n }, (_, i) => i);
const y = Array.from({ length: n }, (_, i) => 1 + ((i * 7919) % 100)); // all positive
const t0 = performance.now();
Plotly.newPlot('plot', [{ type: 'bar', x, y }]).then(() =>
console.log(`newPlot: ${Math.round(performance.now() - t0)} ms`)
);
</script>
</body>
</html>
The same happens from Python with px.bar(x=np.arange(60_000), y=np.random.rand(60_000)).show(), since plotly.py 7.1.0 bundles plotly.js 4.1.1.
Notes
- Introduced by #7872 ("fix(bar): prevent outside text labels from overlapping tilted axis ticks"), commits 6b1363e5a / f291d70fb / 79c20f8ed. It ships in v4.0.0 and is still present in v4.2.0 and
main. - Suggested fix: compute the zero-bar direction once per trace and pass it into
appendBarText. For example, compute it inplotBarsbefore iterating the bars and pass it throughopts, or cache it oncd[0]. Moving the call below the!text/textposition === 'none'early return would also help traces without text, but traces with text would remain quadratic. - Workaround: none from the public API, since
textposition: 'none'does not avoid the call. - Found while benchmarking chart rendering in Streamlit, which bundles plotly.js 4.1.2 for
st.plotly_chart.
- Lenguaje dominante
- JavaScript
- Estrellas
- 18.4k
- Forks
- 2.1k
- Merge medio
- 1 d 5 h
- PR fusionados (30 d)
- 12
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de plotly/plotly.js
-
[BUG]: `sankey`: `node.x` / `node.y` of `0` is ignoredPosiblemente ocupada @adimalkar la tomó hace 3 días. Abiertobug P3 size: 1
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
plotly/plotly.js#8150 · 1 comentario ·
Los mantenedores suelen responder en 2 días
-
[BUG]: `scatterpolargl` drops points between 10000 and 100000 pointsPosiblemente ocupada @Tanishdete333 la tomó hace 3 días. Abiertobug P3 size: 1
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
plotly/plotly.js#8148 · 1 comentario ·
Los mantenedores suelen responder en 2 días
-
[BUG]: findEmpties throws "iterated with no new neighbors" for large NaN regions in contour (double-precision underflow)Posiblemente ocupada @davrens101 la tomó hace 2 días. Abiertobug P3 size: 3
Dificultad 2/5 1-3 horas Aptitud para principiantes 86/100
plotly/plotly.js#8108 · 4 comentarios ·
Los mantenedores suelen responder en 2 días
-
chore P3 plotly-internal size: 3 task
Dificultad 2/5 1-3 horas Aptitud para principiantes 77/100
plotly/plotly.js#8064 · 1 comentario ·
Los mantenedores suelen responder en 2 días
-
chore P1 plotly-internal size: 1 task
Dificultad 1/5 Menos de una hora Aptitud para principiantes 82/100
Los mantenedores suelen responder en 2 días
Todos los issues de plotly/plotly.js
Issues similares
-
Upgrade listener stays on a custom server after close when `preClose` is setPosiblemente ocupada @RaphaelFakhri la tomó hoy. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
fastify/fastify-websocket#388 ·
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
DietrichGebert/ponytail#1343 ·
Los mantenedores suelen responder en 1 día
-
cleanup
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
swombat/souls-house#770 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
Los mantenedores suelen responder en 1 día