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}