datashader-browser 0.1.0a2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Run Datashader over bounded-memory streams in a browser Python runtime."""
|
|
2
|
+
|
|
3
|
+
from .streaming import Progress, StreamingCanvas, iter_csv_chunks, iter_parquet_batches
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"Progress",
|
|
7
|
+
"StreamingCanvas",
|
|
8
|
+
"iter_csv_chunks",
|
|
9
|
+
"iter_parquet_batches",
|
|
10
|
+
]
|
|
11
|
+
__version__ = "0.1.0a2"
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"""Single-pass, bounded-memory Datashader aggregation.
|
|
2
|
+
|
|
3
|
+
The browser cannot assume that the complete dataset fits in its WebAssembly
|
|
4
|
+
heap. This module keeps one input chunk and Datashader's fixed-size aggregate
|
|
5
|
+
state resident at a time. It intentionally starts with point glyphs: they do
|
|
6
|
+
not require overlap between adjacent chunks and therefore provide a sound
|
|
7
|
+
foundation for later Arrow and DuckDB-Wasm sources.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from collections.abc import Callable, Iterable, Iterator, Sequence
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
import sys
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
import pandas as pd
|
|
19
|
+
|
|
20
|
+
import datashader as ds
|
|
21
|
+
from datashader.compiler import compile_components
|
|
22
|
+
from datashader.core import _bypixel_sanitise
|
|
23
|
+
from datashader.glyphs import Point
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True, slots=True)
|
|
27
|
+
class Progress:
|
|
28
|
+
"""Progress reported after an input chunk has been aggregated."""
|
|
29
|
+
|
|
30
|
+
chunks: int
|
|
31
|
+
rows: int
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
ProgressCallback = Callable[[Progress], None]
|
|
35
|
+
CancelCallback = Callable[[], bool]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def iter_csv_chunks(
|
|
39
|
+
path: str | Path,
|
|
40
|
+
*,
|
|
41
|
+
chunksize: int = 250_000,
|
|
42
|
+
columns: Iterable[str] | None = None,
|
|
43
|
+
**read_csv_kwargs: Any,
|
|
44
|
+
) -> Iterator[pd.DataFrame]:
|
|
45
|
+
"""Yield bounded-size DataFrames from a CSV in the browser filesystem.
|
|
46
|
+
|
|
47
|
+
Parameters mirror :func:`pandas.read_csv`. Only requested columns are
|
|
48
|
+
decoded, which is important in memory-constrained WebAssembly runtimes.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
if chunksize <= 0:
|
|
52
|
+
raise ValueError("chunksize must be greater than zero")
|
|
53
|
+
usecols = None if columns is None else list(columns)
|
|
54
|
+
yield from pd.read_csv(
|
|
55
|
+
path,
|
|
56
|
+
chunksize=chunksize,
|
|
57
|
+
usecols=usecols,
|
|
58
|
+
**read_csv_kwargs,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def iter_parquet_batches(
|
|
63
|
+
source: Any,
|
|
64
|
+
*,
|
|
65
|
+
batch_size: int = 250_000,
|
|
66
|
+
columns: Sequence[str] | None = None,
|
|
67
|
+
row_groups: Sequence[int] | None = None,
|
|
68
|
+
use_threads: bool = False,
|
|
69
|
+
) -> Iterator[Any]:
|
|
70
|
+
"""Yield bounded-size Arrow record batches from a Parquet source.
|
|
71
|
+
|
|
72
|
+
``source`` may be a path or any seekable file object accepted by
|
|
73
|
+
:class:`pyarrow.parquet.ParquetFile`. In JupyterLite/Xeus-Python this
|
|
74
|
+
includes files uploaded or mounted in the browser filesystem. PyArrow is
|
|
75
|
+
imported lazily so CSV-only users do not need the optional dependency.
|
|
76
|
+
|
|
77
|
+
Arrow batches are intentionally returned without first materializing a
|
|
78
|
+
complete Pandas DataFrame. :meth:`StreamingCanvas.points` converts one
|
|
79
|
+
batch at a time immediately before aggregation.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
if batch_size <= 0:
|
|
83
|
+
raise ValueError("batch_size must be greater than zero")
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
import pyarrow.parquet as pq
|
|
87
|
+
except ImportError as exc: # pragma: no cover - depends on optional install
|
|
88
|
+
raise ImportError(
|
|
89
|
+
"Parquet streaming requires PyArrow; install "
|
|
90
|
+
"datashader-browser[data]"
|
|
91
|
+
) from exc
|
|
92
|
+
|
|
93
|
+
parquet = pq.ParquetFile(source)
|
|
94
|
+
yield from parquet.iter_batches(
|
|
95
|
+
batch_size=batch_size,
|
|
96
|
+
row_groups=None if row_groups is None else list(row_groups),
|
|
97
|
+
columns=None if columns is None else list(columns),
|
|
98
|
+
use_threads=use_threads,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class StreamingCanvas:
|
|
103
|
+
"""A Datashader canvas that consumes Pandas or Arrow chunks.
|
|
104
|
+
|
|
105
|
+
Unlike ``datashader.Canvas.points``, the full source is never retained.
|
|
106
|
+
The current implementation requires explicit ranges so aggregation stays
|
|
107
|
+
single-pass. Use source metadata, a cheap bounds query, or a preparatory
|
|
108
|
+
pass to determine those ranges.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
def __init__(
|
|
112
|
+
self,
|
|
113
|
+
*,
|
|
114
|
+
plot_width: int = 600,
|
|
115
|
+
plot_height: int = 600,
|
|
116
|
+
x_range: tuple[float, float],
|
|
117
|
+
y_range: tuple[float, float],
|
|
118
|
+
x_axis_type: str = "linear",
|
|
119
|
+
y_axis_type: str = "linear",
|
|
120
|
+
) -> None:
|
|
121
|
+
self._canvas = ds.Canvas(
|
|
122
|
+
plot_width=plot_width,
|
|
123
|
+
plot_height=plot_height,
|
|
124
|
+
x_range=x_range,
|
|
125
|
+
y_range=y_range,
|
|
126
|
+
x_axis_type=x_axis_type,
|
|
127
|
+
y_axis_type=y_axis_type,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def canvas(self) -> ds.Canvas:
|
|
132
|
+
"""The underlying Datashader canvas."""
|
|
133
|
+
|
|
134
|
+
return self._canvas
|
|
135
|
+
|
|
136
|
+
def points(
|
|
137
|
+
self,
|
|
138
|
+
chunks: Iterable[Any],
|
|
139
|
+
x: str,
|
|
140
|
+
y: str,
|
|
141
|
+
*,
|
|
142
|
+
agg: Any | None = None,
|
|
143
|
+
on_progress: ProgressCallback | None = None,
|
|
144
|
+
cancelled: CancelCallback | None = None,
|
|
145
|
+
):
|
|
146
|
+
"""Aggregate point chunks while retaining only fixed-size state.
|
|
147
|
+
|
|
148
|
+
The aggregation uses Datashader's compiler directly, so binning and
|
|
149
|
+
reduction behavior match the native package rather than a separate
|
|
150
|
+
reimplementation.
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
reduction = ds.count() if agg is None else agg
|
|
154
|
+
glyph = Point(x, y)
|
|
155
|
+
iterator = iter(chunks)
|
|
156
|
+
|
|
157
|
+
try:
|
|
158
|
+
first = next(iterator)
|
|
159
|
+
except StopIteration as exc:
|
|
160
|
+
raise ValueError("chunks must contain at least one DataFrame") from exc
|
|
161
|
+
|
|
162
|
+
first, dshape = _prepare_chunk(first, glyph, reduction)
|
|
163
|
+
schema = dshape.measure
|
|
164
|
+
glyph.validate(schema)
|
|
165
|
+
reduction.validate(schema)
|
|
166
|
+
self._canvas.validate()
|
|
167
|
+
|
|
168
|
+
if reduction.uses_row_index(cuda=False, partitioned=False):
|
|
169
|
+
raise NotImplementedError(
|
|
170
|
+
"row-index reductions are not supported by the streaming points MVP"
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
(
|
|
174
|
+
create,
|
|
175
|
+
info,
|
|
176
|
+
append,
|
|
177
|
+
_combine,
|
|
178
|
+
finalize,
|
|
179
|
+
antialias_stage_2,
|
|
180
|
+
antialias_stage_2_funcs,
|
|
181
|
+
_column_names,
|
|
182
|
+
) = compile_components(
|
|
183
|
+
reduction,
|
|
184
|
+
schema,
|
|
185
|
+
glyph,
|
|
186
|
+
antialias=False,
|
|
187
|
+
cuda=False,
|
|
188
|
+
partitioned=False,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
extend = glyph._build_extend( # noqa: SLF001 - Datashader extension API
|
|
192
|
+
self._canvas.x_axis.mapper,
|
|
193
|
+
self._canvas.y_axis.mapper,
|
|
194
|
+
info,
|
|
195
|
+
append,
|
|
196
|
+
antialias_stage_2,
|
|
197
|
+
antialias_stage_2_funcs,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
x_range = self._canvas.x_range
|
|
201
|
+
y_range = self._canvas.y_range
|
|
202
|
+
assert x_range is not None and y_range is not None
|
|
203
|
+
self._canvas.validate_ranges(x_range, y_range)
|
|
204
|
+
|
|
205
|
+
width = self._canvas.plot_width
|
|
206
|
+
height = self._canvas.plot_height
|
|
207
|
+
x_st = self._canvas.x_axis.compute_scale_and_translate(x_range, width)
|
|
208
|
+
y_st = self._canvas.y_axis.compute_scale_and_translate(y_range, height)
|
|
209
|
+
transform = x_st + y_st
|
|
210
|
+
bounds = x_range + y_range
|
|
211
|
+
bases = create((height, width))
|
|
212
|
+
|
|
213
|
+
processed_chunks = 0
|
|
214
|
+
processed_rows = 0
|
|
215
|
+
|
|
216
|
+
for chunk in _prepend(first, iterator):
|
|
217
|
+
if cancelled is not None and cancelled():
|
|
218
|
+
raise RuntimeError("aggregation cancelled")
|
|
219
|
+
prepared, chunk_dshape = _prepare_chunk(chunk, glyph, reduction)
|
|
220
|
+
if chunk_dshape.measure != schema:
|
|
221
|
+
raise TypeError(
|
|
222
|
+
"all chunks must have matching Datashader schemas; "
|
|
223
|
+
f"expected {schema}, received {chunk_dshape.measure}"
|
|
224
|
+
)
|
|
225
|
+
extend(bases, prepared, transform, bounds)
|
|
226
|
+
processed_chunks += 1
|
|
227
|
+
processed_rows += len(prepared)
|
|
228
|
+
if on_progress is not None:
|
|
229
|
+
on_progress(Progress(processed_chunks, processed_rows))
|
|
230
|
+
|
|
231
|
+
x_axis = self._canvas.x_axis.compute_index(x_st, width)
|
|
232
|
+
y_axis = self._canvas.y_axis.compute_index(y_st, height)
|
|
233
|
+
return finalize(
|
|
234
|
+
bases,
|
|
235
|
+
cuda=False,
|
|
236
|
+
coords={glyph.x_label: x_axis, glyph.y_label: y_axis},
|
|
237
|
+
dims=[glyph.y_label, glyph.x_label],
|
|
238
|
+
attrs={
|
|
239
|
+
"x_range": x_range,
|
|
240
|
+
"y_range": y_range,
|
|
241
|
+
"source_chunks": processed_chunks,
|
|
242
|
+
"source_rows": processed_rows,
|
|
243
|
+
},
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _prepare_chunk(chunk: Any, glyph: Point, reduction: Any):
|
|
248
|
+
if not isinstance(chunk, pd.DataFrame):
|
|
249
|
+
to_pandas = getattr(chunk, "to_pandas", None)
|
|
250
|
+
if to_pandas is None or not callable(to_pandas):
|
|
251
|
+
raise TypeError(
|
|
252
|
+
"each chunk must be a pandas DataFrame or Arrow-compatible "
|
|
253
|
+
f"record batch, received {type(chunk)!r}"
|
|
254
|
+
)
|
|
255
|
+
# PyArrow's direct Arrow-to-Pandas bridge can stall in the current
|
|
256
|
+
# Emscripten runtime. Building the bounded DataFrame from Python
|
|
257
|
+
# column values avoids that bridge while preserving the batch limit.
|
|
258
|
+
if sys.platform == "emscripten" and callable(getattr(chunk, "to_pydict", None)):
|
|
259
|
+
chunk = pd.DataFrame(chunk.to_pydict())
|
|
260
|
+
else:
|
|
261
|
+
chunk = to_pandas()
|
|
262
|
+
if not isinstance(chunk, pd.DataFrame):
|
|
263
|
+
raise TypeError(
|
|
264
|
+
"chunk.to_pandas() must return a pandas DataFrame, received "
|
|
265
|
+
f"{type(chunk)!r}"
|
|
266
|
+
)
|
|
267
|
+
return _bypixel_sanitise(chunk, glyph, reduction)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _prepend(first: Any, rest: Iterator[Any]) -> Iterator[Any]:
|
|
271
|
+
yield first
|
|
272
|
+
yield from rest
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: datashader-browser
|
|
3
|
+
Version: 0.1.0a2
|
|
4
|
+
Summary: Bounded-memory Datashader pipelines for Python running in the browser
|
|
5
|
+
Project-URL: Homepage, https://github.com/SuMayaBee/datashader-browser
|
|
6
|
+
Project-URL: Issues, https://github.com/SuMayaBee/datashader-browser/issues
|
|
7
|
+
Project-URL: Repository, https://github.com/SuMayaBee/datashader-browser
|
|
8
|
+
Author: Sumaiya Islam
|
|
9
|
+
License-Expression: BSD-3-Clause
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: datashader<0.20,>=0.19.1
|
|
22
|
+
Requires-Dist: pandas>=2.2
|
|
23
|
+
Provides-Extra: data
|
|
24
|
+
Requires-Dist: pillow>=11; extra == 'data'
|
|
25
|
+
Requires-Dist: pyarrow>=21; extra == 'data'
|
|
26
|
+
Provides-Extra: test
|
|
27
|
+
Requires-Dist: pytest>=8; extra == 'test'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# datashader-browser
|
|
31
|
+
|
|
32
|
+
Run Datashader-powered visualizations entirely in a browser with Python and
|
|
33
|
+
WebAssembly—without assuming that the complete dataset fits in browser memory.
|
|
34
|
+
|
|
35
|
+
This is an early, independent project. It is not yet an official HoloViz
|
|
36
|
+
project and is not affiliated with or endorsed by the Datashader maintainers.
|
|
37
|
+
|
|
38
|
+
[Try the live browser demo](https://sumayabee.github.io/datashader-browser/).
|
|
39
|
+
Open `streaming-points.ipynb` and run all cells.
|
|
40
|
+
|
|
41
|
+
## What works now
|
|
42
|
+
|
|
43
|
+
The first vertical slice is a bounded-memory point aggregator:
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
import datashader as ds
|
|
47
|
+
import datashader.transfer_functions as tf
|
|
48
|
+
|
|
49
|
+
from datashader_browser import StreamingCanvas, iter_csv_chunks, iter_parquet_batches
|
|
50
|
+
|
|
51
|
+
source = iter_csv_chunks(
|
|
52
|
+
"points.csv",
|
|
53
|
+
chunksize=250_000,
|
|
54
|
+
columns=["longitude", "latitude", "value"],
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
canvas = StreamingCanvas(
|
|
58
|
+
plot_width=1000,
|
|
59
|
+
plot_height=600,
|
|
60
|
+
x_range=(-180, 180),
|
|
61
|
+
y_range=(-90, 90),
|
|
62
|
+
)
|
|
63
|
+
aggregate = canvas.points(source, "longitude", "latitude", agg=ds.mean("value"))
|
|
64
|
+
image = tf.shade(aggregate)
|
|
65
|
+
|
|
66
|
+
# A local Parquet file can yield projected Arrow record batches directly.
|
|
67
|
+
parquet_source = iter_parquet_batches(
|
|
68
|
+
"points.parquet", columns=["longitude", "latitude"], batch_size=250_000
|
|
69
|
+
)
|
|
70
|
+
aggregate = canvas.points(parquet_source, "longitude", "latitude", agg=ds.count())
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Only one input chunk and Datashader's fixed-size aggregate buffers are live at
|
|
74
|
+
once. The implementation calls Datashader's own compiler, so point binning and
|
|
75
|
+
reductions match native Datashader instead of being reimplemented.
|
|
76
|
+
|
|
77
|
+
Currently verified reductions are `count`, `sum`, `mean`, `min`, and `max`.
|
|
78
|
+
Explicit `x_range` and `y_range` values are required to preserve a true
|
|
79
|
+
single-pass pipeline.
|
|
80
|
+
|
|
81
|
+
## NYC Taxi real-data verification
|
|
82
|
+
|
|
83
|
+
The streaming accumulator successfully rendered all **11,842,094 pickup
|
|
84
|
+
locations** in HoloViz's transformed
|
|
85
|
+
[January 2015 NYC Taxi dataset](https://examples.holoviz.org/gallery/nyc_taxi/nyc_taxi.html).
|
|
86
|
+
The 385 MB Parquet file was decoded as 48 Arrow/Pandas batches of at most
|
|
87
|
+
250,000 rows; the complete table was never materialized as one DataFrame.
|
|
88
|
+
|
|
89
|
+

|
|
90
|
+
|
|
91
|
+
One warm-cache Linux run produced the 1200-by-700 image in 3.24 seconds with
|
|
92
|
+
514 MiB peak process RSS. Runtime and memory will vary by machine. Reproduce it
|
|
93
|
+
with:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
uv sync --extra data
|
|
97
|
+
uv run python examples/nyc_taxi.py --download
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
This benchmark validates `StreamingCanvas` against a real large dataset on
|
|
101
|
+
native Python. The browser build now includes an experimental PyArrow Parquet
|
|
102
|
+
reader: open `nyc-taxi-browser.ipynb`, place the source file in the browser
|
|
103
|
+
filesystem, and it streams projected Arrow record batches through the same
|
|
104
|
+
accumulator. Full 385 MB end-to-end browser timing and memory results are still
|
|
105
|
+
an explicit validation task, not a claim made by this repository.
|
|
106
|
+
|
|
107
|
+
## Browser runtime
|
|
108
|
+
|
|
109
|
+
The initial deployment target is JupyterLite with the Xeus-Python WebAssembly
|
|
110
|
+
kernel. Its environment is described in [`environment.yml`](environment.yml)
|
|
111
|
+
and uses the experimental Numba and llvmlite packages from emscripten-forge.
|
|
112
|
+
The kernel executes in a Web Worker, keeping aggregation off the browser UI
|
|
113
|
+
thread. The Parquet-capable build is currently about 256 MB because PyArrow
|
|
114
|
+
brings compiled Arrow and compression dependencies; this is an engineering
|
|
115
|
+
baseline, not the final delivery architecture. The first visit therefore needs
|
|
116
|
+
a substantial download, while subsequent use benefits from the browser cache.
|
|
117
|
+
|
|
118
|
+
Browser verification: `streaming-parquet.ipynb` has been run end-to-end in the
|
|
119
|
+
Xeus-Python WebAssembly kernel. It created a 57.2 MB, two-million-row Parquet
|
|
120
|
+
file in the browser filesystem, streamed it through 20 Arrow batches, checked
|
|
121
|
+
the final row count, and rendered the shaded aggregate with no browser console
|
|
122
|
+
errors. The full 385 MB NYC Taxi browser benchmark remains a separate task.
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
uv sync --extra test
|
|
126
|
+
uv run pytest
|
|
127
|
+
|
|
128
|
+
# Build the browser site (requires the build dependencies below).
|
|
129
|
+
uv pip install -r requirements-build.txt
|
|
130
|
+
uv run jupyter lite build \
|
|
131
|
+
--XeusAddon.environment_file=environment.yml \
|
|
132
|
+
--contents content \
|
|
133
|
+
--output-dir dist
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Large-data strategy
|
|
137
|
+
|
|
138
|
+
“In the browser” does not mean that an unlimited dataset can be copied into a
|
|
139
|
+
Pandas DataFrame. The project uses three execution levels:
|
|
140
|
+
|
|
141
|
+
1. **Stream:** scan CSV or local Parquet/Arrow batches through a bounded-memory Datashader
|
|
142
|
+
accumulator. Total row count affects time, not peak aggregate memory.
|
|
143
|
+
2. **Prune:** use Parquet row groups, spatial partitions, and DuckDB-Wasm to
|
|
144
|
+
read only the viewport and required columns.
|
|
145
|
+
3. **Tile:** for billion-row interactive datasets, fetch spatial partitions or
|
|
146
|
+
precomputed multiresolution aggregates instead of rescanning the source on
|
|
147
|
+
every zoom.
|
|
148
|
+
|
|
149
|
+
See [Architecture](docs/architecture.md), [Research](docs/research.md), and the
|
|
150
|
+
[roadmap](ROADMAP.md) for the engineering decisions and current limitations.
|
|
151
|
+
|
|
152
|
+
## Status
|
|
153
|
+
|
|
154
|
+
This repository is an alpha-stage research implementation. The WebAssembly
|
|
155
|
+
Numba/llvmlite stack works in current demonstrations but its patches have not
|
|
156
|
+
yet been fully upstreamed. Production use should wait for reproducible browser
|
|
157
|
+
builds, compatibility CI, and measured browser memory limits.
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
BSD-3-Clause. Datashader is a separate BSD-3-Clause project owned by its
|
|
162
|
+
respective copyright holders and contributors.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
datashader_browser/__init__.py,sha256=2xa8RV0wxXtsyGjW0gJI-dGI4mQvcsKfGDR4yOR0oY0,296
|
|
2
|
+
datashader_browser/streaming.py,sha256=0Zy2C7798MGpFHNeA_sdoikL8m9pZdKKrlrPAOlCAaQ,9070
|
|
3
|
+
datashader_browser-0.1.0a2.dist-info/METADATA,sha256=dl4huRF2j2d5KlRLCQxsCrF-_RVZDDvI50ZorTaShz0,6648
|
|
4
|
+
datashader_browser-0.1.0a2.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
5
|
+
datashader_browser-0.1.0a2.dist-info/licenses/LICENSE,sha256=hTBVAxwAZaXkBLTae5MjdcULGYL7gclAjqJcCs0lHmE,1500
|
|
6
|
+
datashader_browser-0.1.0a2.dist-info/RECORD,,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Sumaiya Islam
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|