diff --git a/gnuplot/Cargo.toml b/gnuplot/Cargo.toml index d28184be..ecb11ff6 100644 --- a/gnuplot/Cargo.toml +++ b/gnuplot/Cargo.toml @@ -62,6 +62,16 @@ path = "examples/box_xy_error.rs" [[example]] +name = "color" +path = "examples/color.rs" + +[[example]] + +name = "variable_color" +path = "examples/variable_color.rs" + +[[example]] + name = "lines_3d" path = "examples/lines_3d.rs" diff --git a/gnuplot/examples/box_and_whisker.rs b/gnuplot/examples/box_and_whisker.rs index a1017ea3..747a3eed 100644 --- a/gnuplot/examples/box_and_whisker.rs +++ b/gnuplot/examples/box_and_whisker.rs @@ -27,7 +27,7 @@ fn example(c: Common) [0.5f32, 0.25, 0.125].iter(), &[ WhiskerBars(0.5), - Color("blue"), + Color("blue".into()), LineWidth(2.0), LineStyle(SmallDot), FillAlpha(0.5), diff --git a/gnuplot/examples/box_xy_error.rs b/gnuplot/examples/box_xy_error.rs index 1ffc6731..bee507f6 100644 --- a/gnuplot/examples/box_xy_error.rs +++ b/gnuplot/examples/box_xy_error.rs @@ -25,7 +25,7 @@ fn example(c: Common) [-1.5f32, 4.5, 3.0].iter(), [0.5f32, 4.75, 0.125].iter(), &[ - Color("blue"), + Color("blue".into()), LineWidth(2.0), LineStyle(SmallDot), FillAlpha(0.5), diff --git a/gnuplot/examples/color.rs b/gnuplot/examples/color.rs new file mode 100644 index 00000000..9a734aa2 --- /dev/null +++ b/gnuplot/examples/color.rs @@ -0,0 +1,140 @@ +use std::{fmt::Debug, iter}; + +// This file is released into Public Domain. +use crate::common::*; +use gnuplot::*; + +mod common; + +fn color_name(color: &PlotOption) -> String +{ + match color + { + Color(color_type) => format!("{:?}", color_type), + _ => panic!(), + } +} + +fn example(c: Common) +{ + let x = 0..5; + + let colors = [ + Color("black".into()), // Conversion to RGBString is implicit + Color(ColorType::RGBString("black")), // Explicit use of RGBString + Color("red".into()), // Conversion to RGBString is implicit + Color(RGBString("#ff0000")), // red using Hex coded RRGGBB + Color(RGBString("#00ff0000")), // red using Hex coded AARRGGBB + Color("#ff8888".into()), // pink using Hex coded RRGGBB. Conversion to RGBString is implict + Color("#88ff0000".into()), // pink using Hex coded AARRGGBB. Conversion to RGBString is implict + Color(ColorType::RGBString("#ffff0000")), // transparent using Hex coded AARRGGBB + Color((128, 0, 255).into()), // purple using implict RGBInteger + Color(RGBInteger(128, 0, 255)), // purple using explict RGBInteger + Color((0.5, 0.0, 1.0).try_into().unwrap()), // purple using implict float to int conversion + Color((128, 128, 0, 255).into()), // pale purple using implict ARGBInteger + Color(ARGBInteger(128, 128, 0, 255)), // pale purple using explict ARGBInteger + Color((0.5, 0.5, 0.0, 1.0).try_into().unwrap()), // pale purple using implict float to int conversion + ]; + + let mut fg = Figure::new(); + let ax = fg.axes2d(); + ax.set_title( + "Demo of RGBString in various forms\nSee code comments for how to construct the colors", + &[], + ) + .set_x_range(Fix(-9.0), Auto) + .set_legend(Graph(0.5), Graph(0.9), &[], &[Font("", 14.0)]); + + let n_colors = colors.len(); + for (i, color) in colors.into_iter().enumerate() + { + ax.box_xy_error_delta( + x.clone(), + iter::repeat((n_colors - 1) - i), + iter::repeat(0.4), + iter::repeat(0.2), + &[ + Caption(&color_name(&color)), + LineWidth(1.0), + BorderColor("black".into()), + color, + ], + ); + } + + // Draw line across the boxes in fixed black and background colors + ax.lines( + [0, 0], + [0, n_colors - 1], + &[ + LineWidth(7.0), + Color(Black), + Caption(&color_name::(&Color(Black))), + ], + ); + + ax.lines( + [4, 4], + [0, n_colors - 1], + &[ + LineWidth(7.0), + Color(Background), + Caption(&color_name::(&Color(Background))), + ], + ); + + // any of the forms used for Color can also be used with TextColor and BorderColor + ax.set_x_label( + "Labels can be colored using the TextColor function", + &[TextColor((128, 0, 255).into())], + ); + + c.show(&mut fg, "rgb_color"); + + // ######################################################################## + + let mut fg = Figure::new(); + let ax = fg.axes2d(); + let max_cb = 10.0; + ax.set_cb_range(Fix(0.0), Fix(max_cb)); + for color_value in 0..=10 + { + let color_float = color_value as f64; + let frac_color = Color(PaletteFracColor(color_float / max_cb)); + let cb_range_color = Color(PaletteCBColor(color_float)); + + ax.box_xy_error_delta( + [color_value], + [0], + [0.4], + [0.4], + &[ + Caption(&color_name(&frac_color)), + LineWidth(1.0), + BorderColor("black".into()), + frac_color, + ], + ) + .box_xy_error_delta( + [color_value], + [1], + [0.4], + [0.4], + &[ + Caption(&color_name(&cb_range_color)), + LineWidth(1.0), + BorderColor("black".into()), + cb_range_color, + ], + ); + } + ax.set_x_range(Fix(-10.0), Fix(11.0)) + .set_y_range(Fix(-0.5), Fix(1.5)) + .set_legend(Graph(0.45), Graph(0.9), &[], &[Font("", 14.0)]); + c.show(&mut fg, "palette_colors"); +} + +fn main() +{ + Common::new().map(|c| example(c)); +} diff --git a/gnuplot/examples/dash_type.rs b/gnuplot/examples/dash_type.rs index c8928173..99c2556f 100644 --- a/gnuplot/examples/dash_type.rs +++ b/gnuplot/examples/dash_type.rs @@ -22,7 +22,7 @@ fn example(c: Common) x.clone().map(|v| v * 2 + 2 * i), &[ LineWidth(2.), - Color("black"), + Color("black".into()), LineStyle(dt), Caption(&format!("{:?}", dt)), ], diff --git a/gnuplot/examples/example1.rs b/gnuplot/examples/example1.rs index f5a28d6e..674df498 100644 --- a/gnuplot/examples/example1.rs +++ b/gnuplot/examples/example1.rs @@ -56,7 +56,7 @@ fn example(c: Common) ArrowType(Closed), ArrowSize(0.1), LineWidth(2.0), - Color("black"), + Color("black".into()), ], ) .label("Here", Axis(5.7912), Axis(3.1), &[TextAlign(AlignCenter)]) @@ -64,18 +64,26 @@ fn example(c: Common) x, y1.map(|&y| y * 0.85 - 1.0), y1.map(|&y| y * 1.15 + 1.0), - &[Color("#aaaaff")], + &[Color("#aaaaff".into())], ) .lines( x, y1, - &[Caption("(x - 4)^2 - 5"), LineWidth(1.5), Color("black")], + &[ + Caption("(x - 4)^2 - 5"), + LineWidth(1.5), + Color("black".into()), + ], ) .y_error_lines( x, y2, repeat(1.0f32), - &[Caption("-(x - 4)^2 + 5"), LineWidth(1.5), Color("red")], + &[ + Caption("-(x - 4)^2 + 5"), + LineWidth(1.5), + Color("red".into()), + ], ) .lines_points( x, @@ -85,7 +93,7 @@ fn example(c: Common) PointSymbol('t'), LineWidth(1.5), LineStyle(Dash), - Color("#11ff11"), + Color("#11ff11".into()), ], ); @@ -95,7 +103,11 @@ fn example(c: Common) fg.axes2d() .set_pos_grid(2, 2, 0) - .lines(x, y1, &[Caption("Lines"), LineWidth(3.0), Color("violet")]) + .lines( + x, + y1, + &[Caption("Lines"), LineWidth(3.0), Color("violet".into())], + ) .set_title("Plot1 fg1.2", &[]); fg.axes2d() @@ -106,7 +118,7 @@ fn example(c: Common) &[ Caption("Points"), PointSymbol('D'), - Color("#ffaa77"), + Color("#ffaa77".into()), PointSize(2.0), ], ) @@ -116,8 +128,11 @@ fn example(c: Common) let mut fg = Figure::new(); - fg.axes2d() - .lines(x, y1, &[Caption("Lines"), LineWidth(3.0), Color("violet")]); + fg.axes2d().lines( + x, + y1, + &[Caption("Lines"), LineWidth(3.0), Color("violet".into())], + ); fg.axes2d() .set_pos(0.2, 0.4) @@ -126,7 +141,7 @@ fn example(c: Common) .points( x, y2, - &[Caption("Points"), PointSymbol('T'), Color("#ffaa77")], + &[Caption("Points"), PointSymbol('T'), Color("#ffaa77".into())], ) .set_title("Inset fg1.3", &[]); @@ -135,7 +150,11 @@ fn example(c: Common) let mut fg = Figure::new(); fg.axes2d() - .lines(x, y1, &[Caption("Lines"), LineWidth(3.0), Color("violet")]) + .lines( + x, + y1, + &[Caption("Lines"), LineWidth(3.0), Color("violet".into())], + ) .set_y_range(Fix(-30.0), Auto) .set_y_label("This axis is manually scaled on the low end", &[]) .set_title("Range fg1.4", &[]); @@ -153,7 +172,7 @@ fn example(c: Common) Caption(r"x\_error\_lines"), LineWidth(2.0), PointSymbol('O'), - Color("red"), + Color("red".into()), ], ) .y_error_lines( @@ -164,20 +183,28 @@ fn example(c: Common) Caption(r"y\_error\_lines"), LineWidth(2.0), PointSymbol('S'), - Color("blue"), + Color("blue".into()), ], ) .x_error_bars( x, y3, x_err, - &[Caption(r"x\_error\_bars"), PointSymbol('T'), Color("cyan")], + &[ + Caption(r"x\_error\_bars"), + PointSymbol('T'), + Color("cyan".into()), + ], ) .y_error_bars( x, y4, y_err, - &[Caption(r"y\_error\_bars"), PointSymbol('R'), Color("green")], + &[ + Caption(r"y\_error\_bars"), + PointSymbol('R'), + Color("green".into()), + ], ) .set_title("Error fg1.5", &[]); @@ -193,7 +220,7 @@ fn example(c: Common) y1, y3, &[ - Color("red"), + Color("red".into()), FillAlpha(0.5), FillRegion(Above), Caption("A > B"), @@ -204,7 +231,7 @@ fn example(c: Common) y1, y3, &[ - Color("green"), + Color("green".into()), FillAlpha(0.5), FillRegion(Below), Caption("A < B"), @@ -215,7 +242,7 @@ fn example(c: Common) y2, y3, &[ - Color("blue"), + Color("blue".into()), FillAlpha(0.5), FillRegion(Between), Caption("Between C and B"), @@ -225,18 +252,22 @@ fn example(c: Common) x, y1, &[ - Color("black"), + Color("black".into()), LineWidth(2.0), LineStyle(Dash), Caption("A"), ], ) - .lines(x, y2, &[Color("black"), LineWidth(2.0), Caption("C")]) + .lines( + x, + y2, + &[Color("black".into()), LineWidth(2.0), Caption("C")], + ) .lines( x, y3, &[ - Color("black"), + Color("black".into()), LineWidth(2.0), LineStyle(DotDotDash), Caption("B"), @@ -267,7 +298,7 @@ fn example(c: Common) &[ Caption("(x - 4)^2 - 5"), LineWidth(3.0), - Color("violet"), + Color("violet".into()), LineStyle(DotDash), ], ) @@ -277,7 +308,7 @@ fn example(c: Common) &[ Caption("-(x - 4)^2 + 5"), PointSymbol('S'), - Color("#ffaa77"), + Color("#ffaa77".into()), ], ) .lines_points( @@ -286,13 +317,13 @@ fn example(c: Common) &[ Caption("x - 4"), PointSymbol('O'), - Color("black"), + Color("black".into()), LineStyle(SmallDot), ], ) .set_x_label( "X Label", - &[Font("Arial", 24.0), TextColor("red"), Rotate(45.0)], + &[Font("Arial", 24.0), TextColor("red".into()), Rotate(45.0)], ) .set_y_label("Y Label", &[Rotate(0.0)]) .set_title( @@ -307,7 +338,7 @@ fn example(c: Common) MarkerSymbol('*'), TextAlign(AlignCenter), TextOffset(0.0, -1.0), - MarkerColor("red"), + MarkerColor(RGBString("red")), MarkerSize(2.0), ], ); diff --git a/gnuplot/examples/example2.rs b/gnuplot/examples/example2.rs index 3d29ccc3..a30d6037 100644 --- a/gnuplot/examples/example2.rs +++ b/gnuplot/examples/example2.rs @@ -41,7 +41,11 @@ fn example(c: Common) fg.axes2d() .set_title("Arrows fg2.1", &[]) - .lines(x, y1, &[LineWidth(3.0), Color("brown"), LineStyle(DotDash)]) + .lines( + x, + y1, + &[LineWidth(3.0), Color("brown".into()), LineStyle(DotDash)], + ) .arrow( Graph(0.5), Graph(1.0), @@ -52,7 +56,7 @@ fn example(c: Common) ArrowSize(0.1), LineStyle(DotDotDash), LineWidth(2.0), - Color("red"), + Color("red".into()), ], ) .arrow( @@ -60,7 +64,7 @@ fn example(c: Common) Graph(1.0), Axis(3.0), Axis(9.0), - &[ArrowType(Open), Color("green")], + &[ArrowType(Open), Color("green".into())], ); c.show(&mut fg, "example2_1"); @@ -74,8 +78,8 @@ fn example(c: Common) y2, &[ LineWidth(2.0), - Color("cyan"), - BorderColor("blue"), + Color("cyan".into()), + BorderColor("blue".into()), LineStyle(DotDash), ], ) @@ -83,7 +87,11 @@ fn example(c: Common) x, y1, w, - &[LineWidth(2.0), Color("gray"), BorderColor("black")], + &[ + LineWidth(2.0), + Color("gray".into()), + BorderColor("black".into()), + ], ); c.show(&mut fg, "example2_2"); @@ -92,12 +100,12 @@ fn example(c: Common) fg.axes2d() .set_title("Axis Ticks fg2.3", &[]) - .lines(x3, y3, &[LineWidth(2.0), Color("blue")]) + .lines(x3, y3, &[LineWidth(2.0), Color("blue".into())]) .set_x_ticks_custom( x3.map(|&x| Major(x as f32, Fix("%.2f ms".to_string()))) .chain(x3.map(|&i| i as f32 + 0.5).map(|x| Minor(x))), &[MajorScale(2.0), MinorScale(0.5), OnAxis(true)], - &[TextColor("blue"), TextAlign(AlignCenter)], + &[TextColor("blue".into()), TextAlign(AlignCenter)], ) .set_x_log(Some(10.0)) .set_y_ticks( @@ -115,9 +123,9 @@ fn example(c: Common) .set_border(true, &[Left, Bottom], &[LineWidth(2.0)]) .set_x_ticks(Some((Fix(1.0), 1)), &[Mirror(false)], &[]) .set_y_ticks(Some((Fix(50.0), 0)), &[Mirror(false)], &[]) - .lines(x3, y3, &[LineWidth(2.0), Color("blue")]) + .lines(x3, y3, &[LineWidth(2.0), Color("blue".into())]) .set_x_axis(true, &[LineWidth(2.0), LineStyle(DotDotDash)]) - .set_y_axis(true, &[LineWidth(2.0), Color("red")]); + .set_y_axis(true, &[LineWidth(2.0), Color("red".into())]); c.show(&mut fg, "example2_4"); @@ -163,10 +171,10 @@ fn example(c: Common) fg.axes2d() .set_title("Axis Grid fg2.8", &[]) - .lines(x3, y3, &[LineWidth(2.0), Color("blue")]) + .lines(x3, y3, &[LineWidth(2.0), Color("blue".into())]) .set_y_ticks(Some((Auto, 2)), &[], &[]) - .set_grid_options(true, &[LineStyle(DotDotDash), Color("black")]) - .set_minor_grid_options(&[LineStyle(SmallDot), Color("red")]) + .set_grid_options(true, &[LineStyle(DotDotDash), Color("black".into())]) + .set_minor_grid_options(&[LineStyle(SmallDot), Color("red".into())]) .set_x_grid(true) .set_y_grid(true) .set_y_minor_grid(true); diff --git a/gnuplot/examples/gif.rs b/gnuplot/examples/gif.rs index 6a511e64..20fa2713 100644 --- a/gnuplot/examples/gif.rs +++ b/gnuplot/examples/gif.rs @@ -24,13 +24,13 @@ fn main() x.iter(), x.iter() .map(|&x| (x + t as f32 * 0.1 * 2. * f32::consts::PI).sin()), - &[Color("blue")], + &[Color("blue".into())], ); ax.lines( x.iter(), x.iter() .map(|&x| (x + t as f32 * 0.1 * 2. * f32::consts::PI).cos()), - &[Color("red")], + &[Color("red".into())], ); t += 0.1; } diff --git a/gnuplot/examples/inverse_api.rs b/gnuplot/examples/inverse_api.rs index f333ff54..235fffec 100644 --- a/gnuplot/examples/inverse_api.rs +++ b/gnuplot/examples/inverse_api.rs @@ -32,8 +32,8 @@ impl PlotElement for Lines PointSize(v) => PointSize(*v), Caption(v) => Caption(&v), LineWidth(v) => LineWidth(*v), - Color(v) => Color(&v), - BorderColor(v) => BorderColor(&v), + Color(v) => Color(v.to_ref()), + BorderColor(v) => BorderColor(v.to_ref()), LineStyle(v) => LineStyle(*v), FillAlpha(v) => FillAlpha(*v), FillRegion(v) => FillRegion(*v), @@ -165,7 +165,7 @@ fn example(c: Common) //~ fg.axes2d().set_title("Old API", &[]).lines( //~ z.clone(), //~ y.clone(), - //~ &[LineWidth(2.), Color("#ffaa77")], + //~ &[LineWidth(2.), Color("#ffaa77".into())], //~ ).lines( //~ z.clone(), //~ x.clone(), @@ -176,11 +176,14 @@ fn example(c: Common) //~ lines(z.clone(), y.clone()).show(); - (lines(z.clone(), y.clone()), lines(z.clone(), x.clone())) - .to_axes2d() - .title("Test") - .x(axis().log_scale(Some(10.))) - .show(); + let mut axes = (lines(z.clone(), y.clone()), lines(z.clone(), x.clone())).to_axes2d(); + axes.title("Test"); + axes.x(axis().log_scale(Some(10.))); + + if !c.no_show + { + axes.show(); + } } fn main() diff --git a/gnuplot/examples/lines_3d.rs b/gnuplot/examples/lines_3d.rs index ed515d3d..f0e936db 100644 --- a/gnuplot/examples/lines_3d.rs +++ b/gnuplot/examples/lines_3d.rs @@ -16,7 +16,7 @@ fn example(c: Common) x, y, z, - &[PointSymbol('o'), Color("#ffaa77"), PointSize(2.0)], + &[PointSymbol('o'), Color("#ffaa77".into()), PointSize(2.0)], ); c.show(&mut fg, "lines_3d"); diff --git a/gnuplot/examples/lines_points_3d.rs b/gnuplot/examples/lines_points_3d.rs index 286260be..b91fc0eb 100644 --- a/gnuplot/examples/lines_points_3d.rs +++ b/gnuplot/examples/lines_points_3d.rs @@ -18,7 +18,7 @@ fn example(c: Common) x, y, z, - &[PointSymbol('o'), Color("#ffaa77"), PointSize(2.0)], + &[PointSymbol('o'), Color("#ffaa77".into()), PointSize(2.0)], ); c.show(&mut fg, "lines_points_3d"); diff --git a/gnuplot/examples/multiple_axes.rs b/gnuplot/examples/multiple_axes.rs index 905f40f3..9c070ecf 100644 --- a/gnuplot/examples/multiple_axes.rs +++ b/gnuplot/examples/multiple_axes.rs @@ -13,19 +13,19 @@ fn example(c: Common) .lines_points( [0.0f32, 1.0, 2.0].iter(), [-1.0f32, 0.0, 1.0].iter(), - &[Axes(X1, Y1), Color("blue")], + &[Axes(X1, Y1), Color("blue".into())], ) .lines_points( [-0.6f32, 1.5, 2.5].iter(), [-5.0f32, 0.0, 5.0].iter(), - &[Axes(X1, Y2), Color("red")], + &[Axes(X1, Y2), Color("red".into())], ) - .set_y_ticks(Some((Auto, 0)), &[Mirror(false)], &[]) // Make Y1 not mirror. - .set_y2_ticks(Some((Auto, 0)), &[Mirror(false)], &[]) // Make Y2 not mirror, and visible. - .set_y_label("Blue", &[]) - .set_y2_label("Red", &[]) - .label("Blue Label", Axis(1.), Axis(0.), &[TextColor("blue"), TextAlign(AlignRight)]) - .label("Red Label", Axis(2.0), Axis2(2.5), &[TextColor("red")]); + .set_y_ticks(Some((Auto, 0)), &[Mirror(false)], &[TextColor("blue".into())]) // Make Y1 not mirror. + .set_y2_ticks(Some((Auto, 0)), &[Mirror(false)], &[TextColor("red".into())]) // Make Y2 not mirror, and visible. + .set_y_label("Blue", &[TextColor("blue".into())]) + .set_y2_label("Red", &[TextColor("red".into())]) + .label("Blue Label", Axis(1.), Axis(0.), &[TextColor("blue".into()), TextAlign(AlignRight)]) + .label("Red Label", Axis(2.0), Axis2(2.5), &[TextColor("red".into())]); c.show(&mut fg, "multiple_axes"); } diff --git a/gnuplot/examples/points_3d.rs b/gnuplot/examples/points_3d.rs index 91b4b48c..34deb01f 100644 --- a/gnuplot/examples/points_3d.rs +++ b/gnuplot/examples/points_3d.rs @@ -16,7 +16,7 @@ fn example(c: Common) x, y, z, - &[PointSymbol('o'), Color("#ffaa77"), PointSize(2.0)], + &[PointSymbol('o'), Color("#ffaa77".into()), PointSize(2.0)], ); c.show(&mut fg, "points_3d"); diff --git a/gnuplot/examples/polygons.rs b/gnuplot/examples/polygons.rs index 2a5c43bd..32534311 100644 --- a/gnuplot/examples/polygons.rs +++ b/gnuplot/examples/polygons.rs @@ -21,20 +21,24 @@ fn example(c: Common) ax.polygon( coords.iter().map(|x| x[0] + 2.), coords.iter().map(|x| x[1]), - &[FillAlpha(0.), BorderColor("black"), LineWidth(4.)], + &[FillAlpha(0.), BorderColor("black".into()), LineWidth(4.)], ); ax.polygon( coords.iter().map(|x| x[0]), coords.iter().map(|x| x[1] + 2.), - &[Color("#FF0000"), BorderColor("black"), LineWidth(4.)], + &[ + Color("#FF0000".into()), + BorderColor("black".into()), + LineWidth(4.), + ], ); ax.polygon( coords.iter().map(|x| x[0] + 2.), coords.iter().map(|x| x[1] + 2.), &[ FillPattern(Fix(BigCrosses)), - Color("#FF0000"), - BorderColor("black"), + Color("#FF0000".into()), + BorderColor("black".into()), LineWidth(4.), LineStyle(Dash), ], diff --git a/gnuplot/examples/variable_color.rs b/gnuplot/examples/variable_color.rs new file mode 100644 index 00000000..5f4e3a5a --- /dev/null +++ b/gnuplot/examples/variable_color.rs @@ -0,0 +1,214 @@ +use std::iter; + +// This file is released into Public Domain. +use crate::common::*; +use gnuplot::{palettes::MAGMA, *}; + +mod common; + +// https://github.com/gnuplot/gnuplot/blob/master/demo/candlesticks.dat +static CANDLESTICKS_STR: &str = "1 1.5 2 2.4 4 6. +2 1.5 3 3.5 4 5.5 +3 4.5 5 5.5 6 6.5 +4 3.7 4.5 5.0 5.5 6.1 +5 3.1 3.5 4.2 5 6.1 +6 1 4 5.0 6 9 +7 4 4 4.8 6 6.1 +8 4 5 5.1 6 6.1 +9 1.5 2 2.4 3 3.5 +10 2.7 3 3.5 4 4.3"; + +fn example(c: Common) +{ + let data: Vec> = CANDLESTICKS_STR + .split("\n") + .map(|line| { + line.split("\t") + .map(|v| v.trim().parse::().unwrap()) + .collect() + }) + .collect(); + let extract_col = |i| data.iter().map(|l| l[i]).collect::>(); + + let d1 = extract_col(0); + let d2 = extract_col(1); + // let d3 = extract_col(2); + // let d4 = extract_col(3); + let d5 = extract_col(4); + let d6 = extract_col(5); + let row_index: Vec<_> = (1..=d1.len() as u8).collect(); + let by3 = |x| (((x % 3.0) + 1.0) / 6.0); + let by4 = |x| (((x % 4.0) + 1.0) / 7.0); + + let argb_formula = |x: &f64| { + let a = 255.0 * (x - 5.5).abs() / 5.5; + let r = x * 51.0 / 2.0; + let g = (11.0 - x) * 51.0 / 2.0; + let b = ((5.5 - x).abs() * 2.0 * 510.0 / 9.0).round(); + (a as u8, r as u8, g as u8, b as u8) + }; + // Demo/test of variable color in many different plot styles + // Inspired by https://gnuplot.sourceforge.net/demo_6.0/varcolor.html + // + // The loop is run four times with different color sets: each one sets all the elements of a given + // plot to a different color, while making sure the colors align by x position: i.e. everything at x = 1 + // uses the first color, everything at x = 2 uses the second and so on. + // + // + // The first color loop demonstrates usage of VariableIndexColor with indices to use gnuplot's default color styles, + // but make them align for multiple plot items on the same axis. This is implicity constructed from a Vec using + // the `IntoColor` trait but could equivalently be created explicitly using `Color(ColorType::VariableIndexColor(row_index.clone()))` + // + // The second color loop uses a `VariablePaletteColor`: this selects the color based on the current color palette and the + // input value for each data point. The palette is scaled to the maximum value in the `Vec` passed + // to the `VariablePaletteColor`. + // + // The third color loop uses an (implicit) `VariableARGBString`. The `Vec<(u8, u8, u8, u8)>` needed to construct the color + // is calculated in this case by the `argb_formula()` closure. An explicit `VariableARGBString` could also be constructed using + // `Color(ColorType::VariableARGBString(data)`. + // As an alternative, `VariableRGBString` is also defined that takes a 3-tuple of u8, rather than + // a 4 tuple. + for (color, label) in [ + (Color(row_index.clone().into()), "VariableIndexColor"), + ( + Color(VariablePaletteColor( + row_index.iter().map(|v| *v as f64).collect(), + )), + "VariablePaletteColor", + ), + ( + Color(d1.iter().map(argb_formula).collect::>().into()), + "VariableARGBString", + ), + ] + { + let mut fg = Figure::new(); + let ax = fg.axes2d(); + ax.set_title( + &format!("variable color boxerror, points, xyerrorbars, and boxxyerror.\nColor used is a {label}"), + &[], + ); + ax.set_y_range(Fix(-4.0), Fix(11.5)); + ax.box_error_low_high_set_width( + &d1, + &d5, + &d2, + &d6, + iter::repeat(0.2), + &[ + color.clone(), + FillAlpha(0.5), + BorderColor(RGBString("black")), + ], + ); + ax.points(&d1, iter::repeat(1), &[color.clone(), PointSymbol('D')]); + ax.xy_error_bars( + &d1, + iter::repeat(8), + d1.iter().map(by3), + d1.iter().map(by4), + &[color.clone()], + ); + ax.points( + &d1, + d2.iter().map(|v| -v / 2.0), + &[color.clone(), PointSymbol('O'), PointSize(3.0)], + ); + ax.box_xy_error_delta( + &d1, + iter::repeat(10), + d1.iter().map(by3), + d1.iter().map(by4), + &[color.clone(), BorderColor(RGBString("black"))], + ); + + c.show(&mut fg, "variable_color"); + } + + // ##################################################################### + // The example below shows the same graphs as in the loop, but using a set of saved colormaps + // similar to palette in gnuplot terms, but the a single (current) palette is applied to all plots by default. + // By contrast, you can create multiple named colormaps. + // + // As with `VariablePaletteColor``, this Color takes a `Vec` that says which point in the colormap to use, + // but it also takes a the name of the colormap from which to draw the colors. + // + // Note that the Color range appears to be shared across plots: i.e. if one plot has + // color data (the `Vec`) in the range 0-1, and another in the range 1-100, all the + // colors in the first plot will be right at the bottom end of its colormap, even if that's + // a different colormap to the one used in the second plot. + let mut fg = Figure::new(); + let ax = fg.axes2d(); + + // First create the colormaps we will later refer to + // MAGMA is one of the colormaps provided with rust gnuplot + ax.create_colormap("magma", MAGMA); + // HOT is one of the colormaps provided with rust gnuplot + ax.create_colormap("hot", HOT); + // ocean (green-blue-white) as per the gnuplot documentation + ax.create_colormap("ocean", PaletteType::Formula(23, 28, 3)); + + let color_values: Vec = row_index.iter().map(|v| *v as f64).collect(); + + ax.set_title( + &format!("variable color boxerror, points, xyerrorbars, and boxxyerror.\nColor used is a SavedColormap"), + &[], + ); + ax.set_y_range(Fix(-4.0), Fix(11.5)); + ax.box_error_low_high_set_width( + &d1, + &d5, + &d2, + &d6, + iter::repeat(0.2), + &[ + Color(SavedColorMap("magma", color_values.clone())), + FillAlpha(0.5), + BorderColor(RGBString("black")), + ], + ); + ax.points( + &d1, + iter::repeat(1), + &[ + Color(SavedColorMap( + "hot", + color_values.iter().map(|v| 11.0 - *v).collect(), + )), + PointSymbol('D'), + ], + ); + ax.xy_error_bars( + &d1, + iter::repeat(8), + d1.iter().map(by3), + d1.iter().map(by4), + &[Color(SavedColorMap("ocean", color_values.clone()))], + ); + ax.points( + &d1, + d2.iter().map(|v| -v / 2.0), + &[ + Color(SavedColorMap("magma", color_values.clone())), + PointSymbol('O'), + PointSize(3.0), + ], + ); + ax.box_xy_error_delta( + &d1, + iter::repeat(10), + d1.iter().map(by3), + d1.iter().map(by4), + &[ + Color(SavedColorMap("hot", color_values.clone())), + BorderColor(RGBString("black")), + ], + ); + + c.show(&mut fg, "variable_palette"); +} + +fn main() +{ + Common::new().map(|c| example(c)); +} diff --git a/gnuplot/src/axes2d.rs b/gnuplot/src/axes2d.rs index 83d11a10..b73d30d4 100644 --- a/gnuplot/src/axes2d.rs +++ b/gnuplot/src/axes2d.rs @@ -2,12 +2,15 @@ // // All rights reserved. Distributed under LGPL 3.0. For full terms see the file LICENSE. +use std::iter; + use crate::axes_common::*; use crate::coordinates::*; use crate::datatype::*; use crate::options::*; use crate::util::{escape, OneWayOwned}; use crate::writer::Writer; +use crate::ColorType; struct LegendData { @@ -101,9 +104,7 @@ impl LegendData first_opt! {self.text_options, TextColor(ref s) => { - w.write_str(" textcolor rgb \""); - w.write_str(&escape(s)); - w.write_str("\""); + write!(w, " textcolor {} ", s.command()); } } first_opt! {self.text_options, @@ -188,7 +189,7 @@ impl ArrowData } w.write_str(",12"); - AxesCommonData::write_color_options(w, &self.plot_options, Some("black")); + AxesCommonData::write_color_options(w, &self.plot_options, false, Some(ColorType::Black)); AxesCommonData::write_line_options( w, &self.plot_options, @@ -233,7 +234,7 @@ impl BorderOptions write!(writer, "{}", f); writer.write_str(if self.front { " front " } else { " back " }); - AxesCommonData::write_color_options(writer, &self.options, Some("black")); + AxesCommonData::write_color_options(writer, &self.options, false, Some(ColorType::Black)); AxesCommonData::write_line_options(writer, &self.options, version); writer.write_str("\n"); @@ -376,11 +377,9 @@ impl Axes2D &'l mut self, x: X, y: Y, options: &[PlotOption<&str>], ) -> &'l mut Self { - self.common.elems.push(PlotElement::new_plot2( - Lines, - x, - y, - options.to_one_way_owned(), + let (data, num_rows, num_cols) = generate_data!(options, x, y); + self.common.elems.push(PlotElement::new_plot( + Lines, data, num_rows, num_cols, options, )); self } @@ -404,11 +403,9 @@ impl Axes2D &'l mut self, x: X, y: Y, options: &[PlotOption<&str>], ) -> &'l mut Self { - self.common.elems.push(PlotElement::new_plot2( - Points, - x, - y, - options.to_one_way_owned(), + let (data, num_rows, num_cols) = generate_data!(options, x, y); + self.common.elems.push(PlotElement::new_plot( + Points, data, num_rows, num_cols, options, )); self } @@ -428,11 +425,13 @@ impl Axes2D &'l mut self, x: X, y: Y, options: &[PlotOption<&str>], ) -> &'l mut Self { - self.common.elems.push(PlotElement::new_plot2( + let (data, num_rows, num_cols) = generate_data!(options, x, y); + self.common.elems.push(PlotElement::new_plot( LinesPoints, - x, - y, - options.to_one_way_owned(), + data, + num_rows, + num_cols, + options, )); self } @@ -460,12 +459,9 @@ impl Axes2D &'l mut self, x: X, y: Y, x_error: XE, options: &[PlotOption<&str>], ) -> &'l mut Self { - self.common.elems.push(PlotElement::new_plot3( - XErrorBars, - x, - y, - x_error, - options.to_one_way_owned(), + let (data, num_rows, num_cols) = generate_data!(options, x, y, x_error); + self.common.elems.push(PlotElement::new_plot( + XErrorBars, data, num_rows, num_cols, options, )); self } @@ -493,12 +489,45 @@ impl Axes2D &'l mut self, x: X, y: Y, y_error: YE, options: &[PlotOption<&str>], ) -> &'l mut Self { - self.common.elems.push(PlotElement::new_plot3( - YErrorBars, - x, - y, - y_error, - options.to_one_way_owned(), + let (data, num_rows, num_cols) = generate_data!(options, x, y, y_error); + self.common.elems.push(PlotElement::new_plot( + YErrorBars, data, num_rows, num_cols, options, + )); + self + } + + /// Plot a 2D scatter-plot with a point standing in for each data point. + /// Additionally, error bars are attached to each data point in the X and Y directions. + /// # Arguments + /// * `x` - x values + /// * `y` - y values + /// * `x_error` - Errors associated with the x value + /// * `options` - Array of PlotOption controlling the appearance of the plot element. The relevant options are: + /// * `Caption` - Specifies the caption for this dataset. Use an empty string to hide it (default). + /// * `PointSymbol` - Sets symbol for each point + /// * `PointSize` - Sets the size of each point + /// * `Color` - Sets the color + pub fn xy_error_bars< + 'l, + Tx: DataType, + X: IntoIterator, + Ty: DataType, + Y: IntoIterator, + Txe: DataType, + XE: IntoIterator, + Tye: DataType, + YE: IntoIterator, + >( + &'l mut self, x: X, y: Y, x_error: XE, y_error: YE, options: &[PlotOption<&str>], + ) -> &'l mut Self + { + let (data, num_rows, num_cols) = generate_data!(options, x, y, x_error, y_error); + self.common.elems.push(PlotElement::new_plot( + XYErrorBars, + data, + num_rows, + num_cols, + options, )); self } @@ -528,12 +557,13 @@ impl Axes2D &'l mut self, x: X, y: Y, x_error: XE, options: &[PlotOption<&str>], ) -> &'l mut Self { - self.common.elems.push(PlotElement::new_plot3( + let (data, num_rows, num_cols) = generate_data!(options, x, y, x_error); + self.common.elems.push(PlotElement::new_plot( XErrorLines, - x, - y, - x_error, - options.to_one_way_owned(), + data, + num_rows, + num_cols, + options, )); self } @@ -563,12 +593,13 @@ impl Axes2D &'l mut self, x: X, y: Y, y_error: YE, options: &[PlotOption<&str>], ) -> &'l mut Self { - self.common.elems.push(PlotElement::new_plot3( + let (data, num_rows, num_cols) = generate_data!(options, x, y, y_error); + self.common.elems.push(PlotElement::new_plot( YErrorLines, - x, - y, - y_error, - options.to_one_way_owned(), + data, + num_rows, + num_cols, + options, )); self } @@ -597,12 +628,13 @@ impl Axes2D &'l mut self, x: X, y_lo: YL, y_hi: YH, options: &[PlotOption<&str>], ) -> &'l mut Self { - self.common.elems.push(PlotElement::new_plot3( + let (data, num_rows, num_cols) = generate_data!(options, x, y_lo, y_hi); + self.common.elems.push(PlotElement::new_plot( FillBetween, - x, - y_lo, - y_hi, - options.to_one_way_owned(), + data, + num_rows, + num_cols, + options, )); self } @@ -628,11 +660,13 @@ impl Axes2D &'l mut self, x: X, y: Y, options: &[PlotOption<&str>], ) -> &'l mut Self { - self.common.elems.push(PlotElement::new_plot2( - Polygons, - x, - y, - options.to_one_way_owned(), + let (data, num_rows, num_cols) = generate_data!(options, x, y); + self.common.elems.push(PlotElement::new_plot( + FillBetween, + data, + num_rows, + num_cols, + options, )); self } @@ -659,11 +693,9 @@ impl Axes2D &'l mut self, x: X, y: Y, options: &[PlotOption<&str>], ) -> &'l mut Self { - self.common.elems.push(PlotElement::new_plot2( - Boxes, - x, - y, - options.to_one_way_owned(), + let (data, num_rows, num_cols) = generate_data!(options, x, y); + self.common.elems.push(PlotElement::new_plot( + Boxes, data, num_rows, num_cols, options, )); self } @@ -693,12 +725,176 @@ impl Axes2D &'l mut self, x: X, y: Y, w: W, options: &[PlotOption<&str>], ) -> &'l mut Self { - self.common.elems.push(PlotElement::new_plot3( - Boxes, - x, - y, - w, - options.to_one_way_owned(), + let (data, num_rows, num_cols) = generate_data!(options, x, y, w); + self.common.elems.push(PlotElement::new_plot( + Boxes, data, num_rows, num_cols, options, + )); + self + } + + /// Plot a 2D box-plot with error bars using boxes of automatic width. + /// Box widths are set so that there are no gaps between successive boxes (i.e. each box may have a different width). + /// Boxes start at the x-axis and go towards the y value of the datapoint. + /// Each box has an error bar from y - y_delta to y + y_delta. + /// # Arguments + /// * `x` - x values (center of the box) + /// * `y` - y values + /// * `y_delta` - errors in y (error bars are plotted from y - y_delta to y + y_delta) + /// * `options` - Array of PlotOption<&str> controlling the appearance of the plot element. The relevant options are: + /// * `Caption` - Specifies the caption for this dataset. Use an empty string to hide it (default). + /// * `LineWidth` - Sets the width of the border + /// * `LineStyle` - Sets the style of the border + /// * `BorderColor` - Sets the color of the border + /// * `Color` - Sets the color of the box fill + /// * `FillAlpha` - Sets the transparency of the box fill + pub fn box_error_delta< + 'l, + Tx: DataType, + X: IntoIterator, + Ty: DataType, + Y: IntoIterator, + Tye: DataType, + YE: IntoIterator, + >( + &'l mut self, x: X, y: Y, y_error: YE, options: &[PlotOption<&str>], + ) -> &'l mut Self + { + let (data, num_rows, num_cols) = generate_data!(options, x, y, y_error); + self.common.elems.push(PlotElement::new_plot( + BoxErrorBars, + data, + num_rows, + num_cols, + options, + )); + self + } + + /// Plot a 2D box-plot with error bars using boxes of specified width. + /// Box widths are set so that there are no gaps between successive boxes (i.e. each box may have a different width). + /// Boxes start at the x-axis and go towards the y value of the datapoint. + /// Each box has an error bar from y - y_delta to y + y_delta. + /// # Arguments + /// * `x` - x values (center of the box) + /// * `y` - y values + /// * `y_delta` - errors in y (error bars are plotted from y - y_delta to y + y_delta) + /// * `x_delta` - errors in x (interpreted as box width) + /// * `options` - Array of PlotOption<&str> controlling the appearance of the plot element. The relevant options are: + /// * `Caption` - Specifies the caption for this dataset. Use an empty string to hide it (default). + /// * `LineWidth` - Sets the width of the border + /// * `LineStyle` - Sets the style of the border + /// * `BorderColor` - Sets the color of the border + /// * `Color` - Sets the color of the box fill + /// * `FillAlpha` - Sets the transparency of the box fill + pub fn box_error_delta_set_width< + 'l, + Tx: DataType, + X: IntoIterator, + Ty: DataType, + Y: IntoIterator, + Tye: DataType, + YE: IntoIterator, + Tw: DataType, + W: IntoIterator, + >( + &'l mut self, x: X, y: Y, y_error: YE, x_delta: W, options: &[PlotOption<&str>], + ) -> &'l mut Self + { + let (data, num_rows, num_cols) = generate_data!(options, x, y, y_error, x_delta); + self.common.elems.push(PlotElement::new_plot( + BoxErrorBars, + data, + num_rows, + num_cols, + options, + )); + self + } + + /// Plot a 2D box-plot with error bars using boxes of automatic width. + /// Box widths are set so that there are no gaps between successive boxes (i.e. each box may have a different width). + /// Boxes start at the x-axis and go towards the y value of the datapoint. + /// Each box has an error bar from y - y_low to y + y_high. + /// # Arguments + /// * `x` - x values (center of the box) + /// * `y` - y values + /// * `y_low` - minimum of error bar + /// * `y_high` - maximum of error bar + /// * `options` - Array of PlotOption<&str> controlling the appearance of the plot element. The relevant options are: + /// * `Caption` - Specifies the caption for this dataset. Use an empty string to hide it (default). + /// * `LineWidth` - Sets the width of the border + /// * `LineStyle` - Sets the style of the border + /// * `BorderColor` - Sets the color of the border + /// * `Color` - Sets the color of the box fill + /// * `FillAlpha` - Sets the transparency of the box fill + pub fn box_error_low_high< + 'l, + Tx: DataType, + X: IntoIterator, + Ty: DataType, + Y: IntoIterator, + Tyl: DataType, + YL: IntoIterator, + Tyh: DataType, + YH: IntoIterator, + >( + &'l mut self, x: X, y: Y, y_low: YL, y_high: YH, options: &[PlotOption<&str>], + ) -> &'l mut Self + { + // The way to get boxerrorbars to interpret low and high y values is to use a dummy negative value for + // xdelta (box width). If you supply four values rather than five, the fourth is interpreted as width. + let dummy_width = iter::repeat(-1.0); + let (data, num_rows, num_cols) = generate_data!(options, x, y, y_low, y_high, dummy_width); + self.common.elems.push(PlotElement::new_plot( + BoxErrorBars, + data, + num_rows, + num_cols, + options, + )); + self + } + + /// Plot a 2D box-plot with error bars using boxes of specified width. + /// Box widths are set so that there are no gaps between successive boxes (i.e. each box may have a different width). + /// Boxes start at the x-axis and go towards the y value of the datapoint. + /// Each box has an error bar from y - y_low to y + y_high. + /// # Arguments + /// * `x` - x values (center of the box) + /// * `y` - y values + /// * `y_low` - minimum of error bar + /// * `y_high` - maximum of error bar + /// * `x_delta` - errors in x (interpreted as box width) + /// * `options` - Array of PlotOption<&str> controlling the appearance of the plot element. The relevant options are: + /// * `Caption` - Specifies the caption for this dataset. Use an empty string to hide it (default). + /// * `LineWidth` - Sets the width of the border + /// * `LineStyle` - Sets the style of the border + /// * `BorderColor` - Sets the color of the border + /// * `Color` - Sets the color of the box fill + /// * `FillAlpha` - Sets the transparency of the box fill + pub fn box_error_low_high_set_width< + 'l, + Tx: DataType, + X: IntoIterator, + Ty: DataType, + Y: IntoIterator, + Tyl: DataType, + YL: IntoIterator, + Tyh: DataType, + YH: IntoIterator, + Tw: DataType, + W: IntoIterator, + >( + &'l mut self, x: X, y: Y, y_low: YL, y_high: YH, x_delta: W, options: &[PlotOption<&str>], + ) -> &'l mut Self + { + let (data, num_rows, num_cols) = generate_data!(options, x, y, y_low, y_high, x_delta); + self.common.elems.push(PlotElement::new_plot( + BoxErrorBars, + data, + num_rows, + num_cols, + options, )); self } @@ -736,14 +932,14 @@ impl Axes2D box_max: BoxMax, options: &[PlotOption<&str>], ) -> &'l mut Self { - self.common.elems.push(PlotElement::new_plot5( + let (data, num_rows, num_cols) = + generate_data!(options, x, box_min, whisker_min, whisker_max, box_max); + self.common.elems.push(PlotElement::new_plot( BoxAndWhisker, - x, - box_min, - whisker_min, - whisker_max, - box_max, - options.to_one_way_owned(), + data, + num_rows, + num_cols, + options, )); self } @@ -784,15 +980,21 @@ impl Axes2D box_max: BoxMax, box_width: BoxWidth, options: &[PlotOption<&str>], ) -> &'l mut Self { - self.common.elems.push(PlotElement::new_plot6( - BoxAndWhisker, + let (data, num_rows, num_cols) = generate_data!( + options, x, box_min, whisker_min, whisker_max, box_max, - box_width, - options.to_one_way_owned(), + box_width + ); + self.common.elems.push(PlotElement::new_plot( + BoxAndWhisker, + data, + num_rows, + num_cols, + options, )); self } @@ -825,13 +1027,9 @@ impl Axes2D &'l mut self, x: X, y: Y, x_delta: XDelta, y_delta: YDelta, options: &[PlotOption<&str>], ) -> &'l mut Self { - self.common.elems.push(PlotElement::new_plot4( - BoxXYError, - x, - y, - x_delta, - y_delta, - options.to_one_way_owned(), + let (data, num_rows, num_cols) = generate_data!(options, x, y, x_delta, y_delta); + self.common.elems.push(PlotElement::new_plot( + BoxXYError, data, num_rows, num_cols, options, )); self } @@ -871,15 +1069,10 @@ impl Axes2D options: &[PlotOption<&str>], ) -> &'l mut Self { - self.common.elems.push(PlotElement::new_plot6( - BoxXYError, - x, - y, - x_low, - x_high, - y_low, - y_high, - options.to_one_way_owned(), + let (data, num_rows, num_cols) = + generate_data!(options, x, y, x_low, x_high, y_low, y_high); + self.common.elems.push(PlotElement::new_plot( + BoxXYError, data, num_rows, num_cols, options, )); self } diff --git a/gnuplot/src/axes3d.rs b/gnuplot/src/axes3d.rs index b76d0548..4f4e9476 100644 --- a/gnuplot/src/axes3d.rs +++ b/gnuplot/src/axes3d.rs @@ -119,12 +119,9 @@ impl Axes3D &'l mut self, x: X, y: Y, z: Z, options: &[PlotOption<&str>], ) -> &'l mut Self { - self.common.elems.push(PlotElement::new_plot3( - Points, - x, - y, - z, - options.to_one_way_owned(), + let (data, num_rows, num_cols) = generate_data!(options, x, y, z); + self.common.elems.push(PlotElement::new_plot( + Points, data, num_rows, num_cols, options, )); self } @@ -151,12 +148,9 @@ impl Axes3D &'l mut self, x: X, y: Y, z: Z, options: &[PlotOption<&str>], ) -> &'l mut Self { - self.common.elems.push(PlotElement::new_plot3( - Lines, - x, - y, - z, - options.to_one_way_owned(), + let (data, num_rows, num_cols) = generate_data!(options, x, y, z); + self.common.elems.push(PlotElement::new_plot( + Lines, data, num_rows, num_cols, options, )); self } @@ -179,12 +173,13 @@ impl Axes3D &'l mut self, x: X, y: Y, z: Z, options: &[PlotOption<&str>], ) -> &'l mut Self { - self.common.elems.push(PlotElement::new_plot3( + let (data, num_rows, num_cols) = generate_data!(options, x, y, z); + self.common.elems.push(PlotElement::new_plot( LinesPoints, - x, - y, - z, - options.to_one_way_owned(), + data, + num_rows, + num_cols, + options, )); self } diff --git a/gnuplot/src/axes_common.rs b/gnuplot/src/axes_common.rs index 5b88cb2e..d2525e5d 100644 --- a/gnuplot/src/axes_common.rs +++ b/gnuplot/src/axes_common.rs @@ -12,6 +12,7 @@ use crate::datatype::*; use crate::options::*; use crate::util::{escape, OneWayOwned}; use crate::writer::*; +use crate::ColorType; use std::borrow::Borrow; use std::fs; use std::path; @@ -29,200 +30,19 @@ pub struct PlotElement impl PlotElement { - pub fn new_plot2( - plot_type: PlotType, x1: X1, x2: X2, options: Vec>, + pub fn new_plot( + plot_type: PlotType, data: Vec, num_rows: usize, num_cols: usize, + options: &[PlotOption<&str>], ) -> PlotElement - where - T1: DataType, - X1: IntoIterator, - T2: DataType, - X2: IntoIterator, { - let mut num_rows = 0; - let mut data = vec![]; - // TODO: Reserve. - for (x1, x2) in x1.into_iter().zip(x2.into_iter()) - { - data.push(x1.get()); - data.push(x2.get()); - num_rows += 1; - } - - PlotElement { - data, - num_rows, - num_cols: 2, - plot_type, - source_type: Record, - is_3d: false, - options, - } - } - - pub fn new_plot3( - plot_type: PlotType, x1: X1, x2: X2, x3: X3, options: Vec>, - ) -> PlotElement - where - T1: DataType, - X1: IntoIterator, - T2: DataType, - X2: IntoIterator, - T3: DataType, - X3: IntoIterator, - { - let mut num_rows = 0; - let mut data = vec![]; - // TODO: Reserve. - for ((x1, x2), x3) in x1.into_iter().zip(x2.into_iter()).zip(x3.into_iter()) - { - data.push(x1.get()); - data.push(x2.get()); - data.push(x3.get()); - num_rows += 1; - } - - PlotElement { - data, - num_rows, - num_cols: 3, - plot_type, - source_type: Record, - is_3d: false, - options, - } - } - - pub fn new_plot4( - plot_type: PlotType, x1: X1, x2: X2, x3: X3, x4: X4, options: Vec>, - ) -> PlotElement - where - T1: DataType, - X1: IntoIterator, - T2: DataType, - X2: IntoIterator, - T3: DataType, - X3: IntoIterator, - T4: DataType, - X4: IntoIterator, - { - let mut num_rows = 0; - let mut data = vec![]; - // TODO: Reserve. - for (((x1, x2), x3), x4) in x1 - .into_iter() - .zip(x2.into_iter()) - .zip(x3.into_iter()) - .zip(x4.into_iter()) - { - data.push(x1.get()); - data.push(x2.get()); - data.push(x3.get()); - data.push(x4.get()); - num_rows += 1; - } - - PlotElement { - data, - num_rows, - num_cols: 4, - plot_type, - source_type: Record, - is_3d: false, - options, - } - } - - pub fn new_plot5( - plot_type: PlotType, x1: X1, x2: X2, x3: X3, x4: X4, x5: X5, - options: Vec>, - ) -> PlotElement - where - T1: DataType, - X1: IntoIterator, - T2: DataType, - X2: IntoIterator, - T3: DataType, - X3: IntoIterator, - T4: DataType, - X4: IntoIterator, - T5: DataType, - X5: IntoIterator, - { - let mut num_rows = 0; - let mut data = vec![]; - // TODO: Reserve. - for ((((x1, x2), x3), x4), x5) in x1 - .into_iter() - .zip(x2.into_iter()) - .zip(x3.into_iter()) - .zip(x4.into_iter()) - .zip(x5.into_iter()) - { - data.push(x1.get()); - data.push(x2.get()); - data.push(x3.get()); - data.push(x4.get()); - data.push(x5.get()); - num_rows += 1; - } - - PlotElement { - data, - num_rows, - num_cols: 5, - plot_type, - source_type: Record, - is_3d: false, - options, - } - } - - pub fn new_plot6( - plot_type: PlotType, x1: X1, x2: X2, x3: X3, x4: X4, x5: X5, x6: X6, - options: Vec>, - ) -> PlotElement - where - T1: DataType, - X1: IntoIterator, - T2: DataType, - X2: IntoIterator, - T3: DataType, - X3: IntoIterator, - T4: DataType, - X4: IntoIterator, - T5: DataType, - X5: IntoIterator, - T6: DataType, - X6: IntoIterator, - { - let mut num_rows = 0; - let mut data = vec![]; - // TODO: Reserve. - for (((((x1, x2), x3), x4), x5), x6) in x1 - .into_iter() - .zip(x2.into_iter()) - .zip(x3.into_iter()) - .zip(x4.into_iter()) - .zip(x5.into_iter()) - .zip(x6.into_iter()) - { - data.push(x1.get()); - data.push(x2.get()); - data.push(x3.get()); - data.push(x4.get()); - data.push(x5.get()); - data.push(x6.get()); - num_rows += 1; - } - PlotElement { data, num_rows, - num_cols: 6, + num_cols, plot_type, source_type: Record, is_3d: false, - options, + options: options.to_one_way_owned(), } } @@ -343,11 +163,13 @@ impl PlotElement YErrorLines => "yerrorlines", XErrorBars => "xerrorbars", YErrorBars => "yerrorbars", + XYErrorBars => "xyerrorbars", FillBetween => "filledcurves", Polygons => "polygons", Boxes => "boxes", BoxAndWhisker => "candlestick", BoxXYError => "boxxyerror", + BoxErrorBars => "boxerrorbars", Pm3D => "pm3d", Image => "image", }; @@ -387,7 +209,17 @@ impl PlotElement if !is_pattern { - writer.write_str("transparent solid"); + let mut color_has_alpha = false; + first_opt! {self.options, + Color(ref c) => { + color_has_alpha = c.has_alpha() + } + } + if !color_has_alpha + { + writer.write_str("transparent "); + } + writer.write_str("solid"); let mut alpha = 1.; first_opt! {self.options, FillAlpha(a) => @@ -400,11 +232,10 @@ impl PlotElement if self.plot_type.is_line() { - writer.write_str(" border"); first_opt! {self.options, BorderColor(ref s) => { - write!(writer, r#" rgb "{}""#, s); + write!(writer, " border {}", s.command()); } } } @@ -436,7 +267,7 @@ impl PlotElement } } - AxesCommonData::write_color_options(writer, &self.options, None); + AxesCommonData::write_color_options(writer, &self.options, self.plot_type.is_fill(), None); writer.write_str(" t \""); first_opt! {self.options, @@ -626,7 +457,7 @@ pub fn write_out_label_options( first_opt! {options, TextColor(ref s) => { - write!(w, r#" tc rgb "{}""#, s); + write!(w, r#" tc {}"#, s.command()); } } @@ -660,7 +491,7 @@ pub fn write_out_label_options( first_opt! {options, MarkerColor(ref s) => { - write!(w, r#" lc rgb "{}""#, s); + write!(w, r#" lc "{}""#, s.command()); } } @@ -760,10 +591,12 @@ pub enum PlotType XErrorLines, YErrorLines, XErrorBars, + XYErrorBars, YErrorBars, FillBetween, Polygons, Boxes, + BoxErrorBars, BoxAndWhisker, BoxXYError, Pm3D, @@ -781,7 +614,8 @@ impl PlotType | XErrorLines | Boxes | YErrorLines | BoxAndWhisker - | BoxXYError | Polygons + | BoxXYError | BoxErrorBars + | Polygons ) } @@ -789,7 +623,12 @@ impl PlotType { matches!( *self, - Points | LinesPoints | XErrorLines | YErrorLines | XErrorBars | YErrorBars + Points + | LinesPoints + | XErrorLines + | YErrorLines + | XErrorBars | YErrorBars + | XYErrorBars ) } @@ -797,7 +636,7 @@ impl PlotType { matches!( *self, - Boxes | FillBetween | BoxAndWhisker | BoxXYError | Polygons + Boxes | FillBetween | BoxAndWhisker | BoxXYError | BoxErrorBars | Polygons ) } } @@ -859,7 +698,12 @@ impl AxisData w.write_str(self.axis.get_axis_str()); w.write_str("zeroaxis "); - AxesCommonData::write_color_options(w, &self.options, Some("black")); + AxesCommonData::write_color_options( + w, + &self.options, + false, + Some(ColorType::RGBString("black".into())), + ); AxesCommonData::write_line_options(w, &self.options, version); } else @@ -1260,6 +1104,7 @@ pub struct AxesCommonData pub aspect_ratio: AutoOption, pub margins: Margins, pub palette: PaletteType>, + pub colormaps: Vec<(String, PaletteType>)>, } impl AxesCommonData @@ -1283,6 +1128,7 @@ impl AxesCommonData aspect_ratio: Auto, margins: Margins::new(), palette: COLOR.to_one_way_owned(), + colormaps: Vec::new(), }; ret.x2_axis.tick_type = TickType::None; ret.y2_axis.tick_type = TickType::None; @@ -1318,10 +1164,10 @@ impl AxesCommonData } AxesCommonData::write_line_options(c, &self.grid_options, version); - AxesCommonData::write_color_options(c, &self.grid_options, None); + AxesCommonData::write_color_options(c, &self.grid_options, false, None); c.write_str(", "); AxesCommonData::write_line_options(c, &self.minor_grid_options, version); - AxesCommonData::write_color_options(c, &self.minor_grid_options, None); + AxesCommonData::write_color_options(c, &self.minor_grid_options, false, None); c.write_str("\n"); } } @@ -1365,10 +1211,13 @@ impl AxesCommonData } pub fn write_color_options( - c: &mut dyn Writer, options: &[PlotOption], default: Option<&str>, + c: &mut dyn Writer, options: &[PlotOption], is_fill: bool, + default: Option, ) { - let mut col = default; + let main_type = if is_fill { "fillcolor" } else { "linecolor" }; + + let mut col = default.as_ref(); first_opt! {options, Color(ref s) => { @@ -1377,7 +1226,7 @@ impl AxesCommonData } if let Some(s) = col { - write!(c, r#" lc rgb "{}""#, s); + write!(c, " {main_type} {}", s.command()); } } @@ -1415,60 +1264,21 @@ impl AxesCommonData } } self.margins.write_out_commands(w); + self.palette.write_out_commands(w); - match self.palette + if !self.colormaps.is_empty() { - Gray(gamma) => - { - assert!(gamma > 0.0, "Gamma must be positive"); - writeln!(w, "set palette gray gamma {:.12e}", gamma); - } - Formula(r, g, b) => - { - assert!(r >= -36 && r <= 36, "Invalid r formula!"); - assert!(g >= -36 && g <= 36, "Invalid g formula!"); - assert!(b >= -36 && b <= 36, "Invalid b formula!"); - writeln!(w, "set palette rgbformulae {},{},{}", r, g, b); - } - CubeHelix(start, rev, sat, gamma) => + // save previous palette + writeln!(w, "set colormap new __ORIGINAL_COLORMAP__"); + for (name, map) in &self.colormaps { - assert!(sat >= 0.0, "Saturation must be non-negative"); - assert!(gamma > 0.0, "Gamma must be positive"); - writeln!( - w, - "set palette cubehelix start {:.12e} cycles {:.12e} saturation {:.12e} gamma {:.12e}", - start, rev, sat, gamma - ); - } - Custom(ref entries) => - { - if entries.len() < 2 - { - panic!("Need at least 2 elements in a custom palette"); - } - write!(w, "set palette defined ("); - - let mut first = true; - let mut old_x = 0.0; - for &(x, r, g, b) in entries - { - if first - { - old_x = x; - first = false; - } - else - { - write!(w, ","); - } - assert!(x >= old_x, "The gray levels must be non-decreasing!"); - old_x = x; - - write!(w, "{:.12e} {:.12e} {:.12e} {:.12e}", x, r, g, b); - } - - writeln!(w, ")"); + // set palette to the requested map + map.write_out_commands(w); + // save current palette to colormap with the requested name + writeln!(w, "set colormap new {name}"); } + // reload previous palette from saved colormap + writeln!(w, "set palette colormap __ORIGINAL_COLORMAP__"); } self.x_axis.write_out_commands(w, version); @@ -2263,7 +2073,8 @@ pub trait AxesCommon: AxesCommonPrivate self } - /// Sets the palette used for 3D surface and image plots + /// Sets the palette used for 3D surface and image plots. See the [palettes][crate::palettes] + /// module for a list of predefined palettes. /// /// # Arguments /// * `palette` - What palette type to use @@ -2272,4 +2083,20 @@ pub trait AxesCommon: AxesCommonPrivate self.get_common_data_mut().palette = palette.to_one_way_owned(); self } + + /// Creates and saves a colormap in the gnuplot environment that can be used for + /// later plots (see examples/color_variable.rs for example usage) + /// + /// # Arguments + /// * `name` - The name with which to save the colormap + /// * `palette` - What palette type to use + fn create_colormap( + &mut self, name: &str, palette: PaletteType<&[(f32, f32, f32, f32)]>, + ) -> &mut Self + { + self.get_common_data_mut() + .colormaps + .push((name.to_owned(), palette.to_one_way_owned())); + self + } } diff --git a/gnuplot/src/color.rs b/gnuplot/src/color.rs new file mode 100644 index 00000000..f35eb818 --- /dev/null +++ b/gnuplot/src/color.rs @@ -0,0 +1,406 @@ +pub use self::ColorType::*; +use crate::util::OneWayOwned; +use std::fmt::{Debug, Display}; + +pub type ColorIndex = u8; +pub type ColorComponent = u8; +pub type ColorInt = u32; +pub type RGBInts = (ColorComponent, ColorComponent, ColorComponent); +pub type ARGBInts = ( + ColorComponent, + ColorComponent, + ColorComponent, + ColorComponent, +); + +/// Option type (for plots, borders, and text) that allows the various different gnuplot +/// color formats. The gnuplot [colorspec reference](http://gnuplot.info/docs_6.0/loc3640.html) +/// also explains these. +/// +/// There are many equivalent ways of specifying colors, and this allows the user to chose the most convenient. +/// For example, all the following will produce the same blue color: +/// `RGBColor("blue".into())`, `RGBColor("0x0000ff".into())`, `RGBColor("#0000ff".into())`, `RGBColor("0x000000ff".into())`, +/// `RGBColor("#000000ff".into())`, `RGBIntegerColor(0, 0, 255)`, `ARGBColor(0, 0, 0, 255)`, +/// +/// See example usages of these colors in `color.rs` and `variable_color.rs` in the +/// [Examples folder](https://github.com/SiegeLord/RustGnuplot/tree/master/gnuplot/examples) on Github +#[derive(Debug, Clone, PartialEq, PartialOrd)] +pub enum ColorType +{ + /// string (`&str` or `String`, but usually created with `&str`) in one of the following gnuplot-supported formats + /// - colorname --- e.g. "blue" [See the gnuplot + /// [list of colornames](http://gnuplot.info/docs_6.0/loc11229.html)] + /// - 0xRRGGBB --- string containing hexadecimal constant + /// - 0xAARRGGBB --- string containing hexadecimal constant + /// - #RRGGBB --- string containing hexadecimal in x11 format + /// - #AARRGGBB --- string containing hexadecimal in x11 format + /// + /// "#AARRGGBB" represents an RGB color with an alpha channel (transparency) value in the high bits. + /// An alpha value of 0 represents a fully opaque color; i.e., "#00RRGGBB" is the same as "#RRGGBB". + /// An alpha value of 255 (FF) represents full transparency. + RGBString(T), + /// tuple of u8 representing red, green and blue values as 0-255 + RGBInteger(ColorComponent, ColorComponent, ColorComponent), + /// tuple of u8 representing alpha, red, green and blue values as 0-255. + /// As with `RGBColor`, an alpha value of 0 represents a fully opaque color; + /// an alpha value of 255 (FF) represents full transparency. + ARGBInteger( + ColorComponent, + ColorComponent, + ColorComponent, + ColorComponent, + ), + /// Vector of tuples of `u8` (as per `RGBColor`), but instead of a single color for the whole + /// plot, the vector should contain a separte color for each data point. + VariableRGBInteger(Vec), + /// Vector of tuples of `u8` (as per `ARGBColor`), but as with `VariableRGBColor`, a separate + /// color value is given for each data point. + VariableARGBInteger(Vec), + /// Sets the color of the plot element to a value picked from the current palette (see + /// [set_palette()](crate::AxesCommon::set_palette())). The value supplied to this color type + /// selects the color within the color range of the palette: i.e. it if the color bar range had been + /// set with `ax.set_cb_range(Fix(min), Fix(max))`, the value would be expected to be between + /// `min` and `max`. + /// + /// Example of usage is give in the `color` example. + /// + /// Compare with [PaletteFracColor] + PaletteFracColor(f64), + /// Sets the color of the plot element to a value picked from the current palette (see + /// [set_palette()](crate::AxesCommon::set_palette()) . The value supplied to this color type + /// selects the color as a fraction of the current color range i.e. it is expected to be + /// between `0` and `1`. + /// + /// Example of usage is give in the `color` example. + /// + /// Comparing with [PaletteCBColor]: given the following code + /// ``` + /// use gnuplot::{PaletteCBColor, PaletteFracColor, Fix, Figure, AxesCommon, Color}; + ///# let min = -5.0; // or any value + ///# let max = 12.0; // or any value + /// + ///# let frac = 0.5; // or any value 0.0 <= frac <= 1.0 + ///# let x = [1,2,3]; + ///# let y = [4,5,6]; + /// assert!(frac >= 0.0); + /// assert!(frac <= 1.0); + /// + /// let mut fg = Figure::new(); + /// let ax = fg.axes2d(); + /// ax.set_cb_range(Fix(min), Fix(max)); + /// let col1 = Color(PaletteFracColor(frac)); + /// let cb_range = max - min; + /// let col2 = Color(PaletteCBColor(min + (frac * cb_range))); + /// ax.lines(x, y, &[col1]); + /// ax.lines(x, y, &[col2]); + /// ``` + /// the two lines should give the same color for any values of `max` and `min`, and `0 <= frac <= 1`. + PaletteCBColor(f64), + /// Vector of `f64` values which act as indexes into the current palette to set the color of + /// each data point. These variable values work in the same was as the single fixed value supplied + /// to a [PaletteCBColor] + VariablePaletteColor(Vec), + /// Similar to `VariablePaletteColor` in that it takes a `Vec` to set the indexes into the + /// color map for each data point, but in addition to the color data it takes a string hold the name + /// of the color map to use. This should have been previously created in the workspace using the + /// [create_colormap()](crate::AxesCommon::create_colormap) function. + SavedColorMap(T, Vec), + /// Set the color of all elements of the plot to the `n`th color in the current gnuplot color cycle. + Index(ColorIndex), + /// A color type that sets the color per element using a index `n` which represents the `n`th + /// color in the current gnuplot color scheme. In gnuplot this is the last element in the plot command, + /// in Rust gnuplot, the color type takes a vector of u8, where each index is treated the same as the + /// fixed `IndexColor`. + /// This is useful for setting bars/boxes etc to be + /// the same color from multiple plot commands. The `variable_color` example has examples of this usage. + VariableIndex(Vec), + /// Set the color of the plot to the current background color. + Background, + /// Fixed black color + Black, +} + +impl ColorType +{ + /// Returns the gnuplot string that will produce the requested color + pub fn command(&self) -> String + { + match self + { + RGBString(s) => format!(r#"rgb "{}""#, s), + RGBInteger(r, g, b) => format!(r#"rgb {}"#, from_argb(0, *r, *g, *b)), + ARGBInteger(a, r, g, b) => format!(r#"rgb {}"#, from_argb(*a, *r, *g, *b)), + VariableRGBInteger(_) => "rgb variable".into(), + VariableARGBInteger(_) => "rgb variable".into(), + PaletteFracColor(v) => format!("palette frac {v}"), + PaletteCBColor(v) => format!("palette cb {v}"), + VariablePaletteColor(_) => "palette z".into(), + SavedColorMap(s, _) => format!("palette {s}"), + VariableIndex(_) => "variable".into(), + Background => "bgnd".into(), + Index(n) => format!("{}", n), + Black => "black".into(), + } + } + + pub fn data(&self) -> Vec + { + match self + { + VariableRGBInteger(items) => items + .iter() + .map(|(r, g, b)| from_argb(0, *r, *g, *b) as f64) + .collect(), + VariableARGBInteger(items) => items + .iter() + .map(|(a, r, g, b)| from_argb(*a, *r, *g, *b) as f64) + .collect(), + VariablePaletteColor(items) => items.clone(), + SavedColorMap(_, items) => items.clone(), + VariableIndex(items) => items.iter().map(|v| *v as f64).collect(), + c => panic!("data() called on non-variable color type: {:?}", *c), + } + } + + pub fn is_variable(&self) -> bool + { + matches!( + self, + VariableRGBInteger(_) + | VariableARGBInteger(_) + | VariableIndex(_) + | VariablePaletteColor(_) + | SavedColorMap(_, _) + ) + } + + pub fn has_alpha(&self) -> bool + { + match self + { + RGBString(s) => + { + let s = s.to_string(); + s.starts_with("0x") && s.len() == 10 || s.starts_with("#") && s.len() == 9 + } + ARGBInteger(_, _, _, _) | VariableARGBInteger(_) => true, + _ => false, + } + } +} + +fn from_argb(a: ColorComponent, r: ColorComponent, g: ColorComponent, b: ColorComponent) + -> ColorInt +{ + ((a as ColorInt) << 24) + ((r as ColorInt) << 16) + ((g as ColorInt) << 8) + (b as ColorInt) +} + +fn float_color_to_int(v: f64) -> Result +{ + if !(0.0..=1.0).contains(&v) + { + Err(format!( + "Float value must be greater than zero and less than one. Actual value: {}", + v + )) + } + else + { + Ok(((v * 255.0).round()) as u8) + } +} + +/// Converts a set of `f64` red, green and blue values in the range `0 <= x <= 1` to a 3-tuple of `u8` suitable for use as +/// an [RGBInteger] +/// +/// Returns an error String if any of the arguments are not in the range `0 <= x <= 1` +/// +/// Ses also [floats_to_argb] +/// +/// # Arguments +/// * r - red. 0: no red, 1: fully red +/// * g - green. 0: no green, 1: fully green +/// * b - blue. 0: no blue, 1: fully blue +fn floats_to_rgb(r: f64, g: f64, b: f64) -> Result +{ + Ok(( + float_color_to_int(r)?, + float_color_to_int(g)?, + float_color_to_int(b)?, + )) +} + +/// Converts a set of `f64` red, green and blue values in the range `0 <= x <= 1` to a 3-tuple of `u8` suitable for use as +/// an [ARGBInteger] +/// +/// Returns an error String if any of the arguments are not in the range `0 <= x <= 1` +/// +/// Ses also [floats_to_rgb] +/// +/// # Arguments +/// * a - alpha (transparency) value. 0: completely opaque, 1: completely transparent. +/// * r - red. 0: no red, 1: fully red +/// * g - green. 0: no green, 1: fully green +/// * b - blue. 0: no blue, 1: fully blue +fn floats_to_argb(a: f64, r: f64, g: f64, b: f64) -> Result +{ + Ok(( + float_color_to_int(a)?, + float_color_to_int(r)?, + float_color_to_int(g)?, + float_color_to_int(b)?, + )) +} + +impl<'l> From<&'l str> for ColorType +{ + /// Converts `&str` into [RGBString] + fn from(value: &'l str) -> Self + { + ColorType::RGBString(String::from(value)) + } +} + +impl<'l> From for ColorType +{ + /// Converts `String` into [RGBString] + fn from(value: String) -> Self + { + ColorType::RGBString(value) + } +} + +impl<'l> From<&'l str> for ColorType<&'l str> +{ + /// Converts `&str` into [RGBString] + fn from(value: &'l str) -> Self + { + ColorType::RGBString(value) + } +} + +impl From for ColorType +{ + /// Converts `(u8, u8, u8, u8)` into [ARGBInteger] + fn from(value: ARGBInts) -> Self + { + ColorType::ARGBInteger(value.0, value.1, value.2, value.3) + } +} + +impl From for ColorType +{ + /// Converts `(u8, u8, u8)` into [RGBInteger] + fn from(value: RGBInts) -> Self + { + ColorType::RGBInteger(value.0, value.1, value.2) + } +} + +impl TryFrom<(f64, f64, f64)> for ColorType +{ + type Error = String; + /// Converts `(f64, f64, f64)` into [RGBInteger]. + /// Returns an error unless all values are in the range `0 <= v <= 1`. + fn try_from(value: (f64, f64, f64)) -> Result + { + let ints = floats_to_rgb(value.0, value.1, value.2)?; + Ok(ColorType::RGBInteger(ints.0, ints.1, ints.2)) + } +} + +impl TryFrom<(f64, f64, f64, f64)> for ColorType +{ + type Error = String; + /// Converts `(f64, f64, f64, f64)` into [ARGBInteger]. + /// Returns an error unless all values are in the range `0 <= v <= 1`. + fn try_from(value: (f64, f64, f64, f64)) -> Result + { + let ints = floats_to_argb(value.0, value.1, value.2, value.3)?; + Ok(ColorType::ARGBInteger(ints.0, ints.1, ints.2, ints.3)) + } +} + +impl From> for ColorType +{ + /// Converts `Vec<(u8, u8, u8)>` into [VariableRGBInteger] + fn from(value: Vec) -> Self + { + ColorType::VariableRGBInteger(value) + } +} + +impl From> for ColorType +{ + /// Converts `Vec<(u8, u8, u8, u8)>` into [VariableARGBInteger] + fn from(value: Vec) -> Self + { + ColorType::VariableARGBInteger(value) + } +} + +impl From for ColorType +{ + /// Converts `u8` into [Index] + fn from(value: ColorIndex) -> Self + { + ColorType::Index(value) + } +} + +impl From> for ColorType +{ + /// Converts `Vec` into [VariableIndex] + fn from(value: Vec) -> Self + { + ColorType::VariableIndex(value) + } +} + +impl OneWayOwned for ColorType +{ + type Output = ColorType; + + fn to_one_way_owned(&self) -> ColorType + { + match self + { + RGBString(s) => RGBString(s.to_string()), + RGBInteger(r, g, b) => RGBInteger(*r, *g, *b), + VariableRGBInteger(d) => VariableRGBInteger(d.clone()), + PaletteFracColor(v) => PaletteFracColor(*v), + PaletteCBColor(v) => PaletteCBColor(*v), + VariablePaletteColor(d) => VariablePaletteColor(d.clone()), + SavedColorMap(s, d) => SavedColorMap(s.to_string(), d.clone()), + VariableIndex(d) => VariableIndex(d.clone()), + Background => Background, + Index(n) => Index(*n), + Black => Black, + ARGBInteger(a, r, g, b) => ARGBInteger(*a, *r, *g, *b), + VariableARGBInteger(d) => VariableARGBInteger(d.clone()), + } + } +} + +impl ColorType +{ + pub fn to_ref(&self) -> ColorType<&str> + { + match self + { + RGBString(s) => RGBString(s), + RGBInteger(r, g, b) => RGBInteger(*r, *g, *b), + VariableRGBInteger(d) => VariableRGBInteger(d.to_vec()), + VariableARGBInteger(d) => VariableARGBInteger(d.to_vec()), + PaletteFracColor(v) => PaletteFracColor(*v), + PaletteCBColor(v) => PaletteCBColor(*v), + VariablePaletteColor(d) => VariablePaletteColor(d.to_vec()), + SavedColorMap(s, d) => SavedColorMap(s, d.to_vec()), + VariableIndex(d) => VariableIndex(d.to_vec()), + Background => Background, + Index(n) => Index(*n), + Black => Black, + ARGBInteger(a, r, g, b) => ARGBInteger(*a, *r, *g, *b), + } + } +} diff --git a/gnuplot/src/lib.rs b/gnuplot/src/lib.rs index b7f89105..907f029b 100644 --- a/gnuplot/src/lib.rs +++ b/gnuplot/src/lib.rs @@ -18,7 +18,7 @@ let x = [0u32, 1, 2]; let y = [3u32, 4, 5]; let mut fg = Figure::new(); fg.axes2d() -.lines(&x, &y, &[Caption("A line"), Color("black")]); +.lines(&x, &y, &[Caption("A line"), Color("black".into())]); fg.show(); # } ~~~ @@ -26,6 +26,7 @@ fg.show(); pub use crate::axes2d::Axes2D; pub use crate::axes3d::Axes3D; pub use crate::axes_common::AxesCommon; +pub use crate::color::*; pub use crate::coordinates::*; pub use crate::datatype::*; pub use crate::error_types::*; @@ -38,6 +39,7 @@ mod util; mod axes2d; mod axes3d; mod axes_common; +mod color; mod coordinates; mod datatype; mod error_types; diff --git a/gnuplot/src/options.rs b/gnuplot/src/options.rs index d3334016..7bf77dae 100644 --- a/gnuplot/src/options.rs +++ b/gnuplot/src/options.rs @@ -20,10 +20,12 @@ pub use self::TickOption::*; pub use self::XAxis::*; pub use self::YAxis::*; use crate::util::OneWayOwned; +use crate::writer::Writer; +use crate::ColorType; /// An enumeration of plot options you can supply to plotting commands, governing /// things like line width, color and others -#[derive(Copy, Clone, Debug, PartialOrd, PartialEq)] +#[derive(Clone, Debug, PartialOrd, PartialEq)] pub enum PlotOption { /// Sets the symbol used for points. The valid characters are as follows: @@ -51,10 +53,10 @@ pub enum PlotOption LineWidth(f64), /// Sets the color of the plot element. The passed string can be a color name /// (e.g. "black" works), or an HTML color specifier (e.g. "#FFFFFF" is white). This specifies the fill color of a filled plot. - Color(T), + Color(ColorType), /// Sets the color of the border of a filled plot (if it has one). The passed string can be a color name /// (e.g. "black" works), or an HTML color specifier (e.g. "#FFFFFF" is white). - BorderColor(T), + BorderColor(ColorType), /// Sets the style of the line. Note that not all gnuplot terminals support dashed lines. See DashType for the available styles. LineStyle(DashType), /// Sets the transparency of a filled plot. `0.0` - fully transparent, `1.0` - fully opaque. Cannot be used with `FillPattern`. @@ -85,8 +87,8 @@ impl<'l> OneWayOwned for PlotOption<&'l str> PointSize(v) => PointSize(v), Caption(v) => Caption(v.into()), LineWidth(v) => LineWidth(v), - Color(v) => Color(v.into()), - BorderColor(v) => BorderColor(v.into()), + Color(ref v) => Color(v.to_one_way_owned()), + BorderColor(ref v) => BorderColor(v.to_one_way_owned()), LineStyle(v) => LineStyle(v), FillAlpha(v) => FillAlpha(v), FillRegion(v) => FillRegion(v), @@ -215,7 +217,7 @@ impl OneWayOwned for AutoOption } /// An enumeration of label options that control label attributes -#[derive(Copy, Clone, Debug, PartialOrd, PartialEq)] +#[derive(Clone, Debug, PartialOrd, PartialEq)] pub enum LabelOption { /// Sets the offset of the label in characters @@ -224,7 +226,7 @@ pub enum LabelOption Font(T, f64), /// Sets the color of the label text. The passed string can be a color name /// (e.g. "black" works), or an HTML color specifier (e.g. "#FFFFFF" is white) - TextColor(T), + TextColor(ColorType), /// Rotates the label by a certain number of degrees Rotate(f64), /// Sets the horizontal alignment of the label text (default is left alignment). See AlignType. @@ -248,7 +250,7 @@ pub enum LabelOption MarkerSymbol(char), /// Sets the color of the marker. The passed string can be a color name /// (e.g. "black" works), or an HTML color specifier (e.g. "#FFFFFF" is white) - MarkerColor(T), + MarkerColor(ColorType), /// Sets the size of the marker. The size acts as a multiplier, with 1.0 being the default. MarkerSize(f64), } @@ -258,15 +260,15 @@ impl<'l> OneWayOwned for LabelOption<&'l str> type Output = LabelOption; fn to_one_way_owned(&self) -> Self::Output { - match *self + match self.clone() { TextOffset(v1, v2) => TextOffset(v1, v2), Font(v1, v2) => Font(v1.into(), v2), - TextColor(v) => TextColor(v.into()), + TextColor(v) => TextColor(v.to_one_way_owned()), Rotate(v) => Rotate(v), TextAlign(v) => TextAlign(v), MarkerSymbol(v) => MarkerSymbol(v), - MarkerColor(v) => MarkerColor(v.into()), + MarkerColor(v) => MarkerColor(v.to_one_way_owned()), MarkerSize(v) => MarkerSize(v), } } @@ -454,6 +456,66 @@ pub const HOT: PaletteType<&'static [(f32, f32, f32, f32)]> = Formula(34, 35, 36 /// A nice default for a cube helix pub const HELIX: PaletteType<&'static [(f32, f32, f32, f32)]> = CubeHelix(0.5, -0.8, 2.0, 1.0); +impl PaletteType> +{ + pub fn write_out_commands(&self, w: &mut dyn Writer) + { + match *self + { + Gray(gamma) => + { + assert!(gamma > 0.0, "Gamma must be positive"); + writeln!(w, "set palette gray gamma {:.12e}", gamma); + } + Formula(r, g, b) => + { + assert!(r >= -36 && r <= 36, "Invalid r formula!"); + assert!(g >= -36 && g <= 36, "Invalid g formula!"); + assert!(b >= -36 && b <= 36, "Invalid b formula!"); + writeln!(w, "set palette rgbformulae {},{},{}", r, g, b); + } + CubeHelix(start, rev, sat, gamma) => + { + assert!(sat >= 0.0, "Saturation must be non-negative"); + assert!(gamma > 0.0, "Gamma must be positive"); + writeln!( + w, + "set palette cubehelix start {:.12e} cycles {:.12e} saturation {:.12e} gamma {:.12e}", + start, rev, sat, gamma + ); + } + Custom(ref entries) => + { + if entries.len() < 2 + { + panic!("Need at least 2 elements in a custom palette"); + } + write!(w, "set palette defined ("); + + let mut first = true; + let mut old_x = 0.0; + for &(x, r, g, b) in entries + { + if first + { + old_x = x; + first = false; + } + else + { + write!(w, ","); + } + assert!(x >= old_x, "The gray levels must be non-decreasing!"); + old_x = x; + + write!(w, "{:.12e} {:.12e} {:.12e} {:.12e}", x, r, g, b); + } + writeln!(w, ")"); + } + } + } +} + /// Gnuplot version identifier. This is used to handle version-specific /// features. #[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq)] diff --git a/gnuplot/src/util.rs b/gnuplot/src/util.rs index 6294d593..4858402f 100644 --- a/gnuplot/src/util.rs +++ b/gnuplot/src/util.rs @@ -48,6 +48,86 @@ macro_rules! first_opt_default ) } +// returns (data, num_rows, num_cols) +macro_rules! generate_data { + ($options: ident, $( $d:ident ),*) => { + { + let mut c_data = None; + + first_opt! {$options, + Color(ref color) => + { + if color.is_variable() { + c_data = Some(color.data()); + } + } + } + if let Some(c_values) = c_data { + generate_data_inner!( + $( + $d, + )* + c_values + ) + } else { + generate_data_inner!( + $( + $d, + )* + ) + } + } + }; +} + +// returns (data, num_rows, num_cols) +macro_rules! generate_data_inner { + ($( $d:ident ),* $(,)?) => { + { + let mut num_rows = 0; + let num_cols = count_data!($($d )*); + let mut data = vec![]; + // TODO: Reserve. + for nested_tuples!($($d,)*) in multizip!($($d, )*) //macro + { + $( data.push($d.get()); )* + num_rows += 1; + } + (data, num_rows, num_cols) + } + } +} + +macro_rules! nested_tuples { + ($last: ident $(,)?)=> + { + $last + }; + ($first: ident, $( $tail:ident ),* $(,)? ) => { + ($first, nested_tuples!($($tail, )*)) + }; +} + +macro_rules! multizip { + ($last: ident $(,)?)=> + { + ($last.into_iter()) + }; + ($first: ident, $( $tail:ident ),* , ) => { + $first.into_iter().zip(multizip!($($tail, )*)) + }; +} + +macro_rules! replace_expr { + ($_t:tt $sub:expr) => { + $sub + }; +} + +macro_rules! count_data { + ($($data:tt)*) => {0usize $(+ replace_expr!($data 1usize))*}; +} + pub(crate) trait OneWayOwned { type Output;