khonsulabs / cushy

An experimental cross-platform graphical user interface (GUI) crate for Rust.
Apache License 2.0
489 stars 25 forks source link

Minor issues in plotters output #179

Closed bluenote10 closed 1 month ago

bluenote10 commented 1 month ago

I noticed that the plotters integration has some minor rendering issues when compared to the "reference" plotters backend. Given the two minimal reproduction examples:

Example using cushy/kludgine backend ```rust // Note that this requires to enable the corresponding plotters feature, // i.e., default-feature = false will not work. use cushy::widget::MakeWidget; use cushy::widgets::Canvas; use cushy::Run; use plotters::prelude::*; pub fn basic_plot( root: &DrawingArea, ) -> Result<(), Box> where A: DrawingBackend, A::ErrorType: 'static, { root.fill(&WHITE)?; let mut chart = ChartBuilder::on(&root) .caption("y=x^2", ("sans-serif", 50).into_font()) .margin(5) .x_label_area_size(30) .y_label_area_size(30) .build_cartesian_2d(-2f32..2f32, -0.1f32..1f32)?; chart.configure_mesh().draw()?; chart .draw_series(LineSeries::new( (-100..=100).map(|x| x as f32 / 50.0).map(|x| (x, x * x)), &RED, ))? .label("y = x^2") .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], &RED)); chart .configure_series_labels() .background_style(&WHITE.mix(0.8)) .border_style(&BLACK) .draw()?; Ok(()) } fn plotters() -> impl MakeWidget { Canvas::new({ move |context| { basic_plot(&context.gfx.as_plot_area()).unwrap(); } }) } fn main() -> cushy::Result<()> { plotters().run() } ```
Example using plain plotters backend ```rust use plotters::prelude::*; fn main() -> Result<(), Box> { let root = BitMapBackend::new("basic_plot.png", (640, 480)).into_drawing_area(); root.fill(&WHITE)?; let mut chart = ChartBuilder::on(&root) .caption("y=x^2", ("sans-serif", 50).into_font()) .margin(5) .x_label_area_size(30) .y_label_area_size(30) .build_cartesian_2d(-2f32..2f32, -0.1f32..1f32)?; chart.configure_mesh().draw()?; chart .draw_series(LineSeries::new( (-100..=100).map(|x| x as f32 / 50.0).map(|x| (x, x * x)), &RED, ))? .label("y = x^2") .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], &RED)); chart .configure_series_labels() .background_style(&WHITE.mix(0.8)) .border_style(&BLACK) .draw()?; root.present()?; Ok(()) } ```

The cushy/kludgine based output is:

image

The plotters based output is:

basic_plot

The main issue is that text labels are wrongly aligned. Minor issues are that the line anti-aliasing doesn't look as good, and that the grid get aliased instead of being pixel perfect (but likely that is expected if the size of the drawing area doesn't allow for integer multiples of the grid size).

Since I'm not entirely sure if this a cushy or kludgine issue (or even further upstream?), I decided to report it on cushy. Do you generally prefer such issues being raised further upstream if possible?

Note that the lack of clipping is an upsteam issue in plotters https://github.com/plotters-rs/plotters/issues/622

ecton commented 1 month ago

Thank you for reporting this! I knew I had under-tested some of the plotters code, but some of that label drawing is pretty bad too.

The fuzziness is almost certainly due to me trying to automatically scale the plotters output based on DPI scaling. It seems like the more correct approach is going to be to let it be pixel-perfect rendering, and require that the plotters-integrator worry about dpi scaling themselves.

As for where to report the issue, I'm happy with most issues being reported on this repository -- it's where most users might actually encounter a bug. I can always create a separate issue and link them if I think someone else might tackle it, but right now I'm usually the person to fix something in Kludgine!

ecton commented 1 month ago

image

Things are looking better! I can't seem to figure out how the labels are sized -- I put a lot of logging code in, and I notice plotters measures the chart caption, but not the labels. My picture shows two series because I was trying to see if that would cause it to measure the text... but it didn't.

The issues were not what I originally suspected. It was that to draw a pixel-perfect 1px wide line, the coordinates need to be offset because lyon generates a rectangle centered on the path coordinates. To ensure the 1px wide line isn't split across two lines with Kludgine's subpixel rendering support, the coordinates need to be offset by 1/2 a pixel, or more generalized, half of the stroke width.

Beyond that, the text drawing code didn't support anchoring or alignment or rotation. This plot doesn't rotate text, but in theory everything is handled correctly now -- don't hesitate to let me know if you run into other issues! If you test the main branch, be sure to use cargo update to get Kludgine's changes.

ecton commented 1 month ago

Actually I'm reopening this because I'd like to have you weigh in on what's happening with the labels, if you know more about how plotters works.

bluenote10 commented 1 month ago

Thanks for the fix!

Actually I'm reopening this because I'd like to have you weigh in on what's happening with the labels, if you know more about how plotters works.

In my opinion, this looks pretty much what I would expect now, so closing the issue should be fine. In case anything else comes up I'll let you know. I also don't have much expertise with plotters, mainly just trying things out so far.