├── Trunk.toml ├── demo ├── assets │ ├── favicon.ico │ ├── icon-1024.png │ ├── icon-256.png │ ├── icon_ios_touch_192.png │ ├── maskable_icon_x512.png │ ├── sw.js │ └── manifest.json ├── src │ ├── lib.rs │ └── main.rs ├── README.md ├── Cargo.toml └── index.html ├── examples ├── lines │ ├── src │ │ ├── main.rs │ │ └── lib.rs │ ├── screenshot.png │ ├── screenshot_thumb.png │ ├── README.md │ └── Cargo.toml ├── heatmap │ ├── src │ │ ├── main.rs │ │ ├── lib.rs │ │ └── app.rs │ ├── screenshot.png │ ├── screenshot_thumb.png │ ├── README.md │ └── Cargo.toml ├── items │ ├── src │ │ ├── main.rs │ │ ├── lib.rs │ │ └── app.rs │ ├── screenshot.png │ ├── screenshot_thumb.png │ ├── README.md │ └── Cargo.toml ├── markers │ ├── src │ │ ├── main.rs │ │ ├── lib.rs │ │ └── app.rs │ ├── screenshot.png │ ├── screenshot_thumb.png │ ├── README.md │ └── Cargo.toml ├── legend │ ├── src │ │ ├── main.rs │ │ ├── lib.rs │ │ └── app.rs │ ├── screenshot.png │ ├── screenshot_thumb.png │ ├── README.md │ └── Cargo.toml ├── plot_span │ ├── src │ │ ├── main.rs │ │ ├── lib.rs │ │ └── app.rs │ ├── screenshot.png │ ├── screenshot_thumb.png │ ├── README.md │ └── Cargo.toml ├── box_plot │ ├── src │ │ ├── main.rs │ │ ├── lib.rs │ │ └── app.rs │ ├── screenshot.png │ ├── screenshot_thumb.png │ ├── README.md │ └── Cargo.toml ├── histogram │ ├── src │ │ ├── main.rs │ │ ├── lib.rs │ │ └── app.rs │ ├── screenshot.png │ ├── screenshot_thumb.png │ ├── README.md │ └── Cargo.toml ├── performance │ ├── src │ │ ├── main.rs │ │ ├── lib.rs │ │ └── app.rs │ ├── screenshot.png │ ├── screenshot_thumb.png │ ├── README.md │ └── Cargo.toml ├── save_plot │ ├── src │ │ ├── main.rs │ │ ├── lib.rs │ │ └── app.rs │ ├── screenshot.png │ ├── screenshot_thumb.png │ ├── README.md │ └── Cargo.toml ├── custom_axes │ ├── src │ │ ├── main.rs │ │ ├── lib.rs │ │ └── app.rs │ ├── screenshot.png │ ├── screenshot_thumb.png │ ├── README.md │ └── Cargo.toml ├── filled_area │ ├── src │ │ ├── main.rs │ │ ├── lib.rs │ │ └── app.rs │ ├── screenshot.png │ ├── screenshot_thumb.png │ ├── Cargo.toml │ └── README.md ├── legend_sort │ ├── src │ │ ├── main.rs │ │ ├── lib.rs │ │ └── app.rs │ ├── screenshot.png │ ├── screenshot_thumb.png │ ├── README.md │ └── Cargo.toml ├── linked_axes │ ├── src │ │ ├── main.rs │ │ ├── lib.rs │ │ └── app.rs │ ├── screenshot.png │ ├── screenshot_thumb.png │ ├── README.md │ └── Cargo.toml ├── stacked_bar │ ├── src │ │ ├── main.rs │ │ ├── lib.rs │ │ └── app.rs │ ├── screenshot.png │ ├── screenshot_thumb.png │ ├── README.md │ └── Cargo.toml ├── interaction │ ├── src │ │ ├── main.rs │ │ ├── lib.rs │ │ └── app.rs │ ├── screenshot.png │ ├── screenshot_thumb.png │ ├── README.md │ └── Cargo.toml ├── borrow_points │ ├── src │ │ ├── main.rs │ │ ├── app.rs │ │ └── lib.rs │ ├── screenshot.png │ ├── screenshot_thumb.png │ ├── README.md │ └── Cargo.toml └── custom_plot_manipulation │ ├── screenshot.png │ ├── screenshot_thumb.png │ ├── src │ ├── main.rs │ └── lib.rs │ ├── README.md │ └── Cargo.toml ├── .gitattributes ├── .typos.toml ├── rustfmt.toml ├── egui_plot ├── src │ ├── overlays │ │ ├── mod.rs │ │ └── coordinates.rs │ ├── cursor.rs │ ├── math.rs │ ├── label.rs │ ├── colors.rs │ ├── rect_elem.rs │ ├── placement.rs │ ├── lib.rs │ ├── memory.rs │ ├── utils.rs │ ├── aesthetics.rs │ └── items │ │ ├── text.rs │ │ └── polygon.rs └── Cargo.toml ├── .github ├── ISSUE_TEMPLATE │ ├── other.md │ ├── feature_request.md │ └── bug_report.md ├── workflows │ ├── cargo_shear.yml │ ├── typos.yml │ ├── kitdiff_comment.yml │ ├── labels.yml │ ├── links.yml │ ├── update_kittest_snapshots.yml │ ├── enforce_branch_name.yml │ ├── pages.yml │ └── rust.yml ├── lint_newlines.sh └── pull_request_template.md ├── scripts ├── accept_snapshots.sh ├── check.sh ├── update_snapshots_from_ci.sh └── clippy_wasm │ └── clippy.toml ├── .cargo └── config.toml ├── examples_utils ├── Cargo.toml └── src │ └── lib.rs ├── rust-toolchain ├── .gitignore ├── LICENSE-MIT ├── .vscode └── settings.json ├── README.md ├── RELEASES.md ├── clippy.toml ├── lychee.toml ├── deny.toml └── ECOSYSTEM.md /Trunk.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | -------------------------------------------------------------------------------- /demo/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emilk/egui_plot/HEAD/demo/assets/favicon.ico -------------------------------------------------------------------------------- /demo/assets/icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emilk/egui_plot/HEAD/demo/assets/icon-1024.png -------------------------------------------------------------------------------- /demo/assets/icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emilk/egui_plot/HEAD/demo/assets/icon-256.png -------------------------------------------------------------------------------- /demo/assets/icon_ios_touch_192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emilk/egui_plot/HEAD/demo/assets/icon_ios_touch_192.png -------------------------------------------------------------------------------- /demo/assets/maskable_icon_x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emilk/egui_plot/HEAD/demo/assets/maskable_icon_x512.png -------------------------------------------------------------------------------- /examples/lines/src/main.rs: -------------------------------------------------------------------------------- 1 | use examples_utils::make_main; 2 | use lines::LineExample; 3 | 4 | make_main!(LineExample); 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | Cargo.lock linguist-generated=false 3 | 4 | *.png filter=lfs diff=lfs merge=lfs -text 5 | -------------------------------------------------------------------------------- /examples/heatmap/src/main.rs: -------------------------------------------------------------------------------- 1 | use examples_utils::make_main; 2 | use heatmap::HeatmapDemo; 3 | 4 | make_main!(HeatmapDemo); 5 | -------------------------------------------------------------------------------- /examples/items/src/main.rs: -------------------------------------------------------------------------------- 1 | use examples_utils::make_main; 2 | use items::ItemsExample; 3 | 4 | make_main!(ItemsExample); 5 | -------------------------------------------------------------------------------- /examples/markers/src/main.rs: -------------------------------------------------------------------------------- 1 | use examples_utils::make_main; 2 | use markers::MarkerDemo; 3 | 4 | make_main!(MarkerDemo); 5 | -------------------------------------------------------------------------------- /examples/legend/src/main.rs: -------------------------------------------------------------------------------- 1 | use examples_utils::make_main; 2 | use legend::LegendExample; 3 | 4 | make_main!(LegendExample); 5 | -------------------------------------------------------------------------------- /examples/plot_span/src/main.rs: -------------------------------------------------------------------------------- 1 | use examples_utils::make_main; 2 | use plot_span::PlotSpanDemo; 3 | 4 | make_main!(PlotSpanDemo); 5 | -------------------------------------------------------------------------------- /examples/box_plot/src/main.rs: -------------------------------------------------------------------------------- 1 | use box_plot::BoxPlotExample; 2 | use examples_utils::make_main; 3 | 4 | make_main!(BoxPlotExample); 5 | -------------------------------------------------------------------------------- /examples/histogram/src/main.rs: -------------------------------------------------------------------------------- 1 | use examples_utils::make_main; 2 | use histogram::HistogramExample; 3 | 4 | make_main!(HistogramExample); 5 | -------------------------------------------------------------------------------- /examples/performance/src/main.rs: -------------------------------------------------------------------------------- 1 | use examples_utils::make_main; 2 | use performance::PerformanceDemo; 3 | 4 | make_main!(PerformanceDemo); 5 | -------------------------------------------------------------------------------- /examples/save_plot/src/main.rs: -------------------------------------------------------------------------------- 1 | use examples_utils::make_main; 2 | use save_plot::SavePlotExample; 3 | 4 | make_main!(SavePlotExample); 5 | -------------------------------------------------------------------------------- /examples/custom_axes/src/main.rs: -------------------------------------------------------------------------------- 1 | use custom_axes::CustomAxesExample; 2 | use examples_utils::make_main; 3 | 4 | make_main!(CustomAxesExample); 5 | -------------------------------------------------------------------------------- /examples/filled_area/src/main.rs: -------------------------------------------------------------------------------- 1 | use examples_utils::make_main; 2 | use filled_area::FilledAreaExample; 3 | 4 | make_main!(FilledAreaExample); 5 | -------------------------------------------------------------------------------- /examples/legend_sort/src/main.rs: -------------------------------------------------------------------------------- 1 | use examples_utils::make_main; 2 | use legend_sort::LegendSortExample; 3 | 4 | make_main!(LegendSortExample); 5 | -------------------------------------------------------------------------------- /examples/linked_axes/src/main.rs: -------------------------------------------------------------------------------- 1 | use examples_utils::make_main; 2 | use linked_axes::LinkedAxesExample; 3 | 4 | make_main!(LinkedAxesExample); 5 | -------------------------------------------------------------------------------- /examples/stacked_bar/src/main.rs: -------------------------------------------------------------------------------- 1 | use examples_utils::make_main; 2 | use stacked_bar::StackedBarExample; 3 | 4 | make_main!(StackedBarExample); 5 | -------------------------------------------------------------------------------- /examples/interaction/src/main.rs: -------------------------------------------------------------------------------- 1 | use examples_utils::make_main; 2 | use interaction::InteractionExample; 3 | 4 | make_main!(InteractionExample); 5 | -------------------------------------------------------------------------------- /examples/borrow_points/src/main.rs: -------------------------------------------------------------------------------- 1 | use borrow_points::BorrowPointsExample; 2 | use examples_utils::make_main; 3 | 4 | make_main!(BorrowPointsExample); 5 | -------------------------------------------------------------------------------- /examples/heatmap/screenshot.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:8c67f3e809896646298f8410b2359d674a7851f1c29c5b90cb528b62e8e97f05 3 | size 26153 4 | -------------------------------------------------------------------------------- /examples/items/screenshot.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:04219a6e92c151fa65cde735b70f8da76031d566d46cc65d394224d42bd74e9f 3 | size 129974 4 | -------------------------------------------------------------------------------- /examples/legend/screenshot.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:8577ddfdf8e60e92050250fee71be868a5a877b034a58ea030e3d849cb362d21 3 | size 104493 4 | -------------------------------------------------------------------------------- /examples/lines/screenshot.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:6d28a4e5c7689d1e3702472335a3058e60656eac6c79d100f96a84e1e8d794b2 3 | size 118611 4 | -------------------------------------------------------------------------------- /examples/markers/screenshot.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:203f8cbc09ac194aa91135a989f12576148ee72dedbbe438f74896c93d5d70ea 3 | size 78233 4 | -------------------------------------------------------------------------------- /examples/box_plot/screenshot.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:f6126490bfe422102f406de40e6a6a9308e15895aed1237df967cfdaa75f92bb 3 | size 64449 4 | -------------------------------------------------------------------------------- /examples/custom_axes/screenshot.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:3e7ae4d645f27414531568da225562a0027c14378278046826cb210e7ddc67fa 3 | size 68270 4 | -------------------------------------------------------------------------------- /examples/filled_area/screenshot.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:37ce32164e02f3106d97b5211d958b9130671909466c0a6eefc0461ad4255995 3 | size 70933 4 | -------------------------------------------------------------------------------- /examples/histogram/screenshot.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:a8432f5a26b2fb194a1ad106efb644682a311bee2ab7c0b2ffbe82b4813055cd 3 | size 62528 4 | -------------------------------------------------------------------------------- /examples/interaction/screenshot.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:24eb6ad7ae52ebea72a85f9bf90b0f3db95087607d837ed3ae8e0a2af2e6ab11 3 | size 35860 4 | -------------------------------------------------------------------------------- /examples/items/screenshot_thumb.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:fc7751c0a7fc00755c4ab5315254f1f8536d2cbc2ddfd0f8d113c507d29559cf 3 | size 26436 4 | -------------------------------------------------------------------------------- /examples/legend/screenshot_thumb.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:efa80be6d0270eb34a039b1cfaadd945e28a0daf9d5a4ef6538dc793b7566083 3 | size 16475 4 | -------------------------------------------------------------------------------- /examples/legend_sort/screenshot.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:77749a163893d5fbcd6d08d920cbbab074eaf3d6415393ee8f72fb222fd76180 3 | size 84497 4 | -------------------------------------------------------------------------------- /examples/lines/screenshot_thumb.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:92aa44a177e3cd17f4fcc62ebbcbf7fc394c2a81139392c8b3f5d0f6806e9115 3 | size 20310 4 | -------------------------------------------------------------------------------- /examples/linked_axes/screenshot.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:1d79069c88a28ac8a17273690bd270257b0515e3442d839178fdbd4f584812c1 3 | size 66367 4 | -------------------------------------------------------------------------------- /examples/performance/screenshot.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:99f4a5341475ec47085193cdafc2676fd57816afe4310f360a90a8e3d6a9284e 3 | size 29101 4 | -------------------------------------------------------------------------------- /examples/plot_span/screenshot.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:97f873eaec908d24aa91fd941edcd31c635480bc9849898563bd2a3cff89d5ce 3 | size 47585 4 | -------------------------------------------------------------------------------- /examples/save_plot/screenshot.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:e0708739b715706bcc8bf8856505d5b632be7e8a66d7e0caba7830692fbb12d1 3 | size 53720 4 | -------------------------------------------------------------------------------- /examples/stacked_bar/screenshot.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:e8ee5915cdc236bddcc563477913b9e37d801af7c3b01e139e45923dc3fcd210 3 | size 97472 4 | -------------------------------------------------------------------------------- /examples/borrow_points/screenshot.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:e0708739b715706bcc8bf8856505d5b632be7e8a66d7e0caba7830692fbb12d1 3 | size 53720 4 | -------------------------------------------------------------------------------- /examples/box_plot/screenshot_thumb.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:7c661e40bfff8bc2fbe88293005b7d1a5520c60e94892eaef9fa24074e0c4361 3 | size 10228 4 | -------------------------------------------------------------------------------- /examples/custom_axes/screenshot_thumb.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:11a445f07acb173d343221beabc7efb72303980532361cd44e42e56ce1569e7b 3 | size 9735 4 | -------------------------------------------------------------------------------- /examples/filled_area/screenshot_thumb.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:6513df7fc03a4a43821fbcd71f4590b95eef02572b19ec6ea211b9a3da01375b 3 | size 13220 4 | -------------------------------------------------------------------------------- /examples/heatmap/screenshot_thumb.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:56ab037b9f98452304f24ab04af3fa59d8c23fb0fb90a9825fb98af79f3e63d9 3 | size 12148 4 | -------------------------------------------------------------------------------- /examples/histogram/screenshot_thumb.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:1ea8cf5b6e6b7fb398296c05eee521192e0eef3951d300e203267b375ed58920 3 | size 6779 4 | -------------------------------------------------------------------------------- /examples/interaction/screenshot_thumb.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:5a495673db49ad00ab1729c610cb97273177f9d51139c7d5b894f7ec7f7776db 3 | size 7033 4 | -------------------------------------------------------------------------------- /examples/legend_sort/screenshot_thumb.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:6ace5aa2e4a346af3085850f3d1a7da10a88efc1993ad3c3978ff5dc57f8a05d 3 | size 14575 4 | -------------------------------------------------------------------------------- /examples/linked_axes/screenshot_thumb.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:553baef060b04f0e8a6f9bfd9989f0cb2d4a984dcbdc3e6b83a08a37a6ccde06 3 | size 14139 4 | -------------------------------------------------------------------------------- /examples/markers/screenshot_thumb.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:b6db6275161fd32ee8688fa981ac6477f3a4bef13ceae052066604e635daa42f 3 | size 22938 4 | -------------------------------------------------------------------------------- /examples/performance/screenshot_thumb.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:5bd75d96b5afc30f43645001a8dcf7932fea780c4c55f4324e4a3f7838a762c0 3 | size 7963 4 | -------------------------------------------------------------------------------- /examples/plot_span/screenshot_thumb.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:681cce5fb8a2e489163eb18781374d39788b40fc2411a6ba28fd7fd9be669702 3 | size 7476 4 | -------------------------------------------------------------------------------- /examples/save_plot/screenshot_thumb.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:d6dbeacd95cbd2b6fc173ce60bfdc7fc46cffe62936a7ac55e082731e26ae7cf 3 | size 7071 4 | -------------------------------------------------------------------------------- /examples/stacked_bar/screenshot_thumb.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:c99f809af744e9110b51b09649db56df51ab6bba5f0e5668f07bcfa0a1940b11 3 | size 8586 4 | -------------------------------------------------------------------------------- /examples/borrow_points/screenshot_thumb.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:d6dbeacd95cbd2b6fc173ce60bfdc7fc46cffe62936a7ac55e082731e26ae7cf 3 | size 7071 4 | -------------------------------------------------------------------------------- /examples/custom_plot_manipulation/screenshot.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:9f27509a263280af60643e177e8022b0187e80726b7c7d3f9a90f14133be01a8 3 | size 48293 4 | -------------------------------------------------------------------------------- /examples/custom_plot_manipulation/screenshot_thumb.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:049812c200c883049d607a5c136064522f8e1e3c04a3c8082ed0bfd34bddb10b 3 | size 7830 4 | -------------------------------------------------------------------------------- /examples/custom_plot_manipulation/src/main.rs: -------------------------------------------------------------------------------- 1 | use custom_plot_manipulation::CustomPlotManipulationExample; 2 | use examples_utils::make_main; 3 | 4 | make_main!(CustomPlotManipulationExample); 5 | -------------------------------------------------------------------------------- /.typos.toml: -------------------------------------------------------------------------------- 1 | # https://github.com/crate-ci/typos 2 | # install: cargo install typos-cli 3 | # run: typos 4 | 5 | [default.extend-words] 6 | egui = "egui" # Example for how to ignore a false positive 7 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 120 2 | format_code_in_doc_comments = true 3 | wrap_comments = true 4 | reorder_imports = true 5 | group_imports = "StdExternalCrate" 6 | blank_lines_upper_bound = 1 7 | imports_granularity = "Item" 8 | -------------------------------------------------------------------------------- /demo/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Demo Gallery collecting all the plotting examples, hosted at . 2 | //! 3 | //! Each push to `main` re-deploys the demo. 4 | 5 | #![warn(clippy::all, rust_2018_idioms)] 6 | 7 | mod app; 8 | 9 | pub use app::DemoGallery; 10 | -------------------------------------------------------------------------------- /egui_plot/src/overlays/mod.rs: -------------------------------------------------------------------------------- 1 | //! Contains widgets that can be added to a plot at some fixed screen 2 | //! coordinates. 3 | 4 | mod coordinates; 5 | mod legend; 6 | 7 | pub use coordinates::CoordinatesFormatter; 8 | pub use legend::ColorConflictHandling; 9 | pub use legend::Legend; 10 | pub use legend::LegendWidget; 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/other.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Other 3 | about: For issues that are neither bugs or feature requests 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | If you are asking a question, use [the egui_plot discussions forum](https://github.com/emilk/egui_plot/discussions/categories/q-a) instead! 11 | -------------------------------------------------------------------------------- /examples/markers/README.md: -------------------------------------------------------------------------------- 1 | # Marker Demo 2 | 3 | This example demonstrates the different marker shapes available for point plots. It shows all available marker types with customizable fill, radius, and color options. 4 | 5 | ## Running 6 | 7 | Native 8 | ```sh 9 | cargo run -p markers 10 | ``` 11 | 12 | Web (WASM) 13 | ```sh 14 | cd examples/markers 15 | trunk serve 16 | ``` 17 | 18 | ![](screenshot.png) 19 | -------------------------------------------------------------------------------- /examples/histogram/README.md: -------------------------------------------------------------------------------- 1 | # Histogram Demo 2 | 3 | This example demonstrates how to create histograms using bar charts. It displays a normal distribution with customizable orientation, zoom, drag, and scroll controls. 4 | 5 | ## Running 6 | 7 | Native 8 | ```sh 9 | cargo run -p histogram 10 | ``` 11 | 12 | Web (WASM) 13 | ```sh 14 | cd examples/histogram 15 | trunk serve 16 | ``` 17 | 18 | ![](screenshot.png) 19 | -------------------------------------------------------------------------------- /examples/legend/README.md: -------------------------------------------------------------------------------- 1 | # Legend Demo 2 | 3 | This example demonstrates how to customize plot legends. It shows how to configure legend position, text style, and background opacity, with multiple lines displayed in the legend. 4 | 5 | ## Running 6 | 7 | Native 8 | ```sh 9 | cargo run -p legend 10 | ``` 11 | 12 | Web (WASM) 13 | ```sh 14 | cd examples/legend 15 | trunk serve 16 | ``` 17 | 18 | ![](screenshot.png) 19 | -------------------------------------------------------------------------------- /examples/plot_span/README.md: -------------------------------------------------------------------------------- 1 | # Plot Span Demo 2 | 3 | This example demonstrates how to add spans to a plot. Spans are shaded regions that can highlight ranges on either the X or Y axis, with customizable borders, colors, and labels. 4 | 5 | ## Running 6 | 7 | Native 8 | ```sh 9 | cargo run -p plot_span 10 | ``` 11 | 12 | Web (WASM) 13 | ```sh 14 | cd examples/plot_span 15 | trunk serve 16 | ``` 17 | 18 | ![](screenshot.png) 19 | -------------------------------------------------------------------------------- /examples/performance/README.md: -------------------------------------------------------------------------------- 1 | # Performance Demo 2 | 3 | This example demonstrates plotting performance with a large number of markers. Use the controls to adjust the number of markers and observe rendering performance. 4 | 5 | ## Running 6 | 7 | Native 8 | ```sh 9 | cargo run -p performance 10 | ``` 11 | 12 | Web (WASM) 13 | ```sh 14 | cd examples/performance 15 | trunk serve 16 | ``` 17 | 18 | ![](screenshot.png) 19 | 20 | 21 | -------------------------------------------------------------------------------- /examples/stacked_bar/README.md: -------------------------------------------------------------------------------- 1 | # Stacked Bar Demo 2 | 3 | This example demonstrates how to create stacked bar charts. It shows multiple bar chart series stacked on top of each other, with customizable orientation and drag controls. 4 | 5 | ## Running 6 | 7 | Native 8 | ```sh 9 | cargo run -p stacked_bar 10 | ``` 11 | 12 | Web (WASM) 13 | ```sh 14 | cd examples/stacked_bar 15 | trunk serve 16 | ``` 17 | 18 | ![](screenshot.png) 19 | -------------------------------------------------------------------------------- /examples/heatmap/README.md: -------------------------------------------------------------------------------- 1 | # Heatmap Demo 2 | 3 | This example demonstrates how to create animated heatmaps with customizable color palettes, dimensions, and labels. It visualizes a 2D grid of values using color gradients with the Turbo colormap. 4 | 5 | ## Running 6 | 7 | Native 8 | ```sh 9 | cargo run -p heatmap 10 | ``` 11 | 12 | Web (WASM) 13 | ```sh 14 | cd examples/heatmap 15 | trunk serve 16 | ``` 17 | 18 | ![](screenshot.png) 19 | -------------------------------------------------------------------------------- /examples/items/README.md: -------------------------------------------------------------------------------- 1 | # Items Demo 2 | 3 | This example demonstrates the various plot items available in `egui_plot`, including lines, polygons, points, arrows, text, images, and horizontal/vertical lines. It showcases the different visual elements you can add to a plot. 4 | 5 | ## Running 6 | 7 | Native 8 | ```sh 9 | cargo run -p items 10 | ``` 11 | 12 | Web (WASM) 13 | ```sh 14 | cd examples/items 15 | trunk serve 16 | ``` 17 | 18 | ![](screenshot.png) 19 | -------------------------------------------------------------------------------- /examples/lines/README.md: -------------------------------------------------------------------------------- 1 | # Line Demo 2 | 3 | This example demonstrates various line plotting features including animated lines, different line styles (solid, dashed, dotted), gradients, fills, axis inversion, and coordinate display. It shows a comprehensive set of line customization options. 4 | 5 | ## Running 6 | 7 | Native 8 | ```sh 9 | cargo run -p lines 10 | ``` 11 | 12 | Web (WASM) 13 | ```sh 14 | cd examples/lines 15 | trunk serve 16 | ``` 17 | 18 | ![](screenshot.png) 19 | -------------------------------------------------------------------------------- /scripts/accept_snapshots.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This script moves all {name}.new.png files to {name}.png. 3 | # Its main use is in the update_kittest_snapshots CI job, but you can also use it locally. 4 | 5 | set -eu 6 | 7 | # rename the .new.png files to .png 8 | find . -type d -path "./examples/*" | while read dir; do 9 | find "$dir" -type f -name "*.new.png" | while read file; do 10 | mv -f "$file" "${file%.new.png}.png" 11 | done 12 | done 13 | 14 | echo "Done!" 15 | -------------------------------------------------------------------------------- /examples/linked_axes/README.md: -------------------------------------------------------------------------------- 1 | # Linked Axes Example 2 | 3 | This example demonstrates how to link axes and cursors across multiple plots. When you zoom, pan, or move the cursor in one plot, the linked plots will synchronize their view, useful for comparing data across different visualizations. 4 | 5 | ## Running 6 | 7 | Native 8 | ```sh 9 | cargo run -p linked_axes 10 | ``` 11 | 12 | Web (WASM) 13 | ```sh 14 | cd examples/linked_axes 15 | trunk serve 16 | ``` 17 | 18 | ![](screenshot.png) 19 | -------------------------------------------------------------------------------- /examples/box_plot/README.md: -------------------------------------------------------------------------------- 1 | # Box Plot Demo 2 | 3 | This example demonstrates how to create box plots (box-and-whisker plots) with customizable orientation, zoom, and drag controls. It shows multiple box plots with different experiments and days, allowing you to visualize statistical distributions. 4 | 5 | ## Running 6 | 7 | Native 8 | ```sh 9 | cargo run -p box_plot 10 | ``` 11 | 12 | Web (WASM) 13 | ```sh 14 | cd examples/box_plot 15 | trunk serve 16 | ``` 17 | 18 | ![](screenshot.png) 19 | 20 | -------------------------------------------------------------------------------- /examples/interaction/README.md: -------------------------------------------------------------------------------- 1 | # Interaction Demo 2 | 3 | This example demonstrates how to interact with plots programmatically. It shows how to access plot bounds, pointer coordinates, drag deltas, and detect hovered plot items, providing a foundation for building interactive plot applications. 4 | 5 | ## Running 6 | 7 | Native 8 | ```sh 9 | cargo run -p interaction 10 | ``` 11 | 12 | Web (WASM) 13 | ```sh 14 | cd examples/interaction 15 | trunk serve 16 | ``` 17 | 18 | ![](screenshot.png) 19 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | # clipboard api is still unstable, so web-sys requires the below flag to be passed for copy (ctrl + c) to work 2 | # https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html 3 | # check status at https://developer.mozilla.org/en-US/docs/Web/API/Clipboard#browser_compatibility 4 | # we don't use `[build]` because of rust analyzer's build cache invalidation https://github.com/emilk/eframe_template/issues/93 5 | [target.wasm32-unknown-unknown] 6 | rustflags = ["--cfg=web_sys_unstable_apis"] 7 | -------------------------------------------------------------------------------- /examples_utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "examples_utils" 3 | version = "0.1.0" 4 | edition.workspace = true 5 | license.workspace = true 6 | rust-version.workspace = true 7 | publish = false 8 | 9 | [dependencies] 10 | eframe = { workspace = true, default-features = false } 11 | egui = { workspace = true } 12 | egui_kittest = { workspace = true, features = ["snapshot", "eframe"] } 13 | 14 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 15 | egui_kittest = { workspace = true, features = ["wgpu"] } 16 | -------------------------------------------------------------------------------- /examples/custom_axes/README.md: -------------------------------------------------------------------------------- 1 | # Custom Axes Demo 2 | 3 | This example demonstrates how to create custom axes with custom formatters and grid spacers. It shows a logistic function with time-based X-axis formatting (days, hours, minutes) and percentage-based Y-axis formatting, demonstrating how to create domain-specific axis labels. 4 | 5 | ## Running 6 | 7 | Native 8 | ```sh 9 | cargo run -p custom_axes 10 | ``` 11 | 12 | Web (WASM) 13 | ```sh 14 | cd examples/custom_axes 15 | trunk serve 16 | ``` 17 | 18 | ![](screenshot.png) 19 | -------------------------------------------------------------------------------- /examples/legend_sort/README.md: -------------------------------------------------------------------------------- 1 | # Legend Sort 2 | 3 | This example demonstrates how to control the sorting order of legend entries. It shows how to use `follow_insertion_order()` to display legend entries in the order they were added to the plot, rather than alphabetically, which is useful for maintaining a specific visual hierarchy in the legend. 4 | 5 | ## Running 6 | 7 | Native 8 | ```sh 9 | cargo run -p legend_sort 10 | ``` 11 | 12 | Web (WASM) 13 | ```sh 14 | cd examples/legend_sort 15 | trunk serve 16 | ``` 17 | 18 | ![](screenshot.png) 19 | 20 | -------------------------------------------------------------------------------- /.github/workflows/cargo_shear.yml: -------------------------------------------------------------------------------- 1 | name: Cargo Shear 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | pull_request: 8 | types: [opened, synchronize] 9 | 10 | jobs: 11 | cargo-shear: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | 17 | - name: Install Cargo Shear 18 | uses: taiki-e/install-action@v2.48.7 19 | with: 20 | tool: cargo-shear@1.1.11 21 | 22 | - name: Run Cargo Shear 23 | run: | 24 | cargo shear 25 | -------------------------------------------------------------------------------- /examples/save_plot/README.md: -------------------------------------------------------------------------------- 1 | # Save Plot 2 | 3 | This example demonstrates how to save a plot as a PNG image file. It shows how to capture a screenshot of the plot using egui's screenshot functionality, extract the plot region, and save it to disk using the image crate. This is useful for exporting visualizations or generating plot images programmatically. 4 | 5 | ## Running 6 | 7 | Native 8 | ```sh 9 | cargo run -p save_plot 10 | ``` 11 | 12 | Web (WASM) 13 | ```sh 14 | cd examples/save_plot 15 | trunk serve 16 | ``` 17 | 18 | ![](screenshot.png) 19 | 20 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | # If you see this, run "rustup self update" to get rustup 1.23 or newer. 2 | 3 | # NOTE: above comment is for older `rustup` (before TOML support was added), 4 | # which will treat the first line as the toolchain name, and therefore show it 5 | # to the user in the error, instead of "error: invalid channel name '[toolchain]'". 6 | 7 | [toolchain] 8 | channel = "1.88" # Avoid specifying a patch version here; see https://github.com/emilk/eframe_template/issues/145 9 | components = ["rustfmt", "clippy"] 10 | targets = ["wasm32-unknown-unknown"] 11 | -------------------------------------------------------------------------------- /.github/workflows/typos.yml: -------------------------------------------------------------------------------- 1 | # Copied from https://github.com/rerun-io/rerun_template 2 | 3 | # https://github.com/crate-ci/typos 4 | # Add exceptions to `.typos.toml` 5 | # install and run locally: cargo install typos-cli && typos 6 | 7 | name: Spell Check 8 | on: [pull_request] 9 | 10 | jobs: 11 | run: 12 | name: Spell Check 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout Actions Repository 16 | uses: actions/checkout@v4 17 | 18 | - name: Check spelling of entire workspace 19 | uses: crate-ci/typos@master 20 | -------------------------------------------------------------------------------- /examples/borrow_points/README.md: -------------------------------------------------------------------------------- 1 | # Borrow Points 2 | 3 | This example demonstrates how to borrow points instead of cloning them when creating plot lines. It shows how to use `PlotPoints::Borrowed` to avoid unnecessary allocations, which is useful for performance-critical applications or when you want to reuse the same data across multiple frames without copying. 4 | 5 | ## Running 6 | 7 | Native 8 | ```sh 9 | cargo run -p borrow_points 10 | ``` 11 | 12 | Web (WASM) 13 | ```sh 14 | cd examples/borrow_points 15 | trunk serve 16 | ``` 17 | 18 | ![](screenshot.png) 19 | 20 | -------------------------------------------------------------------------------- /.github/workflows/kitdiff_comment.yml: -------------------------------------------------------------------------------- 1 | name: preview_comment.yml 2 | on: 3 | pull_request_target: 4 | types: [ opened ] 5 | 6 | permissions: 7 | pull-requests: write 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | timeout-minutes: 60 13 | steps: 14 | - name: Comment PR 15 | uses: thollander/actions-comment-pull-request@v2 16 | with: 17 | message: | 18 | View snapshot changes at [kitdiff](https://rerun-io.github.io/kitdiff/?url=${{ github.event.pull_request.html_url }}) 19 | comment_tag: 'kitdiff' 20 | 21 | -------------------------------------------------------------------------------- /examples/items/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "items" 3 | version = "0.1.0" 4 | license.workspace = true 5 | edition.workspace = true 6 | rust-version.workspace = true 7 | publish = false 8 | 9 | [lints] 10 | workspace = true 11 | 12 | [dependencies] 13 | eframe = { workspace = true, features = ["default"] } 14 | egui_plot.workspace = true 15 | env_logger = { workspace = true, default-features = false, features = [ 16 | "auto-color", 17 | "humantime", 18 | ] } 19 | examples_utils.workspace = true 20 | 21 | [package.metadata.cargo-shear] 22 | ignored = ["env_logger"] # used by make_main! macro 23 | -------------------------------------------------------------------------------- /examples/histogram/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "histogram" 3 | version = "0.1.0" 4 | license.workspace = true 5 | edition.workspace = true 6 | rust-version.workspace = true 7 | publish = false 8 | 9 | [lints] 10 | workspace = true 11 | 12 | [dependencies] 13 | eframe = { workspace = true, features = ["default"] } 14 | egui_plot.workspace = true 15 | env_logger = { workspace = true, default-features = false, features = [ 16 | "auto-color", 17 | "humantime", 18 | ] } 19 | examples_utils.workspace = true 20 | 21 | [package.metadata.cargo-shear] 22 | ignored = ["env_logger"] # used by make_main! macro 23 | -------------------------------------------------------------------------------- /examples/custom_axes/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "custom_axes" 3 | version = "0.1.0" 4 | license.workspace = true 5 | edition.workspace = true 6 | rust-version.workspace = true 7 | publish = false 8 | 9 | [lints] 10 | workspace = true 11 | 12 | [dependencies] 13 | eframe = { workspace = true, features = ["default"] } 14 | egui_plot.workspace = true 15 | env_logger = { workspace = true, default-features = false, features = [ 16 | "auto-color", 17 | "humantime", 18 | ] } 19 | examples_utils.workspace = true 20 | 21 | [package.metadata.cargo-shear] 22 | ignored = ["env_logger"] # used by make_main! macro 23 | -------------------------------------------------------------------------------- /examples/stacked_bar/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stacked_bar" 3 | version = "0.1.0" 4 | license.workspace = true 5 | edition.workspace = true 6 | rust-version.workspace = true 7 | publish = false 8 | 9 | [lints] 10 | workspace = true 11 | 12 | [dependencies] 13 | eframe = { workspace = true, features = ["default"] } 14 | egui_plot.workspace = true 15 | env_logger = { workspace = true, default-features = false, features = [ 16 | "auto-color", 17 | "humantime", 18 | ] } 19 | examples_utils.workspace = true 20 | 21 | [package.metadata.cargo-shear] 22 | ignored = ["env_logger"] # used by make_main! macro 23 | -------------------------------------------------------------------------------- /examples/heatmap/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "heatmap" 3 | version = "0.1.0" 4 | license.workspace = true 5 | edition.workspace = true 6 | rust-version.workspace = true 7 | publish = false 8 | 9 | [lints] 10 | workspace = true 11 | 12 | [dependencies] 13 | eframe = { workspace = true, features = ["default"] } 14 | egui_plot.workspace = true 15 | env_logger = { version = "0.11.8", default-features = false, features = [ 16 | "auto-color", 17 | "humantime", 18 | ] } 19 | examples_utils.workspace = true 20 | 21 | [package.metadata.cargo-shear] 22 | ignored = [ 23 | "env_logger", 24 | ] # used by make_main! macro 25 | -------------------------------------------------------------------------------- /examples/plot_span/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "plot_span" 3 | version = "0.1.0" 4 | license.workspace = true 5 | edition.workspace = true 6 | rust-version.workspace = true 7 | publish = false 8 | 9 | [lints] 10 | workspace = true 11 | 12 | [dependencies] 13 | eframe = { workspace = true, features = ["default"] } 14 | egui_plot.workspace = true 15 | env_logger = { version = "0.11.8", default-features = false, features = [ 16 | "auto-color", 17 | "humantime", 18 | ] } 19 | examples_utils.workspace = true 20 | 21 | [package.metadata.cargo-shear] 22 | ignored = [ 23 | "env_logger", 24 | ] # used by make_main! macro 25 | -------------------------------------------------------------------------------- /examples/filled_area/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "filled_area" 3 | version = "0.1.0" 4 | license.workspace = true 5 | edition.workspace = true 6 | rust-version.workspace = true 7 | publish = false 8 | 9 | [lints] 10 | workspace = true 11 | 12 | [dependencies] 13 | eframe = { workspace = true, features = ["default"] } 14 | egui_plot.workspace = true 15 | env_logger = { version = "0.11.8", default-features = false, features = [ 16 | "auto-color", 17 | "humantime", 18 | ] } 19 | examples_utils.workspace = true 20 | 21 | [package.metadata.cargo-shear] 22 | ignored = [ 23 | "env_logger", 24 | ] # used by make_main! macro 25 | -------------------------------------------------------------------------------- /examples/lines/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lines" 3 | version = "0.1.0" 4 | authors = ["Nicolas "] 5 | license.workspace = true 6 | edition.workspace = true 7 | rust-version.workspace = true 8 | publish = false 9 | 10 | [lints] 11 | workspace = true 12 | 13 | [dependencies] 14 | eframe = { workspace = true, features = ["default"] } 15 | egui_plot.workspace = true 16 | env_logger = { workspace = true, default-features = false, features = [ 17 | "auto-color", 18 | "humantime", 19 | ] } 20 | examples_utils.workspace = true 21 | 22 | [package.metadata.cargo-shear] 23 | ignored = ["env_logger"] # used by make_main! macro 24 | -------------------------------------------------------------------------------- /examples/legend/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "legend" 3 | version = "0.1.0" 4 | authors = ["Nicolas "] 5 | license.workspace = true 6 | edition.workspace = true 7 | rust-version.workspace = true 8 | publish = false 9 | 10 | [lints] 11 | workspace = true 12 | 13 | [dependencies] 14 | eframe = { workspace = true, features = ["default"] } 15 | egui_plot.workspace = true 16 | env_logger = { workspace = true, default-features = false, features = [ 17 | "auto-color", 18 | "humantime", 19 | ] } 20 | examples_utils.workspace = true 21 | 22 | [package.metadata.cargo-shear] 23 | ignored = ["env_logger"] # used by make_main! macro 24 | -------------------------------------------------------------------------------- /examples/markers/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "markers" 3 | version = "0.1.0" 4 | authors = ["Nicolas "] 5 | license.workspace = true 6 | edition.workspace = true 7 | rust-version.workspace = true 8 | publish = false 9 | 10 | [lints] 11 | workspace = true 12 | 13 | [dependencies] 14 | eframe = { workspace = true, features = ["default"] } 15 | egui_plot.workspace = true 16 | env_logger = { workspace = true, default-features = false, features = [ 17 | "auto-color", 18 | "humantime", 19 | ] } 20 | examples_utils.workspace = true 21 | 22 | [package.metadata.cargo-shear] 23 | ignored = ["env_logger"] # used by make_main! macro 24 | -------------------------------------------------------------------------------- /examples/borrow_points/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "borrow_points" 3 | version = "0.1.0" 4 | authors = ["Nicolas "] 5 | license.workspace = true 6 | edition.workspace = true 7 | rust-version.workspace = true 8 | publish = false 9 | 10 | [lints] 11 | workspace = true 12 | 13 | [dependencies] 14 | eframe = { workspace = true, features = ["default"] } 15 | egui_plot.workspace = true 16 | env_logger = { workspace = true, default-features = false, features = [ 17 | "auto-color", 18 | "humantime", 19 | ] } 20 | examples_utils.workspace = true 21 | 22 | [package.metadata.cargo-shear] 23 | ignored = ["env_logger"] # used by make_main! macro 24 | -------------------------------------------------------------------------------- /examples/interaction/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "interaction" 3 | version = "0.1.0" 4 | authors = ["Nicolas "] 5 | license.workspace = true 6 | edition.workspace = true 7 | rust-version.workspace = true 8 | publish = false 9 | 10 | [lints] 11 | workspace = true 12 | 13 | [dependencies] 14 | eframe = { workspace = true, features = ["default"] } 15 | egui_plot.workspace = true 16 | env_logger = { workspace = true, default-features = false, features = [ 17 | "auto-color", 18 | "humantime", 19 | ] } 20 | examples_utils.workspace = true 21 | 22 | [package.metadata.cargo-shear] 23 | ignored = ["env_logger"] # used by make_main! macro 24 | -------------------------------------------------------------------------------- /examples/legend_sort/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "legend_sort" 3 | version = "0.1.0" 4 | authors = ["Nicolas "] 5 | license.workspace = true 6 | edition.workspace = true 7 | rust-version.workspace = true 8 | publish = false 9 | 10 | [lints] 11 | workspace = true 12 | 13 | [dependencies] 14 | eframe = { workspace = true, features = ["default"] } 15 | egui_plot.workspace = true 16 | env_logger = { workspace = true, default-features = false, features = [ 17 | "auto-color", 18 | "humantime", 19 | ] } 20 | examples_utils.workspace = true 21 | 22 | [package.metadata.cargo-shear] 23 | ignored = ["env_logger"] # used by make_main! macro 24 | -------------------------------------------------------------------------------- /examples/linked_axes/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "linked_axes" 3 | version = "0.1.0" 4 | authors = ["Nicolas "] 5 | license.workspace = true 6 | edition.workspace = true 7 | rust-version.workspace = true 8 | publish = false 9 | 10 | [lints] 11 | workspace = true 12 | 13 | [dependencies] 14 | eframe = { workspace = true, features = ["default"] } 15 | egui_plot.workspace = true 16 | env_logger = { workspace = true, default-features = false, features = [ 17 | "auto-color", 18 | "humantime", 19 | ] } 20 | examples_utils.workspace = true 21 | 22 | [package.metadata.cargo-shear] 23 | ignored = ["env_logger"] # used by make_main! macro 24 | -------------------------------------------------------------------------------- /examples/performance/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "performance" 3 | version = "0.1.0" 4 | authors = ["egui_plot contributors"] 5 | license.workspace = true 6 | edition.workspace = true 7 | rust-version.workspace = true 8 | publish = false 9 | 10 | [lints] 11 | workspace = true 12 | 13 | [dependencies] 14 | eframe = { workspace = true, features = ["default"] } 15 | egui_plot.workspace = true 16 | env_logger = { workspace = true, default-features = false, features = [ 17 | "auto-color", 18 | "humantime", 19 | ] } 20 | examples_utils.workspace = true 21 | 22 | [package.metadata.cargo-shear] 23 | ignored = ["env_logger"] # env_logger used by make_main! macro 24 | -------------------------------------------------------------------------------- /examples/custom_plot_manipulation/README.md: -------------------------------------------------------------------------------- 1 | # Custom Plot Manipulation 2 | 3 | This example demonstrates how to implement custom plot manipulation controls using raw input events. It shows how to create alternative pan and zoom behaviors, such as inverting the default Ctrl key behavior, customizing zoom and scroll speeds, and locking axes. This is useful for building specialized interaction patterns that differ from the default `egui_plot` controls. 4 | 5 | ## Running 6 | 7 | Native 8 | ```sh 9 | cargo run -p custom_plot_manipulation 10 | ``` 11 | 12 | Web (WASM) 13 | ```sh 14 | cd examples/custom_plot_manipulation 15 | trunk serve 16 | ``` 17 | 18 | ![](screenshot.png) 19 | 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac stuff: 2 | .DS_Store 3 | 4 | # trunk output folder 5 | demo/dist 6 | 7 | # Rust compile target directories: 8 | target 9 | target_ra 10 | target_wasm 11 | 12 | # https://github.com/lycheeverse/lychee 13 | .lycheecache 14 | 15 | 16 | # kittest 17 | **/tests/snapshots/**/*.diff.png 18 | **/tests/snapshots/**/*.new.png 19 | **/tests/snapshots/**/*.old.png 20 | 21 | # trunk output folder 22 | dist 23 | 24 | # Rust compile target directories: 25 | target 26 | target_ra 27 | target_wasm 28 | 29 | # RecordMyDesktop output folder 30 | videos 31 | 32 | # https://github.com/lycheeverse/lychee 33 | .lycheecache 34 | 35 | TODO 36 | 37 | .qodo 38 | .cursor 39 | .idea 40 | 41 | -------------------------------------------------------------------------------- /demo/assets/sw.js: -------------------------------------------------------------------------------- 1 | var cacheName = 'egui-template-pwa'; 2 | var filesToCache = [ 3 | './', 4 | './index.html', 5 | './demo.js', 6 | './demo_bg.wasm', 7 | ]; 8 | 9 | /* Start the service worker and cache all of the app's content */ 10 | self.addEventListener('install', function (e) { 11 | e.waitUntil( 12 | caches.open(cacheName).then(function (cache) { 13 | return cache.addAll(filesToCache); 14 | }) 15 | ); 16 | }); 17 | 18 | /* Serve cached content when offline */ 19 | self.addEventListener('fetch', function (e) { 20 | e.respondWith( 21 | caches.match(e.request).then(function (response) { 22 | return response || fetch(e.request); 23 | }) 24 | ); 25 | }); 26 | -------------------------------------------------------------------------------- /examples/box_plot/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "box_plot" 3 | version = "0.1.0" 4 | license.workspace = true 5 | edition.workspace = true 6 | rust-version.workspace = true 7 | publish = false 8 | 9 | [lints] 10 | workspace = true 11 | 12 | [dependencies] 13 | eframe = { workspace = true, features = ["default"] } 14 | egui_plot.workspace = true 15 | env_logger = { version = "0.11.8", default-features = false, features = [ 16 | "auto-color", 17 | "humantime", 18 | ] } 19 | examples_utils.workspace = true 20 | 21 | [package.metadata.cargo-shear] 22 | ignored = [ 23 | "env_logger", 24 | ] # used by make_main! macroenv_logger = { version = "0.11.6", default-features = false, features = [ 25 | -------------------------------------------------------------------------------- /examples/custom_plot_manipulation/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "custom_plot_manipulation" 3 | version = "0.1.0" 4 | authors = ["Ygor Souza "] 5 | license.workspace = true 6 | edition.workspace = true 7 | rust-version.workspace = true 8 | publish = false 9 | 10 | [lints] 11 | workspace = true 12 | 13 | [dependencies] 14 | eframe = { workspace = true, features = ["default"] } 15 | egui_plot.workspace = true 16 | env_logger = { workspace = true, default-features = false, features = [ 17 | "auto-color", 18 | "humantime", 19 | ] } 20 | examples_utils.workspace = true 21 | 22 | [package.metadata.cargo-shear] 23 | ignored = ["env_logger"] # used by make_main! macro 24 | -------------------------------------------------------------------------------- /examples/filled_area/README.md: -------------------------------------------------------------------------------- 1 | # Filled Area Example 2 | 3 | This example demonstrates the `FilledArea` plot item which fills the area between two lines. 4 | 5 | ## Features 6 | 7 | - Plots a sine wave with an adjustable confidence band 8 | - Interactive controls to adjust upper and lower bounds 9 | - Shows how to visualize uncertainty and ranges 10 | 11 | ## Usage 12 | 13 | The example shows `sin(x)` with adjustable bounds: 14 | - **delta lower**: offset for the lower boundary (`sin(x) - delta_lower`) 15 | - **delta upper**: offset for the upper boundary (`sin(x) + delta_upper`) 16 | - **points**: number of sampling points 17 | 18 | ## Running 19 | 20 | ```bash 21 | cargo run -p filled_area 22 | ``` 23 | -------------------------------------------------------------------------------- /demo/assets/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "egui Template PWA", 3 | "short_name": "egui-template-pwa", 4 | "icons": [ 5 | { 6 | "src": "./icon-256.png", 7 | "sizes": "256x256", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "./maskable_icon_x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png", 14 | "purpose": "any maskable" 15 | }, 16 | { 17 | "src": "./icon-1024.png", 18 | "sizes": "1024x1024", 19 | "type": "image/png" 20 | } 21 | ], 22 | "lang": "en-US", 23 | "id": "/index.html", 24 | "start_url": "./index.html", 25 | "display": "standalone", 26 | "background_color": "white", 27 | "theme_color": "white" 28 | } 29 | -------------------------------------------------------------------------------- /examples/save_plot/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "save_plot" 3 | version = "0.1.0" 4 | authors = ["hacknus "] 5 | license.workspace = true 6 | edition.workspace = true 7 | rust-version.workspace = true 8 | publish = false 9 | 10 | [lints] 11 | workspace = true 12 | 13 | [dependencies] 14 | eframe = { workspace = true, features = ["default"] } 15 | egui_plot.workspace = true 16 | env_logger = { workspace = true, default-features = false, features = [ 17 | "auto-color", 18 | "humantime", 19 | ] } 20 | examples_utils.workspace = true 21 | 22 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 23 | image = { workspace = true, features = ["png"] } 24 | rfd = "0.16" 25 | 26 | [package.metadata.cargo-shear] 27 | ignored = ["env_logger"] # used by make_main! macro 28 | -------------------------------------------------------------------------------- /.github/lint_newlines.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # List all tracked files 4 | FILES=$(git ls-files) 5 | 6 | EXIT_CODE=0 7 | 8 | for file in $FILES; do 9 | # Skip .svg files 10 | if [[ "$file" == *.svg ]]; then 11 | continue 12 | fi 13 | 14 | # Skip binary files 15 | if file "$file" | grep -qv 'text'; then 16 | continue 17 | fi 18 | 19 | # Skip empty files 20 | if [ ! -s "$file" ]; then 21 | continue 22 | fi 23 | 24 | # Skip special files 25 | if [[ "$file" == *"Cargo.recipe.json" ]]; then 26 | continue 27 | fi 28 | 29 | # Check if the last byte is a newline 30 | if [ "$(tail -c1 "$file")" != "" ]; then 31 | echo "Missing newline at end of file: $file" 32 | EXIT_CODE=1 33 | fi 34 | done 35 | 36 | exit $EXIT_CODE 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: feature-request 6 | assignees: '' 7 | 8 | --- 9 | 10 | 13 | 14 | 15 | **Is your feature request related to a problem? Please describe.** 16 | 17 | 18 | **Describe the solution you'd like** 19 | 20 | 21 | **Describe alternatives you've considered** 22 | 23 | 24 | **Additional context** 25 | 26 | -------------------------------------------------------------------------------- /egui_plot/src/cursor.rs: -------------------------------------------------------------------------------- 1 | use ahash::HashMap; 2 | use egui::Id; 3 | 4 | /// Indicates a vertical or horizontal cursor line in plot coordinates. 5 | #[derive(Copy, Clone, PartialEq)] 6 | pub enum Cursor { 7 | /// Horizontal cursor line at the given y-coordinate. 8 | Horizontal { 9 | /// Y-coordinate of the horizontal cursor line. 10 | y: f64, 11 | }, 12 | 13 | /// Vertical cursor line at the given x-coordinate. 14 | Vertical { 15 | /// X-coordinate of the vertical cursor line. 16 | x: f64, 17 | }, 18 | } 19 | 20 | /// Contains the cursors drawn for a plot widget in a single frame. 21 | #[derive(PartialEq, Clone)] 22 | pub(crate) struct PlotFrameCursors { 23 | pub(crate) id: Id, 24 | pub(crate) cursors: Vec, 25 | } 26 | 27 | #[derive(Default, Clone)] 28 | pub(crate) struct CursorLinkGroups(pub(crate) HashMap>); 29 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 15 | 16 | * Closes 17 | * [ ] I have followed the instructions in the PR template 18 | -------------------------------------------------------------------------------- /.github/workflows/labels.yml: -------------------------------------------------------------------------------- 1 | # Copied from https://github.com/rerun-io/rerun_template 2 | 3 | # https://github.com/marketplace/actions/require-labels 4 | # Check for existence of labels 5 | # See all our labels at https://github.com/rerun-io/rerun/issues/labels 6 | 7 | name: PR Labels 8 | 9 | on: 10 | pull_request: 11 | types: 12 | - opened 13 | - synchronize 14 | - reopened 15 | - labeled 16 | - unlabeled 17 | 18 | jobs: 19 | label: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Check for a "do-not-merge" label 23 | uses: mheap/github-action-required-labels@v3 24 | with: 25 | mode: exactly 26 | count: 0 27 | labels: "do-not-merge" 28 | 29 | - name: Require label "include in changelog" or "exclude from changelog" 30 | uses: mheap/github-action-required-labels@v3 31 | with: 32 | mode: minimum 33 | count: 1 34 | labels: "exclude from changelog, include in changelog" 35 | -------------------------------------------------------------------------------- /.github/workflows/links.yml: -------------------------------------------------------------------------------- 1 | # Copied from https://github.com/rerun-io/rerun_template 2 | on: 3 | push: 4 | branches: 5 | - "main" 6 | pull_request: 7 | types: [ opened, synchronize ] 8 | 9 | name: Link checker 10 | 11 | jobs: 12 | link-checker: 13 | name: Check links 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Restore lychee cache 19 | uses: actions/cache@v4 20 | with: 21 | path: .lycheecache 22 | key: cache-lychee-${{ github.sha }} 23 | restore-keys: cache-lychee- 24 | 25 | # Check https://github.com/lycheeverse/lychee on how to run locally. 26 | - name: Link Checker 27 | id: lychee 28 | uses: lycheeverse/lychee-action@v2 29 | with: 30 | fail: true 31 | # When given a directory, lychee checks only markdown, html and text files, everything else we have to glob in manually. 32 | args: | 33 | --cache --max-cache-age 1d . "**/*.rs" "**/*.toml" "**/*.hpp" "**/*.cpp" "**/CMakeLists.txt" "**/*.py" "**/*.yml" 34 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | 18 | 19 | **Describe the bug** 20 | 21 | 22 | **To Reproduce** 23 | Steps to reproduce the behavior: 24 | 1. 25 | 2. 26 | 3. 27 | 4. 28 | 29 | **Expected behavior** 30 | 31 | 32 | **Screenshots** 33 | 34 | 35 | **Additional context** 36 | 37 | -------------------------------------------------------------------------------- /egui_plot/src/math.rs: -------------------------------------------------------------------------------- 1 | use emath::Float as _; 2 | use emath::Pos2; 3 | 4 | use crate::axis::PlotTransform; 5 | use crate::items::ClosestElem; 6 | use crate::rect_elem::RectElement; 7 | 8 | /// Returns the x-coordinate of a possible intersection between a line segment 9 | /// from `p1` to `p2` and a horizontal line at the given y-coordinate. 10 | pub fn y_intersection(p1: &Pos2, p2: &Pos2, y: f32) -> Option { 11 | ((p1.y > y && p2.y < y) || (p1.y < y && p2.y > y)) 12 | .then_some(((y * (p1.x - p2.x)) - (p1.x * p2.y - p1.y * p2.x)) / (p1.y - p2.y)) 13 | } 14 | 15 | pub fn find_closest_rect<'a, T>( 16 | rects: impl IntoIterator, 17 | point: Pos2, 18 | transform: &PlotTransform, 19 | ) -> Option 20 | where 21 | T: 'a + RectElement, 22 | { 23 | rects 24 | .into_iter() 25 | .enumerate() 26 | .map(|(index, bar)| { 27 | let bar_rect = transform.rect_from_values(&bar.bounds_min(), &bar.bounds_max()); 28 | let dist_sq = bar_rect.distance_sq_to_pos(point); 29 | 30 | ClosestElem { index, dist_sq } 31 | }) 32 | .min_by_key(|e| e.dist_sq.ord()) 33 | } 34 | -------------------------------------------------------------------------------- /examples/plot_span/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | 3 | use eframe::egui; 4 | use examples_utils::PlotExample; 5 | 6 | mod app; 7 | pub use app::PlotSpanDemo; 8 | 9 | impl PlotExample for PlotSpanDemo { 10 | fn name(&self) -> &'static str { 11 | "plot_span" 12 | } 13 | 14 | fn title(&self) -> &'static str { 15 | "Plot Span Demo" 16 | } 17 | 18 | fn description(&self) -> &'static str { 19 | "This example demonstrates how to add spans to a plot. Spans are shaded regions that can highlight ranges on either the X or Y axis." 20 | } 21 | 22 | fn tags(&self) -> &'static [&'static str] { 23 | &["span", "annotation"] 24 | } 25 | 26 | fn thumbnail_bytes(&self) -> &'static [u8] { 27 | include_bytes!("../screenshot_thumb.png") 28 | } 29 | 30 | fn code_bytes(&self) -> &'static [u8] { 31 | include_bytes!("./app.rs") 32 | } 33 | 34 | fn show_ui(&mut self, ui: &mut egui::Ui) -> egui::Response { 35 | self.show_plot(ui) 36 | } 37 | 38 | fn show_controls(&mut self, ui: &mut egui::Ui) -> egui::Response { 39 | self.show_controls(ui) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/borrow_points/src/app.rs: -------------------------------------------------------------------------------- 1 | use eframe::egui; 2 | use eframe::egui::Response; 3 | use egui_plot::Legend; 4 | use egui_plot::Line; 5 | use egui_plot::Plot; 6 | use egui_plot::PlotPoint; 7 | use egui_plot::PlotPoints; 8 | 9 | pub struct BorrowPointsExample { 10 | points: Vec, 11 | } 12 | 13 | impl Default for BorrowPointsExample { 14 | fn default() -> Self { 15 | let points: Vec<[f64; 2]> = vec![[0.0, 1.0], [2.0, 3.0], [3.0, 2.0]]; 16 | let points = points.iter().map(|p| PlotPoint::new(p[0], p[1])).collect(); 17 | Self { points } 18 | } 19 | } 20 | 21 | impl BorrowPointsExample { 22 | pub fn show_plot(&self, ui: &mut egui::Ui) -> Response { 23 | Plot::new("My Plot") 24 | .legend(Legend::default()) 25 | .show(ui, |plot_ui| { 26 | plot_ui.line(Line::new("curve", PlotPoints::Borrowed(&self.points)).name("curve")); 27 | }) 28 | .response 29 | } 30 | 31 | #[expect(clippy::unused_self, reason = "required by the example template")] 32 | pub fn show_controls(&self, ui: &mut egui::Ui) -> Response { 33 | ui.scope(|_ui| {}).response 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/markers/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | 3 | use eframe::egui; 4 | use examples_utils::PlotExample; 5 | 6 | mod app; 7 | pub use app::MarkerDemo; 8 | 9 | impl PlotExample for MarkerDemo { 10 | fn name(&self) -> &'static str { 11 | "markers" 12 | } 13 | 14 | fn title(&self) -> &'static str { 15 | "Marker Demo" 16 | } 17 | 18 | fn description(&self) -> &'static str { 19 | "This example demonstrates the different marker shapes available for point plots. It shows all available marker types with customizable fill, radius, and color options." 20 | } 21 | 22 | fn tags(&self) -> &'static [&'static str] { 23 | &["markers", "points"] 24 | } 25 | 26 | fn thumbnail_bytes(&self) -> &'static [u8] { 27 | include_bytes!("../screenshot_thumb.png") 28 | } 29 | 30 | fn code_bytes(&self) -> &'static [u8] { 31 | include_bytes!("./app.rs") 32 | } 33 | 34 | fn show_ui(&mut self, ui: &mut egui::Ui) -> egui::Response { 35 | self.show_plot(ui) 36 | } 37 | 38 | fn show_controls(&mut self, ui: &mut egui::Ui) -> egui::Response { 39 | self.show_controls(ui) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/legend/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | 3 | use eframe::egui; 4 | use examples_utils::PlotExample; 5 | 6 | mod app; 7 | pub use app::LegendExample; 8 | 9 | impl PlotExample for LegendExample { 10 | fn name(&self) -> &'static str { 11 | "legend" 12 | } 13 | 14 | fn title(&self) -> &'static str { 15 | "Legend Demo" 16 | } 17 | 18 | fn description(&self) -> &'static str { 19 | "This example demonstrates how to customize plot legends. It shows how to configure legend position, text style, and background opacity, with multiple lines displayed in the legend." 20 | } 21 | 22 | fn tags(&self) -> &'static [&'static str] { 23 | &["legend"] 24 | } 25 | 26 | fn thumbnail_bytes(&self) -> &'static [u8] { 27 | include_bytes!("../screenshot_thumb.png") 28 | } 29 | 30 | fn code_bytes(&self) -> &'static [u8] { 31 | include_bytes!("./app.rs") 32 | } 33 | 34 | fn show_ui(&mut self, ui: &mut egui::Ui) -> egui::Response { 35 | self.show_plot(ui) 36 | } 37 | 38 | fn show_controls(&mut self, ui: &mut egui::Ui) -> egui::Response { 39 | self.show_controls(ui) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/stacked_bar/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | 3 | use eframe::egui; 4 | use examples_utils::PlotExample; 5 | 6 | mod app; 7 | pub use app::StackedBarExample; 8 | 9 | impl PlotExample for StackedBarExample { 10 | fn name(&self) -> &'static str { 11 | "stacked_bar" 12 | } 13 | 14 | fn title(&self) -> &'static str { 15 | "Stacked Bar Demo" 16 | } 17 | 18 | fn description(&self) -> &'static str { 19 | "This example demonstrates how to create stacked bar charts. It shows multiple bar chart series stacked on top of each other, with customizable orientation." 20 | } 21 | 22 | fn tags(&self) -> &'static [&'static str] { 23 | &["bar_chart"] 24 | } 25 | 26 | fn thumbnail_bytes(&self) -> &'static [u8] { 27 | include_bytes!("../screenshot_thumb.png") 28 | } 29 | 30 | fn code_bytes(&self) -> &'static [u8] { 31 | include_bytes!("./app.rs") 32 | } 33 | 34 | fn show_ui(&mut self, ui: &mut egui::Ui) -> egui::Response { 35 | self.show_plot(ui) 36 | } 37 | 38 | fn show_controls(&mut self, ui: &mut egui::Ui) -> egui::Response { 39 | self.show_controls(ui) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /scripts/check.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # This scripts runs various CI-like checks in a convenient way. 3 | 4 | set -eu 5 | script_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) 6 | cd "$script_path/.." 7 | set -x 8 | 9 | # Checks all tests, lints etc. 10 | # Basically does what the CI does. 11 | 12 | cargo +1.88 install --quiet typos-cli 13 | 14 | export RUSTFLAGS="-D warnings" 15 | export RUSTDOCFLAGS="-D warnings" # https://github.com/emilk/egui/pull/1454 16 | 17 | # Fast checks first: 18 | typos 19 | ./scripts/lint.py 20 | cargo fmt --all -- --check 21 | cargo deny check 22 | cargo doc --quiet --lib --no-deps --all-features 23 | cargo doc --quiet --document-private-items --no-deps --all-features 24 | 25 | cargo clippy --quiet --all-targets --all-features -- -D warnings 26 | 27 | CLIPPY_CONF_DIR="scripts/clippy_wasm" cargo clippy --quiet --target wasm32-unknown-unknown --lib -- -D warnings 28 | 29 | cargo check --quiet --all-targets 30 | cargo check --quiet --all-targets --no-default-features 31 | cargo check --quiet --all-targets --all-features 32 | cargo test --quiet --all-targets --all-features 33 | cargo test --quiet --doc # slow - checks all doc-tests 34 | 35 | echo "All checks passed." 36 | -------------------------------------------------------------------------------- /examples/histogram/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | 3 | use eframe::egui; 4 | use examples_utils::PlotExample; 5 | 6 | mod app; 7 | pub use app::HistogramExample; 8 | 9 | impl PlotExample for HistogramExample { 10 | fn name(&self) -> &'static str { 11 | "histogram" 12 | } 13 | 14 | fn title(&self) -> &'static str { 15 | "Histogram Demo" 16 | } 17 | 18 | fn description(&self) -> &'static str { 19 | "This example demonstrates how to create histograms using bar charts. It displays a normal distribution with customizable orientation, zoom, drag, and scroll controls." 20 | } 21 | 22 | fn tags(&self) -> &'static [&'static str] { 23 | &["histogram", "bar_chart"] 24 | } 25 | 26 | fn thumbnail_bytes(&self) -> &'static [u8] { 27 | include_bytes!("../screenshot_thumb.png") 28 | } 29 | 30 | fn code_bytes(&self) -> &'static [u8] { 31 | include_bytes!("./app.rs") 32 | } 33 | 34 | fn show_ui(&mut self, ui: &mut egui::Ui) -> egui::Response { 35 | self.show_plot(ui) 36 | } 37 | 38 | fn show_controls(&mut self, ui: &mut egui::Ui) -> egui::Response { 39 | self.show_controls(ui) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/performance/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | 3 | use eframe::egui; 4 | use examples_utils::PlotExample; 5 | 6 | mod app; 7 | pub use app::PerformanceDemo; 8 | 9 | impl PlotExample for PerformanceDemo { 10 | fn name(&self) -> &'static str { 11 | "performance" 12 | } 13 | 14 | fn title(&self) -> &'static str { 15 | "Performance Demo" 16 | } 17 | 18 | fn description(&self) -> &'static str { 19 | "This example demonstrates plotting performance with a large number of markers. Use the controls to adjust the number of markers and observe rendering performance." 20 | } 21 | 22 | fn tags(&self) -> &'static [&'static str] { 23 | &["performance", "markers"] 24 | } 25 | 26 | fn thumbnail_bytes(&self) -> &'static [u8] { 27 | include_bytes!("../screenshot_thumb.png") 28 | } 29 | 30 | fn code_bytes(&self) -> &'static [u8] { 31 | include_bytes!("./app.rs") 32 | } 33 | 34 | fn show_ui(&mut self, ui: &mut egui::Ui) -> egui::Response { 35 | self.show_plot(ui) 36 | } 37 | 38 | fn show_controls(&mut self, ui: &mut egui::Ui) -> egui::Response { 39 | self.show_controls(ui) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/heatmap/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | 3 | use eframe::egui; 4 | use examples_utils::PlotExample; 5 | 6 | mod app; 7 | pub use app::HeatmapDemo; 8 | 9 | impl PlotExample for HeatmapDemo { 10 | fn name(&self) -> &'static str { 11 | "heatmap" 12 | } 13 | 14 | fn title(&self) -> &'static str { 15 | "Heatmap Demo" 16 | } 17 | 18 | fn description(&self) -> &'static str { 19 | "This example demonstrates how to create animated heatmaps with customizable color palettes, dimensions, and labels. It visualizes a 2D grid of values using color gradients." 20 | } 21 | 22 | fn tags(&self) -> &'static [&'static str] { 23 | &["heatmap", "color", "grid", "visualization"] 24 | } 25 | 26 | fn thumbnail_bytes(&self) -> &'static [u8] { 27 | include_bytes!("../screenshot_thumb.png") 28 | } 29 | 30 | fn code_bytes(&self) -> &'static [u8] { 31 | include_bytes!("./app.rs") 32 | } 33 | 34 | fn show_ui(&mut self, ui: &mut egui::Ui) -> egui::Response { 35 | self.show_plot(ui) 36 | } 37 | 38 | fn show_controls(&mut self, ui: &mut egui::Ui) -> egui::Response { 39 | self.show_controls(ui) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /scripts/update_snapshots_from_ci.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # This script searches for the last CI run with your branch name, downloads the test_results artifact 3 | # and replaces your existing snapshots with the new ones. 4 | # Make sure you have the gh cli installed and authenticated before running this script. 5 | # If prompted to select a default repo, choose the emilk/egui one 6 | 7 | set -eu 8 | 9 | BRANCH=$(git rev-parse --abbrev-ref HEAD) 10 | 11 | if [ -z "${RUN_ID:-}" ]; then 12 | RUN_ID=$(gh run list --branch "$BRANCH" --workflow "Rust" --json databaseId -q '.[0].databaseId') 13 | echo "Downloading test results from run $RUN_ID from branch $BRANCH" 14 | else 15 | echo "Using provided RUN_ID: $RUN_ID" 16 | fi 17 | 18 | # remove any existing .new.png that might have been left behind 19 | find . -type d -path "./examples/*" | while read dir; do 20 | find "$dir" -type f -name "*.new.png" | while read file; do 21 | rm "$file" 22 | done 23 | done 24 | 25 | 26 | gh run download "$RUN_ID" --name "test-results" --dir tmp_artefacts 27 | 28 | # move the snapshots to the correct location, overwriting the existing ones 29 | rsync -a tmp_artefacts/ . 30 | 31 | rm -r tmp_artefacts 32 | 33 | ./scripts/accept_snapshots.sh 34 | -------------------------------------------------------------------------------- /examples/box_plot/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | 3 | use eframe::egui; 4 | use examples_utils::PlotExample; 5 | 6 | mod app; 7 | pub use app::BoxPlotExample; 8 | 9 | impl PlotExample for BoxPlotExample { 10 | fn name(&self) -> &'static str { 11 | "box_plot" 12 | } 13 | 14 | fn title(&self) -> &'static str { 15 | "Box Plot Demo" 16 | } 17 | 18 | fn description(&self) -> &'static str { 19 | "This example demonstrates how to create box plots (box-and-whisker plots) with customizable orientation. It shows multiple box plots with different experiments and days, allowing you to visualize statistical distributions." 20 | } 21 | 22 | fn tags(&self) -> &'static [&'static str] { 23 | &["box_plot", "color", "legend"] 24 | } 25 | 26 | fn thumbnail_bytes(&self) -> &'static [u8] { 27 | include_bytes!("../screenshot_thumb.png") 28 | } 29 | 30 | fn code_bytes(&self) -> &'static [u8] { 31 | include_bytes!("./app.rs") 32 | } 33 | 34 | fn show_ui(&mut self, ui: &mut egui::Ui) -> egui::Response { 35 | self.show_plot(ui) 36 | } 37 | 38 | fn show_controls(&mut self, ui: &mut egui::Ui) -> egui::Response { 39 | self.show_controls(ui) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/filled_area/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | 3 | use eframe::egui; 4 | use examples_utils::PlotExample; 5 | 6 | mod app; 7 | pub use app::FilledAreaExample; 8 | 9 | impl PlotExample for FilledAreaExample { 10 | fn name(&self) -> &'static str { 11 | "filled_area" 12 | } 13 | 14 | fn title(&self) -> &'static str { 15 | "Filled Area Demo" 16 | } 17 | 18 | fn description(&self) -> &'static str { 19 | "This example demonstrates how to create filled areas between two lines. It shows a sine wave with an adjustable confidence band around it, useful for visualizing uncertainty, ranges, and confidence intervals." 20 | } 21 | 22 | fn tags(&self) -> &'static [&'static str] { 23 | &["filled_area", "confidence_interval"] 24 | } 25 | 26 | fn thumbnail_bytes(&self) -> &'static [u8] { 27 | include_bytes!("../screenshot_thumb.png") 28 | } 29 | 30 | fn code_bytes(&self) -> &'static [u8] { 31 | include_bytes!("./app.rs") 32 | } 33 | 34 | fn show_ui(&mut self, ui: &mut egui::Ui) -> egui::Response { 35 | self.show_plot(ui) 36 | } 37 | 38 | fn show_controls(&mut self, ui: &mut egui::Ui) -> egui::Response { 39 | self.show_controls(ui) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/lines/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | 3 | use eframe::egui; 4 | use examples_utils::PlotExample; 5 | 6 | mod app; 7 | pub use app::LineExample; 8 | 9 | impl PlotExample for LineExample { 10 | fn name(&self) -> &'static str { 11 | "lines" 12 | } 13 | 14 | fn title(&self) -> &'static str { 15 | "Line Demo" 16 | } 17 | 18 | fn description(&self) -> &'static str { 19 | "This example demonstrates various line plotting features including animated lines, different line styles (solid, dashed, dotted), gradients, fills, axis inversion, and coordinate display. It shows a comprehensive set of line customization options." 20 | } 21 | 22 | fn tags(&self) -> &'static [&'static str] { 23 | &["lines", "animation", "styling"] 24 | } 25 | 26 | fn thumbnail_bytes(&self) -> &'static [u8] { 27 | include_bytes!("../screenshot_thumb.png") 28 | } 29 | 30 | fn code_bytes(&self) -> &'static [u8] { 31 | include_bytes!("./app.rs") 32 | } 33 | 34 | fn show_ui(&mut self, ui: &mut egui::Ui) -> egui::Response { 35 | self.show_plot(ui) 36 | } 37 | 38 | fn show_controls(&mut self, ui: &mut egui::Ui) -> egui::Response { 39 | self.show_controls(ui) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/items/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | 3 | use eframe::egui; 4 | use examples_utils::PlotExample; 5 | 6 | mod app; 7 | pub use app::ItemsExample; 8 | 9 | impl PlotExample for ItemsExample { 10 | fn name(&self) -> &'static str { 11 | "items" 12 | } 13 | 14 | fn title(&self) -> &'static str { 15 | "Items Demo" 16 | } 17 | 18 | fn description(&self) -> &'static str { 19 | "This example demonstrates the various plot items available in `egui_plot`, including lines, polygons, points, arrows, text, images, and horizontal/vertical lines. It showcases the different visual elements you can add to a plot." 20 | } 21 | 22 | fn tags(&self) -> &'static [&'static str] { 23 | &["items", "lines", "polygons", "arrows", "text"] 24 | } 25 | 26 | fn thumbnail_bytes(&self) -> &'static [u8] { 27 | include_bytes!("../screenshot_thumb.png") 28 | } 29 | 30 | fn code_bytes(&self) -> &'static [u8] { 31 | include_bytes!("./app.rs") 32 | } 33 | 34 | fn show_ui(&mut self, ui: &mut egui::Ui) -> egui::Response { 35 | self.show_plot(ui) 36 | } 37 | 38 | fn show_controls(&mut self, ui: &mut egui::Ui) -> egui::Response { 39 | Self::show_controls(self, ui) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/linked_axes/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | 3 | use eframe::egui; 4 | use examples_utils::PlotExample; 5 | 6 | mod app; 7 | pub use app::LinkedAxesExample; 8 | 9 | impl PlotExample for LinkedAxesExample { 10 | fn name(&self) -> &'static str { 11 | "linked_axes" 12 | } 13 | 14 | fn title(&self) -> &'static str { 15 | "Linked Axes Example" 16 | } 17 | 18 | fn description(&self) -> &'static str { 19 | "This example demonstrates how to link axes and cursors across multiple plots. When you zoom, pan, or move the cursor in one plot, the linked plots will synchronize their view, useful for comparing data across different visualizations." 20 | } 21 | 22 | fn tags(&self) -> &'static [&'static str] { 23 | &["axes", "cursor"] 24 | } 25 | 26 | fn thumbnail_bytes(&self) -> &'static [u8] { 27 | include_bytes!("../screenshot_thumb.png") 28 | } 29 | 30 | fn code_bytes(&self) -> &'static [u8] { 31 | include_bytes!("./app.rs") 32 | } 33 | 34 | fn show_ui(&mut self, ui: &mut egui::Ui) -> egui::Response { 35 | self.show_plot(ui) 36 | } 37 | 38 | fn show_controls(&mut self, ui: &mut egui::Ui) -> egui::Response { 39 | self.show_controls(ui) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /egui_plot/src/label.rs: -------------------------------------------------------------------------------- 1 | use emath::NumExt as _; 2 | 3 | use crate::bounds::PlotPoint; 4 | 5 | /// Helper for formatting a number so that we always show at least a few 6 | /// decimals, unless it is an integer, in which case we never show any decimals. 7 | pub fn format_number(number: f64, num_decimals: usize) -> String { 8 | let is_integral = number as i64 as f64 == number; 9 | if is_integral { 10 | // perfect integer - show it as such: 11 | format!("{number:.0}") 12 | } else { 13 | // make sure we tell the user it is not an integer by always showing a decimal 14 | // or two: 15 | format!("{:.*}", num_decimals.at_least(1), number) 16 | } 17 | } 18 | 19 | type LabelFormatterFn<'a> = dyn Fn(&str, &PlotPoint) -> String + 'a; 20 | 21 | /// Optional label formatter function for customizing hover labels. 22 | pub type LabelFormatter<'a> = Box>; 23 | 24 | /// Default label formatter that shows the x and y coordinates with 3 decimal 25 | /// places. 26 | pub fn default_label_formatter(name: &str, value: &PlotPoint) -> String { 27 | let prefix = if name.is_empty() { 28 | String::new() 29 | } else { 30 | format!("{name}\n") 31 | }; 32 | format!("{}x = {:.3}\ny = {:.3}", prefix, value.x, value.y) 33 | } 34 | -------------------------------------------------------------------------------- /egui_plot/src/overlays/coordinates.rs: -------------------------------------------------------------------------------- 1 | use crate::bounds::PlotBounds; 2 | use crate::bounds::PlotPoint; 3 | 4 | type CoordinatesFormatterFn<'a> = dyn Fn(&PlotPoint, &PlotBounds) -> String + 'a; 5 | 6 | /// Specifies the coordinates formatting when passed to 7 | /// [`crate::Plot::coordinates_formatter`]. 8 | pub struct CoordinatesFormatter<'a> { 9 | function: Box>, 10 | } 11 | 12 | impl<'a> CoordinatesFormatter<'a> { 13 | /// Create a new formatter based on the pointer coordinate and the plot 14 | /// bounds. 15 | pub fn new(function: impl Fn(&PlotPoint, &PlotBounds) -> String + 'a) -> Self { 16 | Self { 17 | function: Box::new(function), 18 | } 19 | } 20 | 21 | /// Show a fixed number of decimal places. 22 | pub fn with_decimals(num_decimals: usize) -> Self { 23 | Self { 24 | function: Box::new(move |value, _| format!("x: {:.d$}\ny: {:.d$}", value.x, value.y, d = num_decimals)), 25 | } 26 | } 27 | 28 | pub(crate) fn format(&self, value: &PlotPoint, bounds: &PlotBounds) -> String { 29 | (self.function)(value, bounds) 30 | } 31 | } 32 | 33 | impl Default for CoordinatesFormatter<'_> { 34 | fn default() -> Self { 35 | Self::with_decimals(3) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/interaction/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | 3 | use eframe::egui; 4 | use examples_utils::PlotExample; 5 | 6 | mod app; 7 | pub use app::InteractionExample; 8 | 9 | impl PlotExample for InteractionExample { 10 | fn name(&self) -> &'static str { 11 | "interaction" 12 | } 13 | 14 | fn title(&self) -> &'static str { 15 | "Interaction Demo" 16 | } 17 | 18 | fn description(&self) -> &'static str { 19 | "This example demonstrates how to interact with plots programmatically. It shows how to access plot bounds, pointer coordinates, drag deltas, and detect hovered plot items, providing a foundation for building interactive plot applications." 20 | } 21 | 22 | fn tags(&self) -> &'static [&'static str] { 23 | &["interaction"] 24 | } 25 | 26 | fn thumbnail_bytes(&self) -> &'static [u8] { 27 | include_bytes!("../screenshot_thumb.png") 28 | } 29 | 30 | fn code_bytes(&self) -> &'static [u8] { 31 | include_bytes!("./app.rs") 32 | } 33 | 34 | fn show_ui(&mut self, ui: &mut egui::Ui) -> egui::Response { 35 | self.show_plot(ui) 36 | } 37 | 38 | fn show_controls(&mut self, ui: &mut egui::Ui) -> egui::Response { 39 | Self::show_controls(self, ui) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/custom_axes/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | 3 | use eframe::egui; 4 | use examples_utils::PlotExample; 5 | 6 | mod app; 7 | pub use app::CustomAxesExample; 8 | 9 | impl PlotExample for CustomAxesExample { 10 | fn name(&self) -> &'static str { 11 | "custom_axes" 12 | } 13 | 14 | fn title(&self) -> &'static str { 15 | "Custom Axes Demo" 16 | } 17 | 18 | fn description(&self) -> &'static str { 19 | "This example demonstrates how to create custom axes with custom formatters and grid spacers. It shows a logistic function with time-based X-axis formatting (days, hours, minutes) and percentage-based Y-axis formatting, demonstrating how to create domain-specific axis labels." 20 | } 21 | 22 | fn tags(&self) -> &'static [&'static str] { 23 | &["axes"] 24 | } 25 | 26 | fn thumbnail_bytes(&self) -> &'static [u8] { 27 | include_bytes!("../screenshot_thumb.png") 28 | } 29 | 30 | fn code_bytes(&self) -> &'static [u8] { 31 | include_bytes!("./app.rs") 32 | } 33 | 34 | fn show_ui(&mut self, ui: &mut egui::Ui) -> egui::Response { 35 | self.show_plot(ui) 36 | } 37 | 38 | fn show_controls(&mut self, ui: &mut egui::Ui) -> egui::Response { 39 | Self::show_controls(self, ui) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/legend_sort/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | 3 | use eframe::egui; 4 | use examples_utils::PlotExample; 5 | 6 | mod app; 7 | pub use app::LegendSortExample; 8 | 9 | impl PlotExample for LegendSortExample { 10 | fn name(&self) -> &'static str { 11 | "legend_sort" 12 | } 13 | 14 | fn title(&self) -> &'static str { 15 | "Legend Sorting" 16 | } 17 | 18 | fn description(&self) -> &'static str { 19 | "This example demonstrates how to control the sorting order of legend entries. It shows how to use `follow_insertion_order()` to display legend entries in the order they were added to the plot, rather than alphabetically, which is useful for maintaining a specific visual hierarchy in the legend." 20 | } 21 | 22 | fn tags(&self) -> &'static [&'static str] { 23 | &["legend"] 24 | } 25 | 26 | fn thumbnail_bytes(&self) -> &'static [u8] { 27 | include_bytes!("../screenshot_thumb.png") 28 | } 29 | 30 | fn code_bytes(&self) -> &'static [u8] { 31 | include_bytes!("./app.rs") 32 | } 33 | 34 | fn show_ui(&mut self, ui: &mut egui::Ui) -> egui::Response { 35 | self.show_plot(ui) 36 | } 37 | 38 | fn show_controls(&mut self, ui: &mut egui::Ui) -> egui::Response { 39 | self.show_controls(ui) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.semanticTokenColorCustomizations": { 4 | "rules": { 5 | "*.unsafe:rust": "#eb5046" 6 | } 7 | }, 8 | "files.autoGuessEncoding": true, 9 | "files.insertFinalNewline": true, 10 | "files.trimTrailingWhitespace": true, 11 | // don't share a cargo lock with rust-analyzer. 12 | // see https://github.com/rerun-io/rerun/pull/519 for rationale 13 | "rust-analyzer.check.overrideCommand": [ 14 | "cargo", 15 | "clippy", 16 | "--target-dir=target_ra", 17 | "--workspace", 18 | "--message-format=json", 19 | "--all-targets", 20 | "--all-features", 21 | ], 22 | "rust-analyzer.cargo.buildScripts.overrideCommand": [ 23 | "cargo", 24 | "clippy", 25 | "--quiet", 26 | "--target-dir=target_ra", 27 | "--workspace", 28 | "--message-format=json", 29 | "--all-targets", 30 | "--all-features", 31 | ], 32 | "rust-analyzer.showUnlinkedFileNotification": false, 33 | // Uncomment the following options and restart rust-analyzer to get it to check code behind `cfg(target_arch=wasm32)`. 34 | // Don't forget to put it in a comment again before committing. 35 | // "rust-analyzer.cargo.target": "wasm32-unknown-unknown", 36 | } 37 | -------------------------------------------------------------------------------- /examples/borrow_points/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | 3 | use eframe::egui; 4 | use examples_utils::PlotExample; 5 | 6 | mod app; 7 | pub use app::BorrowPointsExample; 8 | 9 | impl PlotExample for BorrowPointsExample { 10 | fn name(&self) -> &'static str { 11 | "borrow_points" 12 | } 13 | 14 | fn title(&self) -> &'static str { 15 | "Example of borrowing points" 16 | } 17 | 18 | fn description(&self) -> &'static str { 19 | "This example demonstrates how to borrow points instead of cloning them when creating plot lines. It shows how to use `PlotPoints::Borrowed` to avoid unnecessary allocations, which is useful for performance-critical applications or when you want to reuse the same data across multiple frames without copying." 20 | } 21 | 22 | fn tags(&self) -> &'static [&'static str] { 23 | &["performance"] 24 | } 25 | 26 | fn thumbnail_bytes(&self) -> &'static [u8] { 27 | include_bytes!("../screenshot_thumb.png") 28 | } 29 | 30 | fn code_bytes(&self) -> &'static [u8] { 31 | include_bytes!("./app.rs") 32 | } 33 | 34 | fn show_ui(&mut self, ui: &mut egui::Ui) -> egui::Response { 35 | self.show_plot(ui) 36 | } 37 | 38 | fn show_controls(&mut self, ui: &mut egui::Ui) -> egui::Response { 39 | ui.scope(|_ui| {}).response 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/save_plot/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![expect(clippy::print_stderr, reason = "example template")] 2 | #![doc = include_str!("../README.md")] 3 | 4 | use eframe::egui; 5 | use examples_utils::PlotExample; 6 | 7 | mod app; 8 | pub use app::SavePlotExample; 9 | 10 | impl PlotExample for SavePlotExample { 11 | fn name(&self) -> &'static str { 12 | "save_plot" 13 | } 14 | 15 | fn title(&self) -> &'static str { 16 | "Saving plot" 17 | } 18 | 19 | fn description(&self) -> &'static str { 20 | "This example demonstrates how to save a plot as a PNG image file. It shows how to capture a screenshot of the plot using egui's screenshot functionality, extract the plot region, and save it to disk using the image crate. This is useful for exporting visualizations or generating plot images programmatically." 21 | } 22 | 23 | fn tags(&self) -> &'static [&'static str] { 24 | &["export"] 25 | } 26 | 27 | fn thumbnail_bytes(&self) -> &'static [u8] { 28 | include_bytes!("../screenshot_thumb.png") 29 | } 30 | 31 | fn code_bytes(&self) -> &'static [u8] { 32 | include_bytes!("./app.rs") 33 | } 34 | 35 | fn show_ui(&mut self, ui: &mut egui::Ui) -> egui::Response { 36 | self.show_plot(ui) 37 | } 38 | 39 | fn show_controls(&mut self, ui: &mut egui::Ui) -> egui::Response { 40 | Self::show_controls(self, ui) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/custom_plot_manipulation/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | 3 | use eframe::egui; 4 | use examples_utils::PlotExample; 5 | 6 | mod app; 7 | pub use app::CustomPlotManipulationExample; 8 | 9 | impl PlotExample for CustomPlotManipulationExample { 10 | fn name(&self) -> &'static str { 11 | "custom_plot_manipulation" 12 | } 13 | 14 | fn title(&self) -> &'static str { 15 | "Custom Plot Manipulation" 16 | } 17 | 18 | fn description(&self) -> &'static str { 19 | "This example demonstrates how to implement custom plot manipulation controls using raw input events. It shows how to create alternative pan and zoom behaviors, such as inverting the default Ctrl key behavior, customizing zoom and scroll speeds, and locking axes. This is useful for building specialized interaction patterns that differ from the default `egui_plot` controls." 20 | } 21 | 22 | fn tags(&self) -> &'static [&'static str] { 23 | &["interaction", "controls"] 24 | } 25 | 26 | fn thumbnail_bytes(&self) -> &'static [u8] { 27 | include_bytes!("../screenshot_thumb.png") 28 | } 29 | 30 | fn code_bytes(&self) -> &'static [u8] { 31 | include_bytes!("./app.rs") 32 | } 33 | 34 | fn show_ui(&mut self, ui: &mut egui::Ui) -> egui::Response { 35 | self.show_plot(ui) 36 | } 37 | 38 | fn show_controls(&mut self, ui: &mut egui::Ui) -> egui::Response { 39 | self.show_controls(ui) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.github/workflows/update_kittest_snapshots.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: 3 | inputs: 4 | run_id: 5 | description: 'The run ID that produced the artifact' 6 | required: true 7 | type: string 8 | 9 | permissions: 10 | actions: read 11 | 12 | name: Update kittest snapshots 13 | jobs: 14 | update-snapshots: 15 | name: Update snapshots from artifact 16 | runs-on: ubuntu-latest 17 | permissions: 18 | contents: write 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | with: 23 | lfs: true 24 | # We can't use the workflow token since that would prevent our commit to cause further workflows. 25 | # See https://github.com/stefanzweifel/git-auto-commit-action#commits-made-by-this-action-do-not-trigger-new-workflow-runs 26 | # This token should be a personal access token with at least Read and write permission to `Contents`. 27 | # The commit action below will use the token this the code was checked out with. 28 | token: '${{ secrets.SNAPSHOT_COMMIT_GITHUB_TOKEN }}' 29 | 30 | - name: Accept snapshots 31 | env: 32 | GH_TOKEN: ${{ github.token }} 33 | RUN_ID: ${{ github.event.inputs.run_id }} 34 | run: ./scripts/update_snapshots_from_ci.sh 35 | 36 | - name: Git status 37 | run: git status 38 | 39 | - uses: stefanzweifel/git-auto-commit-action@v6 40 | with: 41 | commit_message: 'Update snapshot images' 42 | 43 | -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | # Demo Gallery 2 | 3 | Demo gallery collects all the `examples/` into a browsable gallery. 4 | 5 | ## Contributions welcome! 6 | 7 | [Contributions](../CONTRIBUTING.md) are welcome! 8 | 9 | When you create a new example, first copy an existing one to have the same structure. 10 | A good example to copy is `examples/box_plot`. 11 | 12 | If you'd like to improve the gallery itself, please take a look at following issues: 13 | 14 | ## Features 15 | 16 | - Search/filter by tags: https://github.com/emilk/egui_plot/issues/172 17 | - Add highlighted source code: https://github.com/emilk/egui_plot/issues/173 18 | - Sidebar with examples should be adjustable, and the grid should expand/shrink number of columns: https://github.com/emilk/egui_plot/issues/176 19 | - Render real-time previews (with animations) rather than storing and loading thumbnails: https://github.com/emilk/egui_plot/issues/180 20 | - Add a link to some online "playground" service where one can directly edit the code and see the result: https://github.com/emilk/egui_plot/issues/181 21 | 22 | ## Tweaks 23 | 24 | - Make `interaction` example fully sized: https://github.com/emilk/egui_plot/issues/174 25 | - Make `linked_axis` example fully sized: https://github.com/emilk/egui_plot/issues/175 26 | 27 | ## Bugs 28 | 29 | - Bug: `lines` example overflows when `Square view` is selected: https://github.com/emilk/egui_plot/issues/177 30 | - Bug: `legend` example should not show coordinate crosshair when hovering between legend labels: https://github.com/emilk/egui_plot/issues/178 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # egui_plot 2 | 3 | [github](https://github.com/emilk/egui_plot) 4 | [![Latest version](https://img.shields.io/crates/v/egui_plot.svg)](https://crates.io/crates/egui_plot) 5 | [![Documentation](https://docs.rs/egui_plot/badge.svg)](https://docs.rs/egui_plot) 6 | [![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/) 7 | ![MIT](https://img.shields.io/badge/license-MIT-blue.svg) 8 | ![Apache](https://img.shields.io/badge/license-Apache-blue.svg) 9 | [![Discord](https://img.shields.io/discord/900275882684477440?label=egui%20discord)](https://discord.gg/JFcEma9bJq) 10 | 11 | Immediate mode 2D plotting library for [`egui`](https://crates.io/crates/https://github.com/emilk/egui). 12 | 13 | [![egui_plot_white](https://github.com/user-attachments/assets/b29acf5e-ccbf-4cb7-b03b-7e258fa5db16)](https://emilk.github.io/egui_plot/) 14 | 15 | [Try the web demo gallery](https://emilk.github.io/egui_plot/). 16 | 17 | ## Testing 18 | 19 | - Locally: `cargo run -p demo` 20 | - Web: `(cd demo && trunk serve)` 21 | 22 | ## Plotting libraries in Rust 23 | 24 | To view a list of plotting libraries in Rust, see [notes on Rust plotting ecosystem](ECOSYSTEM.md). 25 | 26 | ## Contributing 27 | 28 | Please see [CONTRIBUTING.md](CONTRIBUTING.md) and [WELCOME_CONTRIBUTIONS.md](WELCOME_CONTRIBUTIONS.md) 29 | 30 | ## History 31 | 32 | This crate was originally hosted at but was extracted into its own repository on 2024-07-15. 33 | -------------------------------------------------------------------------------- /egui_plot/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = [ 3 | "Emil Ernerfeldt ", # https://github.com/emilk 4 | "Jan Haller ", # https://github.com/Bromeon 5 | "Sven Niederberger ", # https://github.com/EmbersArc 6 | ] 7 | categories = ["visualization", "gui"] 8 | description = "Immediate mode plotting for the egui GUI library" 9 | edition.workspace = true 10 | homepage = "https://github.com/emilk/egui_plot" 11 | include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] 12 | keywords = ["egui", "plot", "plotting"] 13 | license.workspace = true 14 | name = "egui_plot" 15 | readme = "../README.md" 16 | repository = "https://github.com/emilk/egui_plot" 17 | rust-version.workspace = true 18 | version.workspace = true 19 | 20 | [lints] 21 | workspace = true 22 | 23 | [package.metadata.docs.rs] 24 | all-features = true 25 | 26 | [lib] 27 | 28 | 29 | [features] 30 | default = [] 31 | 32 | 33 | ## Allow serialization using [`serde`](https://docs.rs/serde). 34 | serde = ["dep:serde", "egui/serde"] 35 | 36 | 37 | [dependencies] 38 | egui = { workspace = true, default-features = false } 39 | emath = { workspace = true, default-features = false } 40 | 41 | ahash.workspace = true 42 | 43 | #! ### Optional dependencies 44 | ## Enable this when generating docs. 45 | document-features = { workspace = true, optional = true } 46 | 47 | serde = { workspace = true, optional = true } 48 | 49 | [dev-dependencies] 50 | assertables.workspace = true 51 | egui_kittest.workspace = true 52 | env_logger.workspace = true 53 | log.workspace = true 54 | -------------------------------------------------------------------------------- /examples/legend_sort/src/app.rs: -------------------------------------------------------------------------------- 1 | use eframe::egui; 2 | use eframe::egui::Response; 3 | use egui_plot::Legend; 4 | use egui_plot::Line; 5 | use egui_plot::Plot; 6 | use egui_plot::PlotPoints; 7 | 8 | pub struct LegendSortExample { 9 | insert_order: bool, 10 | graph: Vec<[f64; 2]>, 11 | graph2: Vec<[f64; 2]>, 12 | graph3: Vec<[f64; 2]>, 13 | } 14 | 15 | impl Default for LegendSortExample { 16 | fn default() -> Self { 17 | Self { 18 | insert_order: false, 19 | graph: vec![[0.0, 1.0], [2.0, 3.0], [3.0, 2.0]], 20 | graph2: vec![[0.0, 2.0], [2.0, 4.0], [3.0, 3.0]], 21 | graph3: vec![[0.0, 3.0], [2.0, 5.0], [3.0, 4.0]], 22 | } 23 | } 24 | } 25 | 26 | impl LegendSortExample { 27 | pub fn show_plot(&self, ui: &mut egui::Ui) -> Response { 28 | Plot::new("My Plot") 29 | .legend(Legend::default().follow_insertion_order(self.insert_order)) 30 | .show(ui, |plot_ui| { 31 | plot_ui.line(Line::new("3rd Curve", PlotPoints::from(self.graph3.clone()))); 32 | plot_ui.line(Line::new("1st Curve", PlotPoints::from(self.graph.clone()))); 33 | plot_ui.line(Line::new("2nd Curve", PlotPoints::from(self.graph2.clone()))); 34 | }) 35 | .response 36 | } 37 | 38 | pub fn show_controls(&mut self, ui: &mut egui::Ui) -> Response { 39 | ui.horizontal(|ui| { 40 | ui.label("If checked the legend will follow the order as the curves are inserted"); 41 | ui.checkbox(&mut self.insert_order, "Insert order"); 42 | }) 43 | .response 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /egui_plot/src/colors.rs: -------------------------------------------------------------------------------- 1 | use egui::Color32; 2 | use egui::Rgba; 3 | use egui::Stroke; 4 | use egui::Ui; 5 | use emath::NumExt as _; 6 | 7 | pub(crate) fn rulers_color(ui: &Ui) -> Color32 { 8 | if ui.visuals().dark_mode { 9 | Color32::from_gray(100).additive() 10 | } else { 11 | Color32::from_black_alpha(180) 12 | } 13 | } 14 | 15 | pub(crate) fn highlighted_color(mut stroke: Stroke, fill: Color32) -> (Stroke, Color32) { 16 | stroke.width *= 2.0; 17 | 18 | let mut fill = Rgba::from(fill); 19 | if fill.is_additive() { 20 | // Make slightly brighter 21 | fill = 1.3 * fill; 22 | } else { 23 | // Make more opaque: 24 | let fill_alpha = (2.0 * fill.a()).at_most(1.0); 25 | fill = fill.to_opaque().multiply(fill_alpha); 26 | } 27 | 28 | (stroke, fill.into()) 29 | } 30 | 31 | pub const DEFAULT_FILL_ALPHA: f32 = 0.05; 32 | 33 | /// Default base colors. Used for now only in heatmap palette. 34 | pub const BASE_COLORS: [Color32; 10] = [ 35 | Color32::from_rgb(48, 18, 59), 36 | Color32::from_rgb(35, 106, 141), 37 | Color32::from_rgb(30, 160, 140), 38 | Color32::from_rgb(88, 200, 98), 39 | Color32::from_rgb(164, 223, 39), 40 | Color32::from_rgb(228, 223, 14), 41 | Color32::from_rgb(250, 187, 13), 42 | Color32::from_rgb(246, 135, 8), 43 | Color32::from_rgb(213, 68, 2), 44 | Color32::from_rgb(122, 4, 2), 45 | ]; 46 | 47 | /// Determine a color from a 0-1 strength value. 48 | pub fn color_from_strength(ui: &Ui, strength: f32) -> Color32 { 49 | let base_color = ui.visuals().text_color(); 50 | base_color.gamma_multiply(strength.sqrt()) 51 | } 52 | -------------------------------------------------------------------------------- /.github/workflows/enforce_branch_name.yml: -------------------------------------------------------------------------------- 1 | name: PR Branch Name Check 2 | 3 | on: 4 | pull_request_target: 5 | types: [opened, reopened, synchronize] 6 | 7 | jobs: 8 | check-source-branch: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Check PR source branch 12 | run: | 13 | # Check if PR is from a fork 14 | if [[ "${{ github.event.pull_request.head.repo.fork }}" == "true" ]]; then 15 | # Check if PR is from the master/main branch of a fork 16 | if [[ "${{ github.event.pull_request.head.ref }}" == "master" || "${{ github.event.pull_request.head.ref }}" == "main" ]]; then 17 | echo "ERROR: Pull requests from the master/main branch of forks are not allowed, because it prevents maintainers from contributing to your PR" 18 | echo "Please create a feature branch in your fork and submit the PR from that branch instead." 19 | exit 1 20 | fi 21 | fi 22 | 23 | - name: Leave comment if PR is from master/main branch of fork d 24 | if: ${{ failure() }} 25 | uses: actions/github-script@v6 26 | with: 27 | github-token: ${{ secrets.GITHUB_TOKEN }} 28 | script: | 29 | github.rest.issues.createComment({ 30 | issue_number: context.issue.number, 31 | owner: context.repo.owner, 32 | repo: context.repo.repo, 33 | body: '⚠️ **ERROR:** Pull requests from the `master`/`main` branch of forks are not allowed, because it prevents maintainers from contributing to your PR. Please create a feature branch in your fork and submit the PR from that branch instead.' 34 | }) 35 | -------------------------------------------------------------------------------- /examples/histogram/src/app.rs: -------------------------------------------------------------------------------- 1 | use eframe::egui; 2 | use eframe::egui::Response; 3 | use egui_plot::Bar; 4 | use egui_plot::BarChart; 5 | use egui_plot::Legend; 6 | use egui_plot::Plot; 7 | 8 | pub struct HistogramExample { 9 | vertical: bool, 10 | } 11 | 12 | impl Default for HistogramExample { 13 | fn default() -> Self { 14 | Self { vertical: true } 15 | } 16 | } 17 | 18 | impl HistogramExample { 19 | pub fn show_controls(&mut self, ui: &mut egui::Ui) -> Response { 20 | ui.horizontal(|ui| { 21 | ui.label("Orientation:"); 22 | ui.selectable_value(&mut self.vertical, true, "Vertical"); 23 | ui.selectable_value(&mut self.vertical, false, "Horizontal"); 24 | }) 25 | .response 26 | } 27 | 28 | pub fn show_plot(&self, ui: &mut egui::Ui) -> Response { 29 | let mut chart = BarChart::new( 30 | "Normal Distribution", 31 | (-395..=395) 32 | .step_by(10) 33 | .map(|x| x as f64 * 0.01) 34 | .map(|x| (x, (-x * x / 2.0).exp() / (2.0 * std::f64::consts::PI).sqrt())) 35 | .map(|(x, f)| Bar::new(x, f * 10.0).width(0.1)) 36 | .collect(), 37 | ) 38 | .color(egui::Color32::LIGHT_BLUE); 39 | 40 | if !self.vertical { 41 | chart = chart.horizontal(); 42 | } 43 | 44 | Plot::new("Normal Distribution Demo") 45 | .legend(Legend::default()) 46 | .clamp_grid(true) 47 | .allow_zoom(egui::Vec2b::new(true, true)) 48 | .allow_drag(egui::Vec2b::new(true, true)) 49 | .allow_scroll(egui::Vec2b::new(true, true)) 50 | .show(ui, |plot_ui| plot_ui.bar_chart(chart)) 51 | .response 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /demo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Emil Ernerfeldt "] 3 | edition.workspace = true 4 | include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] 5 | license.workspace = true 6 | name = "demo" 7 | publish = false 8 | rust-version.workspace = true 9 | version.workspace = true 10 | default-run = "demo" 11 | 12 | [lints] 13 | workspace = true 14 | 15 | [dependencies] 16 | 17 | egui.workspace = true 18 | eframe = { workspace = true, default-features = false, features = [ 19 | "default_fonts", # Embed the default egui fonts. 20 | "glow", # Use the glow rendering backend. Alternative: "wgpu". 21 | "persistence", # Enable restoring app state when restarting the app. 22 | "x11", 23 | ] } 24 | image.workspace = true 25 | log.workspace = true 26 | 27 | egui_chip = "0.3.1" 28 | examples_utils.workspace = true 29 | 30 | # Example dependencies 31 | borrow_points.workspace = true 32 | box_plot.workspace = true 33 | custom_axes.workspace = true 34 | custom_plot_manipulation.workspace = true 35 | filled_area.workspace = true 36 | heatmap.workspace = true 37 | histogram.workspace = true 38 | interaction.workspace = true 39 | items.workspace = true 40 | legend.workspace = true 41 | legend_sort.workspace = true 42 | lines.workspace = true 43 | linked_axes.workspace = true 44 | markers.workspace = true 45 | performance.workspace = true 46 | plot_span.workspace = true 47 | save_plot.workspace = true 48 | stacked_bar.workspace = true 49 | 50 | # native: 51 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 52 | env_logger = { version = "0.11.8", default-features = false, features = [ 53 | "auto-color", 54 | "humantime", 55 | ] } 56 | 57 | # web: 58 | [target.'cfg(target_arch = "wasm32")'.dependencies] 59 | wasm-bindgen-futures.workspace = true 60 | web-sys.workspace = true # to access the DOM (to hide the loading text) 61 | -------------------------------------------------------------------------------- /egui_plot/src/rect_elem.rs: -------------------------------------------------------------------------------- 1 | use crate::aesthetics::Orientation; 2 | use crate::axis::PlotTransform; 3 | use crate::bounds::PlotBounds; 4 | use crate::bounds::PlotPoint; 5 | 6 | /// Trait that abstracts from rectangular 'Value'-like elements, such as bars or 7 | /// boxes 8 | pub(super) trait RectElement { 9 | fn name(&self) -> &str; 10 | 11 | fn bounds_min(&self) -> PlotPoint; 12 | 13 | fn bounds_max(&self) -> PlotPoint; 14 | 15 | fn bounds(&self) -> PlotBounds { 16 | let mut bounds = PlotBounds::NOTHING; 17 | bounds.extend_with(&self.bounds_min()); 18 | bounds.extend_with(&self.bounds_max()); 19 | bounds 20 | } 21 | 22 | /// At which argument (input; usually X) there is a ruler (usually vertical) 23 | fn arguments_with_ruler(&self) -> Vec { 24 | // Default: one at center 25 | vec![self.bounds().center()] 26 | } 27 | 28 | /// At which value (output; usually Y) there is a ruler (usually horizontal) 29 | fn values_with_ruler(&self) -> Vec; 30 | 31 | /// The diagram's orientation (vertical/horizontal) 32 | fn orientation(&self) -> Orientation; 33 | 34 | /// Get X/Y-value for (argument, value) pair, taking into account 35 | /// orientation 36 | fn point_at(&self, argument: f64, value: f64) -> PlotPoint { 37 | match self.orientation() { 38 | Orientation::Horizontal => PlotPoint::new(value, argument), 39 | Orientation::Vertical => PlotPoint::new(argument, value), 40 | } 41 | } 42 | 43 | /// Right top of the rectangle (position of text) 44 | fn corner_value(&self) -> PlotPoint { 45 | PlotPoint { 46 | x: self.bounds_max().x, 47 | y: self.bounds_max().y, 48 | } 49 | } 50 | 51 | /// Debug formatting for hovered-over value, if none is specified by the 52 | /// user 53 | fn default_values_format(&self, transform: &PlotTransform) -> String; 54 | } 55 | 56 | // ---------------------------------------------------------------------------- 57 | // Helper functions 58 | -------------------------------------------------------------------------------- /egui_plot/src/placement.rs: -------------------------------------------------------------------------------- 1 | /// Placement of the horizontal X-Axis. 2 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 3 | pub enum VPlacement { 4 | Top, 5 | Bottom, 6 | } 7 | 8 | /// Placement of the vertical Y-Axis. 9 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 10 | pub enum HPlacement { 11 | Left, 12 | Right, 13 | } 14 | 15 | /// Placement of an axis. 16 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 17 | pub enum Placement { 18 | /// Bottom for X-axis, or left for Y-axis. 19 | LeftBottom, 20 | 21 | /// Top for x-axis and right for y-axis. 22 | RightTop, 23 | } 24 | 25 | impl From for Placement { 26 | #[inline] 27 | fn from(placement: HPlacement) -> Self { 28 | match placement { 29 | HPlacement::Left => Self::LeftBottom, 30 | HPlacement::Right => Self::RightTop, 31 | } 32 | } 33 | } 34 | 35 | impl From for HPlacement { 36 | #[inline] 37 | fn from(placement: Placement) -> Self { 38 | match placement { 39 | Placement::LeftBottom => Self::Left, 40 | Placement::RightTop => Self::Right, 41 | } 42 | } 43 | } 44 | 45 | impl From for Placement { 46 | #[inline] 47 | fn from(placement: VPlacement) -> Self { 48 | match placement { 49 | VPlacement::Top => Self::RightTop, 50 | VPlacement::Bottom => Self::LeftBottom, 51 | } 52 | } 53 | } 54 | 55 | impl From for VPlacement { 56 | #[inline] 57 | fn from(placement: Placement) -> Self { 58 | match placement { 59 | Placement::LeftBottom => Self::Bottom, 60 | Placement::RightTop => Self::Top, 61 | } 62 | } 63 | } 64 | 65 | /// Where to place the plot legend. 66 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 67 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] 68 | pub enum Corner { 69 | LeftTop, 70 | RightTop, 71 | LeftBottom, 72 | RightBottom, 73 | } 74 | 75 | impl Corner { 76 | pub fn all() -> impl Iterator { 77 | [Self::LeftTop, Self::RightTop, Self::LeftBottom, Self::RightBottom] 78 | .iter() 79 | .copied() 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /.github/workflows/pages.yml: -------------------------------------------------------------------------------- 1 | name: Github Pages 2 | 3 | # By default, runs if you push to main. keeps your deployed app in sync with main branch. 4 | on: 5 | push: 6 | branches: 7 | - main 8 | # to only run when you do a new github release, comment out above part and uncomment the below trigger. 9 | # on: 10 | # release: 11 | # types: 12 | # - published 13 | 14 | permissions: 15 | contents: write # for committing to gh-pages branch. 16 | 17 | jobs: 18 | build-github-pages: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v4 # repo checkout 22 | with: 23 | lfs: true 24 | - name: Setup toolchain for wasm 25 | run: | 26 | rustup update stable 27 | rustup default stable 28 | rustup set profile minimal 29 | rustup target add wasm32-unknown-unknown 30 | - name: Rust Cache # cache the rust build artefacts 31 | uses: Swatinem/rust-cache@v2 32 | - name: Download and install Trunk binary 33 | run: wget -qO- https://github.com/thedodd/trunk/releases/latest/download/trunk-x86_64-unknown-linux-gnu.tar.gz | tar -xzf- 34 | - name: Build # build 35 | # Environment $public_url resolves to the github project page. 36 | # If using a user/organization page, remove the `${{ github.event.repository.name }}` part. 37 | # using --public-url something will allow trunk to modify all the href paths like from favicon.ico to repo_name/favicon.ico . 38 | # this is necessary for github pages where the site is deployed to username.github.io/repo_name and all files must be requested 39 | # relatively as eframe_template/favicon.ico. if we skip public-url option, the href paths will instead request username.github.io/favicon.ico which 40 | # will obviously return error 404 not found. 41 | run: (cd demo && ../trunk build --release --public-url $public_url) 42 | env: 43 | public_url: "https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}" 44 | - name: Deploy 45 | uses: JamesIves/github-pages-deploy-action@v4 46 | with: 47 | folder: demo/dist 48 | # this option will not maintain any history of your previous pages deployment 49 | # set to false if you want all page build to be committed to your gh-pages branch history 50 | single-commit: true 51 | -------------------------------------------------------------------------------- /demo/src/main.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::all, rust_2018_idioms)] 2 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release 3 | 4 | // When compiling natively: 5 | #[cfg(not(target_arch = "wasm32"))] 6 | fn main() -> eframe::Result { 7 | env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). 8 | 9 | let native_options = eframe::NativeOptions { 10 | viewport: egui::ViewportBuilder::default() 11 | .with_inner_size([1024.0, 768.0]) 12 | .with_min_inner_size([300.0, 220.0]), 13 | ..Default::default() 14 | }; 15 | eframe::run_native( 16 | "egui_plot demo", 17 | native_options, 18 | Box::new(|cc| Ok(Box::new(demo::DemoGallery::new(&cc.egui_ctx)))), 19 | ) 20 | } 21 | 22 | // When compiling to web using trunk: 23 | #[cfg(target_arch = "wasm32")] 24 | fn main() { 25 | use eframe::wasm_bindgen::JsCast as _; 26 | 27 | // Redirect `log` message to `console.log` and friends: 28 | eframe::WebLogger::init(log::LevelFilter::Debug).ok(); 29 | 30 | let web_options = eframe::WebOptions::default(); 31 | 32 | wasm_bindgen_futures::spawn_local(async { 33 | let document = web_sys::window().expect("No window").document().expect("No document"); 34 | 35 | let canvas = document 36 | .get_element_by_id("the_canvas_id") 37 | .expect("Failed to find the_canvas_id") 38 | .dyn_into::() 39 | .expect("the_canvas_id was not a HtmlCanvasElement"); 40 | 41 | let start_result = eframe::WebRunner::new() 42 | .start( 43 | canvas, 44 | web_options, 45 | Box::new(|cc| Ok(Box::new(demo::DemoGallery::new(&cc.egui_ctx)))), 46 | ) 47 | .await; 48 | 49 | // Remove the loading text and spinner: 50 | if let Some(loading_text) = document.get_element_by_id("loading_text") { 51 | match start_result { 52 | Ok(_) => { 53 | loading_text.remove(); 54 | } 55 | Err(e) => { 56 | loading_text.set_inner_html("

The app has crashed. See the developer console for details.

"); 57 | panic!("Failed to start eframe: {e:?}"); 58 | } 59 | } 60 | } 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /egui_plot/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Simple plotting library for [`egui`](https://github.com/emilk/egui). 2 | //! 3 | //! Check out [`Plot`] for how to get started. 4 | //! 5 | //! [**Looking for maintainer!**](https://github.com/emilk/egui/issues/4705) 6 | //! 7 | //! ## Feature flags 8 | #![cfg_attr(feature = "document-features", doc = document_features::document_features!())] 9 | //! 10 | 11 | mod aesthetics; 12 | mod axis; 13 | mod bounds; 14 | mod colors; 15 | mod cursor; 16 | mod data; 17 | mod grid; 18 | mod items; 19 | mod label; 20 | mod math; 21 | mod memory; 22 | mod overlays; 23 | mod placement; 24 | mod plot; 25 | mod rect_elem; 26 | mod utils; 27 | 28 | pub use crate::aesthetics::LineStyle; 29 | pub use crate::aesthetics::MarkerShape; 30 | pub use crate::aesthetics::Orientation; 31 | pub use crate::axis::Axis; 32 | pub use crate::axis::AxisHints; 33 | pub use crate::axis::PlotTransform; 34 | pub use crate::bounds::PlotBounds; 35 | pub use crate::bounds::PlotPoint; 36 | pub use crate::colors::color_from_strength; 37 | pub use crate::cursor::Cursor; 38 | pub use crate::data::PlotPoints; 39 | pub use crate::grid::GridInput; 40 | pub use crate::grid::GridMark; 41 | pub use crate::grid::log_grid_spacer; 42 | pub use crate::grid::uniform_grid_spacer; 43 | pub use crate::items::Arrows; 44 | pub use crate::items::Bar; 45 | pub use crate::items::BarChart; 46 | pub use crate::items::BoxElem; 47 | pub use crate::items::BoxPlot; 48 | pub use crate::items::BoxSpread; 49 | pub use crate::items::ClosestElem; 50 | pub use crate::items::FilledArea; 51 | pub use crate::items::HLine; 52 | pub use crate::items::Heatmap; 53 | pub use crate::items::Line; 54 | pub use crate::items::PlotConfig; 55 | pub use crate::items::PlotGeometry; 56 | pub use crate::items::PlotImage; 57 | pub use crate::items::PlotItem; 58 | pub use crate::items::PlotItemBase; 59 | pub use crate::items::Points; 60 | pub use crate::items::Polygon; 61 | pub use crate::items::Span; 62 | pub use crate::items::Text; 63 | pub use crate::items::VLine; 64 | pub use crate::label::LabelFormatter; 65 | pub use crate::label::default_label_formatter; 66 | pub use crate::label::format_number; 67 | pub use crate::memory::PlotMemory; 68 | pub use crate::overlays::ColorConflictHandling; 69 | pub use crate::overlays::CoordinatesFormatter; 70 | pub use crate::overlays::Legend; 71 | pub use crate::placement::Corner; 72 | pub use crate::placement::HPlacement; 73 | pub use crate::placement::Placement; 74 | pub use crate::placement::VPlacement; 75 | pub use crate::plot::Plot; 76 | pub use crate::plot::PlotResponse; 77 | pub use crate::plot::PlotUi; 78 | -------------------------------------------------------------------------------- /egui_plot/src/memory.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | use egui::Context; 4 | use egui::Id; 5 | use egui::Pos2; 6 | use egui::Vec2b; 7 | 8 | use crate::axis::PlotTransform; 9 | use crate::bounds::PlotBounds; 10 | 11 | /// Information about the plot that has to persist between frames. 12 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] 13 | #[derive(Clone)] 14 | pub struct PlotMemory { 15 | /// Indicates if the plot uses automatic bounds. 16 | /// 17 | /// This is set to `false` whenever the user modifies 18 | /// the bounds, for example by moving or zooming. 19 | pub auto_bounds: Vec2b, 20 | 21 | /// Hovered legend item if any. 22 | pub hovered_legend_item: Option, 23 | 24 | /// Which items _not_ to show? 25 | pub hidden_items: ahash::HashSet, 26 | 27 | /// The transform from last frame. 28 | pub(crate) transform: PlotTransform, 29 | 30 | /// Allows to remember the first click position when performing a boxed zoom 31 | pub(crate) last_click_pos_for_zoom: Option, 32 | 33 | /// The thickness of each of the axes the previous frame. 34 | /// 35 | /// This is used in the next frame to make the axes thicker 36 | /// in order to fit the labels, if necessary. 37 | pub(crate) x_axis_thickness: BTreeMap, 38 | pub(crate) y_axis_thickness: BTreeMap, 39 | } 40 | 41 | impl PlotMemory { 42 | #[inline] 43 | pub fn transform(&self) -> PlotTransform { 44 | self.transform 45 | } 46 | 47 | #[inline] 48 | pub fn set_transform(&mut self, t: PlotTransform) { 49 | self.transform = t; 50 | } 51 | 52 | /// Plot-space bounds. 53 | #[inline] 54 | pub fn bounds(&self) -> &PlotBounds { 55 | self.transform.bounds() 56 | } 57 | 58 | /// Plot-space bounds. 59 | #[inline] 60 | pub fn set_bounds(&mut self, bounds: PlotBounds) { 61 | self.transform.set_bounds(bounds); 62 | } 63 | } 64 | 65 | #[cfg(feature = "serde")] 66 | impl PlotMemory { 67 | pub fn load(ctx: &Context, id: Id) -> Option { 68 | ctx.data_mut(|d| d.get_persisted(id)) 69 | } 70 | 71 | pub fn store(self, ctx: &Context, id: Id) { 72 | ctx.data_mut(|d| d.insert_persisted(id, self)); 73 | } 74 | } 75 | 76 | #[cfg(not(feature = "serde"))] 77 | impl PlotMemory { 78 | pub fn load(ctx: &Context, id: Id) -> Option { 79 | ctx.data_mut(|d| d.get_temp(id)) 80 | } 81 | 82 | pub fn store(self, ctx: &Context, id: Id) { 83 | ctx.data_mut(|d| d.insert_temp(id, self)); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /examples/plot_span/src/app.rs: -------------------------------------------------------------------------------- 1 | use eframe::egui; 2 | use eframe::egui::Align2; 3 | use eframe::egui::Color32; 4 | use eframe::egui::Response; 5 | use eframe::epaint::Hsva; 6 | use egui_plot::Corner; 7 | use egui_plot::Legend; 8 | use egui_plot::Line; 9 | use egui_plot::Plot; 10 | use egui_plot::PlotPoints; 11 | use egui_plot::Span; 12 | 13 | #[derive(Default)] 14 | pub struct PlotSpanDemo { 15 | show_legend: bool, 16 | } 17 | 18 | impl PlotSpanDemo { 19 | pub fn show_controls(&mut self, ui: &mut egui::Ui) -> Response { 20 | ui.horizontal(|ui| { 21 | ui.checkbox(&mut self.show_legend, "Show legend"); 22 | }) 23 | .response 24 | } 25 | 26 | #[expect(clippy::needless_pass_by_ref_mut, reason = "to allow mutation of self")] 27 | pub fn show_plot(&mut self, ui: &mut egui::Ui) -> Response { 28 | let mut plot = Plot::new("Span Demo"); 29 | if self.show_legend { 30 | plot = plot.legend(Legend::default().position(Corner::LeftBottom)); 31 | } 32 | 33 | plot.show(ui, |plot_ui| { 34 | let span = Span::new("Span 1", -10.0..=-5.0) 35 | .border_style(egui_plot::LineStyle::Dashed { length: 50.0 }) 36 | .border_width(3.0); 37 | plot_ui.span(span); 38 | 39 | let span = Span::new("Span 2", 0.0..=1.0); 40 | plot_ui.span(span); 41 | 42 | let span = Span::new("Span 3", 5.0..=6.0).axis(egui_plot::Axis::Y); 43 | plot_ui.span(span); 44 | 45 | let color4: Color32 = Hsva::new(0.1, 0.85, 0.5, 0.15).into(); 46 | let span4 = Span::new("Span 4", 5.0..=5.5) 47 | .border_width(0.0) 48 | .fill(color4) 49 | .label_align(Align2::LEFT_BOTTOM); 50 | plot_ui.span(span4.clone()); 51 | 52 | let color5: Color32 = Hsva::new(0.3, 0.85, 0.5, 0.15).into(); 53 | let span5 = Span::new("Span 5", 5.5..=6.5) 54 | .border_width(0.0) 55 | .fill(color5) 56 | .label_align(Align2::LEFT_BOTTOM); 57 | plot_ui.span(span5.clone()); 58 | 59 | let span = span4.clone().range(6.5..=8.0); 60 | plot_ui.span(span); 61 | 62 | let span = span5.clone().range(8.0..=10.0); 63 | plot_ui.span(span); 64 | 65 | let span = Span::new("Infinite span", 10.0..=f64::INFINITY); 66 | plot_ui.span(span); 67 | 68 | let sine_points = PlotPoints::from_explicit_callback(|x| x.sin(), .., 5000); 69 | let sine_line = Line::new("Sine", sine_points).name("Sine"); 70 | 71 | plot_ui.line(sine_line); 72 | }) 73 | .response 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /RELEASES.md: -------------------------------------------------------------------------------- 1 | # Releases 2 | ## Cadence 3 | We usually release a new major `egui_plot` whenever there is a new major `egui` release, which is usually once every two months or so. 4 | 5 | 6 | ## Versioning 7 | The version in `main` is always the version of the last published crate. 8 | This is so that users can easily patch their `egui_plot` to the version on `main` if they want to. 9 | 10 | 11 | ## Governance 12 | Releases are generally done by [emilk](https://github.com/emilk/), but the [rerun-io](https://github.com/rerun-io/) organization (where emilk is CTO) also has publish rights to all the crates. 13 | 14 | 15 | ## Rust version policy 16 | Our Minimum Supported Rust Version (MSRV) is always _at least_ two minor release behind the latest Rust version. This means users of `egui_plot` aren't forced to update to the very latest Rust version. 17 | 18 | We don't update the MSRV in a patch release, unless we really, really need to. 19 | 20 | 21 | # Release process 22 | ## Patch release 23 | * [ ] Make a branch off of the latest release 24 | * [ ] cherry-pick what you want to release 25 | * [ ] run `cargo semver-checks` 26 | 27 | ## Optional polish before a major release 28 | * [ ] improve the demo a bit 29 | * [ ] `cargo update` 30 | * [ ] `cargo outdated` (or manually look for outdated crates in each `Cargo.toml`) 31 | 32 | ## Release testing 33 | * [ ] IMPORTANT: test with [Rerun](https://github.com/rerun-io/rerun/) 34 | * [ ] test the demo app 35 | * [ ] test the web demo 36 | - test on mobile 37 | - test on chromium 38 | * [ ] `./scripts/check.sh` 39 | * [ ] check that CI is green 40 | 41 | ## Preparation 42 | * [ ] optionally record gif or take a screenshot for `CHANGELOG.md` release note (and later twitter post) 43 | * [ ] update changelogs using `scripts/generate_changelog.py` 44 | - For major releases, always use the upcoming release, e.g. `--version 0.x.0` 45 | * [ ] bump version numbers in workspace `Cargo.toml` 46 | 47 | ## Actual release 48 | I usually do this all on the `main` branch, but doing it in a release branch is also fine, as long as you remember to merge it into `main` later. 49 | 50 | * [ ] `./scripts/generate_changelog.py --version 0.x.0` 51 | * [ ] bump version number in `Cargo.toml` 52 | * [ ] `cargo clippy` 53 | * [ ] `git commit -m 'Release 0.x.0 - summary'` 54 | * [ ] `cargo publish -p egui_plot` 55 | * [ ] `git tag -a 0.x.0 -m 'Release 0.x.0 - summary'` 56 | * [ ] `git pull --tags ; git tag -d latest && git tag -a latest -m 'Latest release' && git push --tags origin latest --force ; git push --tags` 57 | * [ ] merge release PR or push to `main` 58 | * [ ] check that CI is green 59 | * [ ] do a GitHub release: https://github.com/emilk/egui_plot/releases/new 60 | * [ ] wait for documentation to build: https://docs.rs/releases/queue 61 | -------------------------------------------------------------------------------- /examples/legend/src/app.rs: -------------------------------------------------------------------------------- 1 | use eframe::egui; 2 | use eframe::egui::Response; 3 | use egui_plot::Corner; 4 | use egui_plot::Legend; 5 | use egui_plot::Line; 6 | use egui_plot::Plot; 7 | use egui_plot::PlotPoints; 8 | 9 | #[derive(Default)] 10 | pub struct LegendExample { 11 | config: Legend, 12 | } 13 | 14 | impl LegendExample { 15 | fn line_with_slope<'a>(slope: f64) -> Line<'a> { 16 | Line::new( 17 | "line with slope", 18 | PlotPoints::from_explicit_callback(move |x| slope * x, .., 100), 19 | ) 20 | } 21 | 22 | fn sin<'a>() -> Line<'a> { 23 | Line::new("sin(x)", PlotPoints::from_explicit_callback(move |x| x.sin(), .., 100)) 24 | } 25 | 26 | fn cos<'a>() -> Line<'a> { 27 | Line::new("cos(x)", PlotPoints::from_explicit_callback(move |x| x.cos(), .., 100)) 28 | } 29 | 30 | pub fn show_plot(&mut self, ui: &mut egui::Ui) -> Response { 31 | let Self { config } = self; 32 | let legend_plot = Plot::new("legend_demo").legend(config.clone()).data_aspect(1.0); 33 | legend_plot 34 | .show(ui, |plot_ui| { 35 | plot_ui.line(Self::line_with_slope(0.5).name("lines")); 36 | plot_ui.line(Self::line_with_slope(1.0).name("lines")); 37 | plot_ui.line(Self::line_with_slope(2.0).name("lines")); 38 | plot_ui.line(Self::sin().name("sin(x)")); 39 | plot_ui.line(Self::cos().name("cos(x)")); 40 | }) 41 | .response 42 | } 43 | 44 | pub fn show_controls(&mut self, ui: &mut egui::Ui) -> Response { 45 | let Self { config } = self; 46 | egui::Grid::new("settings") 47 | .show(ui, |ui| { 48 | ui.label("Text style:"); 49 | ui.horizontal(|ui| { 50 | let all_text_styles = ui.style().text_styles(); 51 | for style in all_text_styles { 52 | ui.selectable_value(&mut config.text_style, style.clone(), style.to_string()); 53 | } 54 | }); 55 | ui.end_row(); 56 | 57 | ui.label("Position:"); 58 | ui.horizontal(|ui| { 59 | Corner::all().for_each(|position| { 60 | ui.selectable_value(&mut config.position, position, format!("{position:?}")); 61 | }); 62 | }); 63 | ui.end_row(); 64 | 65 | ui.label("Opacity:"); 66 | ui.add( 67 | egui::DragValue::new(&mut config.background_alpha) 68 | .speed(0.02) 69 | .range(0.0..=1.0), 70 | ); 71 | ui.end_row(); 72 | }) 73 | .response 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /examples/box_plot/src/app.rs: -------------------------------------------------------------------------------- 1 | use eframe::egui; 2 | use eframe::egui::Response; 3 | use egui_plot::BoxElem; 4 | use egui_plot::BoxPlot; 5 | use egui_plot::BoxSpread; 6 | use egui_plot::Legend; 7 | use egui_plot::Plot; 8 | 9 | #[derive(Default)] 10 | pub struct BoxPlotExample { 11 | horizontal: bool, 12 | } 13 | 14 | impl BoxPlotExample { 15 | pub fn show_controls(&mut self, ui: &mut egui::Ui) -> Response { 16 | ui.horizontal(|ui| { 17 | ui.label("Orientation:"); 18 | ui.selectable_value(&mut self.horizontal, false, "Vertical"); 19 | ui.selectable_value(&mut self.horizontal, true, "Horizontal"); 20 | }) 21 | .response 22 | } 23 | 24 | pub fn show_plot(&self, ui: &mut egui::Ui) -> Response { 25 | let yellow = egui::Color32::from_rgb(248, 252, 168); 26 | let mut box1 = BoxPlot::new( 27 | "Experiment A", 28 | vec![ 29 | BoxElem::new(0.5, BoxSpread::new(1.5, 2.2, 2.5, 2.6, 3.1)).name("Day 1"), 30 | BoxElem::new(2.5, BoxSpread::new(0.4, 1.0, 1.1, 1.4, 2.1)).name("Day 2"), 31 | BoxElem::new(4.5, BoxSpread::new(1.7, 2.0, 2.2, 2.5, 2.9)).name("Day 3"), 32 | ], 33 | ); 34 | 35 | let mut box2 = BoxPlot::new( 36 | "Experiment B", 37 | vec![ 38 | BoxElem::new(1.0, BoxSpread::new(0.2, 0.5, 1.0, 2.0, 2.7)).name("Day 1"), 39 | BoxElem::new(3.0, BoxSpread::new(1.5, 1.7, 2.1, 2.9, 3.3)) 40 | .name("Day 2: interesting") 41 | .stroke(egui::Stroke::new(1.5, yellow)) 42 | .fill(yellow.linear_multiply(0.2)), 43 | BoxElem::new(5.0, BoxSpread::new(1.3, 2.0, 2.3, 2.9, 4.0)).name("Day 3"), 44 | ], 45 | ); 46 | 47 | let mut box3 = BoxPlot::new( 48 | "Experiment C", 49 | vec![ 50 | BoxElem::new(1.5, BoxSpread::new(2.1, 2.2, 2.6, 2.8, 3.0)).name("Day 1"), 51 | BoxElem::new(3.5, BoxSpread::new(1.3, 1.5, 1.9, 2.2, 2.4)).name("Day 2"), 52 | BoxElem::new(5.5, BoxSpread::new(0.2, 0.4, 1.0, 1.3, 1.5)).name("Day 3"), 53 | ], 54 | ); 55 | 56 | if self.horizontal { 57 | box1 = box1.horizontal(); 58 | box2 = box2.horizontal(); 59 | box3 = box3.horizontal(); 60 | } 61 | 62 | Plot::new("Box Plot Demo") 63 | .legend(Legend::default()) 64 | .allow_zoom(egui::Vec2b::new(false, false)) 65 | .allow_drag(egui::Vec2b::new(false, false)) 66 | .allow_scroll(egui::Vec2b::new(false, false)) 67 | .show(ui, |plot_ui| { 68 | plot_ui.box_plot(box1); 69 | plot_ui.box_plot(box2); 70 | plot_ui.box_plot(box3); 71 | }) 72 | .response 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /examples/markers/src/app.rs: -------------------------------------------------------------------------------- 1 | use eframe::egui; 2 | use eframe::egui::Response; 3 | use egui_plot::Legend; 4 | use egui_plot::MarkerShape; 5 | use egui_plot::Plot; 6 | use egui_plot::Points; 7 | 8 | pub struct MarkerDemo { 9 | fill_markers: bool, 10 | marker_radius: f32, 11 | automatic_colors: bool, 12 | marker_color: egui::Color32, 13 | } 14 | 15 | impl Default for MarkerDemo { 16 | fn default() -> Self { 17 | Self { 18 | fill_markers: true, 19 | marker_radius: 5.0, 20 | automatic_colors: true, 21 | marker_color: egui::Color32::GREEN, 22 | } 23 | } 24 | } 25 | 26 | impl MarkerDemo { 27 | fn markers<'a>(&self) -> Vec> { 28 | MarkerShape::all() 29 | .enumerate() 30 | .map(|(i, marker)| { 31 | let y_offset = i as f64 * 0.5 + 1.0; 32 | let mut points = Points::new( 33 | "marker", 34 | vec![ 35 | [1.0, 0.0 + y_offset], 36 | [2.0, 0.5 + y_offset], 37 | [3.0, 0.0 + y_offset], 38 | [4.0, 0.5 + y_offset], 39 | [5.0, 0.0 + y_offset], 40 | [6.0, 0.5 + y_offset], 41 | ], 42 | ) 43 | .id(format!("marker_{i}")) 44 | .name(format!("{marker:?}")) 45 | .filled(self.fill_markers) 46 | .radius(self.marker_radius) 47 | .shape(marker); 48 | 49 | if !self.automatic_colors { 50 | points = points.color(self.marker_color); 51 | } 52 | 53 | points 54 | }) 55 | .collect() 56 | } 57 | 58 | pub fn show_plot(&self, ui: &mut egui::Ui) -> Response { 59 | let markers_plot = Plot::new("markers_demo") 60 | .data_aspect(1.0) 61 | .legend(Legend::default().title("Markers")); 62 | markers_plot 63 | .show(ui, |plot_ui| { 64 | for marker in self.markers() { 65 | plot_ui.points(marker); 66 | } 67 | }) 68 | .response 69 | } 70 | 71 | pub fn show_controls(&mut self, ui: &mut egui::Ui) -> Response { 72 | ui.horizontal(|ui| { 73 | ui.checkbox(&mut self.fill_markers, "Fill"); 74 | ui.add( 75 | egui::DragValue::new(&mut self.marker_radius) 76 | .speed(0.1) 77 | .range(0.0..=f64::INFINITY) 78 | .prefix("Radius: "), 79 | ); 80 | ui.checkbox(&mut self.automatic_colors, "Automatic colors"); 81 | if !self.automatic_colors { 82 | ui.color_edit_button_srgba(&mut self.marker_color); 83 | } 84 | }) 85 | .response 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /examples/filled_area/src/app.rs: -------------------------------------------------------------------------------- 1 | use std::f64::consts::PI; 2 | 3 | use eframe::egui; 4 | use eframe::egui::Response; 5 | use egui_plot::FilledArea; 6 | use egui_plot::Legend; 7 | use egui_plot::Line; 8 | use egui_plot::Plot; 9 | use egui_plot::PlotPoints; 10 | 11 | pub struct FilledAreaExample { 12 | delta_lower: f64, 13 | delta_upper: f64, 14 | num_points: usize, 15 | } 16 | 17 | impl Default for FilledAreaExample { 18 | fn default() -> Self { 19 | Self { 20 | delta_lower: 0.5, 21 | delta_upper: 0.5, 22 | num_points: 100, 23 | } 24 | } 25 | } 26 | 27 | impl FilledAreaExample { 28 | pub fn show_controls(&mut self, ui: &mut egui::Ui) -> Response { 29 | ui.horizontal(|ui| { 30 | ui.vertical(|ui| { 31 | ui.label("Lower bound offset:"); 32 | ui.add( 33 | egui::Slider::new(&mut self.delta_lower, 0.0..=2.0) 34 | .text("δ lower") 35 | .step_by(0.1), 36 | ); 37 | }); 38 | ui.vertical(|ui| { 39 | ui.label("Upper bound offset:"); 40 | ui.add( 41 | egui::Slider::new(&mut self.delta_upper, 0.0..=2.0) 42 | .text("δ upper") 43 | .step_by(0.1), 44 | ); 45 | }); 46 | ui.vertical(|ui| { 47 | ui.label("Number of points:"); 48 | ui.add(egui::Slider::new(&mut self.num_points, 10..=500).text("points")); 49 | }); 50 | }) 51 | .response 52 | } 53 | 54 | pub fn show_plot(&self, ui: &mut egui::Ui) -> Response { 55 | // Generate x values 56 | let xs: Vec = (0..self.num_points) 57 | .map(|i| i as f64 * 4.0 * PI / self.num_points as f64) 58 | .collect(); 59 | 60 | // Generate sin(x) and bounds 61 | let ys: Vec = xs.iter().map(|&x| x.sin()).collect(); 62 | let ys_min: Vec = ys.iter().map(|&y| y - self.delta_lower).collect(); 63 | let ys_max: Vec = ys.iter().map(|&y| y + self.delta_upper).collect(); 64 | 65 | // Create the center line 66 | let sin_line = Line::new( 67 | "sin(x)", 68 | xs.iter() 69 | .zip(ys.iter()) 70 | .map(|(&x, &y)| [x, y]) 71 | .collect::>(), 72 | ) 73 | .color(egui::Color32::from_rgb(200, 100, 100)); 74 | 75 | // Create the filled area 76 | let filled_area = FilledArea::new("sin(x) +/- deltas", &xs, &ys_min, &ys_max) 77 | .fill_color(egui::Color32::from_rgba_unmultiplied(100, 200, 100, 50)); 78 | 79 | Plot::new("Filled Area Demo") 80 | .legend(Legend::default()) 81 | .show(ui, |plot_ui| { 82 | plot_ui.add(filled_area); 83 | plot_ui.line(sin_line); 84 | }) 85 | .response 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /examples/save_plot/src/app.rs: -------------------------------------------------------------------------------- 1 | use eframe::egui; 2 | use eframe::egui::Response; 3 | use egui_plot::Legend; 4 | use egui_plot::Line; 5 | use egui_plot::Plot; 6 | use egui_plot::PlotPoints; 7 | 8 | #[derive(Default)] 9 | pub struct SavePlotExample { 10 | plot_rect: Option, 11 | } 12 | 13 | impl SavePlotExample { 14 | pub fn show_plot(&mut self, ui: &mut egui::Ui) -> Response { 15 | let my_plot = Plot::new("My Plot").legend(Legend::default()); 16 | 17 | // let's create a dummy line in the plot 18 | let graph: Vec<[f64; 2]> = vec![[0.0, 1.0], [2.0, 3.0], [3.0, 2.0]]; 19 | let inner = my_plot.show(ui, |plot_ui| { 20 | plot_ui.line(Line::new("curve", PlotPoints::from(graph))); 21 | }); 22 | // Remember the position of the plot 23 | self.plot_rect = Some(inner.response.rect); 24 | 25 | #[cfg(not(target_arch = "wasm32"))] 26 | { 27 | // Check for returned screenshot: 28 | let ctx = ui.ctx(); 29 | let screenshot = ctx.input(|i| { 30 | for event in &i.raw.events { 31 | if let egui::Event::Screenshot { image, .. } = event { 32 | return Some(image.clone()); 33 | } 34 | } 35 | None 36 | }); 37 | if let (Some(screenshot), Some(plot_location)) = (screenshot, self.plot_rect) { 38 | if let Some(mut path) = rfd::FileDialog::new().save_file() { 39 | path.set_extension("png"); 40 | 41 | // for a full size application, we should put this in a different thread, 42 | // so that the GUI doesn't lag during saving 43 | 44 | let pixels_per_point = ctx.pixels_per_point(); 45 | let plot = screenshot.region(&plot_location, Some(pixels_per_point)); 46 | // save the plot to png 47 | let result = image::save_buffer( 48 | &path, 49 | plot.as_raw(), 50 | plot.width() as u32, 51 | plot.height() as u32, 52 | image::ColorType::Rgba8, 53 | ); 54 | match result { 55 | Ok(()) => eprintln!("Image saved to {}", path.display()), 56 | Err(err) => eprintln!("Failed to save image to {}: {err}", path.display()), 57 | } 58 | } 59 | } 60 | } 61 | #[cfg(target_arch = "wasm32")] 62 | { 63 | eprintln!("File saving is not supported on WASM targets"); 64 | } 65 | 66 | inner.response 67 | } 68 | 69 | #[expect(clippy::unused_self, reason = "required by the example template")] 70 | pub fn show_controls(&self, ui: &mut egui::Ui) -> Response { 71 | let response = ui.button("Save Plot"); 72 | if response.clicked() { 73 | ui.ctx() 74 | .send_viewport_cmd(egui::ViewportCommand::Screenshot(Default::default())); 75 | } 76 | response 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /examples/performance/src/app.rs: -------------------------------------------------------------------------------- 1 | use eframe::egui; 2 | use eframe::egui::Response; 3 | use egui_plot::MarkerShape; 4 | use egui_plot::Plot; 5 | use egui_plot::Points; 6 | 7 | /// Simple LCG pseudo-random number generator. Returns a value in [0.0, 1.0]. 8 | fn rng(state: &mut u64) -> f64 { 9 | let mut x = *state; 10 | x ^= x << 13; 11 | x ^= x >> 7; 12 | x ^= x << 17; 13 | *state = x; 14 | (x as f64) / (u64::MAX as f64) 15 | } 16 | 17 | fn make_markers(target_count: usize) -> Vec<[f64; 2]> { 18 | let mut state = 42u64; 19 | (0..target_count).map(|_| [rng(&mut state), rng(&mut state)]).collect() 20 | } 21 | 22 | pub struct PerformanceDemo { 23 | target_count: usize, 24 | marker_radius: f32, 25 | markers: Vec<[f64; 2]>, 26 | marker_shape: MarkerShape, 27 | } 28 | 29 | impl Default for PerformanceDemo { 30 | fn default() -> Self { 31 | Self { 32 | target_count: 100, 33 | marker_radius: 1.0, 34 | markers: make_markers(100), 35 | marker_shape: MarkerShape::Circle, 36 | } 37 | } 38 | } 39 | 40 | impl PerformanceDemo { 41 | pub fn show_plot(&self, ui: &mut egui::Ui) -> Response { 42 | Plot::new("performance_demo") 43 | .data_aspect(1.0) 44 | .show(ui, |plot_ui| { 45 | plot_ui.points( 46 | Points::new("markers", self.markers.clone()) 47 | .radius(self.marker_radius) 48 | .shape(self.marker_shape) 49 | .filled(true), 50 | ); 51 | }) 52 | .response 53 | } 54 | 55 | pub fn show_controls(&mut self, ui: &mut egui::Ui) -> Response { 56 | ui.ctx().request_repaint(); // Continuous repaint for FPS counter 57 | let fps = (1.0 / ui.ctx().input(|i| i.stable_dt)).round(); 58 | 59 | ui.horizontal(|ui| { 60 | ui.label("Markers:"); 61 | if ui 62 | .add( 63 | egui::DragValue::new(&mut self.target_count) 64 | .speed(100) 65 | .range(100..=10_000_000), 66 | ) 67 | .changed() 68 | { 69 | self.markers = make_markers(self.target_count); 70 | } 71 | 72 | ui.label("Radius:"); 73 | ui.add( 74 | egui::DragValue::new(&mut self.marker_radius) 75 | .speed(0.1) 76 | .range(0.5..=5.0), 77 | ); 78 | 79 | ui.label("Shape:"); 80 | egui::ComboBox::from_id_salt("marker_shape") 81 | .selected_text(format!("{:?}", self.marker_shape)) 82 | .show_ui(ui, |ui| { 83 | for shape in MarkerShape::all() { 84 | ui.selectable_value(&mut self.marker_shape, shape, format!("{shape:?}")); 85 | } 86 | }); 87 | 88 | ui.label(format!("FPS: {fps}")); 89 | }); 90 | 91 | ui.label("Note: Less than 100k markers should work fine, beyond that may cause issues."); 92 | ui.response() 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /examples/linked_axes/src/app.rs: -------------------------------------------------------------------------------- 1 | use eframe::egui; 2 | use eframe::egui::Response; 3 | use eframe::egui::TextWrapMode; 4 | use eframe::egui::Vec2b; 5 | use egui_plot::Line; 6 | use egui_plot::Plot; 7 | use egui_plot::PlotPoints; 8 | 9 | pub struct LinkedAxesExample { 10 | link_axis: Vec2b, 11 | link_cursor: Vec2b, 12 | } 13 | 14 | impl Default for LinkedAxesExample { 15 | fn default() -> Self { 16 | Self { 17 | link_axis: Vec2b::new(true, true), 18 | link_cursor: Vec2b::new(true, true), 19 | } 20 | } 21 | } 22 | 23 | impl LinkedAxesExample { 24 | fn line_with_slope<'a>(slope: f64) -> Line<'a> { 25 | Line::new( 26 | "line with slope", 27 | PlotPoints::from_explicit_callback(move |x| slope * x, .., 100), 28 | ) 29 | } 30 | 31 | fn sin<'a>() -> Line<'a> { 32 | Line::new("sin(x)", PlotPoints::from_explicit_callback(move |x| x.sin(), .., 100)) 33 | } 34 | 35 | fn cos<'a>() -> Line<'a> { 36 | Line::new("cos(x)", PlotPoints::from_explicit_callback(move |x| x.cos(), .., 100)) 37 | } 38 | 39 | fn configure_plot(plot_ui: &mut egui_plot::PlotUi<'_>) { 40 | plot_ui.line(Self::line_with_slope(0.5)); 41 | plot_ui.line(Self::line_with_slope(1.0)); 42 | plot_ui.line(Self::line_with_slope(2.0)); 43 | plot_ui.line(Self::sin()); 44 | plot_ui.line(Self::cos()); 45 | } 46 | 47 | pub fn show_plot(&self, ui: &mut egui::Ui) -> Response { 48 | ui.style_mut().wrap_mode = Some(TextWrapMode::Extend); 49 | let link_group_id = ui.id().with("linked_demo"); 50 | ui.horizontal(|ui| { 51 | Plot::new("left-top") 52 | .data_aspect(1.0) 53 | .width(250.0) 54 | .height(250.0) 55 | .link_axis(link_group_id, self.link_axis) 56 | .link_cursor(link_group_id, self.link_cursor) 57 | .show(ui, Self::configure_plot); 58 | Plot::new("right-top") 59 | .data_aspect(2.0) 60 | .width(150.0) 61 | .height(250.0) 62 | .y_axis_label("y") 63 | .y_axis_position(egui_plot::HPlacement::Right) 64 | .link_axis(link_group_id, self.link_axis) 65 | .link_cursor(link_group_id, self.link_cursor) 66 | .show(ui, Self::configure_plot); 67 | }); 68 | Plot::new("left-bottom") 69 | .data_aspect(0.5) 70 | .width(250.0) 71 | .height(150.0) 72 | .x_axis_label("x") 73 | .link_axis(link_group_id, self.link_axis) 74 | .link_cursor(link_group_id, self.link_cursor) 75 | .show(ui, Self::configure_plot) 76 | .response 77 | } 78 | 79 | pub fn show_controls(&mut self, ui: &mut egui::Ui) -> Response { 80 | ui.horizontal(|ui| { 81 | ui.label("Linked axes:"); 82 | ui.checkbox(&mut self.link_axis.x, "X"); 83 | ui.checkbox(&mut self.link_axis.y, "Y"); 84 | }); 85 | ui.horizontal(|ui| { 86 | ui.label("Linked cursors:"); 87 | ui.checkbox(&mut self.link_cursor.x, "X"); 88 | ui.checkbox(&mut self.link_cursor.y, "Y"); 89 | }) 90 | .response 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /examples/stacked_bar/src/app.rs: -------------------------------------------------------------------------------- 1 | use eframe::egui; 2 | use eframe::egui::Response; 3 | use egui_plot::Bar; 4 | use egui_plot::BarChart; 5 | use egui_plot::Legend; 6 | use egui_plot::Plot; 7 | 8 | pub struct StackedBarExample { 9 | vertical: bool, 10 | } 11 | 12 | impl Default for StackedBarExample { 13 | fn default() -> Self { 14 | Self { vertical: true } 15 | } 16 | } 17 | 18 | impl StackedBarExample { 19 | pub fn show_controls(&mut self, ui: &mut egui::Ui) -> Response { 20 | ui.horizontal(|ui| { 21 | ui.label("Orientation:"); 22 | ui.selectable_value(&mut self.vertical, true, "Vertical"); 23 | ui.selectable_value(&mut self.vertical, false, "Horizontal"); 24 | }) 25 | .response 26 | } 27 | 28 | pub fn show_plot(&self, ui: &mut egui::Ui) -> Response { 29 | let mut chart1 = BarChart::new( 30 | "chart1", 31 | vec![ 32 | Bar::new(0.5, 1.0).name("Day 1"), 33 | Bar::new(1.5, 3.0).name("Day 2"), 34 | Bar::new(2.5, 1.0).name("Day 3"), 35 | Bar::new(3.5, 2.0).name("Day 4"), 36 | Bar::new(4.5, 4.0).name("Day 5"), 37 | ], 38 | ) 39 | .width(0.7) 40 | .name("Set 1"); 41 | 42 | let mut chart2 = BarChart::new( 43 | "chart2", 44 | vec![ 45 | Bar::new(0.5, 1.0), 46 | Bar::new(1.5, 1.5), 47 | Bar::new(2.5, 0.1), 48 | Bar::new(3.5, 0.7), 49 | Bar::new(4.5, 0.8), 50 | ], 51 | ) 52 | .width(0.7) 53 | .name("Set 2") 54 | .stack_on(&[&chart1]); 55 | 56 | let mut chart3 = BarChart::new( 57 | "chart3", 58 | vec![ 59 | Bar::new(0.5, -0.5), 60 | Bar::new(1.5, 1.0), 61 | Bar::new(2.5, 0.5), 62 | Bar::new(3.5, -1.0), 63 | Bar::new(4.5, 0.3), 64 | ], 65 | ) 66 | .width(0.7) 67 | .name("Set 3") 68 | .stack_on(&[&chart1, &chart2]); 69 | 70 | let mut chart4 = BarChart::new( 71 | "chart4", 72 | vec![ 73 | Bar::new(0.5, 0.5), 74 | Bar::new(1.5, 1.0), 75 | Bar::new(2.5, 0.5), 76 | Bar::new(3.5, -0.5), 77 | Bar::new(4.5, -0.5), 78 | ], 79 | ) 80 | .width(0.7) 81 | .name("Set 4") 82 | .stack_on(&[&chart1, &chart2, &chart3]); 83 | 84 | if !self.vertical { 85 | chart1 = chart1.horizontal(); 86 | chart2 = chart2.horizontal(); 87 | chart3 = chart3.horizontal(); 88 | chart4 = chart4.horizontal(); 89 | } 90 | 91 | Plot::new("Stacked Bar Chart Demo") 92 | .legend(Legend::default()) 93 | .data_aspect(1.0) 94 | .show(ui, |plot_ui| { 95 | plot_ui.bar_chart(chart1); 96 | plot_ui.bar_chart(chart2); 97 | plot_ui.bar_chart(chart3); 98 | plot_ui.bar_chart(chart4); 99 | }) 100 | .response 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | # Copied from https://github.com/rerun-io/rerun_template 2 | # 3 | # There is also a scripts/clippy_wasm/clippy.toml which forbids some methods that are not available in wasm. 4 | 5 | # ----------------------------------------------------------------------------- 6 | # Section identical to scripts/clippy_wasm/clippy.toml: 7 | 8 | msrv = "1.88" 9 | 10 | allow-unwrap-in-tests = true 11 | 12 | # https://doc.rust-lang.org/nightly/clippy/lint_configuration.html#avoid-breaking-exported-api 13 | # We want suggestions, even if it changes public API. 14 | avoid-breaking-exported-api = false 15 | 16 | excessive-nesting-threshold = 8 17 | 18 | max-fn-params-bools = 1 19 | 20 | # https://rust-lang.github.io/rust-clippy/master/index.html#/large_include_file 21 | max-include-file-size = 1000000 22 | 23 | # https://rust-lang.github.io/rust-clippy/master/index.html#/large_stack_frames 24 | stack-size-threshold = 512000 25 | 26 | too-many-lines-threshold = 200 27 | 28 | # ----------------------------------------------------------------------------- 29 | 30 | # https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_macros 31 | disallowed-macros = [ 32 | { path = "std::dbg", reason = "use tracing macros instead" }, 33 | ] 34 | 35 | # https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods 36 | disallowed-methods = [ 37 | { path = "egui_extras::TableBody::row", reason = "`row` doesn't scale. Use `rows` instead." }, 38 | { path = "glam::Vec2::normalize", reason = "normalize() can create NaNs. Use try_normalize or normalize_or_zero" }, 39 | { path = "glam::Vec3::normalize", reason = "normalize() can create NaNs. Use try_normalize or normalize_or_zero" }, 40 | { path = "sha1::Digest::new", reason = "SHA1 is cryptographically broken" }, 41 | { path = "std::env::temp_dir", reason = "Use the tempdir crate instead" }, 42 | { path = "std::panic::catch_unwind", reason = "We compile with `panic = 'abort'`" }, 43 | { path = "std::thread::spawn", reason = "Use `std::thread::Builder` and name the thread" }, 44 | 45 | # There are many things that aren't allowed on wasm, 46 | # but we cannot disable them all here (because of e.g. https://github.com/rust-lang/rust-clippy/issues/10406) 47 | # so we do that in `scripts/clippy_wasm/clippy.toml` instead. 48 | ] 49 | 50 | # https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_names 51 | disallowed-names = [] 52 | 53 | # https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types 54 | disallowed-types = [ 55 | { path = "ring::digest::SHA1_FOR_LEGACY_USE_ONLY", reason = "SHA1 is cryptographically broken" }, 56 | 57 | { path = "std::sync::Condvar", reason = "Use parking_lot instead" }, 58 | { path = "std::sync::Mutex", reason = "Use parking_lot instead" }, 59 | { path = "std::sync::RwLock", reason = "Use parking_lot instead" }, 60 | 61 | # "std::sync::Once", # enabled for now as the `log_once` macro uses it internally 62 | ] 63 | 64 | # Allow-list of words for markdown in docstrings https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown 65 | doc-valid-idents = [ 66 | # You must also update the same list in `scripts/clippy_wasm/clippy.toml`! 67 | "GitHub", 68 | "GLB", 69 | "GLTF", 70 | "iOS", 71 | "macOS", 72 | "NaN", 73 | "OBJ", 74 | "OpenGL", 75 | "PyPI", 76 | "sRGB", 77 | "sRGBA", 78 | "WebGL", 79 | "WebSocket", 80 | "WebSockets", 81 | ] 82 | -------------------------------------------------------------------------------- /scripts/clippy_wasm/clippy.toml: -------------------------------------------------------------------------------- 1 | # Copied from https://github.com/rerun-io/rerun_template 2 | 3 | # This is used by the CI so we can forbid some methods that are not available in wasm. 4 | # 5 | # We cannot forbid all these methods in the main `clippy.toml` because of 6 | # https://github.com/rust-lang/rust-clippy/issues/10406 7 | 8 | # ----------------------------------------------------------------------------- 9 | # Section identical to the main clippy.toml: 10 | 11 | msrv = "1.88" 12 | 13 | allow-unwrap-in-tests = true 14 | 15 | # https://doc.rust-lang.org/nightly/clippy/lint_configuration.html#avoid-breaking-exported-api 16 | # We want suggestions, even if it changes public API. 17 | avoid-breaking-exported-api = false 18 | 19 | excessive-nesting-threshold = 8 20 | 21 | max-fn-params-bools = 1 22 | 23 | # https://rust-lang.github.io/rust-clippy/master/index.html#/large_include_file 24 | max-include-file-size = 1000000 25 | 26 | too-many-lines-threshold = 200 27 | 28 | # ----------------------------------------------------------------------------- 29 | 30 | # https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods 31 | disallowed-methods = [ 32 | { path = "crossbeam::channel::Receiver::into_iter", reason = "Cannot block on Web" }, 33 | { path = "crossbeam::channel::Receiver::iter", reason = "Cannot block on Web" }, 34 | { path = "crossbeam::channel::Receiver::recv_timeout", reason = "Cannot block on Web" }, 35 | { path = "crossbeam::channel::Receiver::recv", reason = "Cannot block on Web" }, 36 | { path = "poll_promise::Promise::block_and_take", reason = "Cannot block on Web" }, 37 | { path = "poll_promise::Promise::block_until_ready_mut", reason = "Cannot block on Web" }, 38 | { path = "poll_promise::Promise::block_until_ready", reason = "Cannot block on Web" }, 39 | { path = "rayon::spawn", reason = "Cannot spawn threads on wasm" }, 40 | { path = "std::sync::mpsc::Receiver::into_iter", reason = "Cannot block on Web" }, 41 | { path = "std::sync::mpsc::Receiver::iter", reason = "Cannot block on Web" }, 42 | { path = "std::sync::mpsc::Receiver::recv_timeout", reason = "Cannot block on Web" }, 43 | { path = "std::sync::mpsc::Receiver::recv", reason = "Cannot block on Web" }, 44 | { path = "std::thread::spawn", reason = "Cannot spawn threads on wasm" }, 45 | { path = "std::time::Duration::elapsed", reason = "use `web-time` crate instead for wasm/web compatibility" }, 46 | { path = "std::time::Instant::now", reason = "use `web-time` crate instead for wasm/web compatibility" }, 47 | { path = "std::time::SystemTime::now", reason = "use `web-time` or `time` crates instead for wasm/web compatibility" }, 48 | ] 49 | 50 | # https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types 51 | disallowed-types = [ 52 | { path = "instant::SystemTime", reason = "Known bugs. Use web-time." }, 53 | { path = "std::thread::Builder", reason = "Cannot spawn threads on wasm" }, 54 | # { path = "std::path::PathBuf", reason = "Can't read/write files on web" }, // Used in build.rs files (which is fine). 55 | ] 56 | 57 | # Allow-list of words for markdown in docstrings https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown 58 | doc-valid-idents = [ 59 | # You must also update the same list in the root `clippy.toml`! 60 | "..", 61 | "GitHub", 62 | "GLB", 63 | "GLTF", 64 | "iOS", 65 | "macOS", 66 | "NaN", 67 | "OBJ", 68 | "OpenGL", 69 | "PyPI", 70 | "sRGB", 71 | "sRGBA", 72 | "WebGL", 73 | "WebSocket", 74 | "WebSockets", 75 | ] 76 | -------------------------------------------------------------------------------- /lychee.toml: -------------------------------------------------------------------------------- 1 | # Copied from https://github.com/rerun-io/rerun_template 2 | 3 | ################################################################################ 4 | # Config for the link checker lychee. 5 | # 6 | # Download & learn more at: 7 | # https://github.com/lycheeverse/lychee 8 | # 9 | # Example config: 10 | # https://github.com/lycheeverse/lychee/blob/master/lychee.example.toml 11 | # 12 | # Run `lychee . --dump` to list all found links that are being checked. 13 | # 14 | # Note that by default lychee will only check markdown and html files, 15 | # to check any other files you have to point to them explicitly, e.g.: 16 | # `lychee **/*.rs` 17 | # To make things worse, `exclude_path` is ignored for these globs, 18 | # so local runs with lots of gitignored files will be slow. 19 | # (https://github.com/lycheeverse/lychee/issues/1405) 20 | # 21 | # This unfortunately doesn't list anything for non-glob checks. 22 | ################################################################################ 23 | 24 | # Maximum number of concurrent link checks. 25 | # Workaround for "too many open files" error on MacOS, see https://github.com/lycheeverse/lychee/issues/1248 26 | max_concurrency = 32 27 | 28 | # Check links inside `` and `
` blocks as well as Markdown code blocks.
29 | include_verbatim = true
30 | 
31 | # Proceed for server connections considered insecure (invalid TLS).
32 | insecure = true
33 | 
34 | # Maximum number of allowed retries before a link is declared dead.
35 | max_retries = 4
36 | 
37 | # Wait time between attempts in seconds.
38 | retry_wait_time = 2
39 | 
40 | # Comma-separated list of accepted status codes for valid links.
41 | accept = [
42 |   "100..=103", # Informational codes.
43 |   "200..=299", # Success codes.
44 |   "429",       # Too many requests. This is practically never a sign of a broken link.
45 | ]
46 | 
47 | 
48 | # Exclude URLs and mail addresses from checking (supports regex).
49 | exclude = [
50 |   # Strings with replacements.
51 |   '/__VIEWER_VERSION__/', # Replacement variable __VIEWER_VERSION__.
52 |   '/\$',                  # Replacement variable $.
53 |   '/GIT_HASH/',           # Replacement variable GIT_HASH.
54 |   '\{\}',                 # Ignore links with string interpolation.
55 |   '\$relpath\^',          # Relative paths as used by rerun_cpp's doc header.
56 |   '%7B.+%7D',             # Ignore strings that look like ready to use links but contain a replacement strings. The URL escaping is for '{.+}' (this seems to be needed for html embedded urls since lychee assumes they use this encoding).
57 |   '%7B%7D',               # Ignore links with string interpolation, escaped variant.
58 | 
59 |   # Local links that require further setup.
60 |   'http://127.0.0.1',
61 |   'http://localhost',
62 |   'recording:/',      # rrd recording link.
63 |   'ws:/',
64 |   're_viewer.js',     # Build artifact that html is linking to.
65 | 
66 |   # Api endpoints.
67 |   'https://fonts.googleapis.com/', # Font API entrypoint, not a link.
68 |   'https://fonts.gstatic.com/',    # Font API entrypoint, not a link.
69 |   'https://tel.rerun.io/',         # Analytics endpoint.
70 | 
71 |   # Avoid rate limiting.
72 |   'https://crates.io/crates/.*',                   # Avoid crates.io rate-limiting
73 |   'https://github.com/Bromeon',
74 |   'https://github.com/EmbersArc',
75 |   'https://github.com/emilk',
76 |   'https://github.com/emilk/egui_plot/commit/\.*', # Ignore links to our own commits (typically in changelog).
77 |   'https://github.com/emilk/egui_plot/pull/\.*',   # Ignore links to our own pull requests (typically in changelog).
78 | ]
79 | 


--------------------------------------------------------------------------------
/deny.toml:
--------------------------------------------------------------------------------
 1 | # Copied from https://github.com/rerun-io/rerun_template
 2 | #
 3 | # https://github.com/EmbarkStudios/cargo-deny
 4 | #
 5 | # cargo-deny checks our dependency tree for copy-left licenses,
 6 | # duplicate dependencies, and rustsec advisories (https://rustsec.org/advisories).
 7 | #
 8 | # Install: `cargo install cargo-deny`
 9 | # Check: `cargo deny check`.
10 | 
11 | 
12 | # Note: running just `cargo deny check` without a `--target` can result in
13 | # false positives due to https://github.com/EmbarkStudios/cargo-deny/issues/324
14 | [graph]
15 | targets = [
16 |   { triple = "aarch64-apple-darwin" },
17 |   { triple = "i686-pc-windows-gnu" },
18 |   { triple = "i686-pc-windows-msvc" },
19 |   { triple = "i686-unknown-linux-gnu" },
20 |   { triple = "wasm32-unknown-unknown" },
21 |   { triple = "x86_64-apple-darwin" },
22 |   { triple = "x86_64-pc-windows-gnu" },
23 |   { triple = "x86_64-pc-windows-msvc" },
24 |   { triple = "x86_64-unknown-linux-gnu" },
25 |   { triple = "x86_64-unknown-linux-musl" },
26 |   { triple = "x86_64-unknown-redox" },
27 | ]
28 | all-features = true
29 | 
30 | 
31 | [advisories]
32 | version = 2
33 | ignore = [
34 |   "RUSTSEC-2024-0436", # https://rustsec.org/advisories/RUSTSEC-2024-0436 - paste is unmaintained - https://github.com/dtolnay/paste
35 | ]
36 | 
37 | 
38 | [bans]
39 | multiple-versions = "deny"
40 | wildcards = "deny"
41 | deny = [
42 |   { name = "cmake", reason = "It has hurt me too much" },
43 |   { name = "openssl-sys", reason = "Use rustls" },
44 |   { name = "openssl", reason = "Use rustls" },
45 | ]
46 | 
47 | skip = []
48 | skip-tree = [{ name = "eframe", reason = "Dev-dependency" }]
49 | 
50 | 
51 | [licenses]
52 | version = 2
53 | private = { ignore = true }
54 | confidence-threshold = 0.93 # We want really high confidence when inferring licenses from text
55 | allow = [
56 |   "Apache-2.0 WITH LLVM-exception", # https://spdx.org/licenses/LLVM-exception.html
57 |   "Apache-2.0",                     # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)
58 |   "BSD-2-Clause",                   # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd)
59 |   "BSD-3-Clause",                   # https://tldrlegal.com/license/bsd-3-clause-license-(revised)
60 |   "BSL-1.0",                        # https://tldrlegal.com/license/boost-software-license-1.0-explained
61 |   "CC0-1.0",                        # https://creativecommons.org/publicdomain/zero/1.0/
62 |   "ISC",                # https://www.tldrlegal.com/license/isc-license
63 |   # "MIT-0",                          # https://choosealicense.com/licenses/mit-0/
64 |   "MIT", # https://tldrlegal.com/license/mit-license
65 |   "MPL-2.0",                        # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11. Used by webpki-roots on Linux.
66 |   "OFL-1.1", # https://spdx.org/licenses/OFL-1.1.html
67 |   # "OpenSSL",                        # https://www.openssl.org/source/license.html - used on Linux
68 |   "Ubuntu-font-1.0",                # https://ubuntu.com/legal/font-licence
69 |   # "Unicode-DFS-2016",               # https://spdx.org/licenses/Unicode-DFS-2016.html
70 |   "Unicode-3.0", # https://spdx.org/licenses/Unicode-3.0.html
71 |   "Zlib",        # https://tldrlegal.com/license/zlib-libpng-license-(zlib)
72 | ]
73 | exceptions = []
74 | 
75 | [[licenses.clarify]]
76 | name = "webpki"
77 | expression = "ISC"
78 | license-files = [{ path = "LICENSE", hash = 0x001c7e6c }]
79 | 
80 | [[licenses.clarify]]
81 | name = "ring"
82 | expression = "MIT AND ISC AND OpenSSL"
83 | license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }]
84 | 
85 | 
86 | [sources]
87 | unknown-registry = "deny"
88 | unknown-git = "deny"
89 | 
90 | [sources.allow-org]
91 | github = ["emilk", "rerun-io"]
92 | 


--------------------------------------------------------------------------------
/examples/interaction/src/app.rs:
--------------------------------------------------------------------------------
 1 | use eframe::egui;
 2 | use eframe::egui::Response;
 3 | use egui_plot::Line;
 4 | use egui_plot::Plot;
 5 | use egui_plot::PlotPoint;
 6 | use egui_plot::PlotPoints;
 7 | use egui_plot::PlotResponse;
 8 | 
 9 | #[derive(Default)]
10 | pub struct InteractionExample {
11 |     last_bounds: Option,
12 |     last_screen_pos: egui::Pos2,
13 |     last_pointer_coordinate: Option,
14 |     last_pointer_drag_delta: egui::Vec2,
15 |     last_hovered: bool,
16 |     last_hovered_item: Option,
17 | }
18 | 
19 | impl InteractionExample {
20 |     pub fn show_plot(&mut self, ui: &mut egui::Ui) -> Response {
21 |         let id = ui.make_persistent_id("interaction_demo");
22 | 
23 |         let plot = Plot::new("interaction_demo").id(id).height(300.0);
24 | 
25 |         let PlotResponse {
26 |             response,
27 |             inner: (screen_pos, pointer_coordinate, pointer_coordinate_drag_delta, bounds, hovered),
28 |             hovered_plot_item,
29 |             ..
30 |         } = plot.show(ui, |plot_ui| {
31 |             plot_ui.line(
32 |                 Line::new("sin", PlotPoints::from_explicit_callback(move |x| x.sin(), .., 100))
33 |                     .color(egui::Color32::RED),
34 |             );
35 |             plot_ui.line(
36 |                 Line::new("cos", PlotPoints::from_explicit_callback(move |x| x.cos(), .., 100))
37 |                     .color(egui::Color32::BLUE),
38 |             );
39 | 
40 |             (
41 |                 plot_ui.screen_from_plot(PlotPoint::new(0.0, 0.0)),
42 |                 plot_ui.pointer_coordinate(),
43 |                 plot_ui.pointer_coordinate_drag_delta(),
44 |                 plot_ui.plot_bounds(),
45 |                 plot_ui.response().hovered(),
46 |             )
47 |         });
48 | 
49 |         // Store for display in controls
50 |         self.last_bounds = Some(bounds);
51 |         self.last_screen_pos = screen_pos;
52 |         self.last_pointer_coordinate = pointer_coordinate;
53 |         self.last_pointer_drag_delta = pointer_coordinate_drag_delta;
54 |         self.last_hovered = hovered;
55 |         self.last_hovered_item = hovered_plot_item;
56 | 
57 |         response
58 |     }
59 | 
60 |     pub fn show_controls(&self, ui: &mut egui::Ui) -> Response {
61 |         if let Some(bounds) = &self.last_bounds {
62 |             ui.label(format!(
63 |                 "plot bounds: min: {:.02?}, max: {:.02?}",
64 |                 bounds.min(),
65 |                 bounds.max()
66 |             ));
67 |         }
68 | 
69 |         ui.label(format!(
70 |             "origin in screen coordinates: x: {:.02}, y: {:.02}",
71 |             self.last_screen_pos.x, self.last_screen_pos.y
72 |         ));
73 |         ui.label(format!("plot hovered: {}", self.last_hovered));
74 |         let coordinate_text = if let Some(coordinate) = self.last_pointer_coordinate {
75 |             format!("x: {:.02}, y: {:.02}", coordinate.x, coordinate.y)
76 |         } else {
77 |             "None".to_owned()
78 |         };
79 |         ui.label(format!("pointer coordinate: {coordinate_text}"));
80 |         let coordinate_text = format!(
81 |             "x: {:.02}, y: {:.02}",
82 |             self.last_pointer_drag_delta.x, self.last_pointer_drag_delta.y
83 |         );
84 |         ui.label(format!("pointer coordinate drag delta: {coordinate_text}"));
85 | 
86 |         let hovered_item = if self.last_hovered_item == Some(egui::Id::new("sin")) {
87 |             "red sin"
88 |         } else if self.last_hovered_item == Some(egui::Id::new("cos")) {
89 |             "blue cos"
90 |         } else {
91 |             "none"
92 |         };
93 |         ui.label(format!("hovered plot item: {hovered_item}"))
94 |     }
95 | }
96 | 


--------------------------------------------------------------------------------
/egui_plot/src/utils.rs:
--------------------------------------------------------------------------------
 1 | use egui::Color32;
 2 | use egui::FontId;
 3 | use egui::Painter;
 4 | 
 5 | // Utility function to find a truncated candidate to fit a text label into a
 6 | // given width. If the width is large enough for the text, a string with the
 7 | // full text will be returned. If the width is too small to display the full
 8 | // text, it finds the longest text with "..." appended at the end that we can
 9 | // display within the given width. If the width is too small to display the
10 | // first character followed by "..." then we return an empty string.
11 | pub(crate) fn find_name_candidate(name: &str, width: f32, painter: &Painter, font_id: &FontId) -> String {
12 |     let galley = painter.layout_no_wrap(name.to_owned(), font_id.clone(), Color32::BLACK);
13 | 
14 |     if galley.size().x <= width || name.is_empty() {
15 |         return name.to_owned();
16 |     }
17 | 
18 |     // If we don't have enough space for the name to be displayed in the span, we
19 |     // search for the longest candidate that fits, where a candidate is a
20 |     // truncated version of the name followed by "...".
21 |     let chars: Vec = name.chars().collect();
22 | 
23 |     // First test the minimum candidate which is the first letter followed by "..."
24 |     let mut min_candidate = chars[0].to_string();
25 |     min_candidate.push_str("...");
26 |     let galley = painter.layout_no_wrap(min_candidate.clone(), font_id.clone(), Color32::BLACK);
27 |     if galley.size().x > width {
28 |         return String::new();
29 |     }
30 | 
31 |     // Then do a binary search to find the longest possible candidate
32 |     let mut low = 1;
33 |     let mut high = chars.len();
34 |     let mut best = String::new();
35 | 
36 |     while low <= high {
37 |         let mid = usize::midpoint(low, high);
38 |         let mut candidate: String = chars[..mid].iter().collect();
39 |         candidate.push_str("...");
40 | 
41 |         let candidate_width = painter
42 |             .layout_no_wrap(candidate.clone(), font_id.clone(), Color32::BLACK)
43 |             .size()
44 |             .x;
45 | 
46 |         if candidate_width <= width {
47 |             best = candidate;
48 |             low = mid + 1;
49 |         } else {
50 |             high = mid.saturating_sub(1);
51 |             if high == 0 {
52 |                 break;
53 |             }
54 |         }
55 |     }
56 | 
57 |     best
58 | }
59 | 
60 | /// Initialize logging so that the testing framework can capture log output.
61 | ///
62 | /// Call this at the top of a test function to see log output from that test.
63 | /// The logging output will only be shown when the following conditions are met:
64 | /// - The test fails
65 | /// - The `RUST_LOG` environment variable is set to a level at or below the message level
66 | ///
67 | /// When running a specific test:
68 | /// ```sh
69 | /// RUST_LOG=info cargo test auto_bounds_true
70 | /// ```
71 | ///
72 | /// If the something causes the test to panic so hard that it never shows logging output,
73 | /// you can use `--nocapture` to see log output as it happens:
74 | /// ```sh
75 | /// RUST_LOG=info cargo test auto_bounds_true -- --nocapture
76 | /// ```
77 | #[cfg(test)]
78 | pub(crate) fn init_test_logger() {
79 |     use std::io::Write as _;
80 |     let _result: Result<(), log::SetLoggerError> = env_logger::builder()
81 |         .is_test(true)
82 |         .format(|buf, record| {
83 |             let level_style = buf.default_level_style(record.level());
84 |             writeln!(
85 |                 buf,
86 |                 "[{level_style}{}{level_style:#} {}:{}]   {}",
87 |                 record.level(),
88 |                 record.file().unwrap_or("unknown"),
89 |                 record.line().unwrap_or(0),
90 |                 record.args()
91 |             )
92 |         })
93 |         .try_init();
94 | }
95 | 


--------------------------------------------------------------------------------
/examples/items/src/app.rs:
--------------------------------------------------------------------------------
 1 | use std::f64::consts::TAU;
 2 | 
 3 | use eframe::egui;
 4 | use eframe::egui::Response;
 5 | use egui::vec2;
 6 | use egui_plot::Arrows;
 7 | use egui_plot::HLine;
 8 | use egui_plot::Legend;
 9 | use egui_plot::Line;
10 | use egui_plot::Plot;
11 | use egui_plot::PlotImage;
12 | use egui_plot::PlotPoint;
13 | use egui_plot::PlotPoints;
14 | use egui_plot::Points;
15 | use egui_plot::Polygon;
16 | use egui_plot::Text;
17 | use egui_plot::VLine;
18 | 
19 | #[derive(Default)]
20 | pub struct ItemsExample {
21 |     texture: Option,
22 | }
23 | 
24 | impl ItemsExample {
25 |     pub fn show_plot(&mut self, ui: &mut egui::Ui) -> Response {
26 |         let n = 100;
27 |         let mut sin_values: Vec<_> = (0..=n)
28 |             .map(|i| egui::remap(i as f64, 0.0..=n as f64, -TAU..=TAU))
29 |             .map(|i| [i, i.sin()])
30 |             .collect();
31 | 
32 |         let line = Line::new("sin(x)", sin_values.split_off(n / 2)).fill(-1.5);
33 |         let polygon = Polygon::new(
34 |             "polygon",
35 |             PlotPoints::from_parametric_callback(
36 |                 |t| (4.0 * t.sin() + 2.0 * t.cos(), 4.0 * t.cos() + 2.0 * t.sin()),
37 |                 0.0..TAU,
38 |                 100,
39 |             ),
40 |         );
41 |         let points = Points::new("sin(x)", sin_values).stems(-1.5).radius(1.0);
42 | 
43 |         let arrows = {
44 |             let pos_radius = 8.0;
45 |             let tip_radius = 7.0;
46 |             let arrow_origins =
47 |                 PlotPoints::from_parametric_callback(|t| (pos_radius * t.sin(), pos_radius * t.cos()), 0.0..TAU, 36);
48 |             let arrow_tips =
49 |                 PlotPoints::from_parametric_callback(|t| (tip_radius * t.sin(), tip_radius * t.cos()), 0.0..TAU, 36);
50 |             Arrows::new("arrows", arrow_origins, arrow_tips)
51 |         };
52 | 
53 |         let texture: &egui::TextureHandle = self.texture.get_or_insert_with(|| {
54 |             ui.ctx()
55 |                 .load_texture("plot_demo", egui::ColorImage::example(), Default::default())
56 |         });
57 |         let image = PlotImage::new(
58 |             "image",
59 |             texture,
60 |             PlotPoint::new(0.0, 10.0),
61 |             5.0 * vec2(texture.aspect_ratio(), 1.0),
62 |         );
63 | 
64 |         let plot = Plot::new("items_demo")
65 |             .legend(
66 |                 Legend::default()
67 |                     .position(egui_plot::Corner::RightBottom)
68 |                     .title("Items"),
69 |             )
70 |             .show_x(false)
71 |             .show_y(false)
72 |             .data_aspect(1.0);
73 |         plot.show(ui, |plot_ui| {
74 |             plot_ui.hline(HLine::new("Lines horizontal", 9.0));
75 |             plot_ui.hline(HLine::new("Lines horizontal", -9.0));
76 |             plot_ui.vline(VLine::new("Lines vertical", 9.0));
77 |             plot_ui.vline(VLine::new("Lines vertical", -9.0));
78 |             plot_ui.line(line.name("Line with fill").id("line_with_fill"));
79 |             plot_ui.polygon(polygon.name("Convex polygon").id("convex_polygon"));
80 |             plot_ui.points(points.name("Points with stems").id("points_with_stems"));
81 |             plot_ui.text(Text::new("Text", PlotPoint::new(-3.0, -3.0), "wow").id("text0"));
82 |             plot_ui.text(Text::new("Text", PlotPoint::new(-2.0, 2.5), "so graph").id("text1"));
83 |             plot_ui.text(Text::new("Text", PlotPoint::new(3.0, 3.0), "much color").id("text2"));
84 |             plot_ui.text(Text::new("Text", PlotPoint::new(2.5, -2.0), "such plot").id("text3"));
85 |             plot_ui.image(image.name("Image"));
86 |             plot_ui.arrows(arrows.name("Arrows"));
87 |         })
88 |         .response
89 |     }
90 | 
91 |     #[expect(clippy::unused_self, reason = "required by the example template")]
92 |     pub fn show_controls(&self, ui: &mut egui::Ui) -> Response {
93 |         // No controls for this example
94 |         ui.scope(|_ui| {}).response
95 |     }
96 | }
97 | 


--------------------------------------------------------------------------------
/examples/heatmap/src/app.rs:
--------------------------------------------------------------------------------
  1 | use eframe::egui;
  2 | use eframe::egui::Color32;
  3 | use eframe::egui::Response;
  4 | use eframe::egui::vec2;
  5 | use egui_plot::Legend;
  6 | use egui_plot::Plot;
  7 | 
  8 | pub const TURBO_COLORMAP: [Color32; 10] = [
  9 |     Color32::from_rgb(48, 18, 59),
 10 |     Color32::from_rgb(35, 106, 141),
 11 |     Color32::from_rgb(30, 160, 140),
 12 |     Color32::from_rgb(88, 200, 98),
 13 |     Color32::from_rgb(164, 223, 39),
 14 |     Color32::from_rgb(228, 223, 14),
 15 |     Color32::from_rgb(250, 187, 13),
 16 |     Color32::from_rgb(246, 135, 8),
 17 |     Color32::from_rgb(213, 68, 2),
 18 |     Color32::from_rgb(122, 4, 2),
 19 | ];
 20 | 
 21 | pub struct HeatmapDemo {
 22 |     tick: f64,
 23 |     animate: bool,
 24 |     show_labels: bool,
 25 |     palette: Vec,
 26 |     rows: usize,
 27 |     cols: usize,
 28 | }
 29 | 
 30 | impl Default for HeatmapDemo {
 31 |     fn default() -> Self {
 32 |         Self {
 33 |             tick: 0.0,
 34 |             animate: false,
 35 |             show_labels: true,
 36 |             palette: TURBO_COLORMAP.to_vec(),
 37 |             rows: 5,
 38 |             cols: 5,
 39 |         }
 40 |     }
 41 | }
 42 | 
 43 | impl HeatmapDemo {
 44 |     pub fn show_controls(&mut self, ui: &mut egui::Ui) -> Response {
 45 |         ui.horizontal(|ui| {
 46 |             ui.group(|ui| {
 47 |                 ui.vertical(|ui| {
 48 |                     ui.checkbox(&mut self.animate, "Animate");
 49 |                     if self.animate {
 50 |                         ui.ctx().request_repaint();
 51 |                         self.tick += 1.0;
 52 |                     }
 53 |                     ui.checkbox(&mut self.show_labels, "Show labels");
 54 |                 });
 55 |             });
 56 |             ui.group(|ui| {
 57 |                 ui.vertical(|ui| {
 58 |                     ui.add(egui::Slider::new(&mut self.rows, 0..=50).text("Rows"));
 59 |                     ui.add(egui::Slider::new(&mut self.cols, 0..=50).text("Columns"));
 60 |                 });
 61 |             });
 62 |             ui.group(|ui| {
 63 |                 ui.horizontal(|ui| {
 64 |                     ui.add_enabled_ui(self.palette.len() > 1, |ui| {
 65 |                         if ui.button("Pop color").clicked() {
 66 |                             self.palette.pop();
 67 |                         }
 68 |                     });
 69 |                     if ui.button("Push color").clicked()
 70 |                         && let Some(last) = self.palette.last()
 71 |                     {
 72 |                         self.palette.push(*last);
 73 |                     }
 74 |                 });
 75 |                 ui.horizontal(|ui| {
 76 |                     for color in &mut self.palette {
 77 |                         ui.color_edit_button_srgba(color);
 78 |                     }
 79 |                 })
 80 |             })
 81 |         })
 82 |         .response
 83 |     }
 84 | 
 85 |     #[expect(clippy::needless_pass_by_ref_mut, reason = "to allow mutation of self")]
 86 |     pub fn show_plot(&mut self, ui: &mut egui::Ui) -> Response {
 87 |         let mut values = Vec::new();
 88 |         for y in 0..self.rows {
 89 |             for x in 0..self.cols {
 90 |                 let y = y as f64;
 91 |                 let x = x as f64;
 92 |                 let cols = self.cols as f64;
 93 |                 let rows = self.rows as f64;
 94 |                 values.push(((x + self.tick) / rows).sin() + ((y + self.tick) / cols).cos());
 95 |             }
 96 |         }
 97 | 
 98 |         let heatmap = egui_plot::Heatmap::new(values, self.cols)
 99 |             .palette(&self.palette)
100 |             .highlight(true)
101 |             .show_labels(self.show_labels);
102 | 
103 |         Plot::new("Heatmap Demo")
104 |             .legend(Legend::default())
105 |             .allow_zoom(false)
106 |             .allow_scroll(false)
107 |             .allow_drag(false)
108 |             .allow_axis_zoom_drag(false)
109 |             .allow_boxed_zoom(false)
110 |             .set_margin_fraction(vec2(0.0, 0.0))
111 |             .show(ui, |plot_ui| {
112 |                 plot_ui.heatmap(heatmap);
113 |             })
114 |             .response
115 |     }
116 | }
117 | 


--------------------------------------------------------------------------------
/ECOSYSTEM.md:
--------------------------------------------------------------------------------
 1 | # Plotting libraries in Rust
 2 | 
 3 | I searched over published crates on crates.io in November 2025 and found some interesting libraries.
 4 | I briefly describe their functionality/main limitations here. 
 5 | 
 6 | Note that as far as I've looked, `egui_plot` is the only plotting library that supports plot interactions straight through Rust, without going through Javascript or other binding layers.
 7 | Also, `egui_plot` produces a list of painting commands that are sent to a backend renderer, which can be GPU accelerated and easily integrated into other GUIs.
 8 | Therefore, `egui_plot` can efficiently render large number of (interactive) plot items.
 9 | See [`egui`](https://github.com/emilk/egui) for more info about GPU integration.
10 | 
11 | ## Rust rendering libraries
12 | 
13 | | Name                                                        | Description                                                                     |
14 | |-------------------------------------------------------------|---------------------------------------------------------------------------------|
15 | | [`plotters`](https://crates.io/crates/plotters)             | Pure Rust, interesting! But interactivity seems to be done via Javascript only. |
16 | | [`graplot`](https://crates.io/crates/graplot)               | Pure Rust, but inactive                                                         |
17 | | [`quill`](https://crates.io/crates/quill)                   | Pure Rust, SVG, basic plots                                                     |
18 | | [`plotlib`](https://crates.io/crates/plotlib)               | Pure Rust, SVG/text, basic features, looks abandoned                            |
19 | | [`rustplotlib`](https://crates.io/crates/rustplotlib)       | Pure Rust, inactive                                                             |
20 | | [`criterion-plot`](https://crates.io/crates/criterion-plot) | not maintained                                                                  |
21 | | [`runmat-plot`](https://crates.io/crates/runmat-plot)       | Uses matplotlib-like DSL                                                        |
22 | | [`cgrustplot`](https://crates.io/crates/cgrustplot)         | terminal plotting                                                               |
23 | | [`termplot`](https://crates.io/crates/termplot)             | terminal plotting                                                               |
24 | | [`lowcharts`](https://crates.io/crates/lowcharts)           | terminal plotting                                                               |
25 | 
26 | ## Wrappers around other plotting libraries
27 | 
28 | Following crates wrap other plotting libraries:
29 | 
30 | | Name                                                      | Description                                                                                                                                                                   |
31 | |-----------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
32 | | [`dear-imgui-rs`](https://crates.io/crates/dear-imgui-rs) | C/C++ bindings to https://github.com/ocornut/imgui. IMGUI is probably the most interesting library out there, as it is also immediate-mode based like `egui` and `egui_plot`. |
33 | | [`gnuplot`](https://crates.io/crates/gnuplot)             | C/C++ bindings to http://www.gnuplot.info/                                                                                                                                    |
34 | | [`plotly`](https://crates.io/crates/plotly)               | JS wrapper                                                                                                                                                                    |
35 | | [`charming`](https://crates.io/crates/charming)           | JS wrapper                                                                                                                                                                    |
36 | | [`charts-rs`](https://crates.io/crates/charts-rs)         | JS wrapper                                                                                                                                                                    |
37 | | [`plotpy`](https://crates.io/crates/plotpy)               | Python wrapper                                                                                                                                                                |
38 | | [`poloto`](https://crates.io/crates/poloto)               | SVG, no interaction                                                                                                                                                           |
39 | 


--------------------------------------------------------------------------------
/egui_plot/src/aesthetics.rs:
--------------------------------------------------------------------------------
  1 | use egui::Shape;
  2 | use egui::Stroke;
  3 | use egui::epaint::ColorMode;
  4 | use egui::epaint::PathStroke;
  5 | use emath::Pos2;
  6 | use emath::Rect;
  7 | use emath::pos2;
  8 | 
  9 | /// Solid, dotted, dashed, etc.
 10 | #[derive(Debug, PartialEq, Clone, Copy)]
 11 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
 12 | pub enum LineStyle {
 13 |     Solid,
 14 |     Dotted { spacing: f32 },
 15 |     Dashed { length: f32 },
 16 | }
 17 | 
 18 | impl LineStyle {
 19 |     pub fn dashed_loose() -> Self {
 20 |         Self::Dashed { length: 10.0 }
 21 |     }
 22 | 
 23 |     pub fn dashed_dense() -> Self {
 24 |         Self::Dashed { length: 5.0 }
 25 |     }
 26 | 
 27 |     pub fn dotted_loose() -> Self {
 28 |         Self::Dotted { spacing: 10.0 }
 29 |     }
 30 | 
 31 |     pub fn dotted_dense() -> Self {
 32 |         Self::Dotted { spacing: 5.0 }
 33 |     }
 34 | 
 35 |     pub(crate) fn style_line(&self, line: Vec, mut stroke: PathStroke, highlight: bool, shapes: &mut Vec) {
 36 |         let path_stroke_color = match &stroke.color {
 37 |             ColorMode::Solid(c) => *c,
 38 |             ColorMode::UV(callback) => callback(Rect::from_min_max(pos2(0., 0.), pos2(0., 0.)), pos2(0., 0.)),
 39 |         };
 40 |         match line.len() {
 41 |             0 => {}
 42 |             1 => {
 43 |                 let mut radius = stroke.width / 2.0;
 44 |                 if highlight {
 45 |                     radius *= 2f32.sqrt();
 46 |                 }
 47 |                 shapes.push(Shape::circle_filled(line[0], radius, path_stroke_color));
 48 |             }
 49 |             _ => {
 50 |                 match self {
 51 |                     Self::Solid => {
 52 |                         if highlight {
 53 |                             stroke.width *= 2.0;
 54 |                         }
 55 |                         shapes.push(Shape::line(line, stroke));
 56 |                     }
 57 |                     Self::Dotted { spacing } => {
 58 |                         // Take the stroke width for the radius even though it's not "correct",
 59 |                         // otherwise the dots would become too small.
 60 |                         let mut radius = stroke.width;
 61 |                         if highlight {
 62 |                             radius *= 2f32.sqrt();
 63 |                         }
 64 |                         shapes.extend(Shape::dotted_line(&line, path_stroke_color, *spacing, radius));
 65 |                     }
 66 |                     Self::Dashed { length } => {
 67 |                         if highlight {
 68 |                             stroke.width *= 2.0;
 69 |                         }
 70 |                         let golden_ratio = (5.0_f32.sqrt() - 1.0) / 2.0; // 0.61803398875
 71 |                         shapes.extend(Shape::dashed_line(
 72 |                             &line,
 73 |                             Stroke::new(stroke.width, path_stroke_color),
 74 |                             *length,
 75 |                             length * golden_ratio,
 76 |                         ));
 77 |                     }
 78 |                 }
 79 |             }
 80 |         }
 81 |     }
 82 | }
 83 | 
 84 | impl std::fmt::Display for LineStyle {
 85 |     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 86 |         match self {
 87 |             Self::Solid => write!(f, "Solid"),
 88 |             Self::Dotted { spacing } => write!(f, "Dotted({spacing} px)"),
 89 |             Self::Dashed { length } => write!(f, "Dashed({length} px)"),
 90 |         }
 91 |     }
 92 | }
 93 | 
 94 | /// Determines whether a plot element is vertically or horizontally oriented.
 95 | #[derive(Copy, Clone, Debug, PartialEq, Eq)]
 96 | pub enum Orientation {
 97 |     Horizontal,
 98 |     Vertical,
 99 | }
100 | 
101 | impl Default for Orientation {
102 |     fn default() -> Self {
103 |         Self::Vertical
104 |     }
105 | }
106 | 
107 | /// Circle, Diamond, Square, Cross, …
108 | #[derive(Debug, PartialEq, Eq, Clone, Copy)]
109 | pub enum MarkerShape {
110 |     Circle,
111 |     Diamond,
112 |     Square,
113 |     Cross,
114 |     Plus,
115 |     Up,
116 |     Down,
117 |     Left,
118 |     Right,
119 |     Asterisk,
120 | }
121 | 
122 | impl MarkerShape {
123 |     /// Get a vector containing all marker shapes.
124 |     pub fn all() -> impl ExactSizeIterator {
125 |         [
126 |             Self::Circle,
127 |             Self::Diamond,
128 |             Self::Square,
129 |             Self::Cross,
130 |             Self::Plus,
131 |             Self::Up,
132 |             Self::Down,
133 |             Self::Left,
134 |             Self::Right,
135 |             Self::Asterisk,
136 |         ]
137 |         .iter()
138 |         .copied()
139 |     }
140 | }
141 | 


--------------------------------------------------------------------------------
/.github/workflows/rust.yml:
--------------------------------------------------------------------------------
  1 | # Copied from https://github.com/rerun-io/rerun_template
  2 | on:
  3 |   push:
  4 |     branches:
  5 |       - "main"
  6 |   pull_request:
  7 |     types: [ opened, synchronize ]
  8 | 
  9 | name: Rust
 10 | 
 11 | env:
 12 |   RUSTFLAGS: -D warnings
 13 |   RUSTDOCFLAGS: -D warnings
 14 | 
 15 | jobs:
 16 |   rust-check:
 17 |     name: Rust
 18 |     runs-on: ubuntu-latest
 19 |     steps:
 20 |       - uses: actions/checkout@v4
 21 | 
 22 |       - uses: actions-rs/toolchain@v1
 23 |         with:
 24 |           profile: default
 25 |           toolchain: 1.88.0
 26 |           override: true
 27 | 
 28 |       - name: Install packages (Linux)
 29 |         if: runner.os == 'Linux'
 30 |         uses: awalsh128/cache-apt-pkgs-action@v1.4.3
 31 |         with:
 32 |           packages: libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev libgtk-3-dev # libgtk-3-dev is used by rfd
 33 |           version: 1.0
 34 |           execute_install_scripts: true
 35 | 
 36 |       - name: Set up cargo cache
 37 |         uses: Swatinem/rust-cache@v2
 38 | 
 39 |       - name: Rustfmt
 40 |         run: cargo fmt --all -- --check
 41 | 
 42 |       - name: Lint vertical spacing
 43 |         run: ./scripts/lint.py
 44 | 
 45 |       - name: check --all-features
 46 |         run: cargo check --all-features --all-targets
 47 | 
 48 |       - name: check default features
 49 |         run: cargo check --all-targets
 50 | 
 51 |       - name: check --no-default-features
 52 |         run: cargo check --no-default-features --lib --all-targets
 53 | 
 54 |       - name: cargo doc --lib
 55 |         run: cargo doc --lib --no-deps --all-features
 56 | 
 57 |       - name: cargo doc --document-private-items
 58 |         run: cargo doc --document-private-items --no-deps --all-features
 59 | 
 60 |       - name: Clippy
 61 |         run: cargo clippy --all-targets --all-features -- -D warnings
 62 | 
 63 |   # ---------------------------------------------------------------------------
 64 | 
 65 |   check_wasm:
 66 |     name: Check wasm32
 67 |     runs-on: ubuntu-latest
 68 |     steps:
 69 |       - uses: actions/checkout@v4
 70 |       - uses: actions-rs/toolchain@v1
 71 |         with:
 72 |           profile: minimal
 73 |           toolchain: 1.88.0
 74 |           target: wasm32-unknown-unknown
 75 |           override: true
 76 |           components: clippy
 77 | 
 78 |       - name: Set up cargo cache
 79 |         uses: Swatinem/rust-cache@v2
 80 | 
 81 |       - name: Check wasm32
 82 |         run: cargo check --target wasm32-unknown-unknown --lib
 83 | 
 84 |       - name: Clippy wasm32
 85 |         env:
 86 |           CLIPPY_CONF_DIR: "scripts/clippy_wasm" # Use scripts/clippy_wasm/clippy.toml
 87 |         run: cargo clippy --target wasm32-unknown-unknown --lib -- -D warnings
 88 | 
 89 |   # ---------------------------------------------------------------------------
 90 | 
 91 |   cargo-deny:
 92 |     name: Check Rust dependencies (cargo-deny)
 93 |     runs-on: ubuntu-latest
 94 |     steps:
 95 |       - uses: actions/checkout@v3
 96 |       - uses: EmbarkStudios/cargo-deny-action@v2
 97 |         with:
 98 |           rust-version: "1.88.0"
 99 |           log-level: warn
100 |           command: check
101 | 
102 |   # ---------------------------------------------------------------------------
103 | 
104 |   trunk:
105 |     name: trunk build
106 |     runs-on: ubuntu-latest
107 |     steps:
108 |       - uses: actions/checkout@v4
109 |       - uses: actions-rs/toolchain@v1
110 |         with:
111 |           profile: minimal
112 |           toolchain: 1.88.0
113 |           target: wasm32-unknown-unknown
114 |           override: true
115 |       - name: Download and install Trunk binary
116 |         run: wget -qO- https://github.com/thedodd/trunk/releases/latest/download/trunk-x86_64-unknown-linux-gnu.tar.gz | tar -xzf-
117 |       - name: Build
118 |         run: cd demo && ../trunk build
119 | 
120 |   # ---------------------------------------------------------------------------
121 | 
122 |   tests:
123 |     name: Run tests
124 |     # We run the tests on macOS because it will run with an actual GPU,
125 |     # which is needed by the egui_kittest snapshot tests.
126 |     runs-on: macos-latest
127 | 
128 |     steps:
129 |       - uses: actions/checkout@v4
130 |         with:
131 |           lfs: true
132 |       - uses: dtolnay/rust-toolchain@master
133 |         with:
134 |           toolchain: 1.88.0
135 | 
136 |       - name: Set up cargo cache
137 |         uses: Swatinem/rust-cache@v2
138 | 
139 |       - name: Run tests
140 |         run: RUST_BACKTRACE=1 cargo test --all-features
141 | 
142 |       - name: Run doc-tests
143 |         run: RUST_BACKTRACE=1 cargo test --all-features --doc
144 | 
145 |       - name: Upload artifacts
146 |         uses: actions/upload-artifact@v4
147 |         if: failure()
148 |         with:
149 |           name: test-results
150 |           path: "**/*.png"
151 | 


--------------------------------------------------------------------------------
/egui_plot/src/items/text.rs:
--------------------------------------------------------------------------------
  1 | use std::ops::RangeInclusive;
  2 | 
  3 | use egui::Color32;
  4 | use egui::Id;
  5 | use egui::Shape;
  6 | use egui::Stroke;
  7 | use egui::TextStyle;
  8 | use egui::Ui;
  9 | use egui::WidgetText;
 10 | use egui::epaint::TextShape;
 11 | use emath::Align2;
 12 | 
 13 | use crate::axis::PlotTransform;
 14 | use crate::bounds::PlotBounds;
 15 | use crate::bounds::PlotPoint;
 16 | use crate::items::PlotGeometry;
 17 | use crate::items::PlotItem;
 18 | use crate::items::PlotItemBase;
 19 | 
 20 | impl Text {
 21 |     pub fn new(name: impl Into, position: PlotPoint, text: impl Into) -> Self {
 22 |         Self {
 23 |             base: PlotItemBase::new(name.into()),
 24 |             text: text.into(),
 25 |             position,
 26 |             color: Color32::TRANSPARENT,
 27 |             anchor: Align2::CENTER_CENTER,
 28 |         }
 29 |     }
 30 | 
 31 |     /// Text color.
 32 |     #[inline]
 33 |     pub fn color(mut self, color: impl Into) -> Self {
 34 |         self.color = color.into();
 35 |         self
 36 |     }
 37 | 
 38 |     /// Anchor position of the text. Default is `Align2::CENTER_CENTER`.
 39 |     #[inline]
 40 |     pub fn anchor(mut self, anchor: Align2) -> Self {
 41 |         self.anchor = anchor;
 42 |         self
 43 |     }
 44 | 
 45 |     /// Name of this plot item.
 46 |     ///
 47 |     /// This name will show up in the plot legend, if legends are turned on.
 48 |     ///
 49 |     /// Setting the name via this method does not change the item's id, so you
 50 |     /// can use it to change the name dynamically between frames without
 51 |     /// losing the item's state. You should make sure the name passed to
 52 |     /// [`Self::new`] is unique and stable for each item, or set unique and
 53 |     /// stable ids explicitly via [`Self::id`].
 54 |     #[expect(clippy::needless_pass_by_value, reason = "to allow various string types")]
 55 |     #[inline]
 56 |     pub fn name(mut self, name: impl ToString) -> Self {
 57 |         self.base_mut().name = name.to_string();
 58 |         self
 59 |     }
 60 | 
 61 |     /// Highlight this plot item, typically by scaling it up.
 62 |     ///
 63 |     /// If false, the item may still be highlighted via user interaction.
 64 |     #[inline]
 65 |     pub fn highlight(mut self, highlight: bool) -> Self {
 66 |         self.base_mut().highlight = highlight;
 67 |         self
 68 |     }
 69 | 
 70 |     /// Allowed hovering this item in the plot. Default: `true`.
 71 |     #[inline]
 72 |     pub fn allow_hover(mut self, hovering: bool) -> Self {
 73 |         self.base_mut().allow_hover = hovering;
 74 |         self
 75 |     }
 76 | 
 77 |     /// Sets the id of this plot item.
 78 |     ///
 79 |     /// By default the id is determined from the name passed to [`Self::new`],
 80 |     /// but it can be explicitly set to a different value.
 81 |     #[inline]
 82 |     pub fn id(mut self, id: impl Into) -> Self {
 83 |         self.base_mut().id = id.into();
 84 |         self
 85 |     }
 86 | }
 87 | 
 88 | impl PlotItem for Text {
 89 |     fn shapes(&self, ui: &Ui, transform: &PlotTransform, shapes: &mut Vec) {
 90 |         let color = if self.color == Color32::TRANSPARENT {
 91 |             ui.style().visuals.text_color()
 92 |         } else {
 93 |             self.color
 94 |         };
 95 | 
 96 |         let galley =
 97 |             self.text
 98 |                 .clone()
 99 |                 .into_galley(ui, Some(egui::TextWrapMode::Extend), f32::INFINITY, TextStyle::Small);
100 | 
101 |         let pos = transform.position_from_point(&self.position);
102 |         let rect = self.anchor.anchor_size(pos, galley.size());
103 | 
104 |         shapes.push(TextShape::new(rect.min, galley, color).into());
105 | 
106 |         if self.base.highlight {
107 |             shapes.push(Shape::rect_stroke(
108 |                 rect.expand(1.0),
109 |                 1.0,
110 |                 Stroke::new(0.5, color),
111 |                 egui::StrokeKind::Outside,
112 |             ));
113 |         }
114 |     }
115 | 
116 |     fn initialize(&mut self, _x_range: RangeInclusive) {}
117 | 
118 |     fn color(&self) -> Color32 {
119 |         self.color
120 |     }
121 | 
122 |     fn geometry(&self) -> PlotGeometry<'_> {
123 |         PlotGeometry::None
124 |     }
125 | 
126 |     fn bounds(&self) -> PlotBounds {
127 |         let mut bounds = PlotBounds::NOTHING;
128 |         bounds.extend_with(&self.position);
129 |         bounds
130 |     }
131 | 
132 |     fn base(&self) -> &PlotItemBase {
133 |         &self.base
134 |     }
135 | 
136 |     fn base_mut(&mut self) -> &mut PlotItemBase {
137 |         &mut self.base
138 |     }
139 | }
140 | 
141 | /// Text inside the plot.
142 | #[derive(Clone)]
143 | pub struct Text {
144 |     base: PlotItemBase,
145 |     pub(crate) text: WidgetText,
146 |     pub(crate) position: PlotPoint,
147 |     pub(crate) color: Color32,
148 |     pub(crate) anchor: Align2,
149 | }
150 | 


--------------------------------------------------------------------------------
/examples_utils/src/lib.rs:
--------------------------------------------------------------------------------
  1 | use eframe::egui;
  2 | 
  3 | /// Trait for examples that can be displayed in the demo gallery.
  4 | pub trait PlotExample {
  5 |     /// The name of the example. Should match directory name.
  6 |     fn name(&self) -> &'static str;
  7 | 
  8 |     /// The title of the example.
  9 |     fn title(&self) -> &'static str;
 10 | 
 11 |     /// The description of the example.
 12 |     fn description(&self) -> &'static str;
 13 | 
 14 |     /// The tags of the example.
 15 |     fn tags(&self) -> &'static [&'static str];
 16 | 
 17 |     /// The thumbnail image of the example.
 18 |     /// Should be 192x192 pixels. It is automatically generated from the
 19 |     /// screenshot of the example.
 20 |     fn thumbnail_bytes(&self) -> &'static [u8];
 21 | 
 22 |     /// The code of the example.
 23 |     fn code_bytes(&self) -> &'static [u8];
 24 | 
 25 |     /// The UI of the example.
 26 |     fn show_ui(&mut self, ui: &mut egui::Ui) -> egui::Response;
 27 | 
 28 |     /// The controls for the example.
 29 |     fn show_controls(&mut self, ui: &mut egui::Ui) -> egui::Response;
 30 | }
 31 | 
 32 | #[doc(hidden)]
 33 | #[cfg(not(target_arch = "wasm32"))]
 34 | pub mod internal {
 35 |     use std::path::PathBuf;
 36 | 
 37 |     use egui_kittest::Harness;
 38 |     use egui_kittest::SnapshotOptions;
 39 | 
 40 |     pub fn run_screenshot_test(builder: impl Fn(&mut eframe::CreationContext<'_>) -> State, manifest_dir: &str)
 41 |     where
 42 |         State: eframe::App,
 43 |     {
 44 |         let output_path = PathBuf::from(manifest_dir);
 45 |         let options = SnapshotOptions::new()
 46 |             .threshold(2.0)
 47 |             .failed_pixel_count_threshold(5)
 48 |             .output_path(output_path);
 49 | 
 50 |         // Generate main screenshot
 51 |         let mut harness = Harness::builder()
 52 |             .with_size(egui::Vec2::new(800.0, 800.0))
 53 |             .build_eframe(&builder);
 54 |         harness.run();
 55 |         harness.snapshot_options("screenshot", &options);
 56 | 
 57 |         // Generate thumbnail
 58 |         let mut thumb_harness = Harness::builder()
 59 |             .with_size(egui::Vec2::new(192.0, 192.0))
 60 |             .build_eframe(&builder);
 61 |         thumb_harness.run();
 62 |         let _ = thumb_harness.try_snapshot_options("screenshot_thumb", &options);
 63 |     }
 64 | }
 65 | 
 66 | /// Macro to generate a simple native `main` function for an `eframe` example
 67 | /// and a corresponding screenshot test. Intended to be used for [`PlotExample`]
 68 | /// implementations.
 69 | ///
 70 | /// # Example
 71 | ///
 72 | /// ```no_run,ignore
 73 | /// use examples_utils::make_main;
 74 | /// use my_example::MyExample;
 75 | ///
 76 | /// make_main!(MyExample);
 77 | /// ```
 78 | #[macro_export]
 79 | macro_rules! make_main {
 80 |     ($inner:ident) => {
 81 |         use eframe::egui;
 82 | 
 83 |         // Generate wrapper struct
 84 |         #[derive(Default)]
 85 |         pub struct AppWrapper {
 86 |             pub inner: $inner,
 87 |             pub plot_only: bool,
 88 |         }
 89 | 
 90 |         impl eframe::App for AppWrapper {
 91 |             fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
 92 |                 egui::CentralPanel::default().show(ctx, |ui| {
 93 |                     if self.plot_only {
 94 |                         self.inner.show_plot(ui);
 95 |                     } else {
 96 |                         ui.vertical(|ui| {
 97 |                             self.inner.show_controls(ui);
 98 |                             ui.separator();
 99 |                             self.inner.show_plot(ui);
100 |                         });
101 |                     }
102 |                 });
103 |             }
104 |         }
105 | 
106 |         /// Native entry-point for the example.
107 |         fn main() -> eframe::Result {
108 |             use $crate::PlotExample as _;
109 | 
110 |             env_logger::init();
111 | 
112 |             // Derive the application title from the `PlotExample` implementation.
113 |             let app_name: &'static str = <$inner as $crate::PlotExample>::title(&<$inner as Default>::default());
114 | 
115 |             let options = eframe::NativeOptions::default();
116 |             eframe::run_native(
117 |                 app_name,
118 |                 options,
119 |                 Box::new(|_cc| Ok(Box::new(AppWrapper::default()))),
120 |             )
121 |         }
122 | 
123 |         /// Screenshot tests for the example.
124 |         ///
125 |         /// This uses `egui_kittest` under the hood and is only compiled for
126 |         /// non-WASM targets.
127 |         #[cfg(all(test, not(target_arch = "wasm32")))]
128 |         mod screenshot_tests {
129 |             use super::AppWrapper;
130 | 
131 |             #[allow(non_snake_case)]
132 |             #[test]
133 |             fn $inner() {
134 |                 ::examples_utils::internal::run_screenshot_test(
135 |                     |_cc| AppWrapper {
136 |                         plot_only: true,
137 |                         ..Default::default()
138 |                     },
139 |                     env!("CARGO_MANIFEST_DIR"),
140 |                 );
141 |             }
142 |         }
143 |     };
144 | }
145 | 


--------------------------------------------------------------------------------
/examples/custom_axes/src/app.rs:
--------------------------------------------------------------------------------
  1 | use std::ops::RangeInclusive;
  2 | 
  3 | use eframe::egui;
  4 | use eframe::egui::Response;
  5 | use egui_plot::AxisHints;
  6 | use egui_plot::GridInput;
  7 | use egui_plot::GridMark;
  8 | use egui_plot::Line;
  9 | use egui_plot::Plot;
 10 | use egui_plot::PlotPoint;
 11 | use egui_plot::PlotPoints;
 12 | 
 13 | #[derive(Default)]
 14 | pub struct CustomAxesExample {}
 15 | 
 16 | impl CustomAxesExample {
 17 |     const MINS_PER_DAY: f64 = 24.0 * 60.0;
 18 |     const MINS_PER_H: f64 = 60.0;
 19 | 
 20 |     fn logistic_fn<'a>() -> Line<'a> {
 21 |         fn days(min: f64) -> f64 {
 22 |             CustomAxesExample::MINS_PER_DAY * min
 23 |         }
 24 | 
 25 |         let values = PlotPoints::from_explicit_callback(
 26 |             move |x| 1.0 / (1.0 + (-2.5 * (x / Self::MINS_PER_DAY - 2.0)).exp()),
 27 |             days(0.0)..days(5.0),
 28 |             100,
 29 |         );
 30 |         Line::new("logistic fn", values)
 31 |     }
 32 | 
 33 |     #[expect(clippy::needless_pass_by_value, reason = "to allow various range types")]
 34 |     fn x_grid(input: GridInput) -> Vec {
 35 |         let mut marks = vec![];
 36 | 
 37 |         let (min, max) = input.bounds;
 38 |         let min = min.floor() as i32;
 39 |         let max = max.ceil() as i32;
 40 | 
 41 |         for i in min..=max {
 42 |             let step_size = if i % Self::MINS_PER_DAY as i32 == 0 {
 43 |                 Self::MINS_PER_DAY
 44 |             } else if i % Self::MINS_PER_H as i32 == 0 {
 45 |                 Self::MINS_PER_H
 46 |             } else if i % 5 == 0 {
 47 |                 5.0
 48 |             } else {
 49 |                 continue;
 50 |             };
 51 | 
 52 |             marks.push(GridMark {
 53 |                 value: i as f64,
 54 |                 step_size,
 55 |             });
 56 |         }
 57 | 
 58 |         marks
 59 |     }
 60 | 
 61 |     #[expect(clippy::unused_self, reason = "required by the example template")]
 62 |     pub fn show_plot(&self, ui: &mut egui::Ui) -> Response {
 63 |         const MINS_PER_DAY: f64 = CustomAxesExample::MINS_PER_DAY;
 64 |         const MINS_PER_H: f64 = CustomAxesExample::MINS_PER_H;
 65 | 
 66 |         fn day(x: f64) -> f64 {
 67 |             (x / MINS_PER_DAY).floor()
 68 |         }
 69 | 
 70 |         fn hour(x: f64) -> f64 {
 71 |             (x.rem_euclid(MINS_PER_DAY) / MINS_PER_H).floor()
 72 |         }
 73 | 
 74 |         fn minute(x: f64) -> f64 {
 75 |             x.rem_euclid(MINS_PER_H).floor()
 76 |         }
 77 | 
 78 |         fn percent(y: f64) -> f64 {
 79 |             100.0 * y
 80 |         }
 81 | 
 82 |         let time_formatter = |mark: GridMark, _range: &RangeInclusive| {
 83 |             let minutes = mark.value;
 84 |             if !(0.0..5.0 * MINS_PER_DAY).contains(&minutes) {
 85 |                 String::new()
 86 |             } else if is_approx_integer(minutes / MINS_PER_DAY) {
 87 |                 format!("Day {}", day(minutes))
 88 |             } else {
 89 |                 format!("{h}:{m:02}", h = hour(minutes), m = minute(minutes))
 90 |             }
 91 |         };
 92 | 
 93 |         let percentage_formatter = |mark: GridMark, _range: &RangeInclusive| {
 94 |             let percent = 100.0 * mark.value;
 95 |             if is_approx_zero(percent) {
 96 |                 String::new()
 97 |             } else if is_approx_integer(percent) {
 98 |                 format!("{percent:.0}%")
 99 |             } else {
100 |                 String::new()
101 |             }
102 |         };
103 | 
104 |         let label_fmt = |_s: &str, val: &PlotPoint| {
105 |             format!(
106 |                 "Day {d}, {h}:{m:02}\n{p:.2}%",
107 |                 d = day(val.x),
108 |                 h = hour(val.x),
109 |                 m = minute(val.x),
110 |                 p = percent(val.y)
111 |             )
112 |         };
113 | 
114 |         let x_axes = vec![
115 |             AxisHints::new_x()
116 |                 .label("Time")
117 |                 .formatter(time_formatter)
118 |                 .placement(egui_plot::VPlacement::Top),
119 |             AxisHints::new_x().label("Time").formatter(time_formatter),
120 |             AxisHints::new_x().label("Value"),
121 |         ];
122 |         let y_axes = vec![
123 |             AxisHints::new_y().label("Percent").formatter(percentage_formatter),
124 |             AxisHints::new_y()
125 |                 .label("Absolute")
126 |                 .placement(egui_plot::HPlacement::Right),
127 |         ];
128 |         Plot::new("custom_axes")
129 |             .data_aspect(2.0 * MINS_PER_DAY as f32)
130 |             .custom_x_axes(x_axes)
131 |             .custom_y_axes(y_axes)
132 |             .x_grid_spacer(Self::x_grid)
133 |             .label_formatter(label_fmt)
134 |             .show(ui, |plot_ui| {
135 |                 plot_ui.line(Self::logistic_fn());
136 |             })
137 |             .response
138 |     }
139 | 
140 |     #[expect(clippy::unused_self, reason = "required by the example template")]
141 |     pub fn show_controls(&self, ui: &mut egui::Ui) -> Response {
142 |         ui.label("Zoom in on the X-axis to see hours and minutes")
143 |     }
144 | }
145 | 
146 | fn is_approx_zero(val: f64) -> bool {
147 |     val.abs() < 1e-6
148 | }
149 | 
150 | fn is_approx_integer(val: f64) -> bool {
151 |     val.fract().abs() < 1e-6
152 | }
153 | 


--------------------------------------------------------------------------------
/egui_plot/src/items/polygon.rs:
--------------------------------------------------------------------------------
  1 | use std::ops::RangeInclusive;
  2 | 
  3 | use egui::Color32;
  4 | use egui::Id;
  5 | use egui::Shape;
  6 | use egui::Stroke;
  7 | use egui::Ui;
  8 | use egui::epaint::PathStroke;
  9 | 
 10 | use crate::aesthetics::LineStyle;
 11 | use crate::axis::PlotTransform;
 12 | use crate::bounds::PlotBounds;
 13 | use crate::colors::DEFAULT_FILL_ALPHA;
 14 | use crate::data::PlotPoints;
 15 | use crate::items::PlotGeometry;
 16 | use crate::items::PlotItem;
 17 | use crate::items::PlotItemBase;
 18 | 
 19 | /// A convex polygon.
 20 | pub struct Polygon<'a> {
 21 |     base: PlotItemBase,
 22 |     pub(crate) series: PlotPoints<'a>,
 23 |     pub(crate) stroke: Stroke,
 24 |     pub(crate) fill_color: Option,
 25 |     pub(crate) style: LineStyle,
 26 | }
 27 | 
 28 | impl<'a> Polygon<'a> {
 29 |     pub fn new(name: impl Into, series: impl Into>) -> Self {
 30 |         Self {
 31 |             base: PlotItemBase::new(name.into()),
 32 |             series: series.into(),
 33 |             stroke: Stroke::new(1.0, Color32::TRANSPARENT),
 34 |             fill_color: None,
 35 |             style: LineStyle::Solid,
 36 |         }
 37 |     }
 38 | 
 39 |     /// Add a custom stroke.
 40 |     #[inline]
 41 |     pub fn stroke(mut self, stroke: impl Into) -> Self {
 42 |         self.stroke = stroke.into();
 43 |         self
 44 |     }
 45 | 
 46 |     /// Set the stroke width.
 47 |     #[inline]
 48 |     pub fn width(mut self, width: impl Into) -> Self {
 49 |         self.stroke.width = width.into();
 50 |         self
 51 |     }
 52 | 
 53 |     /// Fill color. Defaults to the stroke color with added transparency.
 54 |     #[inline]
 55 |     pub fn fill_color(mut self, color: impl Into) -> Self {
 56 |         self.fill_color = Some(color.into());
 57 |         self
 58 |     }
 59 | 
 60 |     /// Set the outline's style. Default is `LineStyle::Solid`.
 61 |     #[inline]
 62 |     pub fn style(mut self, style: LineStyle) -> Self {
 63 |         self.style = style;
 64 |         self
 65 |     }
 66 | 
 67 |     /// Name of this plot item.
 68 |     ///
 69 |     /// This name will show up in the plot legend, if legends are turned on.
 70 |     ///
 71 |     /// Setting the name via this method does not change the item's id, so you
 72 |     /// can use it to change the name dynamically between frames without
 73 |     /// losing the item's state. You should make sure the name passed to
 74 |     /// [`Self::new`] is unique and stable for each item, or set unique and
 75 |     /// stable ids explicitly via [`Self::id`].
 76 |     #[expect(clippy::needless_pass_by_value, reason = "to allow various string types")]
 77 |     #[inline]
 78 |     pub fn name(mut self, name: impl ToString) -> Self {
 79 |         self.base_mut().name = name.to_string();
 80 |         self
 81 |     }
 82 | 
 83 |     /// Highlight this plot item, typically by scaling it up.
 84 |     ///
 85 |     /// If false, the item may still be highlighted via user interaction.
 86 |     #[inline]
 87 |     pub fn highlight(mut self, highlight: bool) -> Self {
 88 |         self.base_mut().highlight = highlight;
 89 |         self
 90 |     }
 91 | 
 92 |     /// Allowed hovering this item in the plot. Default: `true`.
 93 |     #[inline]
 94 |     pub fn allow_hover(mut self, hovering: bool) -> Self {
 95 |         self.base_mut().allow_hover = hovering;
 96 |         self
 97 |     }
 98 | 
 99 |     /// Sets the id of this plot item.
100 |     ///
101 |     /// By default the id is determined from the name passed to [`Self::new`],
102 |     /// but it can be explicitly set to a different value.
103 |     #[inline]
104 |     pub fn id(mut self, id: impl Into) -> Self {
105 |         self.base_mut().id = id.into();
106 |         self
107 |     }
108 | }
109 | 
110 | impl PlotItem for Polygon<'_> {
111 |     fn shapes(&self, _ui: &Ui, transform: &PlotTransform, shapes: &mut Vec) {
112 |         let Self {
113 |             base,
114 |             series,
115 |             stroke,
116 |             fill_color,
117 |             style,
118 |             ..
119 |         } = self;
120 | 
121 |         let mut values_tf: Vec<_> = series
122 |             .points()
123 |             .iter()
124 |             .map(|v| transform.position_from_point(v))
125 |             .collect();
126 | 
127 |         let fill_color = fill_color.unwrap_or(stroke.color.linear_multiply(DEFAULT_FILL_ALPHA));
128 | 
129 |         let shape = Shape::convex_polygon(values_tf.clone(), fill_color, Stroke::NONE);
130 |         shapes.push(shape);
131 | 
132 |         if let Some(first) = values_tf.first() {
133 |             values_tf.push(*first); // close the polygon
134 |         }
135 | 
136 |         style.style_line(
137 |             values_tf,
138 |             PathStroke::new(stroke.width, stroke.color),
139 |             base.highlight,
140 |             shapes,
141 |         );
142 |     }
143 | 
144 |     fn initialize(&mut self, x_range: RangeInclusive) {
145 |         self.series.generate_points(x_range);
146 |     }
147 | 
148 |     fn color(&self) -> Color32 {
149 |         self.stroke.color
150 |     }
151 | 
152 |     fn geometry(&self) -> PlotGeometry<'_> {
153 |         PlotGeometry::Points(self.series.points())
154 |     }
155 | 
156 |     fn bounds(&self) -> PlotBounds {
157 |         self.series.bounds()
158 |     }
159 | 
160 |     fn base(&self) -> &PlotItemBase {
161 |         &self.base
162 |     }
163 | 
164 |     fn base_mut(&mut self) -> &mut PlotItemBase {
165 |         &mut self.base
166 |     }
167 | }
168 | 


--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
  1 | 
  2 | 
  3 | 
  4 | 
  5 | 
  6 | 
  7 | 
  8 | 
  9 |     
 10 |     egui_plot
 11 | 
 12 |     
 13 |     
 14 |     
 15 |     
 16 | 
 17 |     
 18 | 
 19 | 
 20 |     
 21 |     
 22 |     
 23 |     
 24 |     
 25 |     
 26 | 
 27 | 
 28 |     
 29 |     
 30 |     
 31 |     
 32 | 
 33 |     
119 | 
120 | 
121 | 
122 |     
123 |     
124 |     
125 | 
126 |     
127 |     
128 |

129 | Loading… 130 |

131 |
132 |
133 | 134 | 135 | 136 | 144 | 145 | 146 | 147 | 148 | 149 | --------------------------------------------------------------------------------