1.0, modularity, future.
See original GitHub issueuPlot will just cross 20 KB in size shortly, so it’s good to review some things.
uPlot is currently monolithic, though it’s odd to use that term when the next largest lib is 6x bigger and doesn’t do that much more (and in some cases, less). it’s monolithic because it is not possible to opt out of unused features. e.g:
- few actual use-cases need cursor sync (which pulls in with it a pub-sub impl)
- the candlesticks demo does not need any of the line drawing code, or cursor points.
- the sparklines demo does not need anything but scales and line drawing code
- any line chart not needing a time scale does not need the code used for temporal tick gen, the date formatters, timezone/dst handling, etc.
one benefit of a monolith is reduced code size when you actually do need everything - a modular design would probably be 15-25% larger. another possible monolith benefit is performance because features need to cross fewer API boundaries to get things done by having direct access to internals structures and functions.
i’ve considered several avenues to solving this.
- adding feature gating to the build script as i do with domvm [1]. this allows uPlot to remain a monolith, but also offers a way to opt out of including parts of the API a user might not use. e.g.
FEAT_TIME
,FEAT_LINE
,FEAT_SYNC
. this provides a workable alternative to “tree-shaking” in a monolithic architecture. - converting some features into plugins. now that we have a pretty solid API, it’s likely possible to move some things out of the core. however, there are still large parts which cannot be moved out efficiently or effectively. cursor sync is perf-sensitive, and using hooks/plugins for this will not be ideal.
cursor
and hover points can probably be moved out, as canselect
. live legend can be moved out. temporal features cannot really be moved out. in the end i think this will be only be a half-solution. - changing the API to be more ES6-ish and allowing external tools like rollup to shake away unused code. the main issue with this is that the opts can no longer be simple keys/values. e.g. you’d need to define stuff with actual exported functions and be more verbose everywhere:
scales.x: {time: true}
would become something likescales.x: uPlot.TimeScale(scaleOpts)
,cursor: uPlot.Cursor(cursorOpts)
. this would invalidate a large swath of the current API, and require the end user to compile/bundle the ES6 modules.
long-term, option 3 is likely the way to go but will require a significant refactor and major API changes. this is something i will evaluate for v2.0, but this conversion will take time, increase the API complexity and ultimately may not be worthwhile.
on the other hand, the feature-gating route can work without major breakage and still provide good size savings when needed.
i’d like to get v1.0 tagged within the next couple weeks and given all the above, i’m going to make several final breaking API changes.
new uPlot.Line(opts)
does not make sense if we’re staying a monolith and the line drawing functionality can be disabled (like candlesticks and bars [2]) or even feature-gated away. the best way to describe the common core of uPlot 1.0 is Cartesian-Columnar, which describes the format of data it accepts and the type of chart it outputs (regardless of whether it has lines, points, fills, candlesticks, time. or numeric components. i’m going to go back to the original naming for v1.0:new uPlot(opts)
.- in the same vein as above, i’m going to move line-specific options inwards 1 level to a new
series.line: {width, stroke, fill, dash, paths}
. #10 will bring a newseries.points
, so this will help with uniformity, as well as avoid awkwardness if line-drawing features are gated away. - i may future-proof
cursor.points: true
by moving tocursor.points: {show: true}
- all dimensions in the opts will be in CSS pixels. currently
series.width
is in canvas pixels, which guarantees crispness at high data densities, but the line thickness becomes pixelRatio dependent. this means that if you have a lot of data and prefer crispness over line thickness uniformity, you would explicitly setseries.line.width: 1/devicePixelRatio
.
cc @ldstein @danyalejandro @CrashLaker @dgl @silverwind
[1] https://github.com/domvm/domvm [2] https://github.com/leeoniya/uPlot/issues/9#issuecomment-590289541
Issue Analytics
- State:
- Created 4 years ago
- Reactions:1
- Comments:10 (9 by maintainers)
Top GitHub Comments
1.0.0 is tagged and published:
https://github.com/leeoniya/uPlot/releases/tag/1.0.0 https://www.npmjs.com/package/uplot
i’m not sure i want to go that generic with uPlot, as there are other libs which already fill this space, e.g. https://vega.github.io/vega/ which is an entire visualization grammar that can have an infinite number of renderers, then there’s d3 which is somewhere in the middle.
i’d like to stick to canvas as i don’t think any other options are likely to ever be as fast as raw canvas, and only svg or webgl has any hope of even being interactive; the rest are just static charts, for which many other libs exist.
i’ve noticed a new webgl plotting lib that looks nice for when you need oscilloscope-level update rates: https://github.com/danchitnis/webgl-plot. i think it makes a great complement to uPlot in terms of micro-libs but it has its obvious limits, too: “cannot change the line width due to the OpenGL implementation of a line. The OpenGL specification only guarantees a minimum of a single pixel line width. There are other solutions to increase the line width however they substantially increase the size of data vector and take a hit on the performance.”
this will not work without an absolutely massive perf hit for the sizes of datasets that uPlot currently excels at. an alternative may be to provide a pluggable renderer interface where each command is a function call into your renderer, but this would be such a thin layer over just defining your own
series.paths
as to make it quite pointless.once i’ve added feature-gating to the build script and v1 is stable for a while, i’ll get back to giving this more thought, but i need a good break from the endless refactoring & abstraction-building cycle.