1use std::env;
2
3use plotlars_core::Plot;
4
5use crate::render::{
6 build_plotly_result, ir_to_json, open_html_file, render_html_from_json,
7 render_inline_html_from_json,
8};
9
10pub trait PlotlyExt: Plot {
12 fn plot(&self);
13 fn write_html(&self, path: impl Into<String>);
14 fn to_json(&self) -> Result<String, serde_json::Error>;
15 fn to_html(&self) -> String;
16 fn to_inline_html(&self, plot_div_id: Option<&str>) -> String;
17
18 #[cfg(any(
19 feature = "export-chrome",
20 feature = "export-firefox",
21 feature = "export-default"
22 ))]
23 fn write_image(
24 &self,
25 path: impl Into<String>,
26 width: usize,
27 height: usize,
28 scale: f64,
29 ) -> Result<(), Box<dyn std::error::Error + 'static>>;
30}
31
32impl<T: Plot> PlotlyExt for T {
33 fn plot(&self) {
34 let result = build_plotly_result(self);
35 if result.layout_overrides.is_some() {
36 let json = ir_to_json(self).unwrap_or_default();
40 let html = render_html_from_json(&json);
41 let temp_dir = std::env::temp_dir();
42 let timestamp = std::time::SystemTime::now()
43 .duration_since(std::time::UNIX_EPOCH)
44 .unwrap()
45 .as_nanos();
46 let temp_path = temp_dir.join(format!(
47 "plotlars_{}_{}.html",
48 std::process::id(),
49 timestamp
50 ));
51 std::fs::write(&temp_path, html).expect("failed to write temp html");
52 open_html_file(&temp_path);
53 } else {
54 match env::var("EVCXR_IS_RUNTIME") {
55 Ok(_) => result.plot.evcxr_display(),
56 _ => result.plot.show(),
57 }
58 }
59 }
60
61 fn write_html(&self, path: impl Into<String>) {
62 let result = build_plotly_result(self);
63 if result.layout_overrides.is_some() {
64 let json = ir_to_json(self).unwrap_or_default();
65 let html = render_html_from_json(&json);
66 std::fs::write(path.into(), html).expect("failed to write html output");
67 } else {
68 result.plot.write_html(path.into());
69 }
70 }
71
72 fn to_json(&self) -> Result<String, serde_json::Error> {
73 ir_to_json(self)
74 }
75
76 fn to_html(&self) -> String {
77 let result = build_plotly_result(self);
78 if result.layout_overrides.is_some() {
79 let json = ir_to_json(self).unwrap_or_default();
80 render_html_from_json(&json)
81 } else {
82 result.plot.to_html()
83 }
84 }
85
86 fn to_inline_html(&self, plot_div_id: Option<&str>) -> String {
87 let result = build_plotly_result(self);
88 let div_id = plot_div_id.unwrap_or("plotly-div");
89 if result.layout_overrides.is_some() {
90 let json = ir_to_json(self).unwrap_or_default();
91 render_inline_html_from_json(&json, div_id)
92 } else {
93 result.plot.to_inline_html(plot_div_id)
94 }
95 }
96
97 #[cfg(any(
98 feature = "export-chrome",
99 feature = "export-firefox",
100 feature = "export-default"
101 ))]
102 fn write_image(
103 &self,
104 path: impl Into<String>,
105 width: usize,
106 height: usize,
107 scale: f64,
108 ) -> Result<(), Box<dyn std::error::Error + 'static>> {
109 let path_string = path.into();
110 let result = build_plotly_result(self);
111
112 if let Some((filename, extension)) = path_string.rsplit_once('.') {
117 let format = match extension {
118 "png" => plotly::ImageFormat::PNG,
119 "jpg" | "jpeg" => plotly::ImageFormat::JPEG,
120 "webp" => plotly::ImageFormat::WEBP,
121 "svg" => plotly::ImageFormat::SVG,
122 _ => return Err(format!("Unsupported image format: {extension}").into()),
123 };
124 result
125 .plot
126 .write_image(filename, format, width, height, scale)?;
127 } else {
128 return Err("No extension provided for image.".into());
129 }
130
131 Ok(())
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138 use plotlars_core::components::orientation::Orientation;
139 use plotlars_core::components::Rgb;
140 use plotlars_core::plots::array2dplot::Array2dPlot;
141 use plotlars_core::plots::barplot::BarPlot;
142 use plotlars_core::plots::boxplot::BoxPlot;
143 use plotlars_core::plots::candlestick::CandlestickPlot;
144 use plotlars_core::plots::contourplot::ContourPlot;
145 use plotlars_core::plots::density_mapbox::DensityMapbox;
146 use plotlars_core::plots::heatmap::HeatMap;
147 use plotlars_core::plots::histogram::Histogram;
148 use plotlars_core::plots::lineplot::LinePlot;
149 use plotlars_core::plots::mesh3d::Mesh3D;
150 use plotlars_core::plots::ohlc::OhlcPlot;
151 use plotlars_core::plots::piechart::PieChart;
152 use plotlars_core::plots::sankeydiagram::SankeyDiagram;
153 use plotlars_core::plots::scatter3dplot::Scatter3dPlot;
154 use plotlars_core::plots::scattergeo::ScatterGeo;
155 use plotlars_core::plots::scattermap::ScatterMap;
156 use plotlars_core::plots::scatterplot::ScatterPlot;
157 use plotlars_core::plots::scatterpolar::ScatterPolar;
158 use plotlars_core::plots::surfaceplot::SurfacePlot;
159 use plotlars_core::plots::table::Table;
160 use plotlars_core::plots::timeseriesplot::TimeSeriesPlot;
161 use plotlars_core::Plot;
162 use polars::prelude::*;
163
164 fn to_json_value(plot: &impl Plot) -> serde_json::Value {
165 let json_str = ir_to_json(plot).unwrap();
166 serde_json::from_str(&json_str).unwrap()
167 }
168
169 #[test]
170 fn test_scatter_to_json_has_traces() {
171 let df = df!["x" => [1.0, 2.0, 3.0], "y" => [4.0, 5.0, 6.0]].unwrap();
172 let plot = ScatterPlot::builder().data(&df).x("x").y("y").build();
173 let json = to_json_value(&plot);
174 assert!(json["traces"].is_array());
175 assert_eq!(json["traces"].as_array().unwrap().len(), 1);
176 }
177
178 #[test]
179 fn test_scatter_to_json_has_layout() {
180 let df = df!["x" => [1.0, 2.0, 3.0], "y" => [4.0, 5.0, 6.0]].unwrap();
181 let plot = ScatterPlot::builder().data(&df).x("x").y("y").build();
182 let json = to_json_value(&plot);
183 assert!(json["layout"].is_object());
184 }
185
186 #[test]
187 fn test_scatter_to_json_with_title() {
188 let df = df!["x" => [1.0, 2.0, 3.0], "y" => [4.0, 5.0, 6.0]].unwrap();
189 let plot = ScatterPlot::builder()
190 .data(&df)
191 .x("x")
192 .y("y")
193 .plot_title("My Plot")
194 .build();
195 let json = to_json_value(&plot);
196 let layout_str = serde_json::to_string(&json["layout"]).unwrap();
197 assert!(layout_str.contains("My Plot"));
198 }
199
200 #[test]
201 fn test_bar_to_json_has_traces() {
202 let df = df!["labels" => ["a", "b", "c"], "values" => [10.0, 20.0, 30.0]].unwrap();
203 let plot = BarPlot::builder()
204 .data(&df)
205 .labels("labels")
206 .values("values")
207 .build();
208 let json = to_json_value(&plot);
209 assert_eq!(json["traces"].as_array().unwrap().len(), 1);
210 }
211
212 #[test]
213 fn test_bar_to_json_trace_type() {
214 let df = df!["labels" => ["a", "b", "c"], "values" => [10.0, 20.0, 30.0]].unwrap();
215 let plot = BarPlot::builder()
216 .data(&df)
217 .labels("labels")
218 .values("values")
219 .build();
220 let json = to_json_value(&plot);
221 assert_eq!(json["traces"][0]["type"], "bar");
222 }
223
224 #[test]
225 fn test_pie_to_json_trace_type() {
226 let df = df!["labels" => ["a", "b", "c", "a", "b"]].unwrap();
227 let plot = PieChart::builder().data(&df).labels("labels").build();
228 let json = to_json_value(&plot);
229 assert_eq!(json["traces"][0]["type"], "pie");
230 }
231
232 #[test]
233 fn test_scatter_grouped_to_json() {
234 let df = df![
235 "x" => [1.0, 2.0, 3.0, 4.0],
236 "y" => [10.0, 20.0, 30.0, 40.0],
237 "g" => ["a", "b", "a", "b"]
238 ]
239 .unwrap();
240 let plot = ScatterPlot::builder()
241 .data(&df)
242 .x("x")
243 .y("y")
244 .group("g")
245 .build();
246 let json = to_json_value(&plot);
247 assert_eq!(json["traces"].as_array().unwrap().len(), 2);
248 }
249
250 #[test]
251 fn test_scatter_trace_has_x_and_y() {
252 let df = df!["x" => [1.0, 2.0, 3.0], "y" => [4.0, 5.0, 6.0]].unwrap();
253 let plot = ScatterPlot::builder().data(&df).x("x").y("y").build();
254 let json = to_json_value(&plot);
255 let trace = &json["traces"][0];
256 assert!(trace["x"].is_array());
257 assert!(trace["y"].is_array());
258 assert_eq!(trace["x"].as_array().unwrap().len(), 3);
259 }
260
261 #[test]
262 fn test_histogram_to_json() {
263 let df = df!["x" => [1.0, 2.0, 2.0, 3.0, 3.0, 3.0]].unwrap();
264 let plot = Histogram::builder().data(&df).x("x").build();
265 let json = to_json_value(&plot);
266 assert_eq!(json["traces"].as_array().unwrap().len(), 1);
267 assert_eq!(json["traces"][0]["type"], "histogram");
268 }
269
270 #[test]
271 fn test_line_to_json() {
272 let df = df!["x" => [1.0, 2.0, 3.0], "y" => [4.0, 5.0, 6.0]].unwrap();
273 let plot = LinePlot::builder().data(&df).x("x").y("y").build();
274 let json = to_json_value(&plot);
275 assert_eq!(json["traces"].as_array().unwrap().len(), 1);
276 assert_eq!(json["traces"][0]["type"], "scatter");
277 }
278
279 #[test]
282 fn test_e2e_scatter() {
283 let df = df!["x" => [1.0, 2.0, 3.0], "y" => [4.0, 5.0, 6.0]].unwrap();
284 let plot = ScatterPlot::builder().data(&df).x("x").y("y").build();
285 let json = to_json_value(&plot);
286 let trace = &json["traces"][0];
287 assert_eq!(trace["type"], "scatter");
288 assert_eq!(trace["mode"], "markers");
289 assert_eq!(trace["x"].as_array().unwrap().len(), 3);
290 assert_eq!(trace["y"].as_array().unwrap().len(), 3);
291 }
292
293 #[test]
294 fn test_e2e_scatter_styled() {
295 let df = df!["x" => [1.0, 2.0, 3.0], "y" => [4.0, 5.0, 6.0]].unwrap();
296 let plot = ScatterPlot::builder()
297 .data(&df)
298 .x("x")
299 .y("y")
300 .opacity(0.7)
301 .size(12)
302 .color(Rgb(255, 0, 0))
303 .build();
304 let json = to_json_value(&plot);
305 let marker = &json["traces"][0]["marker"];
306 assert_eq!(marker["opacity"], 0.7);
307 assert_eq!(marker["size"], 12);
308 }
309
310 #[test]
311 fn test_e2e_scatter_grouped() {
312 let df = df![
313 "x" => [1.0, 2.0, 3.0, 4.0],
314 "y" => [10.0, 20.0, 30.0, 40.0],
315 "g" => ["a", "b", "a", "b"]
316 ]
317 .unwrap();
318 let plot = ScatterPlot::builder()
319 .data(&df)
320 .x("x")
321 .y("y")
322 .group("g")
323 .build();
324 let json = to_json_value(&plot);
325 let traces = json["traces"].as_array().unwrap();
326 assert_eq!(traces.len(), 2);
327 assert!(traces[0]["name"].is_string());
328 assert!(traces[1]["name"].is_string());
329 }
330
331 #[test]
334 fn test_e2e_bar() {
335 let df = df!["labels" => ["a", "b", "c"], "values" => [10.0, 20.0, 30.0]].unwrap();
336 let plot = BarPlot::builder()
337 .data(&df)
338 .labels("labels")
339 .values("values")
340 .build();
341 let json = to_json_value(&plot);
342 let trace = &json["traces"][0];
343 assert_eq!(trace["type"], "bar");
344 assert!(trace["x"].is_array() || trace["y"].is_array());
345 }
346
347 #[test]
348 fn test_e2e_bar_horizontal() {
349 let df = df!["labels" => ["a", "b", "c"], "values" => [10.0, 20.0, 30.0]].unwrap();
350 let plot = BarPlot::builder()
351 .data(&df)
352 .labels("labels")
353 .values("values")
354 .orientation(Orientation::Horizontal)
355 .build();
356 let json = to_json_value(&plot);
357 let trace = &json["traces"][0];
358 assert_eq!(trace["type"], "bar");
359 assert_eq!(trace["orientation"], "h");
360 }
361
362 #[test]
363 fn test_e2e_bar_grouped() {
364 let df = df![
365 "labels" => ["a", "b", "a", "b"],
366 "values" => [10.0, 20.0, 30.0, 40.0],
367 "g" => ["x", "x", "y", "y"]
368 ]
369 .unwrap();
370 let plot = BarPlot::builder()
371 .data(&df)
372 .labels("labels")
373 .values("values")
374 .group("g")
375 .build();
376 let json = to_json_value(&plot);
377 assert_eq!(json["traces"].as_array().unwrap().len(), 2);
378 }
379
380 #[test]
383 fn test_e2e_boxplot() {
384 let df = df![
385 "labels" => ["a", "a", "b", "b"],
386 "values" => [1.0, 2.0, 3.0, 4.0]
387 ]
388 .unwrap();
389 let plot = BoxPlot::builder()
390 .data(&df)
391 .labels("labels")
392 .values("values")
393 .build();
394 let json = to_json_value(&plot);
395 assert_eq!(json["traces"][0]["type"], "box");
396 }
397
398 #[test]
401 fn test_e2e_histogram() {
402 let df = df!["x" => [1.0, 2.0, 2.0, 3.0, 3.0, 3.0]].unwrap();
403 let plot = Histogram::builder().data(&df).x("x").build();
404 let json = to_json_value(&plot);
405 assert_eq!(json["traces"][0]["type"], "histogram");
406 assert!(json["traces"][0]["x"].is_array());
407 }
408
409 #[test]
412 fn test_e2e_lineplot() {
413 let df = df!["x" => [1.0, 2.0, 3.0], "y" => [4.0, 5.0, 6.0]].unwrap();
414 let plot = LinePlot::builder().data(&df).x("x").y("y").build();
415 let json = to_json_value(&plot);
416 let trace = &json["traces"][0];
417 assert_eq!(trace["type"], "scatter");
418 assert!(trace["x"].is_array());
419 assert!(trace["y"].is_array());
420 }
421
422 #[test]
423 fn test_e2e_lineplot_additional_lines() {
424 let df = df![
425 "x" => [1.0, 2.0, 3.0],
426 "y1" => [4.0, 5.0, 6.0],
427 "y2" => [7.0, 8.0, 9.0]
428 ]
429 .unwrap();
430 let plot = LinePlot::builder()
431 .data(&df)
432 .x("x")
433 .y("y1")
434 .additional_lines(vec!["y2"])
435 .build();
436 let json = to_json_value(&plot);
437 assert!(json["traces"].as_array().unwrap().len() >= 2);
438 }
439
440 #[test]
443 fn test_e2e_timeseries() {
444 let df = df![
445 "date" => ["2024-01", "2024-02", "2024-03"],
446 "val" => [10.0, 20.0, 30.0]
447 ]
448 .unwrap();
449 let plot = TimeSeriesPlot::builder()
450 .data(&df)
451 .x("date")
452 .y("val")
453 .build();
454 let json = to_json_value(&plot);
455 let trace = &json["traces"][0];
456 assert_eq!(trace["type"], "scatter");
457 assert_eq!(trace["x"].as_array().unwrap().len(), 3);
458 }
459
460 #[test]
463 fn test_e2e_heatmap() {
464 let df = df![
465 "x" => ["a", "b", "c"],
466 "y" => ["d", "e", "f"],
467 "z" => [1.0, 2.0, 3.0]
468 ]
469 .unwrap();
470 let plot = HeatMap::builder().data(&df).x("x").y("y").z("z").build();
471 let json = to_json_value(&plot);
472 assert_eq!(json["traces"][0]["type"], "heatmap");
473 }
474
475 #[test]
478 fn test_e2e_contour() {
479 let df = df![
480 "x" => ["a", "b", "c"],
481 "y" => ["d", "e", "f"],
482 "z" => [1.0, 2.0, 3.0]
483 ]
484 .unwrap();
485 let plot = ContourPlot::builder()
486 .data(&df)
487 .x("x")
488 .y("y")
489 .z("z")
490 .build();
491 let json = to_json_value(&plot);
492 assert_eq!(json["traces"][0]["type"], "contour");
493 }
494
495 #[test]
498 fn test_e2e_piechart() {
499 let df = df!["labels" => ["a", "b", "c", "a", "b"]].unwrap();
500 let plot = PieChart::builder().data(&df).labels("labels").build();
501 let json = to_json_value(&plot);
502 assert_eq!(json["traces"][0]["type"], "pie");
503 assert!(json["traces"][0]["labels"].is_array());
504 }
505
506 #[test]
509 fn test_e2e_sankey() {
510 let df = df![
511 "source" => ["A", "A", "B"],
512 "target" => ["B", "C", "C"],
513 "value" => [10.0, 20.0, 30.0]
514 ]
515 .unwrap();
516 let plot = SankeyDiagram::builder()
517 .data(&df)
518 .sources("source")
519 .targets("target")
520 .values("value")
521 .build();
522 let json = to_json_value(&plot);
523 assert_eq!(json["traces"][0]["type"], "sankey");
524 assert!(json["traces"][0]["node"].is_object());
525 assert!(json["traces"][0]["link"].is_object());
526 }
527
528 #[test]
531 fn test_e2e_candlestick() {
532 let df = df![
533 "date" => ["2024-01-01", "2024-01-02", "2024-01-03"],
534 "open" => [100.0, 105.0, 102.0],
535 "high" => [110.0, 108.0, 107.0],
536 "low" => [ 95.0, 100.0, 98.0],
537 "close" => [105.0, 102.0, 106.0]
538 ]
539 .unwrap();
540 let plot = CandlestickPlot::builder()
541 .data(&df)
542 .dates("date")
543 .open("open")
544 .high("high")
545 .low("low")
546 .close("close")
547 .build();
548 let json = to_json_value(&plot);
549 assert_eq!(json["traces"][0]["type"], "candlestick");
550 }
551
552 #[test]
555 fn test_e2e_ohlc() {
556 let df = df![
557 "date" => ["2024-01-01", "2024-01-02", "2024-01-03"],
558 "open" => [100.0, 105.0, 102.0],
559 "high" => [110.0, 108.0, 107.0],
560 "low" => [ 95.0, 100.0, 98.0],
561 "close" => [105.0, 102.0, 106.0]
562 ]
563 .unwrap();
564 let plot = OhlcPlot::builder()
565 .data(&df)
566 .dates("date")
567 .open("open")
568 .high("high")
569 .low("low")
570 .close("close")
571 .build();
572 let json = to_json_value(&plot);
573 assert_eq!(json["traces"][0]["type"], "ohlc");
574 }
575
576 #[test]
579 fn test_e2e_scatter_polar() {
580 let df = df![
581 "angle" => [0.0, 90.0, 180.0, 270.0],
582 "radius" => [1.0, 2.0, 3.0, 4.0]
583 ]
584 .unwrap();
585 let plot = ScatterPolar::builder()
586 .data(&df)
587 .theta("angle")
588 .r("radius")
589 .build();
590 let json = to_json_value(&plot);
591 assert_eq!(json["traces"][0]["type"], "scatterpolar");
592 assert!(json["traces"][0]["theta"].is_array());
593 assert!(json["traces"][0]["r"].is_array());
594 }
595
596 #[test]
599 fn test_e2e_scatter3d() {
600 let df = df![
601 "x" => [1.0, 2.0, 3.0],
602 "y" => [4.0, 5.0, 6.0],
603 "z" => [7.0, 8.0, 9.0]
604 ]
605 .unwrap();
606 let plot = Scatter3dPlot::builder()
607 .data(&df)
608 .x("x")
609 .y("y")
610 .z("z")
611 .build();
612 let json = to_json_value(&plot);
613 assert_eq!(json["traces"][0]["type"], "scatter3d");
614 assert!(json["traces"][0]["x"].is_array());
615 assert!(json["traces"][0]["y"].is_array());
616 assert!(json["traces"][0]["z"].is_array());
617 }
618
619 #[test]
622 fn test_e2e_surface() {
623 let df = df![
624 "x" => [1.0, 1.0, 2.0, 2.0],
625 "y" => [1.0, 2.0, 1.0, 2.0],
626 "z" => [5.0, 6.0, 7.0, 8.0]
627 ]
628 .unwrap();
629 let plot = SurfacePlot::builder()
630 .data(&df)
631 .x("x")
632 .y("y")
633 .z("z")
634 .build();
635 let json = to_json_value(&plot);
636 assert_eq!(json["traces"][0]["type"], "surface");
637 assert!(json["traces"][0]["z"].is_array());
638 }
639
640 #[test]
643 fn test_e2e_mesh3d() {
644 let df = df![
645 "x" => [0.0, 1.0, 0.5, 0.5],
646 "y" => [0.0, 0.0, 1.0, 0.5],
647 "z" => [0.0, 0.0, 0.0, 1.0]
648 ]
649 .unwrap();
650 let plot = Mesh3D::builder().data(&df).x("x").y("y").z("z").build();
651 let json = to_json_value(&plot);
652 assert_eq!(json["traces"][0]["type"], "mesh3d");
653 }
654
655 #[test]
658 fn test_e2e_scattergeo() {
659 let df = df![
660 "lat" => [40.7, 34.0, 41.8],
661 "lon" => [-74.0, -118.2, -87.6]
662 ]
663 .unwrap();
664 let plot = ScatterGeo::builder()
665 .data(&df)
666 .lat("lat")
667 .lon("lon")
668 .build();
669 let json = to_json_value(&plot);
670 assert_eq!(json["traces"][0]["type"], "scattergeo");
671 assert!(json["traces"][0]["lat"].is_array());
672 assert!(json["traces"][0]["lon"].is_array());
673 }
674
675 #[test]
678 fn test_e2e_scattermap() {
679 let df = df![
680 "latitude" => [48.8, 51.5, 40.7],
681 "longitude" => [2.3, -0.1, -74.0]
682 ]
683 .unwrap();
684 let plot = ScatterMap::builder()
685 .data(&df)
686 .latitude("latitude")
687 .longitude("longitude")
688 .build();
689 let json = to_json_value(&plot);
690 assert_eq!(json["traces"][0]["type"], "scattermapbox");
691 assert!(json["traces"][0]["lat"].is_array());
692 assert!(json["traces"][0]["lon"].is_array());
693 let layout_str = serde_json::to_string(&json["layout"]).unwrap();
694 assert!(layout_str.contains("mapbox"));
695 }
696
697 #[test]
700 fn test_e2e_density_mapbox() {
701 let df = df![
702 "lat" => [40.7, 34.0, 41.8],
703 "lon" => [-74.0, -118.2, -87.6],
704 "density" => [100.0, 200.0, 150.0]
705 ]
706 .unwrap();
707 let plot = DensityMapbox::builder()
708 .data(&df)
709 .lat("lat")
710 .lon("lon")
711 .z("density")
712 .build();
713 let json = to_json_value(&plot);
714 assert_eq!(json["traces"][0]["type"], "densitymapbox");
715 let layout_str = serde_json::to_string(&json["layout"]).unwrap();
716 assert!(layout_str.contains("mapbox"));
717 }
718
719 #[test]
722 fn test_e2e_table() {
723 let df = df![
724 "name" => ["Alice", "Bob", "Carol"],
725 "score" => [90, 85, 95]
726 ]
727 .unwrap();
728 let plot = Table::builder()
729 .data(&df)
730 .columns(vec!["name", "score"])
731 .build();
732 let json = to_json_value(&plot);
733 assert_eq!(json["traces"][0]["type"], "table");
734 assert!(json["traces"][0]["header"].is_object());
735 assert!(json["traces"][0]["cells"].is_object());
736 }
737
738 #[test]
741 fn test_e2e_array2d() {
742 let data = vec![
743 vec![[255, 0, 0], [0, 255, 0], [0, 0, 255]],
744 vec![[0, 0, 255], [255, 0, 0], [0, 255, 0]],
745 ];
746 let plot = Array2dPlot::builder().data(&data).build();
747 let json = to_json_value(&plot);
748 assert_eq!(json["traces"][0]["type"], "image");
749 }
750
751 #[test]
754 fn test_e2e_with_all_titles() {
755 let df = df!["x" => [1.0, 2.0, 3.0], "y" => [4.0, 5.0, 6.0]].unwrap();
756 let plot = ScatterPlot::builder()
757 .data(&df)
758 .x("x")
759 .y("y")
760 .plot_title("Main Title")
761 .x_title("X Axis")
762 .y_title("Y Axis")
763 .legend_title("Groups")
764 .build();
765 let json = to_json_value(&plot);
766 let layout = &json["layout"];
767 let layout_str = serde_json::to_string(layout).unwrap();
768 assert!(layout_str.contains("Main Title"));
769 assert!(layout_str.contains("X Axis"));
770 assert!(layout_str.contains("Y Axis"));
771 assert!(layout_str.contains("Groups"));
772 }
773
774 #[test]
777 fn test_e2e_scatter_faceted() {
778 let df = df![
779 "x" => [1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
780 "y" => [10.0, 20.0, 30.0, 40.0, 50.0, 60.0],
781 "f" => ["a", "a", "b", "b", "c", "c"]
782 ]
783 .unwrap();
784 let plot = ScatterPlot::builder()
785 .data(&df)
786 .x("x")
787 .y("y")
788 .facet("f")
789 .build();
790 let json = to_json_value(&plot);
791 let traces = json["traces"].as_array().unwrap();
792 assert_eq!(traces.len(), 3);
793 let layout_str = serde_json::to_string(&json["layout"]).unwrap();
794 assert!(layout_str.contains("xaxis2") || layout_str.contains("yaxis2"));
795 }
796}