1use super::*;
2
3impl Chart {
5 pub async fn create(canvas: HtmlCanvasElement) -> Option<Self> {
22 Self::create_with_backend(canvas, BackendKind::WebGpu).await
23 }
24
25 pub async fn create_with_backend(
40 canvas: HtmlCanvasElement,
41 preferred: BackendKind,
42 ) -> Option<Self> {
43 let backend: Box<dyn ChartBackend> = BackendFactory::create(&canvas, preferred).await?;
44 let measurer: TextMeasurer = TextMeasurer::new()?;
45 let atlas: GlyphAtlas = GlyphAtlas::new(DEFAULT_ATLAS_SIZE, DEFAULT_ATLAS_SIZE)?;
46 let shaper: TextShaper = TextShaper::new(measurer, atlas);
47
48 let (width, height) = canvas_size(&canvas);
49 let mut chart: Chart = Self {
50 canvas,
51 option: ChartOption::new(),
52 backend,
53 shaper,
54 layout: ChartLayout::default(),
55 display_list: DisplayList::new(),
56 renderer: ChartRenderer::new(),
57 width,
58 height,
59 dpr: detect_dpr(),
60 layout_dirty: true,
61 display_dirty: true,
62 handlers: Vec::new(),
63 animator: Animator::new(),
64 tooltip: None,
65 maps: MapRegistry::new(),
66 commands: Signal::create(Vec::new()),
67 };
68 chart.resize(width, height);
69 Some(chart)
70 }
71
72 pub fn set_option(&mut self, option: ChartOption) -> &mut Self {
87 self.get_mut_animator().begin(&option);
88 *self.get_mut_option() = option;
89 self.set_layout_dirty(true);
90 self.set_display_dirty(true);
91 self.render()
92 }
93
94 pub fn advance(&mut self, delta: f64) -> bool {
108 if !self.get_mut_animator().advance(delta) {
109 return false;
110 }
111 let mut option: ChartOption = self.get_option().clone();
112 self.get_mut_animator().apply(&mut option);
113 *self.get_mut_option() = option;
114 self.set_layout_dirty(true);
115 self.set_display_dirty(true);
116 self.render();
117 true
118 }
119
120 pub fn animator(&mut self) -> &mut Animator {
126 self.get_mut_animator()
127 }
128
129 pub fn option(&self) -> &ChartOption {
135 self.get_option()
136 }
137
138 pub fn option_mut(&mut self) -> &mut ChartOption {
147 self.set_layout_dirty(true);
148 self.set_display_dirty(true);
149 self.get_mut_option()
150 }
151
152 pub fn render(&mut self) -> &mut Self {
162 self.drain_commands();
166 if self.get_layout_dirty() {
167 let layout: ChartLayout = ChartLayout::compute_with_maps(
168 self.get_option(),
169 self.get_width(),
170 self.get_height(),
171 self.get_shaper().measurer(),
172 self.get_maps(),
173 );
174 self.set_layout(layout);
175 self.set_layout_dirty(false);
176 self.set_display_dirty(true);
177 }
178 if self.get_display_dirty() {
179 let Self {
183 renderer,
184 display_list,
185 option,
186 layout,
187 tooltip,
188 ..
189 } = self;
190 renderer.build_with_tooltip(display_list, option, layout, tooltip.as_ref());
191 self.set_display_dirty(false);
192 }
193 let Self {
196 backend,
197 display_list,
198 shaper,
199 ..
200 } = self;
201 backend.render(display_list, shaper);
202 self
203 }
204
205 fn drain_commands(&mut self) {
210 let waiting: bool = self
211 .get_commands()
212 .with(|queue: &Vec<ChartCommand>| !queue.is_empty());
213 if !waiting {
214 return;
215 }
216 let queue: Vec<ChartCommand> = self.get_commands().get();
217 self.get_commands().set(Vec::new());
218 for command in queue {
219 match command {
220 ChartCommand::SetOption(option) => {
221 self.get_mut_animator().begin(&option);
222 *self.get_mut_option() = *option;
223 self.set_layout_dirty(true);
224 self.set_display_dirty(true);
225 }
226 ChartCommand::Resize(width, height) => {
227 self.set_width(width.max(1.0));
228 self.set_height(height.max(1.0));
229 self.set_dpr(detect_dpr());
230 let dpr: f64 = self.get_dpr();
231 let chart_width: f64 = self.get_width();
232 let chart_height: f64 = self.get_height();
233 self.get_mut_backend()
234 .resize(chart_width, chart_height, dpr);
235 self.set_layout_dirty(true);
236 self.set_display_dirty(true);
237 }
238 ChartCommand::Action(action) => {
239 self.dispatch_action(action);
240 }
241 }
242 }
243 }
244
245 pub fn handle(&self) -> ChartHandle {
255 ChartHandle {
256 commands: *self.get_commands(),
257 }
258 }
259
260 pub fn resize(&mut self, width: f64, height: f64) -> &mut Self {
273 self.set_width(width.max(1.0));
274 self.set_height(height.max(1.0));
275 self.set_dpr(detect_dpr());
276 let dpr: f64 = self.get_dpr();
277 let chart_width: f64 = self.get_width();
278 let chart_height: f64 = self.get_height();
279 self.get_mut_backend()
280 .resize(chart_width, chart_height, dpr);
281 self.set_layout_dirty(true);
282 self.set_display_dirty(true);
283 self.render()
284 }
285
286 pub fn backend_kind(&self) -> BackendKind {
292 self.get_backend().kind()
293 }
294
295 pub fn layout(&self) -> &ChartLayout {
301 self.get_layout()
302 }
303
304 pub fn display_list(&self) -> &DisplayList {
310 self.get_display_list()
311 }
312
313 pub fn canvas(&self) -> &HtmlCanvasElement {
319 self.get_canvas()
320 }
321
322 pub fn on(&mut self, event_type: ChartEventType, handler: EventHandler) -> &mut Self {
333 self.get_mut_handlers().push(EventRegistration {
334 event_type,
335 handler,
336 });
337 self
338 }
339
340 pub fn off(&mut self, event_type: ChartEventType) -> &mut Self {
350 self.get_mut_handlers()
351 .retain(|registration: &EventRegistration| registration.event_type != event_type);
352 self
353 }
354
355 pub fn emit(&mut self, event: ChartEvent) -> &mut Self {
365 let handlers: Vec<EventHandler> = self
366 .get_handlers()
367 .iter()
368 .filter(|registration: &&EventRegistration| registration.event_type == event.event_type)
369 .map(|registration: &EventRegistration| registration.handler.clone())
370 .collect();
371 for handler in handlers {
372 handler(&event);
373 }
374 self
375 }
376
377 pub fn dispatch_action(&mut self, action: ChartAction) -> &mut Self {
391 match action {
392 ChartAction::DataZoom { start, end } => {
393 self.apply_data_zoom(start, end);
394 }
395 ChartAction::LegendToggle { name } => {
396 self.toggle_legend(&name);
397 }
398 ChartAction::Restore => {
399 self.restore();
400 }
401 ChartAction::Highlight {
402 series_index,
403 data_index,
404 } => {
405 self.highlight(series_index, data_index);
406 }
407 ChartAction::Downplay {
408 series_index,
409 data_index,
410 } => {
411 self.downplay(series_index, data_index);
412 }
413 ChartAction::ShowTip {
414 series_index,
415 data_index,
416 x,
417 y,
418 } => {
419 self.show_tip(series_index, data_index, x, y);
420 }
421 ChartAction::HideTip => {
422 self.hide_tip();
423 }
424 }
425 self
426 }
427
428 pub fn show_tip(
439 &mut self,
440 series_index: usize,
441 data_index: usize,
442 x: f64,
443 y: f64,
444 ) -> &mut Self {
445 let lines: Vec<(String, String)> = self.tooltip_lines(series_index, data_index);
446 self.set_tooltip(Some(TooltipState {
447 series_index,
448 data_index,
449 x,
450 y,
451 lines,
452 }));
453 self.set_display_dirty(true);
454 self
455 }
456
457 pub fn hide_tip(&mut self) -> &mut Self {
463 self.set_tooltip(None);
464 self.set_display_dirty(true);
465 self
466 }
467
468 pub fn tooltip(&self) -> Option<&TooltipState> {
474 self.try_get_tooltip().as_ref()
475 }
476
477 pub fn hit_test(&self, x: f64, y: f64) -> Option<ChartEvent> {
491 for series in self.get_layout().series.iter().rev() {
492 for point in series.points.iter().rev() {
493 let distance: f64 = ((point.x - x).powi(2) + (point.y - y).powi(2)).sqrt();
494 if distance <= HIT_RADIUS {
495 return Some(ChartEvent {
496 event_type: ChartEventType::Click,
497 series_index: Some(series.index),
498 data_index: Some(point.data_index),
499 series_name: series.name.clone(),
500 name: point.name.clone(),
501 value: Some(point.value),
502 x,
503 y,
504 offset_x: x,
505 offset_y: y,
506 });
507 }
508 }
509 }
510 None
511 }
512
513 fn tooltip_lines(&self, series_index: usize, data_index: usize) -> Vec<(String, String)> {
523 let Some(series) = self.get_option().series.get(series_index) else {
524 return Vec::new();
525 };
526 let Some(data) = series.data.get(data_index) else {
527 return Vec::new();
528 };
529 let value: String = match data {
530 DataValue::Number(number) => format_number(*number),
531 DataValue::List(items) => items
532 .get(1)
533 .and_then(|item: &DataValue| item.to_number())
534 .map(format_number)
535 .unwrap_or_default(),
536 DataValue::Object(_) => data
537 .get("value")
538 .and_then(|item: &DataValue| item.to_number())
539 .map(format_number)
540 .unwrap_or_default(),
541 _ => String::new(),
542 };
543 let name: String = match data {
544 DataValue::List(items) => items
545 .first()
546 .and_then(|item: &DataValue| item.as_text())
547 .unwrap_or_default()
548 .to_string(),
549 DataValue::Object(_) => data
550 .get("name")
551 .and_then(|item: &DataValue| item.as_text())
552 .unwrap_or_default()
553 .to_string(),
554 _ => String::new(),
555 };
556 let mut lines: Vec<(String, String)> = Vec::new();
557 if !series.name.is_empty() {
558 lines.push((series.name.clone(), value.clone()));
559 }
560 if !name.is_empty() {
561 lines.push((name, value));
562 }
563 lines
564 }
565
566 fn highlight(&mut self, series_index: usize, data_index: Option<usize>) {
573 let _: (usize, Option<usize>) = (series_index, data_index);
574 self.set_display_dirty(true);
575 }
576
577 fn downplay(&mut self, series_index: usize, data_index: Option<usize>) {
584 let _: (usize, Option<usize>) = (series_index, data_index);
585 self.set_display_dirty(true);
586 }
587
588 fn apply_data_zoom(&mut self, start: f64, end: f64) {
594 let start: f64 = start.clamp(0.0, 100.0);
595 let end: f64 = end.clamp(0.0, 100.0);
596 if end <= start {
597 return;
598 }
599 for control in self.get_mut_option().data_zoom.iter_mut() {
600 control.start = Some(start);
601 control.end = Some(end);
602 }
603 self.set_layout_dirty(true);
604 self.set_display_dirty(true);
605 }
606
607 fn toggle_legend(&mut self, name: &str) {
613 let Some(legend) = self.get_mut_option().legend.as_mut() else {
614 return;
615 };
616 let mut selected: Vec<String> = legend.selected.take().unwrap_or_default();
617 if let Some(position) = selected.iter().position(|item: &String| item == name) {
618 selected.remove(position);
619 } else {
620 selected.push(name.to_string());
621 }
622 legend.selected = Some(selected);
623 self.set_layout_dirty(true);
624 self.set_display_dirty(true);
625 }
626
627 fn restore(&mut self) {
629 for control in self.get_mut_option().data_zoom.iter_mut() {
630 control.start = None;
631 control.end = None;
632 }
633 if let Some(legend) = self.get_mut_option().legend.as_mut() {
634 legend.selected = None;
635 }
636 self.set_layout_dirty(true);
637 self.set_display_dirty(true);
638 }
639
640 pub fn dispose(&mut self) {
642 self.get_mut_backend().dispose();
643 self.get_mut_display_list().clear();
644 }
645
646 pub fn register_map(&mut self, name: &str, geojson: &DataValue) -> Result<&mut Self, GeoError> {
665 self.get_mut_maps().register_geojson(name, geojson)?;
666 self.set_layout_dirty(true);
667 self.set_display_dirty(true);
668 Ok(self)
669 }
670
671 pub fn register_map_data(&mut self, data: MapData) -> &mut Self {
681 self.get_mut_maps().register(data);
682 self.set_layout_dirty(true);
683 self.set_display_dirty(true);
684 self
685 }
686
687 pub fn maps(&self) -> &MapRegistry {
693 self.get_maps()
694 }
695
696 pub fn map_names(&self) -> Vec<String> {
702 self.get_maps().names()
703 }
704
705 pub fn to_data_url(&self) -> Option<String> {
714 self.get_canvas().to_data_url_with_type("image/png").ok()
715 }
716}
717
718impl ChartHandle {
720 pub fn push(&self, command: ChartCommand) -> &Self {
732 let mut queue: Vec<ChartCommand> = self.get_commands().get();
733 queue.push(command);
734 self.get_commands().set(queue);
735 self
736 }
737
738 pub fn set_option(&self, option: ChartOption) -> &Self {
750 self.push(ChartCommand::SetOption(Box::new(option)))
751 }
752
753 pub fn resize(&self, width: f64, height: f64) -> &Self {
765 self.push(ChartCommand::Resize(width, height))
766 }
767
768 pub fn dispatch_action(&self, action: ChartAction) -> &Self {
780 self.push(ChartCommand::Action(action))
781 }
782
783 pub fn pending(&self) -> usize {
789 self.get_commands().get().len()
790 }
791}
792
793impl Default for ChartRenderer {
795 fn default() -> Self {
797 Self::new()
798 }
799}
800
801impl ChartRenderer {
803 pub fn new() -> Self {
805 Self {
806 series: SeriesRenderer::new(),
807 }
808 }
809
810 pub fn build(&mut self, list: &mut DisplayList, option: &ChartOption, layout: &ChartLayout) {
818 self.build_with_tooltip(list, option, layout, None)
819 }
820
821 pub fn build_with_tooltip(
830 &mut self,
831 list: &mut DisplayList,
832 option: &ChartOption,
833 layout: &ChartLayout,
834 tooltip: Option<&TooltipState>,
835 ) {
836 list.clear();
837 self.draw_background(list, option);
838 self.draw_title(list, option, layout);
839 self.draw_grid(list, option, layout);
840 self.draw_axes(list, option, layout);
841 self.get_mut_series().draw(list, option, layout);
842 self.draw_legend(list, option, layout);
843 if let Some(tooltip) = tooltip {
844 self.draw_tooltip(list, option, layout, tooltip);
845 }
846 }
847
848 fn draw_tooltip(
861 &mut self,
862 list: &mut DisplayList,
863 option: &ChartOption,
864 layout: &ChartLayout,
865 tooltip: &TooltipState,
866 ) {
867 if tooltip.lines.is_empty() {
868 return;
869 }
870 let font_size: f64 = option
871 .tooltip
872 .as_ref()
873 .and_then(|tooltip: &TooltipOption| tooltip.text_style.as_ref())
874 .and_then(|style: &LabelOption| style.font_size)
875 .unwrap_or(DEFAULT_TOOLTIP_FONT_SIZE);
876 let padding: (f64, f64, f64, f64) = option
877 .tooltip
878 .as_ref()
879 .and_then(|tooltip: &TooltipOption| tooltip.padding)
880 .unwrap_or((8.0, 12.0, 8.0, 12.0));
881 let background: Color = option
882 .tooltip
883 .as_ref()
884 .and_then(|tooltip: &TooltipOption| tooltip.background_color.as_deref())
885 .and_then(Color::parse)
886 .unwrap_or_else(|| {
887 Color::parse(DEFAULT_BACKGROUND).unwrap_or(Color::rgb(255, 255, 255))
888 });
889 let border: Color = option
890 .tooltip
891 .as_ref()
892 .and_then(|tooltip: &TooltipOption| tooltip.border_color.as_deref())
893 .and_then(Color::parse)
894 .unwrap_or(Color::rgb(217, 217, 217));
895
896 let mut width: f64 = 0.0_f64;
897 for (name, value) in &tooltip.lines {
898 let text = format!("{}: {}", name, value);
899 width = width.max(text.len() as f64 * font_size * 0.6);
900 }
901 let line_height: f64 = font_size * 1.4;
902 let height: f64 = line_height * tooltip.lines.len() as f64;
903 let box_width: f64 = width + padding.1 + padding.3;
904 let box_height: f64 = height + padding.0 + padding.2;
905
906 let mut left: f64 = tooltip.x + 12.0;
909 if left + box_width > layout.width {
910 left = tooltip.x - 12.0 - box_width;
911 }
912 let mut top: f64 = tooltip.y - box_height * 0.5;
913 if top < 0.0 {
914 top = 0.0;
915 }
916 if top + box_height > layout.height {
917 top = (layout.height - box_height).max(0.0);
918 }
919
920 let path: Path = Path::builder()
921 .round_rect(
922 left as f32,
923 top as f32,
924 box_width as f32,
925 box_height as f32,
926 4.0,
927 )
928 .build();
929 list.fill(path.clone(), Fill::Solid(background), 1.0, Z_TOOLTIP);
930 let border_width: f64 = option
931 .tooltip
932 .as_ref()
933 .and_then(|tooltip: &TooltipOption| tooltip.border_width)
934 .unwrap_or(1.0);
935 if border_width > 0.0 {
936 list.stroke(
937 path,
938 Stroke::Solid(border),
939 LineStyle {
940 width: border_width,
941 ..LineStyle::new()
942 },
943 1.0,
944 Z_TOOLTIP + 1,
945 );
946 }
947
948 let mut style: TextStyle = TextStyle::new();
949 style.size = font_size;
950 style.align = TextAlign::Left;
951 style.vertical_align = TextVerticalAlign::Middle;
952 style.color = Color::rgb(51, 51, 51);
953 for (index, (name, value)) in tooltip.lines.iter().enumerate() {
954 let baseline: f64 = top + padding.0 + line_height * (index as f64 + 0.5);
955 list.text(
956 format!("{}: {}", name, value),
957 left + padding.3,
958 baseline,
959 style.clone(),
960 Z_TOOLTIP + 2,
961 );
962 }
963 }
964
965 fn draw_background(&mut self, list: &mut DisplayList, option: &ChartOption) {
972 let Some(background) = option.background_color.as_deref() else {
973 return;
974 };
975 let Some(color) = Color::parse(background) else {
976 return;
977 };
978 let rect: Path = Path::builder().rect(0.0, 0.0, 1.0, 1.0).build();
979 let mut scaled: Path = rect;
980 scaled = scaled.transformed(&Transform2D::scale(1.0, 1.0));
984 list.fill(scaled, Fill::Solid(color), 1.0, Z_BACKGROUND);
985 }
986
987 fn draw_title(&mut self, list: &mut DisplayList, option: &ChartOption, layout: &ChartLayout) {
995 let Some(title) = &option.title else {
996 return;
997 };
998 if !title.show || title.text.is_empty() {
999 return;
1000 }
1001 let font_size: f64 = title
1002 .text_style
1003 .as_ref()
1004 .and_then(|style: &LabelOption| style.font_size)
1005 .unwrap_or(DEFAULT_TITLE_FONT_SIZE);
1006 let mut style: TextStyle = TextStyle::new();
1007 style.size = font_size;
1008 style.align = TextAlign::Center;
1009 style.vertical_align = TextVerticalAlign::Top;
1010 if let Some(text_style) = &title.text_style {
1011 if let Some(color) = text_style
1012 .color
1013 .as_ref()
1014 .and_then(|color: &String| Color::parse(color))
1015 {
1016 style.color = color;
1017 }
1018 if let Some(family) = &text_style.font_family {
1019 style.family = family.clone();
1020 }
1021 if let Some(weight) = &text_style.font_weight {
1022 style.weight = weight.clone();
1023 }
1024 }
1025 let center_x: f64 = layout.width * 0.5;
1026 list.text(title.text.clone(), center_x, 0.0, style, Z_TITLE);
1027 }
1028
1029 fn draw_grid(&mut self, list: &mut DisplayList, option: &ChartOption, layout: &ChartLayout) {
1037 let grid_option: GridOption = option.grid.clone().unwrap_or_default();
1038 if let Some(background) = grid_option.background_color.as_deref()
1039 && let Some(color) = Color::parse(background)
1040 {
1041 for grid in &layout.grids {
1042 let path: Path = Path::builder()
1043 .rect(
1044 grid.rect.min_x as f32,
1045 grid.rect.min_y as f32,
1046 grid.rect.width() as f32,
1047 grid.rect.height() as f32,
1048 )
1049 .build();
1050 list.fill(path, Fill::Solid(color), 1.0, Z_GRID);
1051 }
1052 }
1053 if !grid_option.show {
1054 return;
1055 }
1056 if let Some(border) = grid_option.border_color.as_deref().and_then(Color::parse) {
1057 let width: f32 = grid_option.border_width.unwrap_or(1.0) as f32;
1058 for grid in &layout.grids {
1059 let path: Path = Path::builder()
1060 .rect(
1061 grid.rect.min_x as f32,
1062 grid.rect.min_y as f32,
1063 grid.rect.width() as f32,
1064 grid.rect.height() as f32,
1065 )
1066 .build();
1067 list.stroke(
1068 path,
1069 Stroke::Solid(border),
1070 LineStyle {
1071 width: width as f64,
1072 ..LineStyle::new()
1073 },
1074 1.0,
1075 Z_GRID + 1,
1076 );
1077 }
1078 }
1079 }
1080
1081 fn draw_axes(&mut self, list: &mut DisplayList, option: &ChartOption, layout: &ChartLayout) {
1090 for grid in &layout.grids {
1091 for axis in &grid.x_axes {
1092 self.draw_axis(list, option, grid, axis, true);
1093 }
1094 for axis in &grid.y_axes {
1095 self.draw_axis(list, option, grid, axis, false);
1096 }
1097 }
1098 }
1099
1100 fn draw_axis(
1117 &mut self,
1118 list: &mut DisplayList,
1119 option: &ChartOption,
1120 grid: &GridLayout,
1121 axis: &AxisLayout,
1122 horizontal: bool,
1123 ) {
1124 if !axis.show {
1125 return;
1126 }
1127 let axis_option: Option<&AxisOption> = if horizontal {
1128 option.x_axis.get(axis.index)
1129 } else {
1130 option.y_axis.get(axis.index)
1131 };
1132
1133 let split_line: Option<&SplitLineOption> =
1136 axis_option.and_then(|option: &AxisOption| option.split_line.as_ref());
1137 let split_line_show: bool = match split_line {
1138 Some(split_line) => split_line.show,
1139 None => matches!(axis.kind, AxisType::Value | AxisType::Log),
1142 };
1143 if split_line_show {
1144 let color: Color = split_line
1145 .and_then(|split_line: &SplitLineOption| split_line.line_style.as_ref())
1146 .and_then(|style: &LineStyleOption| style.color.as_deref())
1147 .and_then(Color::parse)
1148 .unwrap_or_else(|| {
1149 Color::parse(DEFAULT_SPLIT_LINE_COLOR).unwrap_or(Color::rgb(217, 217, 217))
1150 });
1151 let width: f64 = split_line
1152 .and_then(|split_line: &SplitLineOption| split_line.line_style.as_ref())
1153 .and_then(|style: &LineStyleOption| style.width)
1154 .unwrap_or(DEFAULT_SPLIT_LINE_WIDTH);
1155 for (index, tick) in axis.ticks.iter().enumerate() {
1156 if index % axis.label_interval.max(1) != 0 {
1161 continue;
1162 }
1163 let path: Path = if horizontal {
1164 Path::builder()
1165 .move_to(tick.position as f32, grid.rect.min_y as f32)
1166 .line_to(tick.position as f32, grid.rect.max_y as f32)
1167 .build()
1168 } else {
1169 Path::builder()
1170 .move_to(grid.rect.min_x as f32, tick.position as f32)
1171 .line_to(grid.rect.max_x as f32, tick.position as f32)
1172 .build()
1173 };
1174 list.stroke(
1175 path,
1176 Stroke::Solid(color),
1177 LineStyle {
1178 width,
1179 ..LineStyle::new()
1180 },
1181 1.0,
1182 Z_GRID,
1183 );
1184 }
1185 }
1186
1187 let axis_line: Option<&AxisLineOption> =
1189 axis_option.and_then(|option: &AxisOption| option.axis_line.as_ref());
1190 let axis_line_show: bool = match axis_line {
1191 Some(axis_line) => axis_line.show,
1192 None => draws_own_line(axis, grid, horizontal),
1196 };
1197 if axis_line_show {
1198 let color: Color = axis_line
1199 .and_then(|axis_line: &AxisLineOption| axis_line.line_style.as_ref())
1200 .and_then(|style: &LineStyleOption| style.color.as_deref())
1201 .and_then(Color::parse)
1202 .unwrap_or_else(|| {
1203 Color::parse(DEFAULT_AXIS_LINE_COLOR).unwrap_or(Color::rgb(217, 217, 217))
1204 });
1205 let width: f64 = axis_line
1206 .and_then(|axis_line: &AxisLineOption| axis_line.line_style.as_ref())
1207 .and_then(|style: &LineStyleOption| style.width)
1208 .unwrap_or(DEFAULT_AXIS_LINE_WIDTH);
1209 let path: Path = if horizontal {
1210 Path::builder()
1211 .move_to(grid.rect.min_x as f32, axis.offset as f32)
1212 .line_to(grid.rect.max_x as f32, axis.offset as f32)
1213 .build()
1214 } else {
1215 Path::builder()
1216 .move_to(axis.offset as f32, grid.rect.min_y as f32)
1217 .line_to(axis.offset as f32, grid.rect.max_y as f32)
1218 .build()
1219 };
1220 list.stroke(
1221 path,
1222 Stroke::Solid(color),
1223 LineStyle {
1224 width,
1225 ..LineStyle::new()
1226 },
1227 1.0,
1228 Z_AXIS,
1229 );
1230 }
1231
1232 let tick_option: Option<&AxisTickOption> =
1234 axis_option.and_then(|option: &AxisOption| option.axis_tick.as_ref());
1235 let tick_show: bool = match tick_option {
1236 Some(tick) => tick.show,
1237 None => draws_own_line(axis, grid, horizontal),
1240 };
1241 if tick_show {
1242 let length: f64 = tick_option
1243 .and_then(|tick: &AxisTickOption| tick.length)
1244 .unwrap_or(DEFAULT_AXIS_TICK_LENGTH);
1245 let color: Color = tick_option
1246 .and_then(|tick: &AxisTickOption| tick.line_style.as_ref())
1247 .and_then(|style: &LineStyleOption| style.color.as_deref())
1248 .and_then(Color::parse)
1249 .or_else(|| {
1250 axis_line
1251 .and_then(|axis_line: &AxisLineOption| axis_line.line_style.as_ref())
1252 .and_then(|style: &LineStyleOption| style.color.as_deref())
1253 .and_then(Color::parse)
1254 })
1255 .unwrap_or_else(|| {
1256 Color::parse(DEFAULT_AXIS_LINE_COLOR).unwrap_or(Color::rgb(217, 217, 217))
1257 });
1258 let width: f64 = tick_option
1259 .and_then(|tick: &AxisTickOption| tick.line_style.as_ref())
1260 .and_then(|style: &LineStyleOption| style.width)
1261 .unwrap_or(DEFAULT_AXIS_LINE_WIDTH);
1262 for (index, tick) in axis.ticks.iter().enumerate() {
1263 if index % axis.label_interval.max(1) != 0 {
1264 continue;
1265 }
1266 let path: Path = if horizontal {
1267 Path::builder()
1268 .move_to(tick.position as f32, axis.offset as f32)
1269 .line_to(tick.position as f32, (axis.offset + length) as f32)
1270 .build()
1271 } else {
1272 Path::builder()
1273 .move_to(axis.offset as f32, tick.position as f32)
1274 .line_to((axis.offset - length) as f32, tick.position as f32)
1275 .build()
1276 };
1277 list.stroke(
1278 path,
1279 Stroke::Solid(color),
1280 LineStyle {
1281 width,
1282 ..LineStyle::new()
1283 },
1284 1.0,
1285 Z_AXIS,
1286 );
1287 }
1288 }
1289
1290 let label_option: Option<LabelOption> =
1292 axis_option.and_then(|option: &AxisOption| option.axis_label.clone());
1293 let show_labels: bool = label_option
1294 .as_ref()
1295 .map(|label: &LabelOption| label.show)
1296 .unwrap_or(true);
1297 let font_size: f64 = label_option
1298 .as_ref()
1299 .and_then(|label: &LabelOption| label.font_size)
1300 .unwrap_or(DEFAULT_AXIS_LABEL_FONT_SIZE);
1301 let gap: f64 = DEFAULT_AXIS_LABEL_GAP;
1302 if show_labels {
1303 let mut style: TextStyle = TextStyle::new();
1304 style.size = font_size;
1305 style.color = Color::parse(DEFAULT_TEXT_COLOR).unwrap_or(Color::rgb(110, 112, 121));
1306 if let Some(label) = &label_option {
1307 if let Some(color) = label
1308 .color
1309 .as_ref()
1310 .and_then(|color: &String| Color::parse(color))
1311 {
1312 style.color = color;
1313 }
1314 if let Some(family) = &label.font_family {
1315 style.family = family.clone();
1316 }
1317 if let Some(weight) = &label.font_weight {
1318 style.weight = weight.clone();
1319 }
1320 }
1321 for (index, tick) in axis.ticks.iter().enumerate() {
1322 if index % axis.label_interval.max(1) != 0 {
1325 continue;
1326 }
1327 let (x, y) = if horizontal {
1328 (tick.position, axis.offset + gap + font_size)
1329 } else {
1330 (axis.offset - gap, tick.position)
1331 };
1332 let mut label_style: TextStyle = style.clone();
1333 label_style.align = if horizontal {
1334 TextAlign::Center
1335 } else {
1336 TextAlign::Right
1337 };
1338 label_style.vertical_align = if horizontal {
1339 TextVerticalAlign::Top
1340 } else {
1341 TextVerticalAlign::Middle
1342 };
1343 list.text(tick.label.clone(), x, y, label_style, Z_AXIS + 1);
1344 }
1345 }
1346
1347 if let Some(name) = &axis.name
1349 && !name.is_empty()
1350 {
1351 let mut style: TextStyle = TextStyle::new();
1352 style.size = DEFAULT_AXIS_LABEL_FONT_SIZE;
1353 style.color = Color::parse(DEFAULT_TEXT_COLOR).unwrap_or(Color::rgb(110, 112, 121));
1354 style.align = TextAlign::Center;
1355 style.vertical_align = TextVerticalAlign::Middle;
1356 let (x, y) = if horizontal {
1357 (grid.rect.center_x(), axis.offset + gap + font_size * 2.0)
1358 } else {
1359 (
1360 axis.offset - gap - axis.label_space * 0.5,
1361 grid.rect.center_y(),
1362 )
1363 };
1364 list.text(name.clone(), x, y, style, Z_AXIS + 2);
1365 }
1366 }
1367
1368 fn draw_legend(&mut self, list: &mut DisplayList, option: &ChartOption, layout: &ChartLayout) {
1376 let Some(legend) = &option.legend else {
1377 return;
1378 };
1379 if !legend.show || legend.data.is_empty() {
1380 return;
1381 }
1382 let font_size: f64 = legend
1383 .text_style
1384 .as_ref()
1385 .and_then(|style: &LabelOption| style.font_size)
1386 .unwrap_or(DEFAULT_LEGEND_FONT_SIZE);
1387 let item_width: f64 = legend.item_width.unwrap_or(25.0);
1388 let item_height: f64 = legend.item_height.unwrap_or(14.0);
1389 let item_gap: f64 = legend.item_gap.unwrap_or(10.0);
1390 let mut style: TextStyle = TextStyle::new();
1391 style.size = font_size;
1392 style.color = Color::parse(DEFAULT_TEXT_COLOR).unwrap_or(Color::rgb(110, 112, 121));
1393 style.align = TextAlign::Left;
1394 style.vertical_align = TextVerticalAlign::Middle;
1395 if let Some(text_style) = &legend.text_style
1396 && let Some(color) = text_style
1397 .color
1398 .as_ref()
1399 .and_then(|color: &String| Color::parse(color))
1400 {
1401 style.color = color;
1402 }
1403 let horizontal: bool =
1404 legend.orient.unwrap_or(Orientation::Horizontal) == Orientation::Horizontal;
1405 let mut cursor_x: f64 = legend.left.as_ref().map(|_| 10.0).unwrap_or(10.0);
1406 let mut cursor_y: f64 = layout
1407 .title_rect
1408 .as_ref()
1409 .map(|rect: &BBox| rect.max_y + 10.0)
1410 .unwrap_or(10.0);
1411 for (index, name) in legend.data.iter().enumerate() {
1412 let color: Color = palette_color(option, index);
1413 let symbol: Path = Path::builder()
1414 .rect(
1415 cursor_x as f32,
1416 cursor_y as f32,
1417 item_width as f32,
1418 item_height as f32,
1419 )
1420 .build();
1421 list.fill(symbol, Fill::Solid(color), 1.0, Z_LEGEND);
1422 list.text(
1423 name.clone(),
1424 cursor_x + item_width + item_gap * 0.5,
1425 cursor_y + item_height * 0.5,
1426 style.clone(),
1427 Z_LEGEND + 1,
1428 );
1429 let advance: f64 = item_width + item_gap + name.len() as f64 * font_size * 0.6;
1430 if horizontal {
1431 cursor_x += advance;
1432 } else {
1433 cursor_y += item_height + item_gap;
1434 cursor_x = 10.0;
1435 }
1436 }
1437 }
1438}