fjader 0.2.0__tar.gz
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.
- fjader-0.2.0/LICENSE +28 -0
- fjader-0.2.0/NOTICE +114 -0
- fjader-0.2.0/PKG-INFO +181 -0
- fjader-0.2.0/README.md +155 -0
- fjader-0.2.0/fjader/__init__.py +98 -0
- fjader-0.2.0/fjader/_accessor.py +74 -0
- fjader-0.2.0/fjader/_convert.py +36 -0
- fjader-0.2.0/fjader/_data.py +86 -0
- fjader-0.2.0/fjader/_display.py +97 -0
- fjader-0.2.0/fjader/_from_spec.py +131 -0
- fjader-0.2.0/fjader/_streamlit.py +137 -0
- fjader-0.2.0/fjader/_types.py +82 -0
- fjader-0.2.0/fjader/_version.py +1 -0
- fjader-0.2.0/fjader/_widget.py +59 -0
- fjader-0.2.0/fjader/data.py +12 -0
- fjader-0.2.0/fjader/fields.py +177 -0
- fjader-0.2.0/fjader/interactors.py +154 -0
- fjader-0.2.0/fjader/layout.py +163 -0
- fjader-0.2.0/fjader/marks/__init__.py +718 -0
- fjader-0.2.0/fjader/marks/_base.py +175 -0
- fjader-0.2.0/fjader/marks/area.py +54 -0
- fjader-0.2.0/fjader/marks/bar.py +47 -0
- fjader-0.2.0/fjader/marks/dot.py +24 -0
- fjader-0.2.0/fjader/marks/line.py +29 -0
- fjader-0.2.0/fjader/marks/rect.py +69 -0
- fjader-0.2.0/fjader/marks/text.py +30 -0
- fjader-0.2.0/fjader/pandas.py +20 -0
- fjader-0.2.0/fjader/plot.py +210 -0
- fjader-0.2.0/fjader/polars.py +20 -0
- fjader-0.2.0/fjader/static/THIRD_PARTY_LICENSES.txt +2919 -0
- fjader-0.2.0/fjader/static/widget.css +1 -0
- fjader-0.2.0/fjader/static/widget.js +40824 -0
- fjader-0.2.0/fjader/streamlit_frontend/THIRD_PARTY_LICENSES.txt +3313 -0
- fjader-0.2.0/fjader/streamlit_frontend/assets/index-BQspEPyb.css +1 -0
- fjader-0.2.0/fjader/streamlit_frontend/assets/index-CAUOm_8A.js +1887 -0
- fjader-0.2.0/fjader/streamlit_frontend/index.html +29 -0
- fjader-0.2.0/fjader/tests/__init__.py +0 -0
- fjader-0.2.0/fjader/tests/conftest.py +0 -0
- fjader-0.2.0/fjader/tests/test_accessor.py +93 -0
- fjader-0.2.0/fjader/tests/test_composition.py +101 -0
- fjader-0.2.0/fjader/tests/test_convert.py +69 -0
- fjader-0.2.0/fjader/tests/test_fields.py +137 -0
- fjader-0.2.0/fjader/tests/test_from_spec.py +152 -0
- fjader-0.2.0/fjader/tests/test_marks.py +215 -0
- fjader-0.2.0/fjader/tests/test_serialization.py +170 -0
- fjader-0.2.0/fjader/tests/test_transforms.py +64 -0
- fjader-0.2.0/fjader/transforms/__init__.py +56 -0
- fjader-0.2.0/fjader/transforms/_base.py +25 -0
- fjader-0.2.0/fjader/transforms/js.py +40 -0
- fjader-0.2.0/fjader/transforms/sql.py +35 -0
- fjader-0.2.0/pyproject.toml +39 -0
fjader-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026, the fjader authors
|
|
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.
|
fjader-0.2.0/NOTICE
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
================================================================================
|
|
2
|
+
THIRD-PARTY NOTICES
|
|
3
|
+
================================================================================
|
|
4
|
+
|
|
5
|
+
The projects below shaped fjader's design, and parts of its code are adapted
|
|
6
|
+
from Mosaic. Their licenses follow in full.
|
|
7
|
+
|
|
8
|
+
--------------------------------------------------------------------------------
|
|
9
|
+
Mosaic, by the UW Interactive Data Lab
|
|
10
|
+
https://github.com/uwdata/mosaic
|
|
11
|
+
--------------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
fjader began as a Mosaic client, with @uwdata/mosaic-core as a dependency, and
|
|
14
|
+
its query layer is built on what Mosaic showed works. It now has its own query
|
|
15
|
+
layer, still builds every query with @uwdata/mosaic-sql, a direct dependency,
|
|
16
|
+
and kept Mosaic's ideas and, in places, the structure of Mosaic's code:
|
|
17
|
+
|
|
18
|
+
- Pre-aggregation with sufficient statistics. The preaggregation code in
|
|
19
|
+
src/lib/query-engine/ (sufficient-statistics.ts and preagg/) follows
|
|
20
|
+
mosaic-core's preagg module: aggregates rewritten into partial statistics,
|
|
21
|
+
FILTER clauses carried onto them, and preaggregated tables named by a hash
|
|
22
|
+
of their query.
|
|
23
|
+
- The crossfilter selection model, in which a selection clause filters every
|
|
24
|
+
mark except those its source excludes.
|
|
25
|
+
- vgplot's API style, its interactors (interval brushes, toggles) and its
|
|
26
|
+
JSON spec layout (hconcat, vconcat), without being compatible with vgplot.
|
|
27
|
+
|
|
28
|
+
Mosaic's license, reproduced in full, including its own notice for the parts
|
|
29
|
+
it derives from Observable Plot:
|
|
30
|
+
|
|
31
|
+
BSD 3-Clause License
|
|
32
|
+
|
|
33
|
+
Copyright (c) 2023-2025, UW Interactive Data Lab
|
|
34
|
+
|
|
35
|
+
Redistribution and use in source and binary forms, with or without
|
|
36
|
+
modification, are permitted provided that the following conditions are met:
|
|
37
|
+
|
|
38
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
39
|
+
list of conditions and the following disclaimer.
|
|
40
|
+
|
|
41
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
42
|
+
this list of conditions and the following disclaimer in the documentation
|
|
43
|
+
and/or other materials provided with the distribution.
|
|
44
|
+
|
|
45
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
46
|
+
contributors may be used to endorse or promote products derived from
|
|
47
|
+
this software without specific prior written permission.
|
|
48
|
+
|
|
49
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
50
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
51
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
52
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
53
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
54
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
55
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
56
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
57
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
58
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
Portions of this software are derived from Observable Plot, which is released
|
|
63
|
+
under the ISC license.
|
|
64
|
+
|
|
65
|
+
Copyright 2020-2023 Observable, Inc.
|
|
66
|
+
|
|
67
|
+
Permission to use, copy, modify, and/or distribute this software for any purpose
|
|
68
|
+
with or without fee is hereby granted, provided that the above copyright notice
|
|
69
|
+
and this permission notice appear in all copies.
|
|
70
|
+
|
|
71
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
72
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
|
73
|
+
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
74
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
|
75
|
+
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
|
76
|
+
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
|
77
|
+
THIS SOFTWARE.
|
|
78
|
+
|
|
79
|
+
--------------------------------------------------------------------------------
|
|
80
|
+
Observable Plot, by Observable, Inc.
|
|
81
|
+
https://github.com/observablehq/plot
|
|
82
|
+
--------------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
fjader's mark API borrows from Observable Plot, though it is its own API and
|
|
85
|
+
not compatible with Plot's: mark names and channels, the shorthands that imply
|
|
86
|
+
stacking (x for x1/x2, y for y1/y2), the stackX/stackY conventions, z as the
|
|
87
|
+
series grouping channel, and many defaults.
|
|
88
|
+
|
|
89
|
+
fjader contains no code from Observable Plot; its license is included as a
|
|
90
|
+
courtesy. Observable Plot's license (ISC):
|
|
91
|
+
|
|
92
|
+
Copyright 2020-2025 Observable, Inc.
|
|
93
|
+
|
|
94
|
+
Permission to use, copy, modify, and/or distribute this software for any purpose
|
|
95
|
+
with or without fee is hereby granted, provided that the above copyright notice
|
|
96
|
+
and this permission notice appear in all copies.
|
|
97
|
+
|
|
98
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
99
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
|
100
|
+
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
101
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
|
102
|
+
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
|
103
|
+
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
|
104
|
+
THIS SOFTWARE.
|
|
105
|
+
|
|
106
|
+
--------------------------------------------------------------------------------
|
|
107
|
+
Bundled dependencies
|
|
108
|
+
--------------------------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
The Python package ships prebuilt JavaScript bundles that include third-party
|
|
111
|
+
packages, among them Svelte, d3, Flechette, mosaic-sql, Apache Arrow and
|
|
112
|
+
DuckDB-WASM. Each bundle sits next to a THIRD_PARTY_LICENSES.txt listing every
|
|
113
|
+
package compiled into it, with its license in full. The npm package bundles
|
|
114
|
+
nothing: its dependencies install with their own licenses.
|
fjader-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fjader
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: An experimental, declarative charting library for Svelte and Python that queries DuckDB and renders with WebGPU
|
|
5
|
+
License-Expression: BSD-3-Clause
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
License-File: NOTICE
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Framework :: Jupyter
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
13
|
+
Requires-Dist: narwhals>=1.0
|
|
14
|
+
Requires-Dist: anywidget>=0.9
|
|
15
|
+
Requires-Dist: pytest>=7.0 ; extra == 'dev'
|
|
16
|
+
Requires-Dist: mypy>=1.0 ; extra == 'dev'
|
|
17
|
+
Requires-Dist: pyarrow>=14.0 ; extra == 'pyarrow'
|
|
18
|
+
Requires-Dist: streamlit>=1.0 ; extra == 'streamlit'
|
|
19
|
+
Requires-Dist: pyarrow>=14.0 ; extra == 'streamlit'
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Project-URL: Repository, https://github.com/dlyngman/fjader
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Provides-Extra: pyarrow
|
|
24
|
+
Provides-Extra: streamlit
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# fjader
|
|
28
|
+
|
|
29
|
+
An experimental charting library for Svelte and Python with a declarative, grammar-of-graphics API, that queries DuckDB and renders with WebGPU. It is not feature complete, and the API may still change.
|
|
30
|
+
|
|
31
|
+
fjader builds on the ideas of [Mosaic](https://github.com/uwdata/mosaic): charts are described as marks whose data comes from SQL queries run in DuckDB, and linked charts filter each other through selections. Its marks and channels borrow from [Observable Plot](https://observablehq.com/plot/), but the API is its own and not compatible with Plot's. Query results arrive as Arrow and are drawn with WebGPU.
|
|
32
|
+
|
|
33
|
+
There are three ways to use it: Svelte components; a JSON spec, rendered from JavaScript with `Fjader.render(element, spec)`; and a Python package that builds specs and renders them in Jupyter, marimo or Streamlit.
|
|
34
|
+
|
|
35
|
+
The name comes from Swedish _fjäder_ (say "fyeh-der"), which means both spring and feather.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install @fjader/svelte @uwdata/mosaic-sql # Svelte 5 components, and the SQL builder the examples use
|
|
41
|
+
pip install fjader # Python: marimo and Jupyter
|
|
42
|
+
pip install "fjader[streamlit]" # Python: Streamlit
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
fjader needs Chrome 149 or later, or another Chromium browser on that version. It relies on two recent features other browsers don't ship yet: WebGPU's `immediate_address_space`, through which every chart passes its scales to the shaders (so every zoom and pan uses it), and the `Temporal` API, used for calendar offsets. There is no fallback renderer.
|
|
46
|
+
|
|
47
|
+
Three things to know before using it in a deployed app:
|
|
48
|
+
|
|
49
|
+
- **Data goes to the browser in full.** The Python widget and the Streamlit component send every column of a DataFrame to the browser, where DuckDB-WASM queries it. Anyone viewing the app can extract it, so pass only the columns you mean to show. In Jupyter, saved widget state can also store the data in the notebook file.
|
|
50
|
+
- **DuckDB-WASM loads from jsDelivr at runtime.** The browser fetches it from the CDN, so locked-down networks that block it cannot render charts.
|
|
51
|
+
- **Specs are trusted input.** A spec's `{ "sql": "…" }` field runs as raw SQL in the viewer's DuckDB-WASM. Don't build specs from untrusted input.
|
|
52
|
+
|
|
53
|
+
## Svelte
|
|
54
|
+
|
|
55
|
+
```svelte
|
|
56
|
+
<script>
|
|
57
|
+
import {
|
|
58
|
+
initFjader,
|
|
59
|
+
ChartContainer,
|
|
60
|
+
WebGPUCanvas,
|
|
61
|
+
AxisLeft,
|
|
62
|
+
AxisBottom,
|
|
63
|
+
Grid,
|
|
64
|
+
BarMarkY,
|
|
65
|
+
stack
|
|
66
|
+
} from '@fjader/svelte';
|
|
67
|
+
import { count, loadParquet } from '@uwdata/mosaic-sql';
|
|
68
|
+
import { onMount } from 'svelte';
|
|
69
|
+
|
|
70
|
+
const { queryEngine } = initFjader();
|
|
71
|
+
let loaded = $state(false);
|
|
72
|
+
|
|
73
|
+
// DuckDB-WASM fetches only full URLs; a bare path means its own file system.
|
|
74
|
+
onMount(async () => {
|
|
75
|
+
const url = new URL('/data/penguins.parquet', location.href).href;
|
|
76
|
+
await queryEngine.exec([loadParquet('penguins', url).toString()]);
|
|
77
|
+
loaded = true;
|
|
78
|
+
});
|
|
79
|
+
</script>
|
|
80
|
+
|
|
81
|
+
{#if loaded}
|
|
82
|
+
<div style="height: 400px">
|
|
83
|
+
<ChartContainer xLabel="island" yLabel="count">
|
|
84
|
+
{#snippet background()}
|
|
85
|
+
<AxisLeft />
|
|
86
|
+
<AxisBottom />
|
|
87
|
+
{/snippet}
|
|
88
|
+
{#snippet canvas()}
|
|
89
|
+
<WebGPUCanvas>
|
|
90
|
+
{#snippet canvasComponents()}
|
|
91
|
+
<Grid />
|
|
92
|
+
<BarMarkY
|
|
93
|
+
table="penguins"
|
|
94
|
+
x="island"
|
|
95
|
+
y={count()}
|
|
96
|
+
fill="species"
|
|
97
|
+
transforms={[stack()]}
|
|
98
|
+
/>
|
|
99
|
+
{/snippet}
|
|
100
|
+
</WebGPUCanvas>
|
|
101
|
+
{/snippet}
|
|
102
|
+
</ChartContainer>
|
|
103
|
+
</div>
|
|
104
|
+
{/if}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
A chart is a container with layers: axes in the background, marks on the GPU canvas. It fills its parent, so give the parent a height; the Python widget and Streamlit set one for you (400 and 500 px). The same chart can be written as a JSON spec and rendered with `Fjader.render(element, spec)`; the npm package ships Svelte source, so this needs a build that compiles Svelte, such as Vite with the Svelte plugin.
|
|
108
|
+
|
|
109
|
+
## Python
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
import polars as pl
|
|
113
|
+
import fjader as fj
|
|
114
|
+
|
|
115
|
+
weather = pl.read_parquet("seattle-weather.parquet")
|
|
116
|
+
|
|
117
|
+
fj.plot(
|
|
118
|
+
fj.dot(data=weather, x="date", y="temp_max", fill="weather", r="precipitation"),
|
|
119
|
+
x_label="Date",
|
|
120
|
+
y_label="Max temperature (°F)",
|
|
121
|
+
)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Charts display on their own in marimo and Jupyter; in Streamlit, call `fj.streamlit.render(chart)`. polars DataFrames need nothing more; pandas ones need pyarrow (`pip install "fjader[pyarrow]"`). Marks compose: `*` overlays two marks in one chart, `+` places charts side by side, and `df.fj.dot(x=..., y=...)` works straight from a pandas or polars DataFrame.
|
|
125
|
+
|
|
126
|
+
## How it works
|
|
127
|
+
|
|
128
|
+
**Queries.** Aggregation, binning, filtering, top-N, stacking and normalization compile to SQL and run in DuckDB-WASM in the browser. What reaches JavaScript is sized by the chart, not by the data.
|
|
129
|
+
|
|
130
|
+
**Selections.** As in Mosaic, brushes crossfilter the other charts, and pre-aggregated tables binned at pixel resolution keep brushing fast. fjader builds them on the first hover over a chart.
|
|
131
|
+
|
|
132
|
+
**Marks.** Charts are built from marks (dots, bars, rects, lines, areas, rules, text, parallel coordinates). Marks declare the scales they need, and charts sharing a variable share its scale.
|
|
133
|
+
|
|
134
|
+
**Rendering.** WebGPU shaders draw the marks. A fixed-timestep spring simulation animates domain changes and transitions between states. Level-of-detail rungs move between dots, bins and smoothed density; shift+scroll moves through them.
|
|
135
|
+
|
|
136
|
+
## Project structure
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
src/lib/
|
|
140
|
+
components/ Marks, charts, axes, legends, interaction overlays
|
|
141
|
+
query-engine/ DuckDB connection, crossfilter, pre-aggregation
|
|
142
|
+
transforms/ Transform pipeline (SQL first, then JS)
|
|
143
|
+
scales/ Scale requirements, resolution and shared scale state
|
|
144
|
+
gpu/ GPUCoordinator, pipelines, shader libraries
|
|
145
|
+
animation/ Spring physics and the fixed-timestep loop
|
|
146
|
+
lod/ Level-of-detail rungs
|
|
147
|
+
interactors/ Brushes, toggles, tooltips, pan and zoom
|
|
148
|
+
spec/ JSON spec renderer and export
|
|
149
|
+
python/fjader/ Python API, Jupyter widget, Streamlit component
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Development
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
npm install
|
|
156
|
+
npm run fetch-data # Example datasets, from their upstream sources
|
|
157
|
+
npm run dev # Dev server with examples and test routes
|
|
158
|
+
npm run build # Production build
|
|
159
|
+
npm run lint # ESLint + Prettier
|
|
160
|
+
npx vitest # JavaScript tests
|
|
161
|
+
npm run build:python # Widget and Streamlit bundles, then the Python wheel
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The Python package's widget and Streamlit bundles are build output and not in git. In a checkout, run `npm run build:all` before using the Python package, and again after changing JavaScript code.
|
|
165
|
+
|
|
166
|
+
The pages under `src/routes/` are development and test pages for working on fjader, not documentation. They read the example data that `npm run fetch-data` downloads.
|
|
167
|
+
|
|
168
|
+
## Acknowledgements
|
|
169
|
+
|
|
170
|
+
fjader takes ideas from two projects, and in places code from Mosaic:
|
|
171
|
+
|
|
172
|
+
- **[Mosaic](https://github.com/uwdata/mosaic)**, by the UW Interactive Data Lab. fjader began as a Mosaic client, with `@uwdata/mosaic-core` as a dependency, and its query layer still follows Mosaic's design: pre-aggregation with sufficient statistics and the crossfilter selection model. The API, interactors and JSON spec layout are modeled on vgplot's, without being compatible with it. As fjader moved to rendering directly in WebGPU, it needed its queries tied more closely to its marks: results shaped for GPU buffers, query timing coupled to interaction frames, and state that follows Svelte 5's reactivity rather than a callback protocol. So it replaced `mosaic-core` with its own query layer, adapted from Mosaic's, and still builds every query with `@uwdata/mosaic-sql`.
|
|
173
|
+
- **[Observable Plot](https://github.com/observablehq/plot)**, by Observable. Mark names, channel shorthands, stacking conventions and many defaults are modeled on Plot's.
|
|
174
|
+
|
|
175
|
+
Mosaic is described in Jeffrey Heer and Dominik Moritz, [_Mosaic: An Architecture for Scalable & Interoperable Data Views_](https://idl.uw.edu/papers/mosaic), IEEE Transactions on Visualization and Computer Graphics 30 (Proc. VIS 2023), 2024, [doi:10.1109/TVCG.2023.3327189](https://doi.org/10.1109/TVCG.2023.3327189).
|
|
176
|
+
|
|
177
|
+
It also rests on [DuckDB](https://duckdb.org) and [DuckDB-WASM](https://github.com/duckdb/duckdb-wasm), [Flechette](https://github.com/uwdata/flechette), [d3](https://d3js.org) and [Svelte](https://svelte.dev).
|
|
178
|
+
|
|
179
|
+
## License
|
|
180
|
+
|
|
181
|
+
BSD 3-Clause; see [LICENSE](https://github.com/dlyngman/fjader/blob/main/LICENSE). [NOTICE](https://github.com/dlyngman/fjader/blob/main/NOTICE) credits the work fjader adapts and reproduces the licenses of Mosaic and Observable Plot in full. The Python package's bundled JavaScript ships with a `THIRD_PARTY_LICENSES.txt` covering every package compiled into it.
|
fjader-0.2.0/README.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# fjader
|
|
2
|
+
|
|
3
|
+
An experimental charting library for Svelte and Python with a declarative, grammar-of-graphics API, that queries DuckDB and renders with WebGPU. It is not feature complete, and the API may still change.
|
|
4
|
+
|
|
5
|
+
fjader builds on the ideas of [Mosaic](https://github.com/uwdata/mosaic): charts are described as marks whose data comes from SQL queries run in DuckDB, and linked charts filter each other through selections. Its marks and channels borrow from [Observable Plot](https://observablehq.com/plot/), but the API is its own and not compatible with Plot's. Query results arrive as Arrow and are drawn with WebGPU.
|
|
6
|
+
|
|
7
|
+
There are three ways to use it: Svelte components; a JSON spec, rendered from JavaScript with `Fjader.render(element, spec)`; and a Python package that builds specs and renders them in Jupyter, marimo or Streamlit.
|
|
8
|
+
|
|
9
|
+
The name comes from Swedish _fjäder_ (say "fyeh-der"), which means both spring and feather.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @fjader/svelte @uwdata/mosaic-sql # Svelte 5 components, and the SQL builder the examples use
|
|
15
|
+
pip install fjader # Python: marimo and Jupyter
|
|
16
|
+
pip install "fjader[streamlit]" # Python: Streamlit
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
fjader needs Chrome 149 or later, or another Chromium browser on that version. It relies on two recent features other browsers don't ship yet: WebGPU's `immediate_address_space`, through which every chart passes its scales to the shaders (so every zoom and pan uses it), and the `Temporal` API, used for calendar offsets. There is no fallback renderer.
|
|
20
|
+
|
|
21
|
+
Three things to know before using it in a deployed app:
|
|
22
|
+
|
|
23
|
+
- **Data goes to the browser in full.** The Python widget and the Streamlit component send every column of a DataFrame to the browser, where DuckDB-WASM queries it. Anyone viewing the app can extract it, so pass only the columns you mean to show. In Jupyter, saved widget state can also store the data in the notebook file.
|
|
24
|
+
- **DuckDB-WASM loads from jsDelivr at runtime.** The browser fetches it from the CDN, so locked-down networks that block it cannot render charts.
|
|
25
|
+
- **Specs are trusted input.** A spec's `{ "sql": "…" }` field runs as raw SQL in the viewer's DuckDB-WASM. Don't build specs from untrusted input.
|
|
26
|
+
|
|
27
|
+
## Svelte
|
|
28
|
+
|
|
29
|
+
```svelte
|
|
30
|
+
<script>
|
|
31
|
+
import {
|
|
32
|
+
initFjader,
|
|
33
|
+
ChartContainer,
|
|
34
|
+
WebGPUCanvas,
|
|
35
|
+
AxisLeft,
|
|
36
|
+
AxisBottom,
|
|
37
|
+
Grid,
|
|
38
|
+
BarMarkY,
|
|
39
|
+
stack
|
|
40
|
+
} from '@fjader/svelte';
|
|
41
|
+
import { count, loadParquet } from '@uwdata/mosaic-sql';
|
|
42
|
+
import { onMount } from 'svelte';
|
|
43
|
+
|
|
44
|
+
const { queryEngine } = initFjader();
|
|
45
|
+
let loaded = $state(false);
|
|
46
|
+
|
|
47
|
+
// DuckDB-WASM fetches only full URLs; a bare path means its own file system.
|
|
48
|
+
onMount(async () => {
|
|
49
|
+
const url = new URL('/data/penguins.parquet', location.href).href;
|
|
50
|
+
await queryEngine.exec([loadParquet('penguins', url).toString()]);
|
|
51
|
+
loaded = true;
|
|
52
|
+
});
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
{#if loaded}
|
|
56
|
+
<div style="height: 400px">
|
|
57
|
+
<ChartContainer xLabel="island" yLabel="count">
|
|
58
|
+
{#snippet background()}
|
|
59
|
+
<AxisLeft />
|
|
60
|
+
<AxisBottom />
|
|
61
|
+
{/snippet}
|
|
62
|
+
{#snippet canvas()}
|
|
63
|
+
<WebGPUCanvas>
|
|
64
|
+
{#snippet canvasComponents()}
|
|
65
|
+
<Grid />
|
|
66
|
+
<BarMarkY
|
|
67
|
+
table="penguins"
|
|
68
|
+
x="island"
|
|
69
|
+
y={count()}
|
|
70
|
+
fill="species"
|
|
71
|
+
transforms={[stack()]}
|
|
72
|
+
/>
|
|
73
|
+
{/snippet}
|
|
74
|
+
</WebGPUCanvas>
|
|
75
|
+
{/snippet}
|
|
76
|
+
</ChartContainer>
|
|
77
|
+
</div>
|
|
78
|
+
{/if}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
A chart is a container with layers: axes in the background, marks on the GPU canvas. It fills its parent, so give the parent a height; the Python widget and Streamlit set one for you (400 and 500 px). The same chart can be written as a JSON spec and rendered with `Fjader.render(element, spec)`; the npm package ships Svelte source, so this needs a build that compiles Svelte, such as Vite with the Svelte plugin.
|
|
82
|
+
|
|
83
|
+
## Python
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
import polars as pl
|
|
87
|
+
import fjader as fj
|
|
88
|
+
|
|
89
|
+
weather = pl.read_parquet("seattle-weather.parquet")
|
|
90
|
+
|
|
91
|
+
fj.plot(
|
|
92
|
+
fj.dot(data=weather, x="date", y="temp_max", fill="weather", r="precipitation"),
|
|
93
|
+
x_label="Date",
|
|
94
|
+
y_label="Max temperature (°F)",
|
|
95
|
+
)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Charts display on their own in marimo and Jupyter; in Streamlit, call `fj.streamlit.render(chart)`. polars DataFrames need nothing more; pandas ones need pyarrow (`pip install "fjader[pyarrow]"`). Marks compose: `*` overlays two marks in one chart, `+` places charts side by side, and `df.fj.dot(x=..., y=...)` works straight from a pandas or polars DataFrame.
|
|
99
|
+
|
|
100
|
+
## How it works
|
|
101
|
+
|
|
102
|
+
**Queries.** Aggregation, binning, filtering, top-N, stacking and normalization compile to SQL and run in DuckDB-WASM in the browser. What reaches JavaScript is sized by the chart, not by the data.
|
|
103
|
+
|
|
104
|
+
**Selections.** As in Mosaic, brushes crossfilter the other charts, and pre-aggregated tables binned at pixel resolution keep brushing fast. fjader builds them on the first hover over a chart.
|
|
105
|
+
|
|
106
|
+
**Marks.** Charts are built from marks (dots, bars, rects, lines, areas, rules, text, parallel coordinates). Marks declare the scales they need, and charts sharing a variable share its scale.
|
|
107
|
+
|
|
108
|
+
**Rendering.** WebGPU shaders draw the marks. A fixed-timestep spring simulation animates domain changes and transitions between states. Level-of-detail rungs move between dots, bins and smoothed density; shift+scroll moves through them.
|
|
109
|
+
|
|
110
|
+
## Project structure
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
src/lib/
|
|
114
|
+
components/ Marks, charts, axes, legends, interaction overlays
|
|
115
|
+
query-engine/ DuckDB connection, crossfilter, pre-aggregation
|
|
116
|
+
transforms/ Transform pipeline (SQL first, then JS)
|
|
117
|
+
scales/ Scale requirements, resolution and shared scale state
|
|
118
|
+
gpu/ GPUCoordinator, pipelines, shader libraries
|
|
119
|
+
animation/ Spring physics and the fixed-timestep loop
|
|
120
|
+
lod/ Level-of-detail rungs
|
|
121
|
+
interactors/ Brushes, toggles, tooltips, pan and zoom
|
|
122
|
+
spec/ JSON spec renderer and export
|
|
123
|
+
python/fjader/ Python API, Jupyter widget, Streamlit component
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Development
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
npm install
|
|
130
|
+
npm run fetch-data # Example datasets, from their upstream sources
|
|
131
|
+
npm run dev # Dev server with examples and test routes
|
|
132
|
+
npm run build # Production build
|
|
133
|
+
npm run lint # ESLint + Prettier
|
|
134
|
+
npx vitest # JavaScript tests
|
|
135
|
+
npm run build:python # Widget and Streamlit bundles, then the Python wheel
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The Python package's widget and Streamlit bundles are build output and not in git. In a checkout, run `npm run build:all` before using the Python package, and again after changing JavaScript code.
|
|
139
|
+
|
|
140
|
+
The pages under `src/routes/` are development and test pages for working on fjader, not documentation. They read the example data that `npm run fetch-data` downloads.
|
|
141
|
+
|
|
142
|
+
## Acknowledgements
|
|
143
|
+
|
|
144
|
+
fjader takes ideas from two projects, and in places code from Mosaic:
|
|
145
|
+
|
|
146
|
+
- **[Mosaic](https://github.com/uwdata/mosaic)**, by the UW Interactive Data Lab. fjader began as a Mosaic client, with `@uwdata/mosaic-core` as a dependency, and its query layer still follows Mosaic's design: pre-aggregation with sufficient statistics and the crossfilter selection model. The API, interactors and JSON spec layout are modeled on vgplot's, without being compatible with it. As fjader moved to rendering directly in WebGPU, it needed its queries tied more closely to its marks: results shaped for GPU buffers, query timing coupled to interaction frames, and state that follows Svelte 5's reactivity rather than a callback protocol. So it replaced `mosaic-core` with its own query layer, adapted from Mosaic's, and still builds every query with `@uwdata/mosaic-sql`.
|
|
147
|
+
- **[Observable Plot](https://github.com/observablehq/plot)**, by Observable. Mark names, channel shorthands, stacking conventions and many defaults are modeled on Plot's.
|
|
148
|
+
|
|
149
|
+
Mosaic is described in Jeffrey Heer and Dominik Moritz, [_Mosaic: An Architecture for Scalable & Interoperable Data Views_](https://idl.uw.edu/papers/mosaic), IEEE Transactions on Visualization and Computer Graphics 30 (Proc. VIS 2023), 2024, [doi:10.1109/TVCG.2023.3327189](https://doi.org/10.1109/TVCG.2023.3327189).
|
|
150
|
+
|
|
151
|
+
It also rests on [DuckDB](https://duckdb.org) and [DuckDB-WASM](https://github.com/duckdb/duckdb-wasm), [Flechette](https://github.com/uwdata/flechette), [d3](https://d3js.org) and [Svelte](https://svelte.dev).
|
|
152
|
+
|
|
153
|
+
## License
|
|
154
|
+
|
|
155
|
+
BSD 3-Clause; see [LICENSE](https://github.com/dlyngman/fjader/blob/main/LICENSE). [NOTICE](https://github.com/dlyngman/fjader/blob/main/NOTICE) credits the work fjader adapts and reproduces the licenses of Mosaic and Observable Plot in full. The Python package's bundled JavaScript ships with a `THIRD_PARTY_LICENSES.txt` covering every package compiled into it.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Fjader — GPU-accelerated data visualization for Python.
|
|
2
|
+
|
|
3
|
+
Phase 1: Spec generation. Produces JSON specs consumed by the Fjader
|
|
4
|
+
JavaScript renderer.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
import fjader as fj
|
|
8
|
+
|
|
9
|
+
chart = fj.dot(table='penguins', x='bill_length', y='bill_depth', fill='species')
|
|
10
|
+
chart.to_dict()
|
|
11
|
+
|
|
12
|
+
# Overlay (two marks, one chart)
|
|
13
|
+
overlay = fj.dot(table='t', x='a', y='b') * fj.line(table='t', x='a', y='b')
|
|
14
|
+
|
|
15
|
+
# Layout (two charts side by side)
|
|
16
|
+
layout = fj.dot(table='t', x='a', y='b') + fj.bar_y(table='t', x='species', y='count()')
|
|
17
|
+
|
|
18
|
+
# Chart-level config
|
|
19
|
+
fj.plot(fj.dot(table='t', x='a', y='b'), width=600, x_label='X')
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from ._version import __version__
|
|
23
|
+
|
|
24
|
+
# Mark factory functions
|
|
25
|
+
from .marks import dot, bar_x, bar_y, line, area_y, area_x, rect, rect_x, rect_y, text
|
|
26
|
+
|
|
27
|
+
# Chart container
|
|
28
|
+
from .plot import plot
|
|
29
|
+
|
|
30
|
+
# Interactors
|
|
31
|
+
from .interactors import interval_x, interval_y, interval_2d, toggle, derived_interval
|
|
32
|
+
|
|
33
|
+
# Layout helpers
|
|
34
|
+
from .layout import hconcat, vconcat
|
|
35
|
+
|
|
36
|
+
# Transforms
|
|
37
|
+
from .transforms import (
|
|
38
|
+
filter,
|
|
39
|
+
having,
|
|
40
|
+
limit,
|
|
41
|
+
orderby,
|
|
42
|
+
topN,
|
|
43
|
+
stack_y,
|
|
44
|
+
stack_x,
|
|
45
|
+
normalize,
|
|
46
|
+
dodge,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Field spec helpers
|
|
50
|
+
from .fields import count, sum, avg, min_, max_, median, bin, window, sql_expr
|
|
51
|
+
|
|
52
|
+
# Data helpers
|
|
53
|
+
from .data import sql
|
|
54
|
+
|
|
55
|
+
# Display helpers
|
|
56
|
+
from ._display import show
|
|
57
|
+
|
|
58
|
+
# Spec import
|
|
59
|
+
from ._from_spec import from_spec
|
|
60
|
+
|
|
61
|
+
# Lazy submodule access — import only when accessed (avoids hard dep on streamlit)
|
|
62
|
+
import importlib as _importlib
|
|
63
|
+
|
|
64
|
+
def __getattr__(name: str):
|
|
65
|
+
if name == "streamlit":
|
|
66
|
+
return _importlib.import_module("._streamlit", __name__)
|
|
67
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
68
|
+
|
|
69
|
+
# Types (for type annotations in user code)
|
|
70
|
+
from ._types import ColorScheme, CurveType, ScaleType, SortConfig, TextAnchor, LineAnchor
|
|
71
|
+
from .fields import FieldSpec
|
|
72
|
+
|
|
73
|
+
__all__ = [
|
|
74
|
+
"__version__",
|
|
75
|
+
# Marks
|
|
76
|
+
"dot", "bar_x", "bar_y", "line", "area_y", "area_x",
|
|
77
|
+
"rect", "rect_x", "rect_y", "text",
|
|
78
|
+
# Chart
|
|
79
|
+
"plot",
|
|
80
|
+
# Interactors
|
|
81
|
+
"interval_x", "interval_y", "interval_2d", "toggle", "derived_interval",
|
|
82
|
+
# Layout
|
|
83
|
+
"hconcat", "vconcat",
|
|
84
|
+
# Transforms
|
|
85
|
+
"filter", "having", "limit", "orderby",
|
|
86
|
+
"topN", "stack_y", "stack_x", "normalize", "dodge",
|
|
87
|
+
# Fields
|
|
88
|
+
"count", "sum", "avg", "min_", "max_", "median", "bin", "window", "sql_expr",
|
|
89
|
+
# Data
|
|
90
|
+
"sql",
|
|
91
|
+
# Display
|
|
92
|
+
"show",
|
|
93
|
+
# Spec import
|
|
94
|
+
"from_spec",
|
|
95
|
+
# Types
|
|
96
|
+
"FieldSpec", "ColorScheme", "CurveType", "ScaleType",
|
|
97
|
+
"SortConfig", "TextAnchor", "LineAnchor",
|
|
98
|
+
]
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Shared DataFrame accessor for Fjader.
|
|
2
|
+
|
|
3
|
+
Provides ``df.fj.dot(x='a', y='b')`` syntax for quick exploration.
|
|
4
|
+
Returns anywidget instances that auto-display in Jupyter and Marimo,
|
|
5
|
+
and support composition via ``*`` (overlay), ``+`` (layout), and ``.opts()``.
|
|
6
|
+
|
|
7
|
+
Registration is done by importing the appropriate module::
|
|
8
|
+
|
|
9
|
+
import fjader.pandas as fj # registers .fj on pandas DataFrames
|
|
10
|
+
import fjader.polars as fj # registers .fj on polars DataFrames
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from . import marks as _marks
|
|
18
|
+
from ._display import show
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class FjaderAccessor:
|
|
22
|
+
"""DataFrame accessor — ``df.fj.dot(x='a', y='b', fill='species')``."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, df: Any) -> None:
|
|
25
|
+
self._df = df
|
|
26
|
+
|
|
27
|
+
def _call(self, factory: Any, **kwargs: Any) -> Any:
|
|
28
|
+
for key in ("data", "table"):
|
|
29
|
+
if key in kwargs:
|
|
30
|
+
raise TypeError(
|
|
31
|
+
f"Cannot pass '{key}' through the .fj accessor — "
|
|
32
|
+
f"data is implicitly the DataFrame itself."
|
|
33
|
+
)
|
|
34
|
+
return show(factory(data=self._df, **kwargs))
|
|
35
|
+
|
|
36
|
+
def dot(self, **kwargs: Any) -> Any:
|
|
37
|
+
"""Scatter/dot mark. See ``fj.dot()`` for parameters."""
|
|
38
|
+
return self._call(_marks.dot, **kwargs)
|
|
39
|
+
|
|
40
|
+
def bar_x(self, **kwargs: Any) -> Any:
|
|
41
|
+
"""Horizontal bar mark. See ``fj.bar_x()`` for parameters."""
|
|
42
|
+
return self._call(_marks.bar_x, **kwargs)
|
|
43
|
+
|
|
44
|
+
def bar_y(self, **kwargs: Any) -> Any:
|
|
45
|
+
"""Vertical bar mark. See ``fj.bar_y()`` for parameters."""
|
|
46
|
+
return self._call(_marks.bar_y, **kwargs)
|
|
47
|
+
|
|
48
|
+
def line(self, **kwargs: Any) -> Any:
|
|
49
|
+
"""Line mark. See ``fj.line()`` for parameters."""
|
|
50
|
+
return self._call(_marks.line, **kwargs)
|
|
51
|
+
|
|
52
|
+
def area_y(self, **kwargs: Any) -> Any:
|
|
53
|
+
"""Vertical area mark. See ``fj.area_y()`` for parameters."""
|
|
54
|
+
return self._call(_marks.area_y, **kwargs)
|
|
55
|
+
|
|
56
|
+
def area_x(self, **kwargs: Any) -> Any:
|
|
57
|
+
"""Horizontal area mark. See ``fj.area_x()`` for parameters."""
|
|
58
|
+
return self._call(_marks.area_x, **kwargs)
|
|
59
|
+
|
|
60
|
+
def rect(self, **kwargs: Any) -> Any:
|
|
61
|
+
"""Rectangle mark. See ``fj.rect()`` for parameters."""
|
|
62
|
+
return self._call(_marks.rect, **kwargs)
|
|
63
|
+
|
|
64
|
+
def rect_x(self, **kwargs: Any) -> Any:
|
|
65
|
+
"""Horizontal rect mark. See ``fj.rect_x()`` for parameters."""
|
|
66
|
+
return self._call(_marks.rect_x, **kwargs)
|
|
67
|
+
|
|
68
|
+
def rect_y(self, **kwargs: Any) -> Any:
|
|
69
|
+
"""Vertical rect mark. See ``fj.rect_y()`` for parameters."""
|
|
70
|
+
return self._call(_marks.rect_y, **kwargs)
|
|
71
|
+
|
|
72
|
+
def text(self, **kwargs: Any) -> Any:
|
|
73
|
+
"""Text mark. See ``fj.text()`` for parameters."""
|
|
74
|
+
return self._call(_marks.text, **kwargs)
|