[BUG]: Bar traces draw in O(n²) time since v4.0.0 (getZeroBarDir runs once per bar)
I maintainer di solito rispondono entro 2 giorni
@KoolADE85 ci sta già lavorando.
Dal 2/11/2026.
Valutazione
- Difficoltà
- 3/5
- Tempo stimato
- 1-2 giorni
- Idoneità per principianti
- 74/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- javascript
- Ambito
- data-visualization, performance
Direzione di ricerca
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'.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
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.
- Lingua principale
- JavaScript
- Stelle
- 18.4k
- Fork
- 2.1k
- Merge medio
- 1g 5h
- PR unite (30g)
- 12
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di plotly/plotly.js
-
[BUG]: `sankey`: `node.x` / `node.y` of `0` is ignoredForse già presa @adimalkar l’ha presa 3 giorni fa. Apertabug P3 size: 1
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
plotly/plotly.js#8150 · 1 commento ·
I maintainer di solito rispondono entro 2 giorni
-
[BUG]: `scatterpolargl` drops points between 10000 and 100000 pointsForse già presa @Tanishdete333 l’ha presa 3 giorni fa. Apertabug P3 size: 1
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
plotly/plotly.js#8148 · 1 commento ·
I maintainer di solito rispondono entro 2 giorni
-
[BUG]: findEmpties throws "iterated with no new neighbors" for large NaN regions in contour (double-precision underflow)Forse già presa @davrens101 l’ha presa 2 giorni fa. Apertabug P3 size: 3
Difficoltà 2/5 1-3 ore Idoneità per principianti 86/100
plotly/plotly.js#8108 · 4 commenti ·
I maintainer di solito rispondono entro 2 giorni
-
chore P3 plotly-internal size: 3 task
Difficoltà 2/5 1-3 ore Idoneità per principianti 77/100
plotly/plotly.js#8064 · 1 commento ·
I maintainer di solito rispondono entro 2 giorni
-
chore P1 plotly-internal size: 1 task
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 82/100
I maintainer di solito rispondono entro 2 giorni
Tutte le issue di plotly/plotly.js
Issue simili
-
[ordering-review] workshop order graph: 1 confirmed ordering problem, 3 cycles are false positivesAperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 85/100
githubnext/gh-aw-workshop#5331 ·
I maintainer di solito rispondono entro 1 giorno
-
Remove: 21 SexturyApertachannels:remove check:passed
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 65/100
I maintainer di solito rispondono entro 4 giorni
-
[vika] console keeps a stale "in progress" turn after a daemon restart mid-runForse già presa Una pull request collegata a questa issue è aperta o già unita. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 86/100
volcengine/OpenViking#7011 ·
I maintainer di solito rispondono entro 1 giorno
-
__VERSION__ within dist build is not replaced, causes missing version in XDM implementation detailsAperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
I maintainer di solito rispondono entro 1 giorno
-
Issue: ready for confirmation Reported on 2.4.9 Triage: Dev.Experience
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
magento/magento2#41773 · 1 commento ·
I maintainer di solito rispondono entro 5 giorni