charming_fork_zephyr/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2/*!
3Charming is a powerful and versatile chart rendering library for Rust that
4leverages the power of [Apache Echarts](https://echarts.apache.org/en/index.html)
5to deliver high-quality data visualization. Built with the Rust programming
6language, this library aims to provide the Rust ecosystem with an intuitive
7and effective way to generate and visualize charts, using a declarative and
8user-friendly API.
9
10## Basic Usage
11
12Refer to the documentation of the [`Chart`] struct for how to create a chart
13with various components.
14
15Once you create a chart, you can render it into various format. Charming
16provides three types of renderers:
17
18- **HTML renderer**: [`HtmlRenderer`] renders a chart into an HTML fragments and
19  offloads the actual rendering to user's web browser for an interactive,
20  seamless experience. This renderer is useful when you want to render a chart
21  on the client side, e.g., in a web application.
22- **Image renderer**: [`ImageRenderer`] renders a chart into an image file. This
23  renderer makes use of an embed [deno_core](https://github.com/denoland/deno_core)
24  engine to execute the JavaScript code of Echarts and generate an image file.
25  This renderer is disabled by default, and you need to enable the `ssr`
26  (Server-Side Rendering) feature to use it.
27- **WASM renderer**: [`WasmRenderer`] renders a chart in a WebAssembly runtime.
28  This renderer is disabled by default, and you need to enable the `wasm`
29  feature to use it. Note that the `wasm` feature and `ssr` feature are
30  mutually exclusive.
31
32Here is an example of drawing a simple pie chart into an SVG file:
33
34```rust
35use charming::{
36    component::Legend,
37    element::ItemStyle,
38    series::{Pie, PieRoseType},
39    Chart, ImageRenderer
40};
41
42fn main() {
43    let chart = Chart::new()
44        .legend(Legend::new().top("bottom"))
45        .series(
46            Pie::new()
47                .name("Nightingale Chart")
48                .rose_type(PieRoseType::Radius)
49                .radius(vec!["50", "250"])
50                .center(vec!["50%", "50%"])
51                .item_style(ItemStyle::new().border_radius(8))
52                .data(vec![
53                    (40.0, "rose 1"),
54                    (38.0, "rose 2"),
55                    (32.0, "rose 3"),
56                    (30.0, "rose 4"),
57                    (28.0, "rose 5"),
58                    (26.0, "rose 6"),
59                    (22.0, "rose 7"),
60                    (18.0, "rose 8"),
61                ]),
62        );
63
64    let mut renderer = ImageRenderer::new(1000, 800);
65    renderer.save(&chart, "/tmp/nightingale.svg");
66}
67```
68
69## Themes
70
71Charming supports a number of themes out of the box. You can use the
72[`theme::Theme`] enum to specify a theme for your chart. For instance, the
73following code snippet shows how to use the `Westeros` theme:
74
75```rust
76use charming::{Chart, ImageRenderer};
77use charming::theme::Theme;
78use charming::component::Title;
79
80ImageRenderer::new(1000, 800).theme(Theme::Westeros).save(
81    &Chart::new().title(Title::new().text("Westeros")),
82    "/tmp/westeros.svg",
83);
84```
85
86Future versions of Charming will support custom themes.
87 */
88pub mod component;
89pub mod datatype;
90pub mod element;
91pub mod renderer;
92pub mod series;
93pub mod theme;
94
95pub use renderer::*;
96
97use component::{
98    AngleAxis, Aria, Axis, Axis3D, DataZoom, GeoMap, Grid, Grid3D, Legend, ParallelAxis,
99    ParallelCoordinate, PolarCoordinate, RadarCoordinate, RadiusAxis, SaveAsImageType, SingleAxis,
100    Title, Toolbox, VisualMap,
101};
102use datatype::Dataset;
103use element::{process_raw_strings, AxisPointer, Color, MarkLine, Tooltip};
104use serde::Serialize;
105use series::Series;
106
107/**
108The chart representation.
109
110## Anatomy of a Chart
111
112A chart is a collection of different components, each of which is responsible
113for rendering a specific part of the chart. Below is a sample chart with a
114few components:
115
116```txt
117                   Sales Report
118  |                                                        # coffee
11930|                  x                                     x juice
120  |      @           x             @                       @ milk
12120|    # @           x@           x@          #
122  |    #x@          #x@          #x@          #x
12310|    #x@          #x@          #x@          #x@
124  |    #x@          #x@          #x@          #x@
125 0+-----------------------------------------------------
126       Jan          Feb          Mar          Apr
127```
128
129The chart above has the following components: **an x axis**, **an y axis**,
130**a title** on the top center, and **a legend** on the top right.
131
132The creation of charts in Charming is done in a builder-like fashion. Once you
133get a hang of this pattern, you will find that it is very easy to compose a
134chart. For instance, the following code snippet shows how to create the chart
135above:
136
137```rust
138use charming::Chart;
139use charming::component::{Axis, Legend, Title};
140
141let chart = Chart::new()
142    .title(Title::new().text("Sales Report"))
143    .x_axis(Axis::new().data(vec!["Jan", "Feb", "Mar", "Apr"]))
144    .y_axis(Axis::new())
145    .legend(Legend::new().data(vec!["coffee", "juice", "milk"]));
146```
147
148## Components of a Chart
149
150The following sections describe the components of a chart in detail.
151
152### Title
153
154[`Title`] is the title of a chart, including main title and subtitle. A chart
155can have multiple titles, which is useful when you want to show multiple sub-
156charts in a single chart.
157
158```rust
159use charming::Chart;
160use charming::component::Title;
161
162let chart = Chart::new()
163    .title(Title::new().text("Sales Report"));
164```
165
166### Legend
167
168[`Legend`] is the legend of a chart, which is used to show the meaning of the
169symbols and colors in the chart. A chart can have multiple legends.
170
171```rust
172use charming::Chart;
173use charming::component::Legend;
174
175let chart = Chart::new()
176    .legend(Legend::new().data(vec!["coffee", "juice", "milk"]));
177```
178
179### Grid
180
181[`Grid`] is the background grid in a cartesian coordinate system. A chart can
182have multiple grids.
183
184```rust
185use charming::Chart;
186use charming::component::Grid;
187
188let chart = Chart::new()
189    .grid(Grid::new());
190```
191
192### X Axis and Y Axis
193
194[`Axis`] is the axis in a cartesian coordinate system.
195
196```rust
197use charming::Chart;
198use charming::component::Axis;
199
200let chart = Chart::new()
201    .x_axis(Axis::new().data(vec!["Jan", "Feb", "Mar", "Apr"]))
202    .y_axis(Axis::new());
203```
204
205### Polar Coordinate
206
207[`Polar`] is the polar coordinate system. Polar coordinate can be used in
208scatter and line charts. Every polar coordinate has an [`AngleAxis`] and a
209[`RadiusAxis`].
210
211### Radar Coordinate
212
213[`RadarCoordinate`] is the radar coordinate system. Radar coordinate can be in
214radar charts.
215
216### Data Zoom
217
218[`DataZoom`] is used for zooming a specific area, which enables user to view
219data in different scales. A chart can have multiple data zooms.
220
221### Visual Map
222
223[`VisualMap`] is a visual encoding component. It maps data to visual channels,
224such as color, symbol size or symbol shape. A chart can have multiple visual
225maps.
226
227### Tooltip
228
229[`Tooltip`] is a floating box that appears when user hovers over a data item.
230
231### AxisPointer
232
233[`AxisPointer`] is a tool for displaying reference line and axis value under
234mouse pointer.
235
236### Toolbox
237
238[`Toolbox`] is a feature toolbox that includes data view, save as image, data
239zoom, restore, and reset.
240 */
241#[derive(Serialize)]
242#[serde(rename_all = "camelCase")]
243pub struct Chart {
244    #[serde(skip_serializing_if = "Vec::is_empty")]
245    title: Vec<Title>,
246
247    #[serde(skip_serializing_if = "Option::is_none")]
248    tooltip: Option<Tooltip>,
249
250    #[serde(skip_serializing_if = "Option::is_none")]
251    legend: Option<Legend>,
252
253    #[serde(skip_serializing_if = "Option::is_none")]
254    toolbox: Option<Toolbox>,
255
256    #[serde(skip_serializing_if = "Vec::is_empty")]
257    grid: Vec<Grid>,
258
259    #[serde(skip_serializing_if = "Vec::is_empty")]
260    #[serde(rename = "grid3D")]
261    grid3d: Vec<Grid3D>,
262
263    #[serde(skip_serializing_if = "Vec::is_empty")]
264    x_axis: Vec<Axis>,
265
266    #[serde(skip_serializing_if = "Vec::is_empty")]
267    #[serde(rename = "xAxis3D")]
268    x_axis3d: Vec<Axis3D>,
269
270    #[serde(skip_serializing_if = "Vec::is_empty")]
271    y_axis: Vec<Axis>,
272
273    #[serde(skip_serializing_if = "Vec::is_empty")]
274    #[serde(rename = "yAxis3D")]
275    y_axis3d: Vec<Axis3D>,
276
277    #[serde(skip_serializing_if = "Vec::is_empty")]
278    #[serde(rename = "zAxis3D")]
279    z_axis3d: Vec<Axis3D>,
280
281    #[serde(skip_serializing_if = "Vec::is_empty")]
282    polar: Vec<PolarCoordinate>,
283
284    #[serde(skip_serializing_if = "Vec::is_empty")]
285    angle_axis: Vec<AngleAxis>,
286
287    #[serde(skip_serializing_if = "Vec::is_empty")]
288    radius_axis: Vec<RadiusAxis>,
289
290    #[serde(skip_serializing_if = "Option::is_none")]
291    single_axis: Option<SingleAxis>,
292
293    #[serde(skip_serializing_if = "Vec::is_empty")]
294    parallel_axis: Vec<ParallelAxis>,
295
296    #[serde(skip_serializing_if = "Vec::is_empty")]
297    axis_pointer: Vec<AxisPointer>,
298
299    #[serde(skip_serializing_if = "Vec::is_empty")]
300    visual_map: Vec<VisualMap>,
301
302    #[serde(skip_serializing_if = "Vec::is_empty")]
303    data_zoom: Vec<DataZoom>,
304
305    #[serde(skip_serializing_if = "Option::is_none")]
306    parallel: Option<ParallelCoordinate>,
307
308    #[serde(skip_serializing_if = "Option::is_none")]
309    dataset: Option<Dataset>,
310
311    #[serde(skip_serializing_if = "Vec::is_empty")]
312    radar: Vec<RadarCoordinate>,
313
314    #[serde(skip_serializing_if = "Vec::is_empty")]
315    color: Vec<Color>,
316
317    #[serde(skip_serializing_if = "Option::is_none")]
318    background_color: Option<Color>,
319
320    #[serde(skip_serializing_if = "Option::is_none")]
321    mark_line: Option<MarkLine>,
322
323    #[serde(skip_serializing_if = "Option::is_none")]
324    aria: Option<Aria>,
325
326    #[serde(skip_serializing_if = "Vec::is_empty")]
327    series: Vec<Series>,
328
329    #[serde(skip_serializing)]
330    geo_maps: Vec<GeoMap>,
331}
332
333impl Chart {
334    pub fn new() -> Self {
335        Self {
336            title: vec![],
337            toolbox: None,
338            legend: None,
339            tooltip: None,
340            grid: vec![],
341            grid3d: vec![],
342            x_axis: vec![],
343            x_axis3d: vec![],
344            y_axis: vec![],
345            y_axis3d: vec![],
346            z_axis3d: vec![],
347            polar: vec![],
348            angle_axis: vec![],
349            radius_axis: vec![],
350            single_axis: None,
351            parallel_axis: vec![],
352            axis_pointer: vec![],
353            visual_map: vec![],
354            data_zoom: vec![],
355            parallel: None,
356            dataset: None,
357            radar: vec![],
358            color: vec![],
359            background_color: None,
360            mark_line: None,
361            aria: None,
362            series: vec![],
363            geo_maps: vec![],
364        }
365    }
366
367    pub fn title(mut self, title: Title) -> Self {
368        self.title.push(title);
369        self
370    }
371
372    pub fn tooltip(mut self, tooltip: Tooltip) -> Self {
373        self.tooltip = Some(tooltip);
374        self
375    }
376
377    pub fn legend(mut self, legend: Legend) -> Self {
378        self.legend = Some(legend);
379        self
380    }
381
382    pub fn toolbox(mut self, toolbox: Toolbox) -> Self {
383        self.toolbox = Some(toolbox);
384        self
385    }
386
387    pub fn grid(mut self, grid: Grid) -> Self {
388        self.grid.push(grid);
389        self
390    }
391
392    pub fn grid3d(mut self, grid: Grid3D) -> Self {
393        self.grid3d.push(grid);
394        self
395    }
396
397    pub fn x_axis(mut self, x_axis: Axis) -> Self {
398        self.x_axis.push(x_axis);
399        self
400    }
401
402    pub fn x_axis3d(mut self, x_axis: Axis3D) -> Self {
403        self.x_axis3d.push(x_axis);
404        self
405    }
406
407    pub fn y_axis(mut self, y_axis: Axis) -> Self {
408        self.y_axis.push(y_axis);
409        self
410    }
411
412    pub fn y_axis3d(mut self, y_axis: Axis3D) -> Self {
413        self.y_axis3d.push(y_axis);
414        self
415    }
416
417    pub fn z_axis3d(mut self, z_axis: Axis3D) -> Self {
418        self.z_axis3d.push(z_axis);
419        self
420    }
421
422    pub fn polar(mut self, polar: PolarCoordinate) -> Self {
423        self.polar.push(polar);
424        self
425    }
426
427    pub fn angle_axis(mut self, angle_axis: AngleAxis) -> Self {
428        self.angle_axis.push(angle_axis);
429        self
430    }
431
432    pub fn radius_axis(mut self, radius_axis: RadiusAxis) -> Self {
433        self.radius_axis.push(radius_axis);
434        self
435    }
436
437    pub fn single_axis(mut self, single_axis: SingleAxis) -> Self {
438        self.single_axis = Some(single_axis);
439        self
440    }
441
442    pub fn parallel_axis(mut self, parallel_axis: ParallelAxis) -> Self {
443        self.parallel_axis.push(parallel_axis);
444        self
445    }
446
447    pub fn axis_pointer(mut self, axis_pointer: AxisPointer) -> Self {
448        self.axis_pointer.push(axis_pointer);
449        self
450    }
451
452    pub fn visual_map(mut self, visual_map: VisualMap) -> Self {
453        self.visual_map.push(visual_map);
454        self
455    }
456
457    pub fn data_zoom(mut self, data_zoom: DataZoom) -> Self {
458        self.data_zoom.push(data_zoom);
459        self
460    }
461
462    pub fn parallel(mut self, parallel: ParallelCoordinate) -> Self {
463        self.parallel = Some(parallel);
464        self
465    }
466
467    pub fn dataset(mut self, dataset: Dataset) -> Self {
468        self.dataset = Some(dataset);
469        self
470    }
471
472    pub fn radar(mut self, radar: RadarCoordinate) -> Self {
473        self.radar.push(radar);
474        self
475    }
476
477    pub fn color(mut self, color: Vec<Color>) -> Self {
478        self.color = color;
479        self
480    }
481
482    pub fn background_color<C: Into<Color>>(mut self, color: C) -> Self {
483        self.background_color = Some(color.into());
484        self
485    }
486
487    pub fn mark_line(mut self, mark_line: MarkLine) -> Self {
488        self.mark_line = Some(mark_line);
489        self
490    }
491
492    pub fn aria(mut self, aria: Aria) -> Self {
493        self.aria = Some(aria);
494        self
495    }
496
497    pub fn series<S: Into<Series>>(mut self, series: S) -> Self {
498        self.series.push(series.into());
499        self
500    }
501
502    pub fn geo_map<M: Into<GeoMap>>(mut self, map: M) -> Self {
503        self.geo_maps.push(map.into());
504        self
505    }
506
507    pub fn save_as_image_type(&self) -> Option<&SaveAsImageType> {
508        self.toolbox
509            .as_ref()
510            .and_then(|toolbox| toolbox.save_as_image_type())
511    }
512}
513
514impl ToString for Chart {
515    fn to_string(&self) -> String {
516        process_raw_strings(serde_json::to_string_pretty(self).unwrap().as_str())
517    }
518}
519
520#[derive(Debug)]
521pub enum EchartsError {
522    HtmlRenderingError(String),
523    ImageRenderingError(String),
524    JsRuntimeError(String),
525    WasmError(String),
526}
527
528impl std::error::Error for EchartsError {}
529impl std::fmt::Display for EchartsError {
530    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
531        write!(f, "{}", self)
532    }
533}