├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── general-questions.md ├── dependabot.yml └── workflows │ ├── plotters-backend.yml │ ├── plotters-bitmap.yml │ ├── plotters-core.yml │ ├── plotters-svg.yml │ ├── rust-clippy.yml │ └── wasm.yml ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE ├── README.md ├── RELEASE-NOTES.md ├── doc-template ├── examples │ ├── chart.rs │ ├── composable_elements.rs │ ├── drawing_area.rs │ ├── drawing_backends.rs │ ├── elements.rs │ └── quick_start.rs ├── gen-toc.sh ├── latest_version ├── msrv.txt ├── readme.template.md ├── readme │ ├── examples │ ├── gallery │ └── style ├── render_readme.sh ├── rustdoc │ ├── examples │ ├── gallery │ └── style └── update_readme.sh ├── plotters-backend ├── Cargo.toml ├── LICENSE ├── README.md └── src │ ├── lib.rs │ ├── rasterizer │ ├── circle.rs │ ├── line.rs │ ├── mod.rs │ ├── path.rs │ ├── polygon.rs │ └── rect.rs │ ├── style.rs │ └── text.rs ├── plotters-bitmap ├── Cargo.toml ├── LICENSE ├── README.md ├── benches │ ├── benches │ │ ├── mod.rs │ │ ├── parallel.rs │ │ └── rasterizer.rs │ └── main.rs └── src │ ├── bitmap.rs │ ├── bitmap │ ├── target.rs │ └── test.rs │ ├── bitmap_pixel │ ├── bgrx.rs │ ├── mod.rs │ ├── pixel_format.rs │ └── rgb.rs │ ├── error.rs │ ├── gif_support.rs │ └── lib.rs ├── plotters-svg ├── Cargo.toml ├── LICENSE ├── README.md └── src │ ├── lib.rs │ └── svg.rs └── plotters ├── .cargo └── config ├── Cargo.toml ├── LICENSE ├── README.md ├── benches ├── benches │ ├── data.rs │ └── mod.rs └── main.rs ├── blub.png ├── examples ├── 3d-plot.rs ├── 3d-plot2.rs ├── README.md ├── animation.rs ├── area-chart.rs ├── blit-bitmap.rs ├── boxplot.rs ├── chart.rs ├── colormaps.rs ├── console.rs ├── customized_coord.rs ├── errorbar.rs ├── full_palette.rs ├── histogram.rs ├── mandelbrot.rs ├── matshow.rs ├── nested_coord.rs ├── normal-dist.rs ├── normal-dist2.rs ├── pie.rs ├── relative_size.rs ├── sierpinski.rs ├── slc-temp.rs ├── snowflake.rs ├── stock.rs ├── tick_control.rs └── two-scales.rs └── src ├── chart ├── axes3d.rs ├── builder.rs ├── context.rs ├── context │ ├── cartesian2d │ │ ├── draw_impl.rs │ │ └── mod.rs │ └── cartesian3d │ │ ├── draw_impl.rs │ │ └── mod.rs ├── dual_coord.rs ├── mesh.rs ├── mod.rs ├── series.rs └── state.rs ├── coord ├── mod.rs ├── ranged1d │ ├── combinators │ │ ├── ckps.rs │ │ ├── group_by.rs │ │ ├── linspace.rs │ │ ├── logarithmic.rs │ │ ├── mod.rs │ │ ├── nested.rs │ │ └── partial_axis.rs │ ├── discrete.rs │ ├── mod.rs │ └── types │ │ ├── datetime.rs │ │ ├── mod.rs │ │ ├── numeric.rs │ │ └── slice.rs ├── ranged2d │ ├── cartesian.rs │ └── mod.rs ├── ranged3d │ ├── cartesian3d.rs │ ├── mod.rs │ └── projection.rs └── translate.rs ├── data ├── data_range.rs ├── float.rs ├── mod.rs └── quartiles.rs ├── drawing ├── area.rs ├── backend_impl │ ├── mocked.rs │ └── mod.rs └── mod.rs ├── element ├── basic_shapes.rs ├── basic_shapes_3d.rs ├── boxplot.rs ├── candlestick.rs ├── composable.rs ├── dynelem.rs ├── errorbar.rs ├── image.rs ├── mod.rs ├── pie.rs ├── points.rs └── text.rs ├── evcxr.rs ├── lib.rs ├── series ├── area_series.rs ├── histogram.rs ├── line_series.rs ├── mod.rs ├── point_series.rs └── surface.rs ├── style ├── color.rs ├── colors │ ├── colormaps.rs │ ├── full_palette.rs │ └── mod.rs ├── font │ ├── ab_glyph.rs │ ├── font_desc.rs │ ├── mod.rs │ ├── naive.rs │ ├── ttf.rs │ └── web.rs ├── mod.rs ├── palette.rs ├── shape.rs ├── size.rs └── text.rs └── test.rs /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: About unexpected behaviors 4 | title: "[BUG] " 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | Describe what is expected, what you actually get. 12 | It would be nice to have screenshot or result image uploaded 13 | 14 | **To Reproduce** 15 | Some minimal reproduce code is highly recommended 16 | 17 | **Version Information** 18 | Please give us what version you are using. If you are pulling `Plotters` directly from git repo, please mention this as well 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea to Plotter maintainers 4 | title: "[Feature Request]" 5 | labels: feature request 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### What is the feature ? 11 | *Detailed feature descrption* 12 | 13 | ### (Optional) Why this feature is useful and how people would use the feature ? 14 | *Explain why this feature is important* 15 | 16 | ### (Optional) Additional Information 17 | *More details are appreciated:)* 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/general-questions.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: General Questions 3 | about: Any other issues 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | If you are asking usage question, please go to discussion tab. Thanks! 11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "12:00" 8 | open-pull-requests-limit: 10 9 | ignore: 10 | - dependency-name: ttf-parser 11 | versions: 12 | - 0.10.1 13 | - 0.11.0 14 | - dependency-name: gif 15 | versions: 16 | - 0.11.1 17 | - package-ecosystem: "github-actions" 18 | directory: "/" 19 | schedule: 20 | interval: "daily" 21 | -------------------------------------------------------------------------------- /.github/workflows/plotters-backend.yml: -------------------------------------------------------------------------------- 1 | name: Plotters Backend 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build_and_test: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | os: [ubuntu-latest, windows-latest, macos-latest] 11 | steps: 12 | - uses: actions/checkout@v4 13 | with: 14 | submodules: recursive 15 | - uses: actions-rs/toolchain@v1 16 | with: 17 | toolchain: stable 18 | override: true 19 | - uses: actions-rs/cargo@v1 20 | with: 21 | command: test 22 | args: --verbose --package=plotters-backend 23 | -------------------------------------------------------------------------------- /.github/workflows/plotters-bitmap.yml: -------------------------------------------------------------------------------- 1 | name: Plotters Bitmap Backend 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build_and_test: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | os: [ubuntu-latest, windows-latest, macos-latest] 11 | steps: 12 | - uses: actions/checkout@v4 13 | with: 14 | submodules: recursive 15 | - uses: actions-rs/toolchain@v1 16 | with: 17 | toolchain: stable 18 | override: true 19 | - uses: actions-rs/cargo@v1 20 | with: 21 | command: test 22 | args: --verbose --package=plotters-bitmap 23 | - uses: actions-rs/cargo@v1 24 | with: 25 | command: test 26 | args: --verbose --no-default-features --lib --package=plotters-bitmap 27 | -------------------------------------------------------------------------------- /.github/workflows/plotters-core.yml: -------------------------------------------------------------------------------- 1 | name: Plotters Core Crate 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | doc: 7 | name: cargo-doc 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | - uses: actions-rs/toolchain@v1 12 | with: 13 | profile: minimal 14 | toolchain: stable 15 | override: true 16 | - uses: actions-rs/cargo@v1 17 | with: 18 | command: doc 19 | args: --no-deps --all-features 20 | msrv: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v4 24 | with: 25 | submodules: recursive 26 | - uses: actions-rs/toolchain@v1 27 | with: 28 | toolchain: 1.56.0 29 | override: true 30 | args: --all-features 31 | build_and_test: 32 | runs-on: ${{ matrix.os }} 33 | strategy: 34 | matrix: 35 | os: [ubuntu-latest, windows-latest, macos-latest] 36 | steps: 37 | - uses: actions/checkout@v4 38 | with: 39 | submodules: recursive 40 | - uses: actions-rs/toolchain@v1 41 | with: 42 | toolchain: stable 43 | override: true 44 | - uses: actions-rs/cargo@v1 45 | with: 46 | command: test 47 | args: --verbose 48 | - uses: actions-rs/cargo@v1 49 | with: 50 | command: test 51 | args: --verbose --no-default-features --features=svg_backend --lib 52 | test_all_features: 53 | runs-on: ubuntu-latest 54 | steps: 55 | - uses: actions/checkout@v4 56 | with: 57 | submodules: recursive 58 | - uses: actions-rs/toolchain@v1 59 | with: 60 | toolchain: stable 61 | override: true 62 | - uses: actions-rs/cargo@v1 63 | with: 64 | command: test 65 | args: --verbose --all-features 66 | run_all_examples: 67 | runs-on: ubuntu-latest 68 | steps: 69 | - uses: actions/checkout@v4 70 | with: 71 | submodules: recursive 72 | - uses: actions-rs/cargo@v1 73 | with: 74 | command: build 75 | args: --verbose --release --examples 76 | - name: Run all the examples 77 | run: | 78 | cd plotters 79 | for example in examples/*.rs 80 | do 81 | ../target/release/examples/$(basename ${example} .rs) 82 | done 83 | tar -czvf example-outputs.tar.gz plotters-doc-data 84 | - uses: actions/upload-artifact@v4 85 | with: 86 | name: example-outputs 87 | path: plotters/example-outputs.tar.gz 88 | -------------------------------------------------------------------------------- /.github/workflows/plotters-svg.yml: -------------------------------------------------------------------------------- 1 | name: Plotters SVG Backend 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build_and_test: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | os: [ubuntu-latest, windows-latest, macos-latest] 11 | steps: 12 | - uses: actions/checkout@v4 13 | with: 14 | submodules: recursive 15 | - uses: actions-rs/toolchain@v1 16 | with: 17 | toolchain: stable 18 | override: true 19 | - uses: actions-rs/cargo@v1 20 | with: 21 | command: test 22 | args: --verbose --package=plotters-svg 23 | -------------------------------------------------------------------------------- /.github/workflows/rust-clippy.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | # rust-clippy is a tool that runs a bunch of lints to catch common 6 | # mistakes in your Rust code and help improve your Rust code. 7 | # More details at https://github.com/rust-lang/rust-clippy 8 | # and https://rust-lang.github.io/rust-clippy/ 9 | 10 | name: rust-clippy analyze 11 | 12 | on: 13 | push: 14 | branches: [ "master" ] 15 | pull_request: 16 | # The branches below must be a subset of the branches above 17 | branches: [ "master" ] 18 | schedule: 19 | - cron: '44 13 * * 2' 20 | 21 | jobs: 22 | rust-clippy-analyze: 23 | name: Run rust-clippy analyzing 24 | runs-on: ubuntu-latest 25 | permissions: 26 | contents: read 27 | security-events: write 28 | steps: 29 | - name: Checkout code 30 | uses: actions/checkout@v4 31 | 32 | - name: Install Rust toolchain 33 | uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af #@v1 34 | with: 35 | profile: minimal 36 | toolchain: stable 37 | components: clippy 38 | override: true 39 | 40 | - name: Install required cargo 41 | run: cargo install clippy-sarif sarif-fmt 42 | 43 | - name: Run rust-clippy 44 | run: 45 | cargo clippy 46 | --all-features 47 | --message-format=json | clippy-sarif | tee rust-clippy-results.sarif | sarif-fmt 48 | continue-on-error: true 49 | 50 | - name: Upload analysis results to GitHub 51 | uses: github/codeql-action/upload-sarif@v3 52 | with: 53 | sarif_file: rust-clippy-results.sarif 54 | wait-for-processing: true 55 | -------------------------------------------------------------------------------- /.github/workflows/wasm.yml: -------------------------------------------------------------------------------- 1 | name: WASM Target 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v4 12 | with: 13 | submodules: recursive 14 | - name: Install WASM tool chain 15 | run: rustup target add wasm32-unknown-unknown 16 | - name: Check WASM Target Compiles 17 | run: cargo build --verbose --target=wasm32-unknown-unknown 18 | 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .*.sw* 3 | **/target 4 | .vscode/* 5 | Cargo.lock 6 | .idea 7 | plotters/*.svg 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "plotters-doc-data"] 2 | path = plotters/plotters-doc-data 3 | url = https://github.com/38/plotters-doc-data 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Thanks for contributing to `Plotters`! 2 | 3 | Here's the useful information about contributing to Plotters. 4 | 5 | # License 6 | 7 | The `Plotters` project is under MIT license. 8 | You may interested in reading [the full text of the license](https://github.com/plotters-rs/plotters/blob/master/LICENSE). 9 | If you have any questions or concerns please contact us at . 10 | 11 | # Contributing to Plotters codebase 12 | 13 | You are warmly welcomed to contribute code and make Plotters better. Here's a few things that may be helpful to you. 14 | 15 | ## How to make sure my code works 16 | 17 | *Help wanted:* You may realize that `Plotters` doesn't have a high testing coverage, but we are trying hard to improve. It would be nice if you add more test cases for newly added code, or contribute new test cases directly. 18 | 19 | Before you finalize your PR, please check the following thing: 20 | 21 | - Please make sure all test case passes. If any case fails, we need to dig into that. For more details about testing, please read the [Testing notes](#testing-notes). 22 | 23 | - Please run the benchmark to check if the performance changed compare to the master branch. 24 | 25 | - Please run the following command to check if the example output changes. (There shouldn't be any change if you are not modifying the layout) 26 | 27 | ```bash 28 | cd plotters 29 | cargo test --doc 30 | cargo build --release --examples 31 | for i in examples/*.rs 32 | do 33 | ../target/release/examples/$(basename $i .rs) 34 | done 35 | cd plotters-doc-data 36 | git status 37 | ``` 38 | 39 | - Please make sure the WASM target works as well. The easiest way to do that is try to run our WASM demo under [`plotters/examples/wasm-demo`](https://github.com/plotters-rs/plotters/tree/master/plotters/examples/wasm-demo) directory and follow the instruction in the [`README.md`](./plotters/examples/wasm-demo/README.md) file under that directory. 40 | 41 | ## Does my code meet the styling guidelines 42 | 43 | Although there's no strictly enforced rules for the style, but please read the following recommendations before you start work. 44 | 45 | - In general, the only guide line is we need to make sure `cargo fmt` doesn't change anything. So it's recommended use `cargo fmt` to fix the code styling issues before you wrap up the work. (Such as submit a PR) 46 | - For naming, acronyms or initials aren't normally used in the code base. Descriptive identifier is highly recommended. 47 | - Documentation is highly recommended. (But there are still a lot of undocumented code unfortunately). 48 | - For API documentation, we normally follows Doxygen's style, which looks like 49 | 50 | ```rust 51 | /// Some description to this API 52 | /// - `param_1`: What param_1 do 53 | /// - `param_2`: What param_2 do 54 | /// - **returns**: The return value description 55 | fn foo(param_1: u32, param_2: u32) -> u32{ 0 } 56 | ``` 57 | 58 | ## Top Level Documentation and Readme 59 | 60 | Please notice we put almost same content for top level `rustdoc` and `README.md`. Thus the both part are generated by script. 61 | If you need to modify the readme and documentation, please change the template at [`plotters/doc-template/readme.template.md`](https://github.com/plotters-rs/plotters/blob/master/doc-template/readme.template.md) and 62 | use the following command to synchronize the doc to both `src/lib.rs` and `README.md`. 63 | 64 | ```bash 65 | bash doc-template/update_readme.sh 66 | ``` 67 | 68 | ## Testing Notes 69 | 70 | As the project is intended to work in various environments, it's important to test its all features and different feature combinations. The notes below may help you with that task. 71 | 72 | ### Native 73 | 74 | Testing all features: 75 | 76 | ```bash 77 | cargo test --all-features 78 | ``` 79 | 80 | Testing no features at all: 81 | 82 | ```bash 83 | cargo test --no-default-features --lib 84 | ``` 85 | 86 | Since all examples and most doc-test requires `bitmap` features, so we don't test examples and doc test in this case. 87 | 88 | ### WebAssembly 89 | 90 | Wasm target is not tested by default, and you may want to use [wasm-bindgen](https://rustwasm.github.io/docs/wasm-bindgen/wasm-bindgen-test/usage.html) CLI tool. 91 | 92 | Installation: 93 | 94 | ```bash 95 | rustup target add wasm32-unknown-unknown 96 | cargo install wasm-bindgen-cli 97 | ``` 98 | 99 | Additionally, the web browser and its driver should be available, please see [Configuring Which Browser is Used](https://rustwasm.github.io/wasm-bindgen/wasm-bindgen-test/browsers.html#configuring-which-browser-is-used-1). For example, to use Firefox, its binary (`firefox`) and [geckodriver](https://github.com/mozilla/geckodriver/releases) must be on your `$PATH`. 100 | 101 | Usage (only library tests are supported for now): 102 | 103 | ```bash 104 | cargo test --lib --target wasm32-unknown-unknown 105 | ``` 106 | 107 | For the debugging you could set the `NO_HEADLESS=1` environment variable to run the tests using the local server instead of the headless browser. 108 | 109 | ### Minimal Supported Compiler Version 110 | 111 | Currently we should make sure Plotters is compatible with rustc 1.36.0. 112 | Before making a PR, please check if the code compile with 1.36.0 (with default features). 113 | 114 | ### Code Coverage 115 | 116 | For for the code coverage information you may want to use [cargo-tarpaulin](https://crates.io/crates/cargo-tarpaulin). Please note that it works with x86_64 GNU/Linux only, and the doc tests coverage require nightly Rust. 117 | 118 | Installation ([pycobertura](https://pypi.python.org/pypi/pycobertura) is used to get the detailed report about the coverage): 119 | 120 | ```bash 121 | cargo install cargo-tarpaulin 122 | pip install pycobertura 123 | ``` 124 | 125 | Usage: 126 | 127 | ```bash 128 | cargo tarpaulin --all-features --run-types Tests Doctests -o Xml --output-dir target/test 129 | pycobertura show target/test/cobertura.xml 130 | ``` 131 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["plotters", "plotters-backend", "plotters-bitmap", "plotters-svg"] 3 | default-members = ["plotters"] 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2022 Hao Hou 4 | Copyright (c) 2022-2025 The plotters-rs contributors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /RELEASE-NOTES.md: -------------------------------------------------------------------------------- 1 | # Plotters Release Notes 2 | 3 | This documents contains the release notes for every major release since v0.3. 4 | 5 | ## Plotters v0.3 6 | 7 | Plotters v0.3 is shipped with multiple major improvements. 8 | 9 | ## Plug-and-play backend importing 10 | 11 | ### Introduction 12 | 13 | Previously, Plotters implements all supported backend in the core crate. As the project is becoming bigger and bigger and 14 | more and more backend is supported, those backend implementation cause a huge mantainance burden. 15 | 16 | For example, when `cairo-rs` crate is updated, plotters should release a new version with updated `cairo-rs` dependency for 17 | our cairo backend. However, most of the users doesn't actually use this backend and pushing new version for updating cairo backend 18 | seems to be annoying for most of the people. As more and more backend is added, the dependency is out of control. 19 | 20 | ### Details 21 | 22 | To address this, we are now move all backend implementation code out of the plotters crate. To use a specific backend, you need to 23 | explicitly add backend crate to your dependency. For example, to use bitmap backend for v0.2.x, we can do this 24 | 25 | ```rust 26 | use plotters::prelude::*; 27 | fn main() { 28 | let backend = BitMapBackend::new(...) 29 | } 30 | ``` 31 | 32 | After this update, you should do the following code instead: 33 | 34 | ```rust 35 | use plotters::prelude::*; 36 | use plotter_bitmap::BitMapBackend; // <= This extra import is used to plug the backend to Plotters 37 | 38 | fn main() { 39 | let backend = BitMapBackend::new(...) 40 | } 41 | 42 | ``` 43 | 44 | ### Backward compatibility 45 | 46 | Plotters 0.3 has introduced concept of tier 1 backends, which is the most supported. 47 | All tier 1 backends could be imported automatically with the core crate (But can be opt-out as well). 48 | Currently we have two tier 1 backends: `plotters-bitmap` and `plotters-svg`. 49 | These are used by most of the people. 50 | 51 | To ease the upgrade for tier 1 backends, we still keep feature options in the core crate that can opt in those crates. And this is enabled by default. 52 | 53 | Thus, if plotters is imported with default feature set, there would require no change. If the default feature set is opt out, then the following change 54 | should be make with your `Crates.toml`: 55 | 56 | ```toml 57 | plotters = {version = "0.3", default-features = false, features = ["bitmap_backend", "svg_backend"]} # Instead of using feature "bitmap" and "svg" 58 | ``` 59 | 60 | For non tier 1 backends, manmually import is required (Please note tier on backends can be imported in same way). For example: 61 | 62 | ```toml 63 | plotters = {version = "0.3", default-features = false} # Instead of having features = ["cairo"] at this point 64 | plotters-cairo = "0.3" # We should import the cairo backend in this way. 65 | ``` 66 | 67 | And in your code, instead of import `plotters::prelude::CairoBackend`, you should import `plotters_cairo::CairoBackend` 68 | 69 | ## Enhanced Coordinate System Abstraction 70 | 71 | ### Details 72 | 73 | Plotters 0.3 ships with tons of improvement made in the coordinate abstraction and support much more kinds of coordinate. 74 | 75 | * `chrono::NaiveDate` and `chrono::NaiveDateTime` are now supported 76 | * Better abstraction of discrete coordinates 77 | * Linspace coordinate, which can be used converting a continuous coorindate into a discrete buckets 78 | * Nested coordinate 79 | * Slices can now be used as cooradnite specification, which allows people define an axis of category. 80 | * 3D Coordinate is now supported 81 | 82 | ## Fix bugs and improve testing 83 | -------------------------------------------------------------------------------- /doc-template/examples/chart.rs: -------------------------------------------------------------------------------- 1 | use plotters::prelude::*; 2 | fn main() -> Result<(), Box> { 3 | let root = BitMapBackend::new("plotters-doc-data/5.png", (640, 480)).into_drawing_area(); 4 | root.fill(&WHITE); 5 | let root = root.margin(10, 10, 10, 10); 6 | // After this point, we should be able to construct a chart context 7 | let mut chart = ChartBuilder::on(&root) 8 | // Set the caption of the chart 9 | .caption("This is our first plot", ("sans-serif", 40).into_font()) 10 | // Set the size of the label region 11 | .x_label_area_size(20) 12 | .y_label_area_size(40) 13 | // Finally attach a coordinate on the drawing area and make a chart context 14 | .build_cartesian_2d(0f32..10f32, 0f32..10f32)?; 15 | 16 | // Then we can draw a mesh 17 | chart 18 | .configure_mesh() 19 | // We can customize the maximum number of labels allowed for each axis 20 | .x_labels(5) 21 | .y_labels(5) 22 | // We can also change the format of the label text 23 | .y_label_formatter(&|x| format!("{:.3}", x)) 24 | .draw()?; 25 | 26 | // And we can draw something in the drawing area 27 | chart.draw_series(LineSeries::new( 28 | vec![(0.0, 0.0), (5.0, 5.0), (8.0, 7.0)], 29 | &RED, 30 | ))?; 31 | // Similarly, we can draw point series 32 | chart.draw_series(PointSeries::of_element( 33 | vec![(0.0, 0.0), (5.0, 5.0), (8.0, 7.0)], 34 | 5, 35 | &RED, 36 | &|c, s, st| { 37 | return EmptyElement::at(c) // We want to construct a composed element on-the-fly 38 | + Circle::new((0,0),s,st.filled()) // At this point, the new pixel coordinate is established 39 | + Text::new(format!("{:?}", c), (10, 0), ("sans-serif", 10).into_font()); 40 | }, 41 | ))?; 42 | root.present()?; 43 | Ok(()) 44 | } 45 | -------------------------------------------------------------------------------- /doc-template/examples/composable_elements.rs: -------------------------------------------------------------------------------- 1 | use plotters::prelude::*; 2 | use plotters::coord::types::RangedCoordf32; 3 | 4 | fn main() -> Result<(), Box> { 5 | let root = BitMapBackend::new("plotters-doc-data/4.png", (640, 480)).into_drawing_area(); 6 | 7 | root.fill(&RGBColor(240, 200, 200))?; 8 | 9 | let root = root.apply_coord_spec(Cartesian2d::::new( 10 | 0f32..1f32, 11 | 0f32..1f32, 12 | (0..640, 0..480), 13 | )); 14 | 15 | let dot_and_label = |x: f32, y: f32| { 16 | return EmptyElement::at((x, y)) 17 | + Circle::new((0, 0), 3, ShapeStyle::from(&BLACK).filled()) 18 | + Text::new( 19 | format!("({:.2},{:.2})", x, y), 20 | (10, 0), 21 | ("sans-serif", 15.0).into_font(), 22 | ); 23 | }; 24 | 25 | root.draw(&dot_and_label(0.5, 0.6))?; 26 | root.draw(&dot_and_label(0.25, 0.33))?; 27 | root.draw(&dot_and_label(0.8, 0.8))?; 28 | root.present()?; 29 | Ok(()) 30 | } 31 | -------------------------------------------------------------------------------- /doc-template/examples/drawing_area.rs: -------------------------------------------------------------------------------- 1 | use plotters::prelude::*; 2 | fn main() -> Result<(), Box> { 3 | let root_drawing_area = 4 | BitMapBackend::new("plotters-doc-data/2.png", (300, 200)).into_drawing_area(); 5 | // And we can split the drawing area into 3x3 grid 6 | let child_drawing_areas = root_drawing_area.split_evenly((3, 3)); 7 | // Then we fill the drawing area with different color 8 | for (area, color) in child_drawing_areas.into_iter().zip(0..) { 9 | area.fill(&Palette99::pick(color))?; 10 | } 11 | root_drawing_area.present()?; 12 | Ok(()) 13 | } 14 | -------------------------------------------------------------------------------- /doc-template/examples/drawing_backends.rs: -------------------------------------------------------------------------------- 1 | use plotters::prelude::*; 2 | fn main() -> Result<(), Box> { 3 | // Create a 800*600 bitmap and start drawing 4 | let mut backend = BitMapBackend::new("plotters-doc-data/1.png", (300, 200)); 5 | // And if we want SVG backend 6 | // let backend = SVGBackend::new("output.svg", (800, 600)); 7 | backend.draw_rect((50, 50), (200, 150), &RED, true)?; 8 | backend.present()?; 9 | Ok(()) 10 | } 11 | -------------------------------------------------------------------------------- /doc-template/examples/elements.rs: -------------------------------------------------------------------------------- 1 | use plotters::prelude::*; 2 | fn main() -> Result<(), Box> { 3 | let root = BitMapBackend::new("plotters-doc-data/3.png", (300, 200)).into_drawing_area(); 4 | root.fill(&WHITE)?; 5 | // Draw an circle on the drawing area 6 | root.draw(&Circle::new( 7 | (100, 100), 8 | 50, 9 | Into::::into(&GREEN).filled(), 10 | ))?; 11 | root.present()?; 12 | Ok(()) 13 | } 14 | -------------------------------------------------------------------------------- /doc-template/examples/quick_start.rs: -------------------------------------------------------------------------------- 1 | use plotters::prelude::*; 2 | fn main() -> Result<(), Box> { 3 | let root = BitMapBackend::new("plotters-doc-data/0.png", (640, 480)).into_drawing_area(); 4 | root.fill(&WHITE)?; 5 | let mut chart = ChartBuilder::on(&root) 6 | .caption("y=x^2", ("sans-serif", 50).into_font()) 7 | .margin(5) 8 | .x_label_area_size(30) 9 | .y_label_area_size(30) 10 | .build_cartesian_2d(-1f32..1f32, -0.1f32..1f32)?; 11 | 12 | chart.configure_mesh().draw()?; 13 | 14 | chart 15 | .draw_series(LineSeries::new( 16 | (-50..=50).map(|x| x as f32 / 50.0).map(|x| (x, x * x)), 17 | &RED, 18 | ))? 19 | .label("y = x^2") 20 | .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], &RED)); 21 | 22 | chart 23 | .configure_series_labels() 24 | .background_style(&WHITE.mix(0.8)) 25 | .border_style(&BLACK) 26 | .draw()?; 27 | 28 | root.present()?; 29 | 30 | Ok(()) 31 | } 32 | -------------------------------------------------------------------------------- /doc-template/gen-toc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sed -n 's/^##\(#* .*\)$/\1/gp' $1 \ 3 | | sed -e 's/^ /1\t/g' -e 's/^# /2\t/g' \ 4 | | awk -F'\t' ' 5 | BEGIN { 6 | level[1] = "* "; 7 | level[2] = " + "; 8 | print "## Table of Contents" 9 | } 10 | { 11 | link=tolower($2) 12 | gsub(/[^a-z0-9 \-]/, "", link); 13 | gsub(/[^a-z0-9]/, "-", link); 14 | print " "level[$1]"["$2"](#"link")" 15 | }' 16 | -------------------------------------------------------------------------------- /doc-template/latest_version: -------------------------------------------------------------------------------- 1 | 0.3.3 2 | -------------------------------------------------------------------------------- /doc-template/msrv.txt: -------------------------------------------------------------------------------- 1 | 1.56.0 2 | -------------------------------------------------------------------------------- /doc-template/readme/examples: -------------------------------------------------------------------------------- 1 | ../examples -------------------------------------------------------------------------------- /doc-template/readme/gallery: -------------------------------------------------------------------------------- 1 | To view the source code for each example, please click on the example image. 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /doc-template/readme/style: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plotters-rs/plotters/0f195eadaac7d9a2390a3707fbe192f8e2645d34/doc-template/readme/style -------------------------------------------------------------------------------- /doc-template/render_readme.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | DOC_ROOT=$(readlink -f $(dirname $0)) 3 | DIR=$2 4 | TOC_FILENAME=$(mktemp) 5 | 6 | ${DOC_ROOT}/gen-toc.sh $1 > ${TOC_FILENAME} 7 | 8 | awk '{ 9 | if($0 ~ /\$\$.*\$\$/) { 10 | filename = substr($0,3,length($0)-4); 11 | if(filename == "[TOC]") { 12 | filename ="'${TOC_FILENAME}'" 13 | } else { 14 | filename = "'${DIR}/'"filename 15 | } 16 | while((getline content < filename) > 0) { 17 | print content; 18 | } 19 | } else { 20 | print $0; 21 | } 22 | } 23 | ' $1 | sed 's/\$LATEST_VERSION/'$(cat ${DOC_ROOT}/latest_version)'/g' 24 | 25 | rm -f ${TOC_FILENAME} 26 | -------------------------------------------------------------------------------- /doc-template/rustdoc/examples: -------------------------------------------------------------------------------- 1 | ../examples -------------------------------------------------------------------------------- /doc-template/rustdoc/style: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | -------------------------------------------------------------------------------- /doc-template/update_readme.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ev 3 | REPO_BASE=`readlink -f $(dirname $(readlink -f $0))/../` 4 | ${REPO_BASE}/doc-template/render_readme.sh ${REPO_BASE}/doc-template/readme.template.md ${REPO_BASE}/doc-template/readme > ${REPO_BASE}/README.md 5 | 6 | awk ' 7 | NR == FNR { 8 | doc = doc"\n"$0; 9 | } 10 | NR != FNR{ 11 | if($0 == "/*!") { 12 | in_doc = 1; 13 | } 14 | if(!in_doc) { 15 | print $0 16 | } 17 | if($0 == "*/") { 18 | print "/*!" 19 | print doc 20 | print "*/" 21 | in_doc = 0; 22 | } 23 | }' <(${REPO_BASE}/doc-template/render_readme.sh ${REPO_BASE}/doc-template/readme.template.md ${REPO_BASE}/doc-template/rustdoc) ${REPO_BASE}/plotters/src/lib.rs > ${REPO_BASE}/plotters/src/lib.rs.tmp 24 | 25 | mv ${REPO_BASE}/plotters/src/lib.rs.tmp ${REPO_BASE}/plotters/src/lib.rs 26 | cargo fmt 27 | -------------------------------------------------------------------------------- /plotters-backend/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "plotters-backend" 3 | version = "0.3.7" 4 | authors = ["Hao Hou "] 5 | edition = "2018" 6 | license = "MIT" 7 | description = "Plotters Backend API" 8 | homepage = "https://plotters-rs.github.io" 9 | repository = "https://github.com/plotters-rs/plotters" 10 | readme = "README.md" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | -------------------------------------------------------------------------------- /plotters-backend/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /plotters-backend/README.md: -------------------------------------------------------------------------------- 1 | # plotters-backend - The base crate for implementing a backend for Plotters 2 | 3 | This is a part of plotters project. For more details, please check the following links: 4 | 5 | - For high-level intro of Plotters, see: [Plotters on crates.io](https://crates.io/crates/plotters) 6 | - Check the main repo at [Plotters repo](https://github.com/plotters-rs/plotters.git) 7 | - For detailed documentation about this crate, check [plotters-backend on docs.rs](https://docs.rs/plotters-backend/) 8 | - You can also visit Plotters [Homepage](https://plotters-rs.github.io) 9 | -------------------------------------------------------------------------------- /plotters-backend/src/rasterizer/line.rs: -------------------------------------------------------------------------------- 1 | use crate::{BackendCoord, BackendStyle, DrawingBackend, DrawingErrorKind}; 2 | 3 | pub fn draw_line( 4 | back: &mut DB, 5 | mut from: BackendCoord, 6 | mut to: BackendCoord, 7 | style: &S, 8 | ) -> Result<(), DrawingErrorKind> { 9 | if style.color().alpha == 0.0 || style.stroke_width() == 0 { 10 | return Ok(()); 11 | } 12 | 13 | if style.stroke_width() != 1 { 14 | // If the line is wider than 1px, then we need to make it a polygon 15 | let v = (i64::from(to.0 - from.0), i64::from(to.1 - from.1)); 16 | let l = ((v.0 * v.0 + v.1 * v.1) as f64).sqrt(); 17 | 18 | if l < 1e-5 { 19 | return Ok(()); 20 | } 21 | 22 | let v = (v.0 as f64 / l, v.1 as f64 / l); 23 | 24 | let r = f64::from(style.stroke_width()) / 2.0; 25 | let mut trans = [(v.1 * r, -v.0 * r), (-v.1 * r, v.0 * r)]; 26 | let mut vertices = vec![]; 27 | 28 | for point in [from, to].iter() { 29 | for t in trans.iter() { 30 | vertices.push(( 31 | (f64::from(point.0) + t.0) as i32, 32 | (f64::from(point.1) + t.1) as i32, 33 | )) 34 | } 35 | 36 | trans.swap(0, 1); 37 | } 38 | 39 | return back.fill_polygon(vertices, style); 40 | } 41 | 42 | if from.0 == to.0 { 43 | if from.1 > to.1 { 44 | std::mem::swap(&mut from, &mut to); 45 | } 46 | for y in from.1..=to.1 { 47 | check_result!(back.draw_pixel((from.0, y), style.color())); 48 | } 49 | return Ok(()); 50 | } 51 | 52 | if from.1 == to.1 { 53 | if from.0 > to.0 { 54 | std::mem::swap(&mut from, &mut to); 55 | } 56 | for x in from.0..=to.0 { 57 | check_result!(back.draw_pixel((x, from.1), style.color())); 58 | } 59 | return Ok(()); 60 | } 61 | 62 | let steep = (from.0 - to.0).abs() < (from.1 - to.1).abs(); 63 | 64 | if steep { 65 | from = (from.1, from.0); 66 | to = (to.1, to.0); 67 | } 68 | 69 | let (from, to) = if from.0 > to.0 { 70 | (to, from) 71 | } else { 72 | (from, to) 73 | }; 74 | 75 | let mut size_limit = back.get_size(); 76 | 77 | if steep { 78 | size_limit = (size_limit.1, size_limit.0); 79 | } 80 | 81 | let grad = f64::from(to.1 - from.1) / f64::from(to.0 - from.0); 82 | 83 | let mut put_pixel = |(x, y): BackendCoord, b: f64| { 84 | if steep { 85 | back.draw_pixel((y, x), style.color().mix(b)) 86 | } else { 87 | back.draw_pixel((x, y), style.color().mix(b)) 88 | } 89 | }; 90 | 91 | let y_step_limit = 92 | (f64::from(to.1.min(size_limit.1 as i32 - 1).max(0) - from.1) / grad).floor() as i32; 93 | 94 | let batch_start = (f64::from(from.1.min(size_limit.1 as i32 - 2).max(0) - from.1) / grad) 95 | .abs() 96 | .ceil() as i32 97 | + from.0; 98 | 99 | let batch_limit = 100 | to.0.min(size_limit.0 as i32 - 2) 101 | .min(from.0 + y_step_limit - 1); 102 | 103 | let mut y = f64::from(from.1) + f64::from(batch_start - from.0) * grad; 104 | 105 | for x in batch_start..=batch_limit { 106 | check_result!(put_pixel((x, y as i32), 1.0 + y.floor() - y)); 107 | check_result!(put_pixel((x, y as i32 + 1), y - y.floor())); 108 | 109 | y += grad; 110 | } 111 | 112 | if to.0 > batch_limit && y < f64::from(to.1) { 113 | let x = batch_limit + 1; 114 | if 1.0 + y.floor() - y > 1e-5 { 115 | check_result!(put_pixel((x, y as i32), 1.0 + y.floor() - y)); 116 | } 117 | if y - y.floor() > 1e-5 && y + 1.0 < f64::from(to.1) { 118 | check_result!(put_pixel((x, y as i32 + 1), y - y.floor())); 119 | } 120 | } 121 | 122 | Ok(()) 123 | } 124 | -------------------------------------------------------------------------------- /plotters-backend/src/rasterizer/mod.rs: -------------------------------------------------------------------------------- 1 | /*! # The built-in rasterizers. 2 | 3 | Plotters make a minimal backend ability assumption - which is drawing a pixel on 4 | backend. And this is the rasterizer that utilize this minimal ability to build a 5 | fully functioning backend. 6 | 7 | */ 8 | 9 | // TODO: We need to revisit this. It has been a long time since last time we figured out 10 | // the question mark operator has a huge performance impact due to LLVM unable to handle it. 11 | // So the question is if this trick is still useful, or LLVM is smart enough to handle it since 12 | // then. 13 | // 14 | // -- 15 | // Original comment: 16 | // 17 | // ? operator is very slow. See issue #58 for details 18 | macro_rules! check_result { 19 | ($e:expr) => { 20 | let result = $e; 21 | #[allow(clippy::question_mark)] 22 | if result.is_err() { 23 | return result; 24 | } 25 | }; 26 | } 27 | 28 | mod line; 29 | pub use line::draw_line; 30 | 31 | mod rect; 32 | pub use rect::draw_rect; 33 | 34 | mod circle; 35 | pub use circle::draw_circle; 36 | 37 | mod polygon; 38 | pub use polygon::fill_polygon; 39 | 40 | mod path; 41 | pub use path::polygonize; 42 | -------------------------------------------------------------------------------- /plotters-backend/src/rasterizer/rect.rs: -------------------------------------------------------------------------------- 1 | use crate::{BackendCoord, BackendStyle, DrawingBackend, DrawingErrorKind}; 2 | 3 | pub fn draw_rect( 4 | b: &mut B, 5 | upper_left: BackendCoord, 6 | bottom_right: BackendCoord, 7 | style: &S, 8 | fill: bool, 9 | ) -> Result<(), DrawingErrorKind> { 10 | if style.color().alpha == 0.0 { 11 | return Ok(()); 12 | } 13 | let (upper_left, bottom_right) = ( 14 | ( 15 | upper_left.0.min(bottom_right.0), 16 | upper_left.1.min(bottom_right.1), 17 | ), 18 | ( 19 | upper_left.0.max(bottom_right.0), 20 | upper_left.1.max(bottom_right.1), 21 | ), 22 | ); 23 | 24 | if fill { 25 | if bottom_right.0 - upper_left.0 < bottom_right.1 - upper_left.1 { 26 | for x in upper_left.0..=bottom_right.0 { 27 | check_result!(b.draw_line((x, upper_left.1), (x, bottom_right.1), style)); 28 | } 29 | } else { 30 | for y in upper_left.1..=bottom_right.1 { 31 | check_result!(b.draw_line((upper_left.0, y), (bottom_right.0, y), style)); 32 | } 33 | } 34 | } else { 35 | b.draw_line( 36 | (upper_left.0, upper_left.1), 37 | (upper_left.0, bottom_right.1), 38 | style, 39 | )?; 40 | b.draw_line( 41 | (upper_left.0, upper_left.1), 42 | (bottom_right.0, upper_left.1), 43 | style, 44 | )?; 45 | b.draw_line( 46 | (bottom_right.0, bottom_right.1), 47 | (upper_left.0, bottom_right.1), 48 | style, 49 | )?; 50 | b.draw_line( 51 | (bottom_right.0, bottom_right.1), 52 | (bottom_right.0, upper_left.1), 53 | style, 54 | )?; 55 | } 56 | Ok(()) 57 | } 58 | -------------------------------------------------------------------------------- /plotters-backend/src/style.rs: -------------------------------------------------------------------------------- 1 | /// The color type that is used by all the backend 2 | #[derive(Clone, Copy)] 3 | pub struct BackendColor { 4 | pub alpha: f64, 5 | pub rgb: (u8, u8, u8), 6 | } 7 | 8 | impl BackendColor { 9 | #[inline(always)] 10 | pub fn mix(&self, alpha: f64) -> Self { 11 | Self { 12 | alpha: self.alpha * alpha, 13 | rgb: self.rgb, 14 | } 15 | } 16 | } 17 | 18 | /// The style data for the backend drawing API 19 | pub trait BackendStyle { 20 | /// Get the color of current style 21 | fn color(&self) -> BackendColor; 22 | 23 | /// Get the stroke width of current style 24 | fn stroke_width(&self) -> u32 { 25 | 1 26 | } 27 | } 28 | 29 | impl BackendStyle for BackendColor { 30 | fn color(&self) -> BackendColor { 31 | *self 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /plotters-bitmap/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "plotters-bitmap" 3 | version = "0.3.7" 4 | authors = ["Hao Hou "] 5 | edition = "2018" 6 | license = "MIT" 7 | description = "Plotters Bitmap Backend" 8 | homepage = "https://plotters-rs.github.io" 9 | repository = "https://github.com/plotters-rs/plotters" 10 | readme = "README.md" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | gif = { version = "0.12.0", optional = true } 16 | 17 | [dependencies.plotters-backend] 18 | version = "0.3.6" 19 | path = "../plotters-backend" 20 | 21 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies.image] 22 | version = "0.24.3" 23 | optional = true 24 | default-features = false 25 | features = ["jpeg", "png", "bmp"] 26 | 27 | [features] 28 | default = ["image_encoder", "gif_backend"] 29 | image_encoder = ["image"] 30 | gif_backend = ["gif", "image_encoder"] 31 | 32 | [dev-dependencies.plotters] 33 | default-features = false 34 | features = ["ttf", "line_series", "bitmap_backend"] 35 | path = "../plotters" 36 | 37 | [dev-dependencies] 38 | criterion = "0.5.1" 39 | rayon = "1.5.1" 40 | 41 | [[bench]] 42 | name = "benchmark" 43 | harness = false 44 | path = "benches/main.rs" 45 | -------------------------------------------------------------------------------- /plotters-bitmap/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /plotters-bitmap/README.md: -------------------------------------------------------------------------------- 1 | # plotters-bitmap - The bitmap backend for Plotters 2 | 3 | This is a part of plotters project. For more details, please check the following links: 4 | 5 | - For high-level intro of Plotters, see: [Plotters on crates.io](https://crates.io/crates/plotters) 6 | - Check the main repo at [Plotters repo](https://github.com/plotters-rs/plotters.git) 7 | - For detailed documentation about this crate, check [plotters-backend on docs.rs](https://docs.rs/plotters-backend/) 8 | - You can also visit Plotters [Homepage](https://plotters-rs.github.io) 9 | -------------------------------------------------------------------------------- /plotters-bitmap/benches/benches/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod parallel; 2 | pub mod rasterizer; 3 | -------------------------------------------------------------------------------- /plotters-bitmap/benches/main.rs: -------------------------------------------------------------------------------- 1 | use criterion::criterion_main; 2 | 3 | mod benches; 4 | 5 | criterion_main! { 6 | benches::parallel::parallel_group, 7 | benches::rasterizer::rasterizer_group, 8 | } 9 | -------------------------------------------------------------------------------- /plotters-bitmap/src/bitmap/target.rs: -------------------------------------------------------------------------------- 1 | #[cfg(all(feature = "gif", not(target_arch = "wasm32"), feature = "image"))] 2 | use crate::gif_support; 3 | use std::marker::PhantomData; 4 | #[cfg(all(not(target_arch = "wasm32"), feature = "image"))] 5 | use std::path::Path; 6 | 7 | pub(super) enum Target<'a> { 8 | #[cfg(all(not(target_arch = "wasm32"), feature = "image"))] 9 | File(&'a Path), 10 | Buffer(PhantomData<&'a u32>), 11 | #[cfg(all(feature = "gif", not(target_arch = "wasm32"), feature = "image"))] 12 | Gif(Box), 13 | } 14 | 15 | pub(super) enum Buffer<'a> { 16 | #[cfg(all(not(target_arch = "wasm32"), feature = "image"))] 17 | Owned(Vec), 18 | Borrowed(&'a mut [u8]), 19 | } 20 | 21 | impl<'a> Buffer<'a> { 22 | #[inline(always)] 23 | pub(super) fn borrow_buffer(&mut self) -> &mut [u8] { 24 | match self { 25 | #[cfg(all(not(target_arch = "wasm32"), feature = "image"))] 26 | Buffer::Owned(buf) => &mut buf[..], 27 | Buffer::Borrowed(buf) => buf, 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /plotters-bitmap/src/bitmap_pixel/mod.rs: -------------------------------------------------------------------------------- 1 | mod bgrx; 2 | mod pixel_format; 3 | mod rgb; 4 | 5 | pub use bgrx::BGRXPixel; 6 | pub use pixel_format::PixelFormat; 7 | pub use rgb::RGBPixel; 8 | -------------------------------------------------------------------------------- /plotters-bitmap/src/bitmap_pixel/pixel_format.rs: -------------------------------------------------------------------------------- 1 | use crate::BitMapBackend; 2 | use plotters_backend::DrawingBackend; 3 | 4 | #[inline(always)] 5 | pub(super) fn blend(prev: &mut u8, new: u8, a: u64) { 6 | if new > *prev { 7 | *prev += (u64::from(new - *prev) * a / 256) as u8 8 | } else { 9 | *prev -= (u64::from(*prev - new) * a / 256) as u8 10 | } 11 | } 12 | 13 | /// The trait that describes some details about a particular pixel format 14 | pub trait PixelFormat: Sized { 15 | /// Number of bytes per pixel 16 | const PIXEL_SIZE: usize; 17 | 18 | /// Number of effective bytes per pixel, e.g. for BGRX pixel format, the size of pixel 19 | /// is 4 but the effective size is 3, since the 4th byte isn't used 20 | const EFFECTIVE_PIXEL_SIZE: usize; 21 | 22 | /// Encoding a pixel and returns the idx-th byte for the pixel 23 | fn byte_at(r: u8, g: u8, b: u8, a: u64, idx: usize) -> u8; 24 | 25 | /// Decode a pixel at the given location 26 | fn decode_pixel(data: &[u8]) -> (u8, u8, u8, u64); 27 | 28 | /// The fast alpha blending algorithm for this pixel format 29 | /// 30 | /// - `target`: The target bitmap backend 31 | /// - `upper_left`: The upper-left coord for the rect 32 | /// - `bottom_right`: The bottom-right coord for the rect 33 | /// - `r`, `g`, `b`, `a`: The blending color and alpha value 34 | fn blend_rect_fast( 35 | target: &mut BitMapBackend<'_, Self>, 36 | upper_left: (i32, i32), 37 | bottom_right: (i32, i32), 38 | r: u8, 39 | g: u8, 40 | b: u8, 41 | a: f64, 42 | ); 43 | 44 | /// The fast vertical line filling algorithm 45 | /// 46 | /// - `target`: The target bitmap backend 47 | /// - `x`: the X coordinate for the entire line 48 | /// - `ys`: The range of y coord 49 | /// - `r`, `g`, `b`: The blending color and alpha value 50 | fn fill_vertical_line_fast( 51 | target: &mut BitMapBackend<'_, Self>, 52 | x: i32, 53 | ys: (i32, i32), 54 | r: u8, 55 | g: u8, 56 | b: u8, 57 | ) { 58 | let (w, h) = target.get_size(); 59 | let w = w as i32; 60 | let h = h as i32; 61 | 62 | // Make sure we are in the range 63 | if x < 0 || x >= w { 64 | return; 65 | } 66 | 67 | let dst = target.get_raw_pixel_buffer(); 68 | let (mut y0, mut y1) = ys; 69 | if y0 > y1 { 70 | std::mem::swap(&mut y0, &mut y1); 71 | } 72 | // And check the y axis isn't out of bound 73 | y0 = y0.max(0); 74 | y1 = y1.min(h - 1); 75 | // This is ok because once y0 > y1, there won't be any iteration anymore 76 | for y in y0..=y1 { 77 | for idx in 0..Self::EFFECTIVE_PIXEL_SIZE { 78 | dst[(y * w + x) as usize * Self::PIXEL_SIZE + idx] = Self::byte_at(r, g, b, 0, idx); 79 | } 80 | } 81 | } 82 | 83 | /// The fast rectangle filling algorithm 84 | /// 85 | /// - `target`: The target bitmap backend 86 | /// - `upper_left`: The upper-left coord for the rect 87 | /// - `bottom_right`: The bottom-right coord for the rect 88 | /// - `r`, `g`, `b`: The filling color 89 | fn fill_rect_fast( 90 | target: &mut BitMapBackend<'_, Self>, 91 | upper_left: (i32, i32), 92 | bottom_right: (i32, i32), 93 | r: u8, 94 | g: u8, 95 | b: u8, 96 | ); 97 | 98 | #[inline(always)] 99 | /// Drawing a single pixel in this format 100 | /// 101 | /// - `target`: The target bitmap backend 102 | /// - `point`: The coord of the point 103 | /// - `r`, `g`, `b`: The filling color 104 | /// - `alpha`: The alpha value 105 | fn draw_pixel( 106 | target: &mut BitMapBackend<'_, Self>, 107 | point: (i32, i32), 108 | (r, g, b): (u8, u8, u8), 109 | alpha: f64, 110 | ) { 111 | let (x, y) = (point.0 as usize, point.1 as usize); 112 | let (w, _) = target.get_size(); 113 | let buf = target.get_raw_pixel_buffer(); 114 | let w = w as usize; 115 | let base = (y * w + x) * Self::PIXEL_SIZE; 116 | 117 | if base < buf.len() { 118 | unsafe { 119 | if alpha >= 1.0 - 1.0 / 256.0 { 120 | for idx in 0..Self::EFFECTIVE_PIXEL_SIZE { 121 | *buf.get_unchecked_mut(base + idx) = Self::byte_at(r, g, b, 0, idx); 122 | } 123 | } else { 124 | if alpha <= 0.0 { 125 | return; 126 | } 127 | 128 | let alpha = (alpha * 256.0).floor() as u64; 129 | for idx in 0..Self::EFFECTIVE_PIXEL_SIZE { 130 | blend( 131 | buf.get_unchecked_mut(base + idx), 132 | Self::byte_at(r, g, b, 0, idx), 133 | alpha, 134 | ); 135 | } 136 | } 137 | } 138 | } 139 | } 140 | 141 | /// Indicates if this pixel format can be saved as image. 142 | /// Note: Currently we only using RGB pixel format in the image crate, but later we may lift 143 | /// this restriction 144 | /// 145 | /// - `returns`: If the image can be saved as image file 146 | fn can_be_saved() -> bool { 147 | false 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /plotters-bitmap/src/error.rs: -------------------------------------------------------------------------------- 1 | #[cfg(all(not(target_arch = "wasm32"), feature = "image"))] 2 | use image::ImageError; 3 | 4 | #[derive(Debug)] 5 | /// Indicates some error occurs within the bitmap backend 6 | pub enum BitMapBackendError { 7 | /// The buffer provided is invalid, for example, wrong pixel buffer size 8 | InvalidBuffer, 9 | /// Some IO error occurs while the bitmap manipulation 10 | IOError(std::io::Error), 11 | #[cfg(all(feature = "gif", not(target_arch = "wasm32"), feature = "image"))] 12 | GifEncodingError(gif::EncodingError), 13 | #[cfg(all(not(target_arch = "wasm32"), feature = "image"))] 14 | /// Image encoding error 15 | ImageError(ImageError), 16 | } 17 | 18 | impl std::fmt::Display for BitMapBackendError { 19 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 20 | write!(f, "{:?}", self) 21 | } 22 | } 23 | 24 | impl std::error::Error for BitMapBackendError {} 25 | -------------------------------------------------------------------------------- /plotters-bitmap/src/gif_support.rs: -------------------------------------------------------------------------------- 1 | use crate::error::BitMapBackendError; 2 | use gif::{Encoder as GifEncoder, Frame as GifFrame, Repeat}; 3 | use std::fs::File; 4 | use std::path::Path; 5 | 6 | pub(super) struct GifFile { 7 | encoder: GifEncoder, 8 | height: u32, 9 | width: u32, 10 | delay: u32, 11 | } 12 | 13 | impl GifFile { 14 | pub(super) fn new>( 15 | path: T, 16 | dim: (u32, u32), 17 | delay: u32, 18 | ) -> Result { 19 | let mut encoder = GifEncoder::new( 20 | File::create(path.as_ref()).map_err(BitMapBackendError::IOError)?, 21 | dim.0 as u16, 22 | dim.1 as u16, 23 | &[], 24 | ) 25 | .map_err(BitMapBackendError::GifEncodingError)?; 26 | 27 | encoder 28 | .set_repeat(Repeat::Infinite) 29 | .map_err(BitMapBackendError::GifEncodingError)?; 30 | 31 | Ok(Self { 32 | encoder, 33 | width: dim.0, 34 | height: dim.1, 35 | delay: (delay + 5) / 10, 36 | }) 37 | } 38 | 39 | pub(super) fn flush_frame(&mut self, buffer: &[u8]) -> Result<(), BitMapBackendError> { 40 | let mut frame = GifFrame::from_rgb_speed(self.width as u16, self.height as u16, buffer, 10); 41 | 42 | frame.delay = self.delay as u16; 43 | 44 | self.encoder 45 | .write_frame(&frame) 46 | .map_err(BitMapBackendError::GifEncodingError)?; 47 | 48 | Ok(()) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /plotters-bitmap/src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | The Plotters bitmap backend. 3 | 4 | The plotters bitmap backend allows you to render images by Plotters into bitmap. 5 | You can either generate image file(PNG, JPG, GIF, etc) or rendering the bitmap within internal buffer (for example for framebuffer, etc). 6 | 7 | See the documentation for [BitMapBackend](struct.BitMapBackend.html) for more details. 8 | */ 9 | 10 | #[cfg(all(feature = "gif", not(target_arch = "wasm32"), feature = "image"))] 11 | mod gif_support; 12 | 13 | pub mod bitmap_pixel; 14 | mod error; 15 | 16 | mod bitmap; 17 | pub use bitmap::BitMapBackend; 18 | pub use error::BitMapBackendError; 19 | 20 | /*pub mod bitmap_pixel { 21 | pub use super::bitmap::{BGRXPixel, RGBPixel}; 22 | }*/ 23 | -------------------------------------------------------------------------------- /plotters-svg/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "plotters-svg" 3 | version = "0.3.7" 4 | authors = ["Hao Hou "] 5 | edition = "2018" 6 | license = "MIT" 7 | description = "Plotters SVG backend" 8 | homepage = "https://plotters-rs.github.io" 9 | repository = "https://github.com/plotters-rs/plotters.git" 10 | readme = "README.md" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies.plotters-backend] 15 | version = "0.3.6" 16 | path = "../plotters-backend" 17 | 18 | [dependencies.image] 19 | version = "0.24.2" 20 | optional = true 21 | default-features = false 22 | features = ["jpeg", "png", "bmp"] 23 | 24 | [features] 25 | debug = [] 26 | bitmap_encoder = ["image"] 27 | 28 | [dev-dependencies.plotters] 29 | default-features = false 30 | features = ["ttf"] 31 | path = "../plotters" 32 | -------------------------------------------------------------------------------- /plotters-svg/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /plotters-svg/README.md: -------------------------------------------------------------------------------- 1 | # plotters-svg - The SVG backend for Plotters 2 | 3 | This is a part of plotters project. For more details, please check the following links: 4 | 5 | - For high-level intro of Plotters, see: [Plotters on crates.io](https://crates.io/crates/plotters) 6 | - Check the main repo at [Plotters repo](https://github.com/plotters-rs/plotters.git) 7 | - For detailed documentation about this crate, check [plotters-backend on docs.rs](https://docs.rs/plotters-backend/) 8 | - You can also visit Plotters [Homepage](https://plotters-rs.github.io) 9 | -------------------------------------------------------------------------------- /plotters-svg/src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | The Plotters SVG backend. 3 | 4 | The plotters bitmap backend allows you to render images by Plotters into SVG vector graphs. 5 | 6 | See the documentation for [SVGBackend](struct.SVGBackend.html) for more details. 7 | */ 8 | mod svg; 9 | 10 | pub use svg::SVGBackend; 11 | -------------------------------------------------------------------------------- /plotters/.cargo/config: -------------------------------------------------------------------------------- 1 | [target.wasm32-unknown-unknown] 2 | runner = 'wasm-bindgen-test-runner' 3 | -------------------------------------------------------------------------------- /plotters/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "plotters" 3 | version = "0.3.7" 4 | authors = ["Hao Hou "] 5 | edition = "2018" 6 | license = "MIT" 7 | msrv = "1.56" 8 | description = "A Rust drawing library focus on data plotting for both WASM and native applications" 9 | repository = "https://github.com/plotters-rs/plotters" 10 | homepage = "https://plotters-rs.github.io/" 11 | keywords = ["WebAssembly", "Visualization", "Plotting", "Drawing"] 12 | categories = ["visualization", "wasm"] 13 | readme = "../README.md" 14 | exclude = ["doc-template", "plotters-doc-data"] 15 | 16 | [lints.rust] 17 | unexpected_cfgs = { level = "warn", check-cfg = ['cfg(doc_cfg)'] } 18 | deprecated = { level = "allow" } 19 | 20 | [dependencies] 21 | num-traits = "0.2.14" 22 | chrono = { version = "0.4.32", optional = true } 23 | serde = { version = "1.0.139", optional = true } 24 | 25 | [dependencies.plotters-backend] 26 | version = "0.3.6" 27 | path = "../plotters-backend" 28 | 29 | [dependencies.plotters-bitmap] 30 | version = "0.3.6" 31 | default-features = false 32 | optional = true 33 | path = "../plotters-bitmap" 34 | 35 | [dependencies.plotters-svg] 36 | version = "0.3.6" 37 | optional = true 38 | path = "../plotters-svg" 39 | 40 | [target.'cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))'.dependencies] 41 | ttf-parser = { version = "0.20.0", optional = true } 42 | lazy_static = { version = "1.4.0", optional = true } 43 | pathfinder_geometry = { version = "0.5.1", optional = true } 44 | font-kit = { version = "0.14.2", optional = true } 45 | ab_glyph = { version = "0.2.12", optional = true } 46 | once_cell = { version = "1.8.0", optional = true } 47 | 48 | 49 | [target.'cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))'.dependencies.image] 50 | version = "0.24.3" 51 | optional = true 52 | default-features = false 53 | features = ["jpeg", "png", "bmp"] 54 | 55 | [target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dependencies.wasm-bindgen] 56 | version = "0.2.89" 57 | 58 | [target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dependencies.web-sys] 59 | version = "0.3.66" 60 | features = [ 61 | "Document", 62 | "DomRect", 63 | "Element", 64 | "HtmlElement", 65 | "Node", 66 | "Window", 67 | "HtmlCanvasElement", 68 | "CanvasRenderingContext2d", 69 | ] 70 | 71 | [features] 72 | default = [ 73 | "bitmap_backend", "bitmap_encoder", "bitmap_gif", 74 | "svg_backend", 75 | "chrono", 76 | "ttf", 77 | "image", 78 | "deprecated_items", "all_series", "all_elements", 79 | "full_palette", 80 | "colormaps" 81 | ] 82 | all_series = ["area_series", "line_series", "point_series", "surface_series"] 83 | all_elements = ["errorbar", "candlestick", "boxplot", "histogram"] 84 | 85 | # Tier 1 Backends 86 | bitmap_backend = ["plotters-bitmap"] 87 | bitmap_encoder = ["plotters-bitmap/image_encoder"] 88 | bitmap_gif = ["plotters-bitmap/gif_backend"] 89 | svg_backend = ["plotters-svg"] 90 | 91 | # Colors 92 | full_palette = [] 93 | colormaps = [] 94 | 95 | # Elements 96 | errorbar = [] 97 | candlestick = [] 98 | boxplot = [] 99 | 100 | # Series 101 | histogram = [] 102 | area_series = [] 103 | line_series = [] 104 | point_series = [] 105 | surface_series = [] 106 | 107 | # Font implementation 108 | ttf = ["font-kit", "ttf-parser", "lazy_static", "pathfinder_geometry"] 109 | # dlopen fontconfig C library at runtime instead of linking at build time 110 | # Can be useful for cross compiling, especially considering fontconfig has lots of C dependencies 111 | fontconfig-dlopen = ["font-kit/source-fontconfig-dlopen"] 112 | 113 | ab_glyph = ["dep:ab_glyph", "once_cell"] 114 | 115 | # Misc 116 | datetime = ["chrono"] 117 | serialization = ["serde"] 118 | evcxr = ["svg_backend"] 119 | evcxr_bitmap = ["evcxr", "bitmap_backend", "plotters-svg/bitmap_encoder"] 120 | deprecated_items = [] # Keep some of the deprecated items for backward compatibility 121 | 122 | [dev-dependencies] 123 | itertools = "0.10.0" 124 | criterion = "0.5.1" 125 | rayon = "1.5.1" 126 | serde_json = "1.0.82" 127 | serde_derive = "1.0.140" 128 | plotters = { path = ".", features = ["serialization"] } 129 | 130 | [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] 131 | rand = "0.8.3" 132 | rand_distr = "0.4.0" 133 | rand_xorshift = "0.3.0" 134 | 135 | [target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dev-dependencies] 136 | wasm-bindgen-test = "0.3.39" 137 | 138 | [[bench]] 139 | name = "benchmark" 140 | harness = false 141 | path = "benches/main.rs" 142 | 143 | [package.metadata.docs.rs] 144 | all-features = true 145 | rustdoc-args = ["--cfg", "doc_cfg"] 146 | 147 | -------------------------------------------------------------------------------- /plotters/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /plotters/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /plotters/benches/benches/data.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, Criterion}; 2 | use plotters::data::Quartiles; 3 | 4 | struct Lcg { 5 | state: u32, 6 | } 7 | 8 | impl Lcg { 9 | fn new() -> Lcg { 10 | Lcg { state: 0 } 11 | } 12 | } 13 | 14 | impl Iterator for Lcg { 15 | type Item = u32; 16 | 17 | fn next(&mut self) -> Option { 18 | self.state = self.state.wrapping_mul(1_103_515_245).wrapping_add(12_345); 19 | self.state %= 1 << 31; 20 | Some(self.state) 21 | } 22 | } 23 | 24 | fn quartiles_calc(c: &mut Criterion) { 25 | let src: Vec = Lcg::new().take(100000).collect(); 26 | c.bench_function("data::quartiles_calc", |b| { 27 | b.iter(|| { 28 | Quartiles::new(&src); 29 | }) 30 | }); 31 | } 32 | 33 | criterion_group! { 34 | name = quartiles_group; 35 | config = Criterion::default().sample_size(10); 36 | targets = quartiles_calc 37 | } 38 | -------------------------------------------------------------------------------- /plotters/benches/benches/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod data; 2 | -------------------------------------------------------------------------------- /plotters/benches/main.rs: -------------------------------------------------------------------------------- 1 | use criterion::criterion_main; 2 | 3 | mod benches; 4 | 5 | criterion_main! { 6 | benches::data::quartiles_group 7 | } 8 | -------------------------------------------------------------------------------- /plotters/blub.png: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /plotters/examples/3d-plot.rs: -------------------------------------------------------------------------------- 1 | use plotters::prelude::*; 2 | const OUT_FILE_NAME: &str = "plotters-doc-data/3d-plot.svg"; 3 | fn main() -> Result<(), Box> { 4 | let area = SVGBackend::new(OUT_FILE_NAME, (1024, 760)).into_drawing_area(); 5 | 6 | area.fill(&WHITE)?; 7 | 8 | let x_axis = (-3.0..3.0).step(0.1); 9 | let z_axis = (-3.0..3.0).step(0.1); 10 | 11 | let mut chart = ChartBuilder::on(&area) 12 | .caption("3D Plot Test", ("sans", 20)) 13 | .build_cartesian_3d(x_axis.clone(), -3.0..3.0, z_axis.clone())?; 14 | 15 | chart.with_projection(|mut pb| { 16 | pb.yaw = 0.5; 17 | pb.scale = 0.9; 18 | pb.into_matrix() 19 | }); 20 | 21 | chart 22 | .configure_axes() 23 | .light_grid_style(BLACK.mix(0.15)) 24 | .max_light_lines(3) 25 | .draw()?; 26 | 27 | chart 28 | .draw_series( 29 | SurfaceSeries::xoz( 30 | (-30..30).map(|f| f as f64 / 10.0), 31 | (-30..30).map(|f| f as f64 / 10.0), 32 | |x, z| (x * x + z * z).cos(), 33 | ) 34 | .style(BLUE.mix(0.2).filled()), 35 | )? 36 | .label("Surface") 37 | .legend(|(x, y)| Rectangle::new([(x + 5, y - 5), (x + 15, y + 5)], BLUE.mix(0.5).filled())); 38 | 39 | chart 40 | .draw_series(LineSeries::new( 41 | (-100..100) 42 | .map(|y| y as f64 / 40.0) 43 | .map(|y| ((y * 10.0).sin(), y, (y * 10.0).cos())), 44 | &BLACK, 45 | ))? 46 | .label("Line") 47 | .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], BLACK)); 48 | 49 | chart.configure_series_labels().border_style(BLACK).draw()?; 50 | 51 | // To avoid the IO failure being ignored silently, we manually call the present function 52 | area.present().expect("Unable to write result to file, please make sure 'plotters-doc-data' dir exists under current dir"); 53 | println!("Result has been saved to {}", OUT_FILE_NAME); 54 | Ok(()) 55 | } 56 | #[test] 57 | fn entry_point() { 58 | main().unwrap() 59 | } 60 | -------------------------------------------------------------------------------- /plotters/examples/3d-plot2.rs: -------------------------------------------------------------------------------- 1 | use plotters::prelude::*; 2 | fn pdf(x: f64, y: f64) -> f64 { 3 | const SDX: f64 = 0.1; 4 | const SDY: f64 = 0.1; 5 | const A: f64 = 5.0; 6 | let x = x / 10.0; 7 | let y = y / 10.0; 8 | A * (-x * x / 2.0 / SDX / SDX - y * y / 2.0 / SDY / SDY).exp() 9 | } 10 | 11 | const OUT_FILE_NAME: &str = "plotters-doc-data/3d-plot2.gif"; 12 | fn main() -> Result<(), Box> { 13 | let root = BitMapBackend::gif(OUT_FILE_NAME, (600, 400), 100)?.into_drawing_area(); 14 | 15 | for pitch in 0..157 { 16 | root.fill(&WHITE)?; 17 | 18 | let mut chart = ChartBuilder::on(&root) 19 | .caption("2D Gaussian PDF", ("sans-serif", 20)) 20 | .build_cartesian_3d(-3.0..3.0, 0.0..6.0, -3.0..3.0)?; 21 | chart.with_projection(|mut p| { 22 | p.pitch = 1.57 - (1.57 - pitch as f64 / 50.0).abs(); 23 | p.scale = 0.7; 24 | p.into_matrix() // build the projection matrix 25 | }); 26 | 27 | chart 28 | .configure_axes() 29 | .light_grid_style(BLACK.mix(0.15)) 30 | .max_light_lines(3) 31 | .draw()?; 32 | 33 | chart.draw_series( 34 | SurfaceSeries::xoz( 35 | (-15..=15).map(|x| x as f64 / 5.0), 36 | (-15..=15).map(|x| x as f64 / 5.0), 37 | pdf, 38 | ) 39 | .style_func(&|&v| (VulcanoHSL::get_color(v / 5.0)).into()), 40 | )?; 41 | 42 | root.present()?; 43 | } 44 | 45 | // To avoid the IO failure being ignored silently, we manually call the present function 46 | root.present().expect("Unable to write result to file, please make sure 'plotters-doc-data' dir exists under current dir"); 47 | println!("Result has been saved to {}", OUT_FILE_NAME); 48 | 49 | Ok(()) 50 | } 51 | #[test] 52 | fn entry_point() { 53 | main().unwrap() 54 | } 55 | -------------------------------------------------------------------------------- /plotters/examples/README.md: -------------------------------------------------------------------------------- 1 | # plotters examples 2 | 3 | * The example projects have been moved to independent git repository under plotters-rs organization, please check the [Example Project](#example-project) section for the links. 4 | 5 | To run any example, from within the repo, run `cargo run --example ` where `` is the name of the file without the `.rs` extension. 6 | 7 | All the examples assumes the directory [plotters-doc-data](https://github.com/38/plotters-doc-data) exists, otherwise those example crashes. 8 | 9 | The output of these example files are used to generate the [plotters-doc-data](https://github.com/38/plotters-doc-data) repo that populates the sample images in the main README. 10 | We also rely on the output of examples to detect potential layout changes. 11 | For that reason, **they must be run with `cargo` from within the repo, or you must change the output filename in the example code to a directory that exists.** 12 | 13 | The examples that have their own directories and `Cargo.toml` files work differently. They are run the same way you would a standalone project. 14 | 15 | ## Example Projects 16 | 17 | - For WebAssembly sample project, check [plotters-wasm-demo](https://github.com/plotters-rs/plotters-wasm-demo) 18 | - For Frame Buffer, Realtime Readering example, check [plotters-minifb-demo](https://github.com/plotters-rs/plotters-minifb-demo) 19 | - For GTK integration, check [plotters-gtk-demo](https://github.com/plotters-rs/plotters-gtk-demo) 20 | -------------------------------------------------------------------------------- /plotters/examples/animation.rs: -------------------------------------------------------------------------------- 1 | use plotters::prelude::*; 2 | 3 | fn snowflake_iter(points: &[(f64, f64)]) -> Vec<(f64, f64)> { 4 | let mut ret = vec![]; 5 | for i in 0..points.len() { 6 | let (start, end) = (points[i], points[(i + 1) % points.len()]); 7 | let t = ((end.0 - start.0) / 3.0, (end.1 - start.1) / 3.0); 8 | let s = ( 9 | t.0 * 0.5 - t.1 * (0.75f64).sqrt(), 10 | t.1 * 0.5 + (0.75f64).sqrt() * t.0, 11 | ); 12 | ret.push(start); 13 | ret.push((start.0 + t.0, start.1 + t.1)); 14 | ret.push((start.0 + t.0 + s.0, start.1 + t.1 + s.1)); 15 | ret.push((start.0 + t.0 * 2.0, start.1 + t.1 * 2.0)); 16 | } 17 | ret 18 | } 19 | 20 | const OUT_FILE_NAME: &str = "plotters-doc-data/animation.gif"; 21 | fn main() -> Result<(), Box> { 22 | let root = BitMapBackend::gif(OUT_FILE_NAME, (800, 600), 1_000)?.into_drawing_area(); 23 | 24 | for i in 0..8 { 25 | root.fill(&WHITE)?; 26 | 27 | let mut chart = ChartBuilder::on(&root) 28 | .caption( 29 | format!("Koch's Snowflake (n_iter = {})", i), 30 | ("sans-serif", 50), 31 | ) 32 | .build_cartesian_2d(-2.0..2.0, -1.5..1.5)?; 33 | 34 | let mut snowflake_vertices = { 35 | let mut current: Vec<(f64, f64)> = vec![ 36 | (0.0, 1.0), 37 | ((3.0f64).sqrt() / 2.0, -0.5), 38 | (-(3.0f64).sqrt() / 2.0, -0.5), 39 | ]; 40 | for _ in 0..i { 41 | current = snowflake_iter(¤t[..]); 42 | } 43 | current 44 | }; 45 | 46 | chart.draw_series(std::iter::once(Polygon::new( 47 | snowflake_vertices.clone(), 48 | RED.mix(0.2), 49 | )))?; 50 | 51 | snowflake_vertices.push(snowflake_vertices[0]); 52 | chart.draw_series(std::iter::once(PathElement::new(snowflake_vertices, RED)))?; 53 | 54 | root.present()?; 55 | } 56 | 57 | println!("Result has been saved to {}", OUT_FILE_NAME); 58 | 59 | Ok(()) 60 | } 61 | 62 | #[test] 63 | fn entry_point() { 64 | main().unwrap() 65 | } 66 | -------------------------------------------------------------------------------- /plotters/examples/area-chart.rs: -------------------------------------------------------------------------------- 1 | use plotters::prelude::*; 2 | 3 | use rand::SeedableRng; 4 | use rand_distr::{Distribution, Normal}; 5 | use rand_xorshift::XorShiftRng; 6 | 7 | const OUT_FILE_NAME: &str = "plotters-doc-data/area-chart.png"; 8 | fn main() -> Result<(), Box> { 9 | let data: Vec<_> = { 10 | let norm_dist = Normal::new(500.0, 100.0).unwrap(); 11 | let mut x_rand = XorShiftRng::from_seed(*b"MyFragileSeed123"); 12 | let x_iter = norm_dist.sample_iter(&mut x_rand); 13 | x_iter 14 | .filter(|x| *x < 1500.0) 15 | .take(100) 16 | .zip(0..) 17 | .map(|(x, b)| x + (b as f64).powf(1.2)) 18 | .collect() 19 | }; 20 | 21 | let root = BitMapBackend::new(OUT_FILE_NAME, (1024, 768)).into_drawing_area(); 22 | 23 | root.fill(&WHITE)?; 24 | 25 | let mut chart = ChartBuilder::on(&root) 26 | .set_label_area_size(LabelAreaPosition::Left, 60) 27 | .set_label_area_size(LabelAreaPosition::Bottom, 60) 28 | .caption("Area Chart Demo", ("sans-serif", 40)) 29 | .build_cartesian_2d(0..(data.len() - 1), 0.0..1500.0)?; 30 | 31 | chart 32 | .configure_mesh() 33 | .disable_x_mesh() 34 | .disable_y_mesh() 35 | .draw()?; 36 | 37 | chart.draw_series( 38 | AreaSeries::new( 39 | (0..).zip(data.iter()).map(|(x, y)| (x, *y)), 40 | 0.0, 41 | RED.mix(0.2), 42 | ) 43 | .border_style(RED), 44 | )?; 45 | 46 | // To avoid the IO failure being ignored silently, we manually call the present function 47 | root.present().expect("Unable to write result to file, please make sure 'plotters-doc-data' dir exists under current dir"); 48 | println!("Result has been saved to {}", OUT_FILE_NAME); 49 | Ok(()) 50 | } 51 | #[test] 52 | fn entry_point() { 53 | main().unwrap() 54 | } 55 | -------------------------------------------------------------------------------- /plotters/examples/blit-bitmap.rs: -------------------------------------------------------------------------------- 1 | use plotters::prelude::*; 2 | 3 | use image::{imageops::FilterType, ImageFormat}; 4 | 5 | use std::fs::File; 6 | use std::io::BufReader; 7 | 8 | const OUT_FILE_NAME: &str = "plotters-doc-data/blit-bitmap.png"; 9 | 10 | fn main() -> Result<(), Box> { 11 | let root = BitMapBackend::new(OUT_FILE_NAME, (1024, 768)).into_drawing_area(); 12 | root.fill(&WHITE)?; 13 | 14 | let mut chart = ChartBuilder::on(&root) 15 | .caption("Bitmap Example", ("sans-serif", 30)) 16 | .margin(5) 17 | .set_label_area_size(LabelAreaPosition::Left, 40) 18 | .set_label_area_size(LabelAreaPosition::Bottom, 40) 19 | .build_cartesian_2d(0.0..1.0, 0.0..1.0)?; 20 | 21 | chart.configure_mesh().disable_mesh().draw()?; 22 | 23 | let (w, h) = chart.plotting_area().dim_in_pixel(); 24 | let image = image::load( 25 | BufReader::new( 26 | File::open("plotters-doc-data/cat.png").map_err(|e| { 27 | eprintln!("Unable to open file plotters-doc-data.png, please make sure you have clone this repo with --recursive"); 28 | e 29 | })?), 30 | ImageFormat::Png, 31 | )? 32 | .resize_exact(w - w / 10, h - h / 10, FilterType::Nearest); 33 | 34 | let elem: BitMapElement<_> = ((0.05, 0.95), image).into(); 35 | 36 | chart.draw_series(std::iter::once(elem))?; 37 | // To avoid the IO failure being ignored silently, we manually call the present function 38 | root.present().expect("Unable to write result to file, please make sure 'plotters-doc-data' dir exists under current dir"); 39 | println!("Result has been saved to {}", OUT_FILE_NAME); 40 | Ok(()) 41 | } 42 | #[test] 43 | fn entry_point() { 44 | main().unwrap() 45 | } 46 | -------------------------------------------------------------------------------- /plotters/examples/chart.rs: -------------------------------------------------------------------------------- 1 | use plotters::prelude::*; 2 | 3 | const OUT_FILE_NAME: &str = "plotters-doc-data/sample.png"; 4 | fn main() -> Result<(), Box> { 5 | let root_area = BitMapBackend::new(OUT_FILE_NAME, (1024, 768)).into_drawing_area(); 6 | 7 | root_area.fill(&WHITE)?; 8 | 9 | let root_area = root_area.titled("Image Title", ("sans-serif", 60))?; 10 | 11 | let (upper, lower) = root_area.split_vertically(512); 12 | 13 | let x_axis = (-3.4f32..3.4).step(0.1); 14 | 15 | let mut cc = ChartBuilder::on(&upper) 16 | .margin(5) 17 | .set_all_label_area_size(50) 18 | .caption("Sine and Cosine", ("sans-serif", 40)) 19 | .build_cartesian_2d(-3.4f32..3.4, -1.2f32..1.2f32)?; 20 | 21 | cc.configure_mesh() 22 | .x_labels(20) 23 | .y_labels(10) 24 | .disable_mesh() 25 | .x_label_formatter(&|v| format!("{:.1}", v)) 26 | .y_label_formatter(&|v| format!("{:.1}", v)) 27 | .draw()?; 28 | 29 | cc.draw_series(LineSeries::new(x_axis.values().map(|x| (x, x.sin())), &RED))? 30 | .label("Sine") 31 | .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], RED)); 32 | 33 | cc.draw_series(LineSeries::new( 34 | x_axis.values().map(|x| (x, x.cos())), 35 | &BLUE, 36 | ))? 37 | .label("Cosine") 38 | .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], BLUE)); 39 | 40 | cc.configure_series_labels().border_style(BLACK).draw()?; 41 | 42 | /* 43 | // It's possible to use a existing pointing element 44 | cc.draw_series(PointSeries::<_, _, Circle<_>>::new( 45 | (-3.0f32..2.1f32).step(1.0).values().map(|x| (x, x.sin())), 46 | 5, 47 | Into::::into(&RGBColor(255,0,0)).filled(), 48 | ))?;*/ 49 | 50 | // Otherwise you can use a function to construct your pointing element yourself 51 | cc.draw_series(PointSeries::of_element( 52 | (-3.0f32..2.1f32).step(1.0).values().map(|x| (x, x.sin())), 53 | 5, 54 | ShapeStyle::from(&RED).filled(), 55 | &|coord, size, style| { 56 | EmptyElement::at(coord) 57 | + Circle::new((0, 0), size, style) 58 | + Text::new(format!("{:?}", coord), (0, 15), ("sans-serif", 15)) 59 | }, 60 | ))?; 61 | 62 | let drawing_areas = lower.split_evenly((1, 2)); 63 | 64 | for (drawing_area, idx) in drawing_areas.iter().zip(1..) { 65 | let mut cc = ChartBuilder::on(drawing_area) 66 | .x_label_area_size(30) 67 | .y_label_area_size(30) 68 | .margin_right(20) 69 | .caption(format!("y = x^{}", 1 + 2 * idx), ("sans-serif", 40)) 70 | .build_cartesian_2d(-1f32..1f32, -1f32..1f32)?; 71 | cc.configure_mesh() 72 | .x_labels(5) 73 | .y_labels(3) 74 | .max_light_lines(4) 75 | .draw()?; 76 | 77 | cc.draw_series(LineSeries::new( 78 | (-1f32..1f32) 79 | .step(0.01) 80 | .values() 81 | .map(|x| (x, x.powf(idx as f32 * 2.0 + 1.0))), 82 | &BLUE, 83 | ))?; 84 | } 85 | 86 | // To avoid the IO failure being ignored silently, we manually call the present function 87 | root_area.present().expect("Unable to write result to file, please make sure 'plotters-doc-data' dir exists under current dir"); 88 | println!("Result has been saved to {}", OUT_FILE_NAME); 89 | Ok(()) 90 | } 91 | #[test] 92 | fn entry_point() { 93 | main().unwrap() 94 | } 95 | -------------------------------------------------------------------------------- /plotters/examples/colormaps.rs: -------------------------------------------------------------------------------- 1 | use plotters::prelude::*; 2 | 3 | const OUT_FILE_NAME: &str = "plotters-doc-data/colormaps.png"; 4 | 5 | fn main() -> Result<(), Box> { 6 | let colormaps_rgb: [(Box>, &str); 4] = [ 7 | (Box::new(ViridisRGB {}), "Viridis"), 8 | (Box::new(BlackWhite {}), "BlackWhite"), 9 | (Box::new(Bone {}), "Bone"), 10 | (Box::new(Copper {}), "Copper"), 11 | ]; 12 | 13 | let colormaps_hsl: [(Box>, &str); 2] = [ 14 | (Box::new(MandelbrotHSL {}), "MandelbrotHSL"), 15 | (Box::new(VulcanoHSL {}), "VulcanoHSL"), 16 | ]; 17 | 18 | let size_x: i32 = 800; 19 | let n_colormaps = colormaps_rgb.len() + colormaps_hsl.len(); 20 | let size_y = 200 + n_colormaps as u32 * 100; 21 | let root = BitMapBackend::new(OUT_FILE_NAME, (size_x as u32, size_y)).into_drawing_area(); 22 | 23 | root.fill(&WHITE)?; 24 | 25 | let mut chart = ChartBuilder::on(&root) 26 | .caption("Demonstration of predefined colormaps", ("sans-serif", 20)) 27 | .build_cartesian_2d( 28 | -150.0..size_x as f32 + 50.0, 29 | 0.0..3.0 * (n_colormaps as f32), 30 | )?; 31 | 32 | use plotters::style::text_anchor::*; 33 | let centered = Pos::new(HPos::Center, VPos::Center); 34 | let label_style = TextStyle::from(("monospace", 14.0).into_font()).pos(centered); 35 | 36 | let mut colormap_counter = 0; 37 | macro_rules! plot_colormaps( 38 | ($colormap:expr) => { 39 | for (colormap, colormap_name) in $colormap.iter() { 40 | chart.draw_series( 41 | (0..size_x as i32).map(|x| { 42 | Rectangle::new([ 43 | (x as f32, 3.0*(n_colormaps - 1 - colormap_counter) as f32 + 0.5), 44 | (x as f32+1.0, 3.0*(n_colormaps - 1 - colormap_counter) as f32 + 2.5) 45 | ], 46 | colormap.get_color_normalized(x as f32, 0.0, size_x as f32).filled()) 47 | }) 48 | )?; 49 | chart.draw_series( 50 | [Text::new(colormap_name.to_owned(), (-75.0, 3.0*(n_colormaps-1-colormap_counter) as f32 + 1.5), &label_style)] 51 | )?; 52 | colormap_counter+=1; 53 | } 54 | } 55 | ); 56 | 57 | plot_colormaps!(colormaps_rgb); 58 | plot_colormaps!(colormaps_hsl); 59 | 60 | // To avoid the IO failure being ignored silently, we manually call the present function 61 | root.present().expect("Unable to write result to file, please make sure 'plotters-doc-data' dir exists under current dir"); 62 | println!("Result has been saved to {}", OUT_FILE_NAME); 63 | 64 | Ok(()) 65 | } 66 | #[test] 67 | fn entry_point() { 68 | main().unwrap() 69 | } 70 | -------------------------------------------------------------------------------- /plotters/examples/customized_coord.rs: -------------------------------------------------------------------------------- 1 | use plotters::{ 2 | coord::ranged1d::{KeyPointHint, NoDefaultFormatting, ValueFormatter}, 3 | prelude::*, 4 | }; 5 | const OUT_FILE_NAME: &str = "plotters-doc-data/customized_coord.svg"; 6 | 7 | struct CustomizedX(u32); 8 | 9 | impl Ranged for CustomizedX { 10 | type ValueType = u32; 11 | type FormatOption = NoDefaultFormatting; 12 | fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 { 13 | let size = limit.1 - limit.0; 14 | ((*value as f64 / self.0 as f64) * size as f64) as i32 + limit.0 15 | } 16 | 17 | fn range(&self) -> std::ops::Range { 18 | 0..self.0 19 | } 20 | 21 | fn key_points(&self, hint: Hint) -> Vec { 22 | if hint.max_num_points() < (self.0 as usize) { 23 | return vec![]; 24 | } 25 | 26 | (0..self.0).collect() 27 | } 28 | } 29 | 30 | impl ValueFormatter for CustomizedX { 31 | fn format_ext(&self, value: &u32) -> String { 32 | format!("{} of {}", value, self.0) 33 | } 34 | } 35 | 36 | fn main() -> Result<(), Box> { 37 | let area = SVGBackend::new(OUT_FILE_NAME, (1024, 760)).into_drawing_area(); 38 | area.fill(&WHITE)?; 39 | 40 | let mut chart = ChartBuilder::on(&area) 41 | .set_all_label_area_size(50) 42 | .build_cartesian_2d(CustomizedX(7), 0.0..10.0)?; 43 | 44 | chart.configure_mesh().draw()?; 45 | 46 | area.present().expect("Unable to write result to file, please make sure 'plotters-doc-data' dir exists under current dir"); 47 | println!("Result has been saved to {}", OUT_FILE_NAME); 48 | Ok(()) 49 | } 50 | 51 | #[test] 52 | fn entry_point() { 53 | main().unwrap() 54 | } 55 | -------------------------------------------------------------------------------- /plotters/examples/errorbar.rs: -------------------------------------------------------------------------------- 1 | use plotters::prelude::*; 2 | 3 | use rand::SeedableRng; 4 | use rand_distr::{Distribution, Normal}; 5 | use rand_xorshift::XorShiftRng; 6 | 7 | use itertools::Itertools; 8 | 9 | use num_traits::sign::Signed; 10 | 11 | const OUT_FILE_NAME: &str = "plotters-doc-data/errorbar.png"; 12 | fn main() -> Result<(), Box> { 13 | let data = generate_random_data(); 14 | let down_sampled = down_sample(&data[..]); 15 | 16 | let root = BitMapBackend::new(OUT_FILE_NAME, (1024, 768)).into_drawing_area(); 17 | 18 | root.fill(&WHITE)?; 19 | 20 | let mut chart = ChartBuilder::on(&root) 21 | .caption("Linear Function with Noise", ("sans-serif", 60)) 22 | .margin(10) 23 | .set_label_area_size(LabelAreaPosition::Left, 40) 24 | .set_label_area_size(LabelAreaPosition::Bottom, 40) 25 | .build_cartesian_2d(-10f64..10f64, -10f64..10f64)?; 26 | 27 | chart.configure_mesh().draw()?; 28 | 29 | chart 30 | .draw_series(LineSeries::new(data, &GREEN.mix(0.3)))? 31 | .label("Raw Data") 32 | .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], GREEN)); 33 | 34 | chart.draw_series(LineSeries::new( 35 | down_sampled.iter().map(|(x, _, y, _)| (*x, *y)), 36 | &BLUE, 37 | ))?; 38 | 39 | chart 40 | .draw_series( 41 | down_sampled.iter().map(|(x, yl, ym, yh)| { 42 | ErrorBar::new_vertical(*x, *yl, *ym, *yh, BLUE.filled(), 20) 43 | }), 44 | )? 45 | .label("Down-sampled") 46 | .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], BLUE)); 47 | 48 | chart 49 | .configure_series_labels() 50 | .background_style(WHITE.filled()) 51 | .draw()?; 52 | 53 | // To avoid the IO failure being ignored silently, we manually call the present function 54 | root.present().expect("Unable to write result to file, please make sure 'plotters-doc-data' dir exists under current dir"); 55 | println!("Result has been saved to {}", OUT_FILE_NAME); 56 | 57 | Ok(()) 58 | } 59 | 60 | fn generate_random_data() -> Vec<(f64, f64)> { 61 | let norm_dist = Normal::new(0.0, 1.0).unwrap(); 62 | let mut x_rand = XorShiftRng::from_seed(*b"MyFragileSeed123"); 63 | let x_iter = norm_dist.sample_iter(&mut x_rand); 64 | x_iter 65 | .take(20000) 66 | .filter(|x| x.abs() <= 4.0) 67 | .zip(-10000..10000) 68 | .map(|(yn, x)| { 69 | ( 70 | x as f64 / 1000.0, 71 | x as f64 / 1000.0 + yn * x as f64 / 10000.0, 72 | ) 73 | }) 74 | .collect() 75 | } 76 | 77 | fn down_sample(data: &[(f64, f64)]) -> Vec<(f64, f64, f64, f64)> { 78 | let down_sampled: Vec<_> = data 79 | .iter() 80 | .group_by(|x| (x.0 * 1.0).round() / 1.0) 81 | .into_iter() 82 | .map(|(x, g)| { 83 | let mut g: Vec<_> = g.map(|(_, y)| *y).collect(); 84 | g.sort_by(|a, b| a.partial_cmp(b).unwrap()); 85 | ( 86 | x, 87 | g[0], 88 | g.iter().sum::() / g.len() as f64, 89 | g[g.len() - 1], 90 | ) 91 | }) 92 | .collect(); 93 | down_sampled 94 | } 95 | #[test] 96 | fn entry_point() { 97 | main().unwrap() 98 | } 99 | -------------------------------------------------------------------------------- /plotters/examples/histogram.rs: -------------------------------------------------------------------------------- 1 | use plotters::prelude::*; 2 | const OUT_FILE_NAME: &str = "plotters-doc-data/histogram.png"; 3 | fn main() -> Result<(), Box> { 4 | let root = BitMapBackend::new(OUT_FILE_NAME, (640, 480)).into_drawing_area(); 5 | 6 | root.fill(&WHITE)?; 7 | 8 | let mut chart = ChartBuilder::on(&root) 9 | .x_label_area_size(35) 10 | .y_label_area_size(40) 11 | .margin(5) 12 | .caption("Histogram Test", ("sans-serif", 50.0)) 13 | .build_cartesian_2d((0u32..10u32).into_segmented(), 0u32..10u32)?; 14 | 15 | chart 16 | .configure_mesh() 17 | .disable_x_mesh() 18 | .bold_line_style(WHITE.mix(0.3)) 19 | .y_desc("Count") 20 | .x_desc("Bucket") 21 | .axis_desc_style(("sans-serif", 15)) 22 | .draw()?; 23 | 24 | let data = [ 25 | 0u32, 1, 1, 1, 4, 2, 5, 7, 8, 6, 4, 2, 1, 8, 3, 3, 3, 4, 4, 3, 3, 3, 26 | ]; 27 | 28 | chart.draw_series( 29 | Histogram::vertical(&chart) 30 | .style(RED.mix(0.5).filled()) 31 | .data(data.iter().map(|x: &u32| (*x, 1))), 32 | )?; 33 | 34 | // To avoid the IO failure being ignored silently, we manually call the present function 35 | root.present().expect("Unable to write result to file, please make sure 'plotters-doc-data' dir exists under current dir"); 36 | println!("Result has been saved to {}", OUT_FILE_NAME); 37 | 38 | Ok(()) 39 | } 40 | #[test] 41 | fn entry_point() { 42 | main().unwrap() 43 | } 44 | -------------------------------------------------------------------------------- /plotters/examples/mandelbrot.rs: -------------------------------------------------------------------------------- 1 | use plotters::prelude::*; 2 | use std::ops::Range; 3 | 4 | const OUT_FILE_NAME: &str = "plotters-doc-data/mandelbrot.png"; 5 | fn main() -> Result<(), Box> { 6 | let root = BitMapBackend::new(OUT_FILE_NAME, (800, 600)).into_drawing_area(); 7 | 8 | root.fill(&WHITE)?; 9 | 10 | let mut chart = ChartBuilder::on(&root) 11 | .margin(20) 12 | .x_label_area_size(10) 13 | .y_label_area_size(10) 14 | .build_cartesian_2d(-2.1f64..0.6f64, -1.2f64..1.2f64)?; 15 | 16 | chart 17 | .configure_mesh() 18 | .disable_x_mesh() 19 | .disable_y_mesh() 20 | .draw()?; 21 | 22 | let plotting_area = chart.plotting_area(); 23 | 24 | let range = plotting_area.get_pixel_range(); 25 | 26 | let (pw, ph) = (range.0.end - range.0.start, range.1.end - range.1.start); 27 | let (xr, yr) = (chart.x_range(), chart.y_range()); 28 | 29 | for (x, y, c) in mandelbrot_set(xr, yr, (pw as usize, ph as usize), 100) { 30 | if c != 100 { 31 | plotting_area.draw_pixel((x, y), &MandelbrotHSL::get_color(c as f64 / 100.0))?; 32 | } else { 33 | plotting_area.draw_pixel((x, y), &BLACK)?; 34 | } 35 | } 36 | 37 | // To avoid the IO failure being ignored silently, we manually call the present function 38 | root.present().expect("Unable to write result to file, please make sure 'plotters-doc-data' dir exists under current dir"); 39 | println!("Result has been saved to {}", OUT_FILE_NAME); 40 | 41 | Ok(()) 42 | } 43 | 44 | fn mandelbrot_set( 45 | real: Range, 46 | complex: Range, 47 | samples: (usize, usize), 48 | max_iter: usize, 49 | ) -> impl Iterator { 50 | let step = ( 51 | (real.end - real.start) / samples.0 as f64, 52 | (complex.end - complex.start) / samples.1 as f64, 53 | ); 54 | (0..(samples.0 * samples.1)).map(move |k| { 55 | let c = ( 56 | real.start + step.0 * (k % samples.0) as f64, 57 | complex.start + step.1 * (k / samples.0) as f64, 58 | ); 59 | let mut z = (0.0, 0.0); 60 | let mut cnt = 0; 61 | while cnt < max_iter && z.0 * z.0 + z.1 * z.1 <= 1e10 { 62 | z = (z.0 * z.0 - z.1 * z.1 + c.0, 2.0 * z.0 * z.1 + c.1); 63 | cnt += 1; 64 | } 65 | (c.0, c.1, cnt) 66 | }) 67 | } 68 | #[test] 69 | fn entry_point() { 70 | main().unwrap() 71 | } 72 | -------------------------------------------------------------------------------- /plotters/examples/matshow.rs: -------------------------------------------------------------------------------- 1 | use plotters::prelude::*; 2 | 3 | const OUT_FILE_NAME: &str = "plotters-doc-data/matshow.png"; 4 | fn main() -> Result<(), Box> { 5 | let root = BitMapBackend::new(OUT_FILE_NAME, (1024, 768)).into_drawing_area(); 6 | 7 | root.fill(&WHITE)?; 8 | 9 | let mut chart = ChartBuilder::on(&root) 10 | .caption("Matshow Example", ("sans-serif", 80)) 11 | .margin(5) 12 | .top_x_label_area_size(40) 13 | .y_label_area_size(40) 14 | .build_cartesian_2d(0i32..15i32, 0i32..15i32)?; 15 | 16 | chart 17 | .configure_mesh() 18 | .x_labels(15) 19 | .y_labels(15) 20 | .max_light_lines(4) 21 | .x_label_offset(35) 22 | .y_label_offset(25) 23 | .disable_x_mesh() 24 | .disable_y_mesh() 25 | .label_style(("sans-serif", 20)) 26 | .draw()?; 27 | 28 | let mut matrix = [[0; 15]; 15]; 29 | 30 | for i in 0..15 { 31 | matrix[i][i] = i + 4; 32 | } 33 | 34 | chart.draw_series( 35 | matrix 36 | .iter() 37 | .zip(0..) 38 | .flat_map(|(l, y)| l.iter().zip(0..).map(move |(v, x)| (x, y, v))) 39 | .map(|(x, y, v)| { 40 | Rectangle::new( 41 | [(x, y), (x + 1, y + 1)], 42 | HSLColor( 43 | 240.0 / 360.0 - 240.0 / 360.0 * (*v as f64 / 20.0), 44 | 0.7, 45 | 0.1 + 0.4 * *v as f64 / 20.0, 46 | ) 47 | .filled(), 48 | ) 49 | }), 50 | )?; 51 | 52 | // To avoid the IO failure being ignored silently, we manually call the present function 53 | root.present().expect("Unable to write result to file, please make sure 'plotters-doc-data' dir exists under current dir"); 54 | println!("Result has been saved to {}", OUT_FILE_NAME); 55 | 56 | Ok(()) 57 | } 58 | #[test] 59 | fn entry_point() { 60 | main().unwrap() 61 | } 62 | -------------------------------------------------------------------------------- /plotters/examples/nested_coord.rs: -------------------------------------------------------------------------------- 1 | use plotters::prelude::*; 2 | const OUT_FILE_NAME: &str = "plotters-doc-data/nested_coord.png"; 3 | fn main() -> Result<(), Box> { 4 | let root = BitMapBackend::new(OUT_FILE_NAME, (640, 480)).into_drawing_area(); 5 | 6 | root.fill(&WHITE)?; 7 | 8 | let mut chart = ChartBuilder::on(&root) 9 | .x_label_area_size(35) 10 | .y_label_area_size(40) 11 | .margin(5) 12 | .caption("Nested Coord", ("sans-serif", 50.0)) 13 | .build_cartesian_2d( 14 | ["Linear", "Quadratic"].nested_coord(|_| 0.0..10.0), 15 | 0.0..10.0, 16 | )?; 17 | 18 | chart 19 | .configure_mesh() 20 | .disable_mesh() 21 | .axis_desc_style(("sans-serif", 15)) 22 | .draw()?; 23 | 24 | chart.draw_series(LineSeries::new( 25 | (0..10) 26 | .map(|x| x as f64 / 1.0) 27 | .map(|x| ((&"Linear", x).into(), x)), 28 | &RED, 29 | ))?; 30 | 31 | chart.draw_series(LineSeries::new( 32 | (0..10) 33 | .map(|x| x as f64 / 1.0) 34 | .map(|x| ((&"Quadratic", x).into(), x * x / 10.0)), 35 | &RED, 36 | ))?; 37 | 38 | // To avoid the IO failure being ignored silently, we manually call the present function 39 | root.present().expect("Unable to write result to file, please make sure 'plotters-doc-data' dir exists under current dir"); 40 | println!("Result has been saved to {}", OUT_FILE_NAME); 41 | 42 | Ok(()) 43 | } 44 | #[test] 45 | fn entry_point() { 46 | main().unwrap() 47 | } 48 | -------------------------------------------------------------------------------- /plotters/examples/normal-dist.rs: -------------------------------------------------------------------------------- 1 | use plotters::prelude::*; 2 | 3 | use rand::SeedableRng; 4 | use rand_distr::{Distribution, Normal}; 5 | use rand_xorshift::XorShiftRng; 6 | 7 | const OUT_FILE_NAME: &str = "plotters-doc-data/normal-dist.png"; 8 | fn main() -> Result<(), Box> { 9 | let root = BitMapBackend::new(OUT_FILE_NAME, (1024, 768)).into_drawing_area(); 10 | 11 | root.fill(&WHITE)?; 12 | 13 | let sd = 0.13; 14 | 15 | let random_points: Vec<(f64, f64)> = { 16 | let norm_dist = Normal::new(0.5, sd).unwrap(); 17 | let mut x_rand = XorShiftRng::from_seed(*b"MyFragileSeed123"); 18 | let mut y_rand = XorShiftRng::from_seed(*b"MyFragileSeed321"); 19 | let x_iter = norm_dist.sample_iter(&mut x_rand); 20 | let y_iter = norm_dist.sample_iter(&mut y_rand); 21 | x_iter.zip(y_iter).take(5000).collect() 22 | }; 23 | 24 | let areas = root.split_by_breakpoints([944], [80]); 25 | 26 | let mut x_hist_ctx = ChartBuilder::on(&areas[0]) 27 | .y_label_area_size(40) 28 | .build_cartesian_2d((0.0..1.0).step(0.01).use_round().into_segmented(), 0..250)?; 29 | let mut y_hist_ctx = ChartBuilder::on(&areas[3]) 30 | .x_label_area_size(40) 31 | .build_cartesian_2d(0..250, (0.0..1.0).step(0.01).use_round())?; 32 | let mut scatter_ctx = ChartBuilder::on(&areas[2]) 33 | .x_label_area_size(40) 34 | .y_label_area_size(40) 35 | .build_cartesian_2d(0f64..1f64, 0f64..1f64)?; 36 | scatter_ctx 37 | .configure_mesh() 38 | .disable_x_mesh() 39 | .disable_y_mesh() 40 | .draw()?; 41 | scatter_ctx.draw_series( 42 | random_points 43 | .iter() 44 | .map(|(x, y)| Circle::new((*x, *y), 2, GREEN.filled())), 45 | )?; 46 | let x_hist = Histogram::vertical(&x_hist_ctx) 47 | .style(GREEN.filled()) 48 | .margin(0) 49 | .data(random_points.iter().map(|(x, _)| (*x, 1))); 50 | let y_hist = Histogram::horizontal(&y_hist_ctx) 51 | .style(GREEN.filled()) 52 | .margin(0) 53 | .data(random_points.iter().map(|(_, y)| (*y, 1))); 54 | x_hist_ctx.draw_series(x_hist)?; 55 | y_hist_ctx.draw_series(y_hist)?; 56 | 57 | // To avoid the IO failure being ignored silently, we manually call the present function 58 | root.present().expect("Unable to write result to file, please make sure 'plotters-doc-data' dir exists under current dir"); 59 | println!("Result has been saved to {}", OUT_FILE_NAME); 60 | 61 | Ok(()) 62 | } 63 | #[test] 64 | fn entry_point() { 65 | main().unwrap() 66 | } 67 | -------------------------------------------------------------------------------- /plotters/examples/normal-dist2.rs: -------------------------------------------------------------------------------- 1 | use plotters::prelude::*; 2 | 3 | use rand::SeedableRng; 4 | use rand_distr::{Distribution, Normal}; 5 | use rand_xorshift::XorShiftRng; 6 | 7 | use num_traits::sign::Signed; 8 | 9 | const OUT_FILE_NAME: &str = "plotters-doc-data/normal-dist2.png"; 10 | fn main() -> Result<(), Box> { 11 | let sd = 0.60; 12 | 13 | let random_points: Vec = { 14 | let norm_dist = Normal::new(0.0, sd).unwrap(); 15 | let mut x_rand = XorShiftRng::from_seed(*b"MyFragileSeed123"); 16 | let x_iter = norm_dist.sample_iter(&mut x_rand); 17 | x_iter.take(5000).filter(|x| x.abs() <= 4.0).collect() 18 | }; 19 | 20 | let root = BitMapBackend::new(OUT_FILE_NAME, (1024, 768)).into_drawing_area(); 21 | 22 | root.fill(&WHITE)?; 23 | 24 | let mut chart = ChartBuilder::on(&root) 25 | .margin(5) 26 | .caption("1D Gaussian Distribution Demo", ("sans-serif", 30)) 27 | .set_label_area_size(LabelAreaPosition::Left, 60) 28 | .set_label_area_size(LabelAreaPosition::Bottom, 60) 29 | .set_label_area_size(LabelAreaPosition::Right, 60) 30 | .build_cartesian_2d(-4f64..4f64, 0f64..0.1)? 31 | .set_secondary_coord( 32 | (-4f64..4f64).step(0.1).use_round().into_segmented(), 33 | 0u32..500u32, 34 | ); 35 | 36 | chart 37 | .configure_mesh() 38 | .disable_x_mesh() 39 | .disable_y_mesh() 40 | .y_label_formatter(&|y| format!("{:.0}%", *y * 100.0)) 41 | .y_desc("Percentage") 42 | .draw()?; 43 | 44 | chart.configure_secondary_axes().y_desc("Count").draw()?; 45 | 46 | let actual = Histogram::vertical(chart.borrow_secondary()) 47 | .style(GREEN.filled()) 48 | .margin(3) 49 | .data(random_points.iter().map(|x| (*x, 1))); 50 | 51 | chart 52 | .draw_secondary_series(actual)? 53 | .label("Observed") 54 | .legend(|(x, y)| Rectangle::new([(x, y - 5), (x + 10, y + 5)], GREEN.filled())); 55 | 56 | let pdf = LineSeries::new( 57 | (-400..400).map(|x| x as f64 / 100.0).map(|x| { 58 | ( 59 | x, 60 | (-x * x / 2.0 / sd / sd).exp() / (2.0 * std::f64::consts::PI * sd * sd).sqrt() 61 | * 0.1, 62 | ) 63 | }), 64 | &RED, 65 | ); 66 | 67 | chart 68 | .draw_series(pdf)? 69 | .label("PDF") 70 | .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], RED.filled())); 71 | 72 | chart.configure_series_labels().draw()?; 73 | 74 | // To avoid the IO failure being ignored silently, we manually call the present function 75 | root.present().expect("Unable to write result to file, please make sure 'plotters-doc-data' dir exists under current dir"); 76 | println!("Result has been saved to {}", OUT_FILE_NAME); 77 | 78 | Ok(()) 79 | } 80 | #[test] 81 | fn entry_point() { 82 | main().unwrap() 83 | } 84 | -------------------------------------------------------------------------------- /plotters/examples/pie.rs: -------------------------------------------------------------------------------- 1 | use plotters::{prelude::*, style::full_palette::ORANGE}; 2 | 3 | const OUT_FILE_NAME: &str = "plotters-doc-data/pie-chart.png"; 4 | fn main() -> Result<(), Box> { 5 | let root_area = BitMapBackend::new(&OUT_FILE_NAME, (950, 700)).into_drawing_area(); 6 | root_area.fill(&WHITE).unwrap(); 7 | let title_style = TextStyle::from(("sans-serif", 30).into_font()).color(&(BLACK)); 8 | root_area.titled("BEST CIRCLES", title_style).unwrap(); 9 | 10 | let dims = root_area.dim_in_pixel(); 11 | let center = (dims.0 as i32 / 2, dims.1 as i32 / 2); 12 | let radius = 300.0; 13 | let sizes = vec![66.0, 33.0]; 14 | let _rgba = RGBAColor(0, 50, 255, 1.0); 15 | let colors = vec![RGBColor(0, 50, 255), CYAN]; 16 | let labels = vec!["Pizza", "Pacman"]; 17 | 18 | let mut pie = Pie::new(¢er, &radius, &sizes, &colors, &labels); 19 | pie.start_angle(66.0); 20 | pie.label_style((("sans-serif", 50).into_font()).color(&(ORANGE))); 21 | pie.percentages((("sans-serif", radius * 0.08).into_font()).color(&BLACK)); 22 | root_area.draw(&pie)?; 23 | 24 | Ok(()) 25 | } 26 | -------------------------------------------------------------------------------- /plotters/examples/relative_size.rs: -------------------------------------------------------------------------------- 1 | use plotters::coord::Shift; 2 | use plotters::prelude::*; 3 | 4 | fn draw_chart(root: &DrawingArea) -> DrawResult<(), B> { 5 | let mut chart = ChartBuilder::on(root) 6 | .caption( 7 | "Relative Size Example", 8 | ("sans-serif", (5).percent_height()), 9 | ) 10 | .x_label_area_size((10).percent_height()) 11 | .y_label_area_size((10).percent_width()) 12 | .margin(5) 13 | .build_cartesian_2d(-5.0..5.0, -1.0..1.0)?; 14 | 15 | chart 16 | .configure_mesh() 17 | .disable_x_mesh() 18 | .disable_y_mesh() 19 | .label_style(("sans-serif", (3).percent_height())) 20 | .draw()?; 21 | 22 | chart.draw_series(LineSeries::new( 23 | (0..1000) 24 | .map(|x| x as f64 / 100.0 - 5.0) 25 | .map(|x| (x, x.sin())), 26 | &RED, 27 | ))?; 28 | Ok(()) 29 | } 30 | 31 | const OUT_FILE_NAME: &str = "plotters-doc-data/relative_size.png"; 32 | fn main() -> Result<(), Box> { 33 | let root = BitMapBackend::new(OUT_FILE_NAME, (1024, 768)).into_drawing_area(); 34 | 35 | root.fill(&WHITE)?; 36 | 37 | let (left, right) = root.split_horizontally((70).percent_width()); 38 | 39 | draw_chart(&left)?; 40 | 41 | let (upper, lower) = right.split_vertically(300); 42 | 43 | draw_chart(&upper)?; 44 | draw_chart(&lower)?; 45 | let root = root.shrink((200, 200), (150, 100)); 46 | draw_chart(&root)?; 47 | 48 | // To avoid the IO failure being ignored silently, we manually call the present function 49 | root.present().expect("Unable to write result to file, please make sure 'plotters-doc-data' dir exists under current dir"); 50 | println!("Result has been saved to {}", OUT_FILE_NAME); 51 | 52 | Ok(()) 53 | } 54 | #[test] 55 | fn entry_point() { 56 | main().unwrap() 57 | } 58 | -------------------------------------------------------------------------------- /plotters/examples/sierpinski.rs: -------------------------------------------------------------------------------- 1 | use plotters::coord::Shift; 2 | use plotters::prelude::*; 3 | 4 | pub fn sierpinski_carpet( 5 | depth: u32, 6 | drawing_area: &DrawingArea, 7 | ) -> Result<(), Box> { 8 | if depth > 0 { 9 | let sub_areas = drawing_area.split_evenly((3, 3)); 10 | for (idx, sub_area) in (0..).zip(sub_areas.iter()) { 11 | if idx != 4 { 12 | sub_area.fill(&BLUE)?; 13 | sierpinski_carpet(depth - 1, sub_area)?; 14 | } else { 15 | sub_area.fill(&WHITE)?; 16 | } 17 | } 18 | } 19 | Ok(()) 20 | } 21 | 22 | const OUT_FILE_NAME: &str = "plotters-doc-data/sierpinski.png"; 23 | fn main() -> Result<(), Box> { 24 | let root = BitMapBackend::new(OUT_FILE_NAME, (1024, 768)).into_drawing_area(); 25 | 26 | root.fill(&WHITE)?; 27 | 28 | let root = root 29 | .titled("Sierpinski Carpet Demo", ("sans-serif", 60))? 30 | .shrink(((1024 - 700) / 2, 0), (700, 700)); 31 | 32 | sierpinski_carpet(5, &root)?; 33 | 34 | // To avoid the IO failure being ignored silently, we manually call the present function 35 | root.present().expect("Unable to write result to file, please make sure 'plotters-doc-data' dir exists under current dir"); 36 | println!("Result has been saved to {}", OUT_FILE_NAME); 37 | 38 | Ok(()) 39 | } 40 | #[test] 41 | fn entry_point() { 42 | main().unwrap() 43 | } 44 | -------------------------------------------------------------------------------- /plotters/examples/slc-temp.rs: -------------------------------------------------------------------------------- 1 | use plotters::prelude::*; 2 | 3 | use chrono::{TimeZone, Utc}; 4 | 5 | use std::error::Error; 6 | 7 | const OUT_FILE_NAME: &str = "plotters-doc-data/slc-temp.png"; 8 | fn main() -> Result<(), Box> { 9 | let root = BitMapBackend::new(OUT_FILE_NAME, (1024, 768)).into_drawing_area(); 10 | 11 | root.fill(&WHITE)?; 12 | 13 | let mut chart = ChartBuilder::on(&root) 14 | .margin(10) 15 | .caption( 16 | "Monthly Average Temperate in Salt Lake City, UT", 17 | ("sans-serif", 40), 18 | ) 19 | .set_label_area_size(LabelAreaPosition::Left, 60) 20 | .set_label_area_size(LabelAreaPosition::Right, 60) 21 | .set_label_area_size(LabelAreaPosition::Bottom, 40) 22 | .build_cartesian_2d( 23 | (Utc.ymd(2010, 1, 1)..Utc.ymd(2018, 12, 1)).monthly(), 24 | 14.0..104.0, 25 | )? 26 | .set_secondary_coord( 27 | (Utc.ymd(2010, 1, 1)..Utc.ymd(2018, 12, 1)).monthly(), 28 | -10.0..40.0, 29 | ); 30 | 31 | chart 32 | .configure_mesh() 33 | .disable_x_mesh() 34 | .disable_y_mesh() 35 | .x_labels(30) 36 | .max_light_lines(4) 37 | .y_desc("Average Temp (F)") 38 | .draw()?; 39 | chart 40 | .configure_secondary_axes() 41 | .y_desc("Average Temp (C)") 42 | .draw()?; 43 | 44 | chart.draw_series(LineSeries::new( 45 | DATA.iter().map(|(y, m, t)| (Utc.ymd(*y, *m, 1), *t)), 46 | &BLUE, 47 | ))?; 48 | 49 | chart.draw_series( 50 | DATA.iter() 51 | .map(|(y, m, t)| Circle::new((Utc.ymd(*y, *m, 1), *t), 3, BLUE.filled())), 52 | )?; 53 | 54 | // To avoid the IO failure being ignored silently, we manually call the present function 55 | root.present().expect("Unable to write result to file, please make sure 'plotters-doc-data' dir exists under current dir"); 56 | println!("Result has been saved to {}", OUT_FILE_NAME); 57 | 58 | Ok(()) 59 | } 60 | 61 | const DATA: [(i32, u32, f64); 12 * 9] = [ 62 | (2010, 1, 32.4), 63 | (2010, 2, 37.5), 64 | (2010, 3, 44.5), 65 | (2010, 4, 50.3), 66 | (2010, 5, 55.0), 67 | (2010, 6, 70.0), 68 | (2010, 7, 78.7), 69 | (2010, 8, 76.5), 70 | (2010, 9, 68.9), 71 | (2010, 10, 56.3), 72 | (2010, 11, 40.3), 73 | (2010, 12, 36.5), 74 | (2011, 1, 28.8), 75 | (2011, 2, 35.1), 76 | (2011, 3, 45.5), 77 | (2011, 4, 48.9), 78 | (2011, 5, 55.1), 79 | (2011, 6, 68.8), 80 | (2011, 7, 77.9), 81 | (2011, 8, 78.4), 82 | (2011, 9, 68.2), 83 | (2011, 10, 55.0), 84 | (2011, 11, 41.5), 85 | (2011, 12, 31.0), 86 | (2012, 1, 35.6), 87 | (2012, 2, 38.1), 88 | (2012, 3, 49.1), 89 | (2012, 4, 56.1), 90 | (2012, 5, 63.4), 91 | (2012, 6, 73.0), 92 | (2012, 7, 79.0), 93 | (2012, 8, 79.0), 94 | (2012, 9, 68.8), 95 | (2012, 10, 54.9), 96 | (2012, 11, 45.2), 97 | (2012, 12, 34.9), 98 | (2013, 1, 19.7), 99 | (2013, 2, 31.1), 100 | (2013, 3, 46.2), 101 | (2013, 4, 49.8), 102 | (2013, 5, 61.3), 103 | (2013, 6, 73.3), 104 | (2013, 7, 80.3), 105 | (2013, 8, 77.2), 106 | (2013, 9, 68.3), 107 | (2013, 10, 52.0), 108 | (2013, 11, 43.2), 109 | (2013, 12, 25.7), 110 | (2014, 1, 31.5), 111 | (2014, 2, 39.3), 112 | (2014, 3, 46.4), 113 | (2014, 4, 52.5), 114 | (2014, 5, 63.0), 115 | (2014, 6, 71.3), 116 | (2014, 7, 81.0), 117 | (2014, 8, 75.3), 118 | (2014, 9, 70.0), 119 | (2014, 10, 58.6), 120 | (2014, 11, 42.1), 121 | (2014, 12, 38.0), 122 | (2015, 1, 35.3), 123 | (2015, 2, 45.2), 124 | (2015, 3, 50.9), 125 | (2015, 4, 54.3), 126 | (2015, 5, 60.5), 127 | (2015, 6, 77.1), 128 | (2015, 7, 76.2), 129 | (2015, 8, 77.3), 130 | (2015, 9, 70.4), 131 | (2015, 10, 60.6), 132 | (2015, 11, 40.9), 133 | (2015, 12, 32.4), 134 | (2016, 1, 31.5), 135 | (2016, 2, 35.1), 136 | (2016, 3, 49.1), 137 | (2016, 4, 55.1), 138 | (2016, 5, 60.9), 139 | (2016, 6, 76.9), 140 | (2016, 7, 80.0), 141 | (2016, 8, 77.0), 142 | (2016, 9, 67.1), 143 | (2016, 10, 59.1), 144 | (2016, 11, 47.4), 145 | (2016, 12, 31.8), 146 | (2017, 1, 29.4), 147 | (2017, 2, 42.4), 148 | (2017, 3, 51.7), 149 | (2017, 4, 51.7), 150 | (2017, 5, 62.5), 151 | (2017, 6, 74.8), 152 | (2017, 7, 81.3), 153 | (2017, 8, 78.1), 154 | (2017, 9, 65.7), 155 | (2017, 10, 52.5), 156 | (2017, 11, 49.0), 157 | (2017, 12, 34.4), 158 | (2018, 1, 38.1), 159 | (2018, 2, 37.5), 160 | (2018, 3, 45.4), 161 | (2018, 4, 54.6), 162 | (2018, 5, 64.0), 163 | (2018, 6, 74.9), 164 | (2018, 7, 82.5), 165 | (2018, 8, 78.1), 166 | (2018, 9, 71.9), 167 | (2018, 10, 53.2), 168 | (2018, 11, 39.7), 169 | (2018, 12, 33.6), 170 | ]; 171 | #[test] 172 | fn entry_point() { 173 | main().unwrap() 174 | } 175 | -------------------------------------------------------------------------------- /plotters/examples/snowflake.rs: -------------------------------------------------------------------------------- 1 | use plotters::prelude::*; 2 | 3 | fn snowflake_iter(points: &[(f64, f64)]) -> Vec<(f64, f64)> { 4 | let mut ret = vec![]; 5 | for i in 0..points.len() { 6 | let (start, end) = (points[i], points[(i + 1) % points.len()]); 7 | let t = ((end.0 - start.0) / 3.0, (end.1 - start.1) / 3.0); 8 | let s = ( 9 | t.0 * 0.5 - t.1 * (0.75f64).sqrt(), 10 | t.1 * 0.5 + (0.75f64).sqrt() * t.0, 11 | ); 12 | ret.push(start); 13 | ret.push((start.0 + t.0, start.1 + t.1)); 14 | ret.push((start.0 + t.0 + s.0, start.1 + t.1 + s.1)); 15 | ret.push((start.0 + t.0 * 2.0, start.1 + t.1 * 2.0)); 16 | } 17 | ret 18 | } 19 | 20 | const OUT_FILE_NAME: &str = "plotters-doc-data/snowflake.png"; 21 | fn main() -> Result<(), Box> { 22 | let root = BitMapBackend::new(OUT_FILE_NAME, (1024, 768)).into_drawing_area(); 23 | 24 | root.fill(&WHITE)?; 25 | 26 | let mut chart = ChartBuilder::on(&root) 27 | .caption("Koch's Snowflake", ("sans-serif", 50)) 28 | .build_cartesian_2d(-2.0..2.0, -1.5..1.5)?; 29 | 30 | let mut snowflake_vertices = { 31 | let mut current: Vec<(f64, f64)> = vec![ 32 | (0.0, 1.0), 33 | ((3.0f64).sqrt() / 2.0, -0.5), 34 | (-(3.0f64).sqrt() / 2.0, -0.5), 35 | ]; 36 | for _ in 0..6 { 37 | current = snowflake_iter(¤t[..]); 38 | } 39 | current 40 | }; 41 | 42 | chart.draw_series(std::iter::once(Polygon::new( 43 | snowflake_vertices.clone(), 44 | RED.mix(0.2), 45 | )))?; 46 | snowflake_vertices.push(snowflake_vertices[0]); 47 | chart.draw_series(std::iter::once(PathElement::new(snowflake_vertices, RED)))?; 48 | 49 | // To avoid the IO failure being ignored silently, we manually call the present function 50 | root.present().expect("Unable to write result to file, please make sure 'plotters-doc-data' dir exists under current dir"); 51 | println!("Result has been saved to {}", OUT_FILE_NAME); 52 | Ok(()) 53 | } 54 | #[test] 55 | fn entry_point() { 56 | main().unwrap() 57 | } 58 | -------------------------------------------------------------------------------- /plotters/examples/stock.rs: -------------------------------------------------------------------------------- 1 | use chrono::offset::{Local, TimeZone}; 2 | use chrono::{Date, Duration}; 3 | use plotters::prelude::*; 4 | fn parse_time(t: &str) -> Date { 5 | Local 6 | .datetime_from_str(&format!("{} 0:0", t), "%Y-%m-%d %H:%M") 7 | .unwrap() 8 | .date() 9 | } 10 | const OUT_FILE_NAME: &str = "plotters-doc-data/stock.png"; 11 | fn main() -> Result<(), Box> { 12 | let data = get_data(); 13 | let root = BitMapBackend::new(OUT_FILE_NAME, (1024, 768)).into_drawing_area(); 14 | root.fill(&WHITE)?; 15 | 16 | let (to_date, from_date) = ( 17 | parse_time(data[0].0) + Duration::days(1), 18 | parse_time(data[29].0) - Duration::days(1), 19 | ); 20 | 21 | let mut chart = ChartBuilder::on(&root) 22 | .x_label_area_size(40) 23 | .y_label_area_size(40) 24 | .caption("MSFT Stock Price", ("sans-serif", 50.0).into_font()) 25 | .build_cartesian_2d(from_date..to_date, 110f32..135f32)?; 26 | 27 | chart.configure_mesh().light_line_style(WHITE).draw()?; 28 | 29 | chart.draw_series( 30 | data.iter().map(|x| { 31 | CandleStick::new(parse_time(x.0), x.1, x.2, x.3, x.4, GREEN.filled(), RED, 15) 32 | }), 33 | )?; 34 | 35 | // To avoid the IO failure being ignored silently, we manually call the present function 36 | root.present().expect("Unable to write result to file, please make sure 'plotters-doc-data' dir exists under current dir"); 37 | println!("Result has been saved to {}", OUT_FILE_NAME); 38 | 39 | Ok(()) 40 | } 41 | 42 | fn get_data() -> Vec<(&'static str, f32, f32, f32, f32)> { 43 | vec![ 44 | ("2019-04-25", 130.06, 131.37, 128.83, 129.15), 45 | ("2019-04-24", 125.79, 125.85, 124.52, 125.01), 46 | ("2019-04-23", 124.1, 125.58, 123.83, 125.44), 47 | ("2019-04-22", 122.62, 124.0000, 122.57, 123.76), 48 | ("2019-04-18", 122.19, 123.52, 121.3018, 123.37), 49 | ("2019-04-17", 121.24, 121.85, 120.54, 121.77), 50 | ("2019-04-16", 121.64, 121.65, 120.1, 120.77), 51 | ("2019-04-15", 120.94, 121.58, 120.57, 121.05), 52 | ("2019-04-12", 120.64, 120.98, 120.37, 120.95), 53 | ("2019-04-11", 120.54, 120.85, 119.92, 120.33), 54 | ("2019-04-10", 119.76, 120.35, 119.54, 120.19), 55 | ("2019-04-09", 118.63, 119.54, 118.58, 119.28), 56 | ("2019-04-08", 119.81, 120.02, 118.64, 119.93), 57 | ("2019-04-05", 119.39, 120.23, 119.37, 119.89), 58 | ("2019-04-04", 120.1, 120.23, 118.38, 119.36), 59 | ("2019-04-03", 119.86, 120.43, 119.15, 119.97), 60 | ("2019-04-02", 119.06, 119.48, 118.52, 119.19), 61 | ("2019-04-01", 118.95, 119.1085, 118.1, 119.02), 62 | ("2019-03-29", 118.07, 118.32, 116.96, 117.94), 63 | ("2019-03-28", 117.44, 117.58, 116.13, 116.93), 64 | ("2019-03-27", 117.875, 118.21, 115.5215, 116.77), 65 | ("2019-03-26", 118.62, 118.705, 116.85, 117.91), 66 | ("2019-03-25", 116.56, 118.01, 116.3224, 117.66), 67 | ("2019-03-22", 119.5, 119.59, 117.04, 117.05), 68 | ("2019-03-21", 117.135, 120.82, 117.09, 120.22), 69 | ("2019-03-20", 117.39, 118.75, 116.71, 117.52), 70 | ("2019-03-19", 118.09, 118.44, 116.99, 117.65), 71 | ("2019-03-18", 116.17, 117.61, 116.05, 117.57), 72 | ("2019-03-15", 115.34, 117.25, 114.59, 115.91), 73 | ("2019-03-14", 114.54, 115.2, 114.33, 114.59), 74 | ] 75 | } 76 | #[test] 77 | fn entry_point() { 78 | main().unwrap() 79 | } 80 | -------------------------------------------------------------------------------- /plotters/examples/tick_control.rs: -------------------------------------------------------------------------------- 1 | // Data is pulled from https://covid.ourworldindata.org/data/owid-covid-data.json 2 | use plotters::prelude::*; 3 | use std::fs::File; 4 | use std::io::BufReader; 5 | 6 | #[derive(serde_derive::Deserialize)] 7 | struct DailyData { 8 | #[serde(default)] 9 | new_cases: f64, 10 | #[serde(default)] 11 | total_cases: f64, 12 | } 13 | 14 | #[derive(serde_derive::Deserialize)] 15 | struct CountryData { 16 | data: Vec, 17 | } 18 | 19 | const OUT_FILE_NAME: &str = "plotters-doc-data/tick_control.svg"; 20 | fn main() -> Result<(), Box> { 21 | let root = SVGBackend::new(OUT_FILE_NAME, (1024, 768)).into_drawing_area(); 22 | root.fill(&WHITE)?; 23 | 24 | let (upper, lower) = root.split_vertically(750); 25 | 26 | lower.titled( 27 | "Data Source: https://covid.ourworldindata.org/data/owid-covid-data.json", 28 | ("sans-serif", 10).into_font().color(&BLACK.mix(0.5)), 29 | )?; 30 | 31 | let mut chart = ChartBuilder::on(&upper) 32 | .caption("World COVID-19 Cases", ("sans-serif", (5).percent_height())) 33 | .set_label_area_size(LabelAreaPosition::Left, (8).percent()) 34 | .set_label_area_size(LabelAreaPosition::Bottom, (4).percent()) 35 | .margin((1).percent()) 36 | .build_cartesian_2d( 37 | (20u32..5000_0000u32) 38 | .log_scale() 39 | .with_key_points(vec![50, 100, 1000, 10000, 100000, 1000000, 10000000]), 40 | (0u32..50_0000u32) 41 | .log_scale() 42 | .with_key_points(vec![10, 50, 100, 1000, 10000, 100000, 200000]), 43 | )?; 44 | 45 | chart 46 | .configure_mesh() 47 | .x_desc("Total Cases") 48 | .y_desc("New Cases") 49 | .draw()?; 50 | 51 | let data: std::collections::HashMap = serde_json::from_reader( 52 | BufReader::new(File::open("plotters-doc-data/covid-data.json")?), 53 | )?; 54 | 55 | for (idx, &series) in ["CHN", "USA", "RUS", "JPN", "DEU", "IND", "OWID_WRL"] 56 | .iter() 57 | .enumerate() 58 | { 59 | let color = Palette99::pick(idx).mix(0.9); 60 | chart 61 | .draw_series(LineSeries::new( 62 | data[series].data.iter().map( 63 | |&DailyData { 64 | new_cases, 65 | total_cases, 66 | .. 67 | }| (total_cases as u32, new_cases as u32), 68 | ), 69 | color.stroke_width(3), 70 | ))? 71 | .label(series) 72 | .legend(move |(x, y)| Rectangle::new([(x, y - 5), (x + 10, y + 5)], color.filled())); 73 | } 74 | 75 | chart.configure_series_labels().border_style(BLACK).draw()?; 76 | 77 | // To avoid the IO failure being ignored silently, we manually call the present function 78 | root.present().expect("Unable to write result to file, please make sure 'plotters-doc-data' dir exists under current dir"); 79 | println!("Result has been saved to {}", OUT_FILE_NAME); 80 | 81 | Ok(()) 82 | } 83 | #[test] 84 | fn entry_point() { 85 | main().unwrap() 86 | } 87 | -------------------------------------------------------------------------------- /plotters/examples/two-scales.rs: -------------------------------------------------------------------------------- 1 | use plotters::prelude::*; 2 | 3 | const OUT_FILE_NAME: &str = "plotters-doc-data/twoscale.png"; 4 | fn main() -> Result<(), Box> { 5 | let root = BitMapBackend::new(OUT_FILE_NAME, (1024, 768)).into_drawing_area(); 6 | root.fill(&WHITE)?; 7 | 8 | let mut chart = ChartBuilder::on(&root) 9 | .x_label_area_size(35) 10 | .y_label_area_size(40) 11 | .right_y_label_area_size(40) 12 | .margin(5) 13 | .caption("Dual Y-Axis Example", ("sans-serif", 50.0).into_font()) 14 | .build_cartesian_2d(0f32..10f32, (0.1f32..1e10f32).log_scale())? 15 | .set_secondary_coord(0f32..10f32, -1.0f32..1.0f32); 16 | 17 | chart 18 | .configure_mesh() 19 | .disable_x_mesh() 20 | .disable_y_mesh() 21 | .y_desc("Log Scale") 22 | .y_label_formatter(&|x| format!("{:e}", x)) 23 | .draw()?; 24 | 25 | chart 26 | .configure_secondary_axes() 27 | .y_desc("Linear Scale") 28 | .draw()?; 29 | 30 | chart 31 | .draw_series(LineSeries::new( 32 | (0..=100).map(|x| (x as f32 / 10.0, (1.02f32).powf(x as f32 * x as f32 / 10.0))), 33 | &BLUE, 34 | ))? 35 | .label("y = 1.02^x^2") 36 | .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], BLUE)); 37 | 38 | chart 39 | .draw_secondary_series(LineSeries::new( 40 | (0..=100).map(|x| (x as f32 / 10.0, (x as f32 / 5.0).sin())), 41 | &RED, 42 | ))? 43 | .label("y = sin(2x)") 44 | .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], RED)); 45 | 46 | chart 47 | .configure_series_labels() 48 | .background_style(RGBColor(128, 128, 128)) 49 | .draw()?; 50 | 51 | // To avoid the IO failure being ignored silently, we manually call the present function 52 | root.present().expect("Unable to write result to file, please make sure 'plotters-doc-data' dir exists under current dir"); 53 | println!("Result has been saved to {}", OUT_FILE_NAME); 54 | 55 | Ok(()) 56 | } 57 | #[test] 58 | fn entry_point() { 59 | main().unwrap() 60 | } 61 | -------------------------------------------------------------------------------- /plotters/src/chart/context/cartesian2d/mod.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Range; 2 | 3 | use plotters_backend::{BackendCoord, DrawingBackend}; 4 | 5 | use crate::chart::{ChartContext, DualCoordChartContext, MeshStyle}; 6 | use crate::coord::{ 7 | cartesian::Cartesian2d, 8 | ranged1d::{AsRangedCoord, Ranged, ValueFormatter}, 9 | Shift, 10 | }; 11 | use crate::drawing::DrawingArea; 12 | 13 | mod draw_impl; 14 | 15 | impl<'a, DB, XT, YT, X, Y> ChartContext<'a, DB, Cartesian2d> 16 | where 17 | DB: DrawingBackend, 18 | X: Ranged + ValueFormatter, 19 | Y: Ranged + ValueFormatter, 20 | { 21 | pub(crate) fn is_overlapping_drawing_area( 22 | &self, 23 | area: Option<&DrawingArea>, 24 | ) -> bool { 25 | if let Some(area) = area { 26 | let (x0, y0) = area.get_base_pixel(); 27 | let (w, h) = area.dim_in_pixel(); 28 | let (x1, y1) = (x0 + w as i32, y0 + h as i32); 29 | let (dx0, dy0) = self.drawing_area.get_base_pixel(); 30 | let (w, h) = self.drawing_area.dim_in_pixel(); 31 | let (dx1, dy1) = (dx0 + w as i32, dy0 + h as i32); 32 | 33 | let (ox0, ox1) = (x0.max(dx0), x1.min(dx1)); 34 | let (oy0, oy1) = (y0.max(dy0), y1.min(dy1)); 35 | 36 | ox1 > ox0 && oy1 > oy0 37 | } else { 38 | false 39 | } 40 | } 41 | 42 | /// Initialize a mesh configuration object and mesh drawing can be finalized by calling 43 | /// the function `MeshStyle::draw`. 44 | pub fn configure_mesh(&mut self) -> MeshStyle<'a, '_, X, Y, DB> { 45 | MeshStyle::new(self) 46 | } 47 | } 48 | 49 | impl<'a, DB: DrawingBackend, X: Ranged, Y: Ranged> ChartContext<'a, DB, Cartesian2d> { 50 | /// Get the range of X axis 51 | pub fn x_range(&self) -> Range { 52 | self.drawing_area.get_x_range() 53 | } 54 | 55 | /// Get range of the Y axis 56 | pub fn y_range(&self) -> Range { 57 | self.drawing_area.get_y_range() 58 | } 59 | 60 | /// Maps the coordinate to the backend coordinate. This is typically used 61 | /// with an interactive chart. 62 | pub fn backend_coord(&self, coord: &(X::ValueType, Y::ValueType)) -> BackendCoord { 63 | self.drawing_area.map_coordinate(coord) 64 | } 65 | } 66 | 67 | impl<'a, DB: DrawingBackend, X: Ranged, Y: Ranged> ChartContext<'a, DB, Cartesian2d> { 68 | /// Convert this chart context into a dual axis chart context and attach a second coordinate spec 69 | /// on the chart context. For more detailed information, see documentation for [struct DualCoordChartContext](struct.DualCoordChartContext.html) 70 | /// 71 | /// - `x_coord`: The coordinate spec for the X axis 72 | /// - `y_coord`: The coordinate spec for the Y axis 73 | /// - **returns** The newly created dual spec chart context 74 | #[allow(clippy::type_complexity)] 75 | pub fn set_secondary_coord( 76 | self, 77 | x_coord: SX, 78 | y_coord: SY, 79 | ) -> DualCoordChartContext< 80 | 'a, 81 | DB, 82 | Cartesian2d, 83 | Cartesian2d, 84 | > { 85 | let mut pixel_range = self.drawing_area.get_pixel_range(); 86 | pixel_range.1 = pixel_range.1.end..pixel_range.1.start; 87 | 88 | DualCoordChartContext::new(self, Cartesian2d::new(x_coord, y_coord, pixel_range)) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /plotters/src/chart/context/cartesian3d/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::chart::{axes3d::Axes3dStyle, ChartContext}; 2 | use crate::coord::{ 3 | cartesian::Cartesian3d, 4 | ranged1d::{Ranged, ValueFormatter}, 5 | ranged3d::{ProjectionMatrix, ProjectionMatrixBuilder}, 6 | }; 7 | use plotters_backend::DrawingBackend; 8 | 9 | mod draw_impl; 10 | 11 | #[derive(Clone, Debug)] 12 | pub(crate) enum Coord3D { 13 | X(X), 14 | Y(Y), 15 | Z(Z), 16 | } 17 | 18 | impl Coord3D { 19 | fn get_x(&self) -> &X { 20 | match self { 21 | Coord3D::X(ret) => ret, 22 | _ => panic!("Invalid call!"), 23 | } 24 | } 25 | fn get_y(&self) -> &Y { 26 | match self { 27 | Coord3D::Y(ret) => ret, 28 | _ => panic!("Invalid call!"), 29 | } 30 | } 31 | fn get_z(&self) -> &Z { 32 | match self { 33 | Coord3D::Z(ret) => ret, 34 | _ => panic!("Invalid call!"), 35 | } 36 | } 37 | 38 | fn build_coord([x, y, z]: [&Self; 3]) -> (X, Y, Z) 39 | where 40 | X: Clone, 41 | Y: Clone, 42 | Z: Clone, 43 | { 44 | (x.get_x().clone(), y.get_y().clone(), z.get_z().clone()) 45 | } 46 | } 47 | 48 | impl<'a, DB, X, Y, Z, XT, YT, ZT> ChartContext<'a, DB, Cartesian3d> 49 | where 50 | DB: DrawingBackend, 51 | X: Ranged + ValueFormatter, 52 | Y: Ranged + ValueFormatter, 53 | Z: Ranged + ValueFormatter, 54 | { 55 | /** 56 | Create an axis configuration object, to set line styles, labels, sizes, etc. 57 | 58 | Default values for axis configuration are set by function `Axes3dStyle::new()`. 59 | 60 | # Example 61 | 62 | ``` 63 | use plotters::prelude::*; 64 | let drawing_area = SVGBackend::new("configure_axes.svg", (300, 200)).into_drawing_area(); 65 | drawing_area.fill(&WHITE).unwrap(); 66 | let mut chart_builder = ChartBuilder::on(&drawing_area); 67 | let mut chart_context = chart_builder.margin_bottom(30).build_cartesian_3d(0.0..4.0, 0.0..3.0, 0.0..2.7).unwrap(); 68 | chart_context.configure_axes().tick_size(8).x_labels(4).y_labels(3).z_labels(2) 69 | .max_light_lines(5).axis_panel_style(GREEN.mix(0.1)).bold_grid_style(BLUE.mix(0.3)) 70 | .light_grid_style(BLUE.mix(0.2)).label_style(("Calibri", 10)) 71 | .x_formatter(&|x| format!("x={x}")).draw().unwrap(); 72 | ``` 73 | 74 | The resulting chart reflects the customizations specified through `configure_axes()`: 75 | 76 | ![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@4c3cef4/apidoc/configure_axes.svg) 77 | 78 | All these customizations are `Axes3dStyle` methods. 79 | 80 | In the chart, `tick_size(8)` produces tick marks 8 pixels long. You can use 81 | `(5u32).percent().max(5).in_pixels(chart.plotting_area()` to tell Plotters to calculate the tick mark 82 | size as a percentage of the dimensions of the figure. See [`crate::style::RelativeSize`] and 83 | [`crate::style::SizeDesc`] for more information. 84 | 85 | `x_labels(4)` specifies a maximum of 4 86 | tick marks and labels in the X axis. `max_light_lines(5)` specifies a maximum of 5 minor grid lines 87 | between any two tick marks. `axis_panel_style(GREEN.mix(0.1))` specifies the style of the panels in 88 | the background, a light green color. `bold_grid_style(BLUE.mix(0.3))` and `light_grid_style(BLUE.mix(0.2))` 89 | specify the style of the major and minor grid lines, respectively. `label_style()` specifies the text 90 | style of the axis labels, and `x_formatter(|x| format!("x={x}"))` specifies the string format of the X 91 | axis labels. 92 | 93 | # See also 94 | 95 | [`ChartContext::configure_mesh()`], a similar function for 2D plots 96 | */ 97 | pub fn configure_axes(&mut self) -> Axes3dStyle<'a, '_, X, Y, Z, DB> { 98 | Axes3dStyle::new(self) 99 | } 100 | } 101 | 102 | impl<'a, DB, X: Ranged, Y: Ranged, Z: Ranged> ChartContext<'a, DB, Cartesian3d> 103 | where 104 | DB: DrawingBackend, 105 | { 106 | /// Override the 3D projection matrix. This function allows to override the default projection 107 | /// matrix. 108 | /// - `pf`: A function that takes the default projection matrix configuration and returns the 109 | /// projection matrix. This function will allow you to adjust the pitch, yaw angle and the 110 | /// centeral point of the projection, etc. You can also build a projection matrix which is not 111 | /// relies on the default configuration as well. 112 | pub fn with_projection ProjectionMatrix>( 113 | &mut self, 114 | pf: P, 115 | ) -> &mut Self { 116 | let (actual_x, actual_y) = self.drawing_area.get_pixel_range(); 117 | self.drawing_area 118 | .as_coord_spec_mut() 119 | .set_projection(actual_x, actual_y, pf); 120 | self 121 | } 122 | /// Sets the 3d coordinate pixel range. 123 | pub fn set_3d_pixel_range(&mut self, size: (i32, i32, i32)) -> &mut Self { 124 | let (actual_x, actual_y) = self.drawing_area.get_pixel_range(); 125 | self.drawing_area 126 | .as_coord_spec_mut() 127 | .set_coord_pixel_range(actual_x, actual_y, size); 128 | self 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /plotters/src/chart/mod.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | The high-level plotting abstractions. 3 | 4 | Plotters uses `ChartContext`, a thin layer on the top of `DrawingArea`, to provide 5 | high-level chart specific drawing functionalities, like, mesh line, coordinate label 6 | and other common components for the data chart. 7 | 8 | To draw a series, `ChartContext::draw_series` is used to draw a series on the chart. 9 | In Plotters, a series is abstracted as an iterator of elements. 10 | 11 | `ChartBuilder` is used to construct a chart. To learn more detailed information, check the 12 | detailed description for each struct. 13 | */ 14 | 15 | mod axes3d; 16 | mod builder; 17 | mod context; 18 | mod dual_coord; 19 | mod mesh; 20 | mod series; 21 | mod state; 22 | 23 | pub use builder::{ChartBuilder, LabelAreaPosition}; 24 | pub use context::ChartContext; 25 | pub use dual_coord::{DualCoordChartContext, DualCoordChartState}; 26 | pub use mesh::{MeshStyle, SecondaryMeshStyle}; 27 | pub use series::{SeriesAnno, SeriesLabelPosition, SeriesLabelStyle}; 28 | pub use state::ChartState; 29 | 30 | use context::Coord3D; 31 | -------------------------------------------------------------------------------- /plotters/src/chart/state.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use super::ChartContext; 4 | use crate::coord::{CoordTranslate, Shift}; 5 | use crate::drawing::DrawingArea; 6 | use plotters_backend::DrawingBackend; 7 | 8 | /// A chart context state - This is the data that is needed to reconstruct the chart context 9 | /// without actually drawing the chart. This is useful when we want to do realtime rendering and 10 | /// want to incrementally update the chart. 11 | /// 12 | /// For each frame, instead of updating the entire backend, we are able to keep the keep the figure 13 | /// component like axis, labels untouched and make updates only in the plotting drawing area. 14 | /// This is very useful for incremental render. 15 | /// ```rust 16 | /// use plotters::prelude::*; 17 | /// let mut buffer = vec![0u8;1024*768*3]; 18 | /// let area = BitMapBackend::with_buffer(&mut buffer[..], (1024, 768)) 19 | /// .into_drawing_area() 20 | /// .split_evenly((1,2)); 21 | /// let chart = ChartBuilder::on(&area[0]) 22 | /// .caption("Incremental Example", ("sans-serif", 20)) 23 | /// .set_all_label_area_size(30) 24 | /// .build_cartesian_2d(0..10, 0..10) 25 | /// .expect("Unable to build ChartContext"); 26 | /// // Draw the first frame at this point 27 | /// area[0].present().expect("Present"); 28 | /// let state = chart.into_chart_state(); 29 | /// // Let's draw the second frame 30 | /// let chart = state.restore(&area[0]); 31 | /// chart.plotting_area().fill(&WHITE).unwrap(); // Clear the previously drawn graph 32 | /// // At this point, you are able to draw next frame 33 | ///``` 34 | #[derive(Clone)] 35 | pub struct ChartState { 36 | drawing_area_pos: (i32, i32), 37 | drawing_area_size: (u32, u32), 38 | coord: CT, 39 | } 40 | 41 | impl<'a, DB: DrawingBackend, CT: CoordTranslate> From> for ChartState { 42 | fn from(chart: ChartContext<'a, DB, CT>) -> ChartState { 43 | ChartState { 44 | drawing_area_pos: chart.drawing_area_pos, 45 | drawing_area_size: chart.drawing_area.dim_in_pixel(), 46 | coord: chart.drawing_area.into_coord_spec(), 47 | } 48 | } 49 | } 50 | 51 | impl<'a, DB: DrawingBackend, CT: CoordTranslate> ChartContext<'a, DB, CT> { 52 | /// Convert a chart context into a chart state, by doing so, the chart context is consumed and 53 | /// a saved chart state is created for later use. This is typically used in incremental rendering. See documentation of `ChartState` for more detailed example. 54 | pub fn into_chart_state(self) -> ChartState { 55 | self.into() 56 | } 57 | 58 | /// Convert the chart context into a sharable chart state. 59 | /// Normally a chart state can not be clone, since the coordinate spec may not be able to be 60 | /// cloned. In this case, we can use an `Arc` get the coordinate wrapped thus the state can be 61 | /// cloned and shared by multiple chart context 62 | pub fn into_shared_chart_state(self) -> ChartState> { 63 | ChartState { 64 | drawing_area_pos: self.drawing_area_pos, 65 | drawing_area_size: self.drawing_area.dim_in_pixel(), 66 | coord: Arc::new(self.drawing_area.into_coord_spec()), 67 | } 68 | } 69 | } 70 | 71 | impl<'a, DB, CT> From<&ChartContext<'a, DB, CT>> for ChartState 72 | where 73 | DB: DrawingBackend, 74 | CT: CoordTranslate + Clone, 75 | { 76 | fn from(chart: &ChartContext<'a, DB, CT>) -> ChartState { 77 | ChartState { 78 | drawing_area_pos: chart.drawing_area_pos, 79 | drawing_area_size: chart.drawing_area.dim_in_pixel(), 80 | coord: chart.drawing_area.as_coord_spec().clone(), 81 | } 82 | } 83 | } 84 | 85 | impl<'a, DB: DrawingBackend, CT: CoordTranslate + Clone> ChartContext<'a, DB, CT> { 86 | /// Make the chart context, do not consume the chart context and clone the coordinate spec 87 | pub fn to_chart_state(&self) -> ChartState { 88 | self.into() 89 | } 90 | } 91 | 92 | impl ChartState { 93 | /// Restore the chart context on the given drawing area 94 | /// 95 | /// - `area`: The given drawing area where we want to restore the chart context 96 | /// - **returns** The newly created chart context 97 | pub fn restore<'a, DB: DrawingBackend>( 98 | self, 99 | area: &DrawingArea, 100 | ) -> ChartContext<'a, DB, CT> { 101 | let area = area 102 | .clone() 103 | .shrink(self.drawing_area_pos, self.drawing_area_size); 104 | ChartContext { 105 | x_label_area: [None, None], 106 | y_label_area: [None, None], 107 | drawing_area: area.apply_coord_spec(self.coord), 108 | series_anno: vec![], 109 | drawing_area_pos: self.drawing_area_pos, 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /plotters/src/coord/mod.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | 3 | One of the key features of Plotters is flexible coordinate system abstraction and this module 4 | provides all the abstraction used for the coordinate abstraction of Plotters. 5 | 6 | Generally speaking, the coordinate system in Plotters is responsible for mapping logic data points into 7 | pixel based backend coordinate. This task is abstracted by a simple trait called 8 | [CoordTranslate](trait.CoordTranslate.html). Please note `CoordTranslate` trait doesn't assume any property 9 | about the coordinate values, thus we are able to extend Plotters's coordinate system to other types of coorindate 10 | easily. 11 | 12 | Another important trait is [ReverseCoordTranslate](trait.ReverseCoordTranslate.html). This trait allows some coordinate 13 | retrieve the logic value based on the pixel-based backend coordinate. This is particularly interesting for interactive plots. 14 | 15 | Plotters contains a set of pre-defined coordinate specifications that fulfills the most common use. See documentation for 16 | module [types](types/index.html) for details about the basic 1D types. 17 | 18 | The coordinate system also can be tweaked by the coordinate combinators, such as logarithmic coordinate, nested coordinate, etc. 19 | See documentation for module [combinators](combinators/index.html) for details. 20 | 21 | Currently we support the following 2D coordinate system: 22 | 23 | - 2-dimensional Cartesian Coordinate: This is done by the combinator [Cartesian2d](cartesian/struct.Cartesian2d.html). 24 | 25 | */ 26 | 27 | use plotters_backend::BackendCoord; 28 | 29 | pub mod ranged1d; 30 | 31 | /// The coordinate combinators 32 | /// 33 | /// Coordinate combinators are very important part of Plotters' coordinate system. 34 | /// The combinator is more about the "combinator pattern", which takes one or more coordinate specification 35 | /// and transform them into a new coordinate specification. 36 | pub mod combinators { 37 | pub use super::ranged1d::combinators::*; 38 | } 39 | 40 | /// The primitive types supported by Plotters coordinate system 41 | pub mod types { 42 | pub use super::ranged1d::types::*; 43 | } 44 | 45 | mod ranged2d; 46 | /// Ranged coordinates in 3d. 47 | pub mod ranged3d; 48 | 49 | /// Groups Cartesian ranged coordinates in 2d and 3d. 50 | pub mod cartesian { 51 | pub use super::ranged2d::cartesian::{Cartesian2d, MeshLine}; 52 | pub use super::ranged3d::Cartesian3d; 53 | } 54 | 55 | mod translate; 56 | pub use translate::{CoordTranslate, ReverseCoordTranslate}; 57 | 58 | /// The coordinate translation that only impose shift 59 | #[derive(Debug, Clone)] 60 | pub struct Shift(pub BackendCoord); 61 | 62 | impl CoordTranslate for Shift { 63 | type From = BackendCoord; 64 | fn translate(&self, from: &Self::From) -> BackendCoord { 65 | (from.0 + (self.0).0, from.1 + (self.0).1) 66 | } 67 | } 68 | 69 | impl ReverseCoordTranslate for Shift { 70 | fn reverse_translate(&self, input: BackendCoord) -> Option { 71 | Some((input.0 - (self.0).0, input.1 - (self.0).1)) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /plotters/src/coord/ranged1d/combinators/group_by.rs: -------------------------------------------------------------------------------- 1 | use crate::coord::ranged1d::{ 2 | AsRangedCoord, DiscreteRanged, KeyPointHint, NoDefaultFormatting, Ranged, ValueFormatter, 3 | }; 4 | use std::ops::Range; 5 | 6 | /// Grouping the value in the coordinate specification. 7 | /// 8 | /// This combinator doesn't change the coordinate mapping behavior. But it changes how 9 | /// the key point is generated, this coordinate specification will enforce that only the first value in each group 10 | /// can be emitted as the bold key points. 11 | /// 12 | /// This is useful, for example, when we have an X axis is a integer and denotes days. 13 | /// And we are expecting the tick mark denotes weeks, in this way we can make the range 14 | /// spec grouping by 7 elements. 15 | /// With the help of the GroupBy decorator, this can be archived quite easily: 16 | ///```rust 17 | ///use plotters::prelude::*; 18 | ///let mut buf = vec![0;1024*768*3]; 19 | ///let area = BitMapBackend::with_buffer(buf.as_mut(), (1024, 768)).into_drawing_area(); 20 | ///let chart = ChartBuilder::on(&area) 21 | /// .build_cartesian_2d((0..100).group_by(7), 0..100) 22 | /// .unwrap(); 23 | ///``` 24 | /// 25 | /// To apply this combinator, call [ToGroupByRange::group_by](trait.ToGroupByRange.html#tymethod.group_by) method on any discrete coordinate spec. 26 | #[derive(Clone)] 27 | pub struct GroupBy(T, usize); 28 | 29 | /// The trait that provides method `Self::group_by` function which creates a 30 | /// `GroupBy` decorated ranged value. 31 | pub trait ToGroupByRange: AsRangedCoord + Sized 32 | where 33 | Self::CoordDescType: DiscreteRanged, 34 | { 35 | /// Make a grouping ranged value, see the documentation for `GroupBy` for details. 36 | /// 37 | /// - `value`: The number of values we want to group it 38 | /// - **return**: The newly created grouping range specification 39 | fn group_by(self, value: usize) -> GroupBy<::CoordDescType> { 40 | GroupBy(self.into(), value) 41 | } 42 | } 43 | 44 | impl ToGroupByRange for T where T::CoordDescType: DiscreteRanged {} 45 | 46 | impl DiscreteRanged for GroupBy { 47 | fn size(&self) -> usize { 48 | (self.0.size() + self.1 - 1) / self.1 49 | } 50 | fn index_of(&self, value: &Self::ValueType) -> Option { 51 | self.0.index_of(value).map(|idx| idx / self.1) 52 | } 53 | fn from_index(&self, index: usize) -> Option { 54 | self.0.from_index(index * self.1) 55 | } 56 | } 57 | 58 | impl + ValueFormatter> ValueFormatter for GroupBy { 59 | fn format(value: &T) -> String { 60 | R::format(value) 61 | } 62 | } 63 | 64 | impl Ranged for GroupBy { 65 | type FormatOption = NoDefaultFormatting; 66 | type ValueType = T::ValueType; 67 | fn map(&self, value: &T::ValueType, limit: (i32, i32)) -> i32 { 68 | self.0.map(value, limit) 69 | } 70 | fn range(&self) -> Range { 71 | self.0.range() 72 | } 73 | // TODO: See issue issue #88 74 | fn key_points(&self, hint: HintType) -> Vec { 75 | let range = 0..(self.0.size() + self.1) / self.1; 76 | //let logic_range: RangedCoordusize = range.into(); 77 | 78 | let interval = 79 | ((range.end - range.start + hint.bold_points() - 1) / hint.bold_points()).max(1); 80 | let count = (range.end - range.start) / interval; 81 | 82 | let idx_iter = (0..hint.bold_points()).map(|x| x * interval); 83 | 84 | if hint.weight().allow_light_points() && count < hint.bold_points() * 2 { 85 | let outer_ticks = idx_iter; 86 | let outer_tick_size = interval * self.1; 87 | let inner_ticks_per_group = hint.max_num_points() / outer_ticks.len(); 88 | let inner_ticks = (outer_tick_size + inner_ticks_per_group - 1) / inner_ticks_per_group; 89 | let inner_ticks: Vec<_> = (0..(outer_tick_size / inner_ticks)) 90 | .map(move |x| x * inner_ticks) 91 | .collect(); 92 | let size = self.0.size(); 93 | return outer_ticks 94 | .flat_map(|base| inner_ticks.iter().map(move |&ofs| base * self.1 + ofs)) 95 | .take_while(|&idx| idx < size) 96 | .map(|x| self.0.from_index(x).unwrap()) 97 | .collect(); 98 | } 99 | 100 | idx_iter 101 | .map(|x| self.0.from_index(x * self.1).unwrap()) 102 | .collect() 103 | } 104 | } 105 | 106 | #[cfg(test)] 107 | mod test { 108 | use super::*; 109 | #[test] 110 | fn test_group_by() { 111 | let coord = (0..100).group_by(10); 112 | assert_eq!(coord.size(), 11); 113 | for (idx, val) in (0..).zip(coord.values()) { 114 | assert_eq!(val, idx * 10); 115 | assert_eq!(coord.from_index(idx as usize), Some(val)); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /plotters/src/coord/ranged1d/combinators/mod.rs: -------------------------------------------------------------------------------- 1 | mod ckps; 2 | pub use ckps::{BindKeyPointMethod, BindKeyPoints, WithKeyPointMethod, WithKeyPoints}; 3 | 4 | mod group_by; 5 | pub use group_by::{GroupBy, ToGroupByRange}; 6 | 7 | mod linspace; 8 | pub use linspace::{IntoLinspace, Linspace}; 9 | 10 | mod logarithmic; 11 | pub use logarithmic::{IntoLogRange, LogCoord, LogScalable}; 12 | 13 | #[allow(deprecated)] 14 | pub use logarithmic::LogRange; 15 | 16 | mod nested; 17 | pub use nested::{BuildNestedCoord, NestedRange, NestedValue}; 18 | 19 | mod partial_axis; 20 | pub use partial_axis::{make_partial_axis, IntoPartialAxis}; 21 | -------------------------------------------------------------------------------- /plotters/src/coord/ranged1d/combinators/partial_axis.rs: -------------------------------------------------------------------------------- 1 | use crate::coord::ranged1d::{ 2 | AsRangedCoord, DefaultFormatting, DiscreteRanged, KeyPointHint, Ranged, 3 | }; 4 | use std::ops::Range; 5 | 6 | /// This axis decorator will make the axis partially display on the axis. 7 | /// At some time, we want the axis only covers some part of the value. 8 | /// This decorator will have an additional display range defined. 9 | #[derive(Clone)] 10 | pub struct PartialAxis(R, Range); 11 | 12 | /// The trait for the types that can be converted into a partial axis 13 | pub trait IntoPartialAxis: AsRangedCoord { 14 | /// Make the partial axis 15 | /// 16 | /// - `axis_range`: The range of the axis to be displayed 17 | /// - **returns**: The converted range specification 18 | fn partial_axis( 19 | self, 20 | axis_range: Range<::ValueType>, 21 | ) -> PartialAxis { 22 | PartialAxis(self.into(), axis_range) 23 | } 24 | } 25 | 26 | impl IntoPartialAxis for R {} 27 | 28 | impl Ranged for PartialAxis 29 | where 30 | R::ValueType: Clone, 31 | { 32 | type FormatOption = DefaultFormatting; 33 | type ValueType = R::ValueType; 34 | 35 | fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 { 36 | self.0.map(value, limit) 37 | } 38 | 39 | fn key_points(&self, hint: Hint) -> Vec { 40 | self.0.key_points(hint) 41 | } 42 | 43 | fn range(&self) -> Range { 44 | self.0.range() 45 | } 46 | 47 | fn axis_pixel_range(&self, limit: (i32, i32)) -> Range { 48 | let left = self.map(&self.1.start, limit); 49 | let right = self.map(&self.1.end, limit); 50 | 51 | left.min(right)..left.max(right) 52 | } 53 | } 54 | 55 | impl DiscreteRanged for PartialAxis 56 | where 57 | R: Ranged, 58 | ::ValueType: Eq + Clone, 59 | { 60 | fn size(&self) -> usize { 61 | self.0.size() 62 | } 63 | 64 | fn index_of(&self, value: &R::ValueType) -> Option { 65 | self.0.index_of(value) 66 | } 67 | 68 | fn from_index(&self, index: usize) -> Option { 69 | self.0.from_index(index) 70 | } 71 | } 72 | 73 | /// Make a partial axis based on the percentage of visible portion. 74 | /// We can use `into_partial_axis` to create a partial axis range specification. 75 | /// But sometimes, we want to directly specify the percentage visible to the user. 76 | /// 77 | /// - `axis_range`: The range specification 78 | /// - `part`: The visible part of the axis. Each value is from [0.0, 1.0] 79 | /// - **returns**: The partial axis created from the input, or `None` when not possible 80 | pub fn make_partial_axis( 81 | axis_range: Range, 82 | part: Range, 83 | ) -> Option as AsRangedCoord>::CoordDescType>> 84 | where 85 | Range: AsRangedCoord, 86 | T: num_traits::NumCast + Clone, 87 | { 88 | let left: f64 = num_traits::cast(axis_range.start.clone())?; 89 | let right: f64 = num_traits::cast(axis_range.end.clone())?; 90 | 91 | let full_range_size = (right - left) / (part.end - part.start); 92 | 93 | let full_left = left - full_range_size * part.start; 94 | let full_right = right + full_range_size * (1.0 - part.end); 95 | 96 | let full_range: Range = num_traits::cast(full_left)?..num_traits::cast(full_right)?; 97 | 98 | let axis_range: as AsRangedCoord>::CoordDescType = axis_range.into(); 99 | 100 | Some(PartialAxis(full_range.into(), axis_range.range())) 101 | } 102 | 103 | #[cfg(test)] 104 | mod test { 105 | use super::*; 106 | #[test] 107 | fn test_make_partial_axis() { 108 | let r = make_partial_axis(20..80, 0.2..0.8).unwrap(); 109 | assert_eq!(r.size(), 101); 110 | assert_eq!(r.range(), 0..100); 111 | assert_eq!(r.axis_pixel_range((0, 100)), 20..80); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /plotters/src/coord/ranged1d/types/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "chrono")] 2 | mod datetime; 3 | #[cfg(feature = "chrono")] 4 | #[cfg_attr(doc_cfg, doc(cfg(feature = "chrono")))] 5 | pub use datetime::{ 6 | IntoMonthly, IntoYearly, Monthly, RangedDate, RangedDateTime, RangedDuration, Yearly, 7 | }; 8 | 9 | mod numeric; 10 | pub use numeric::{ 11 | RangedCoordf32, RangedCoordf64, RangedCoordi128, RangedCoordi32, RangedCoordi64, 12 | RangedCoordisize, RangedCoordu128, RangedCoordu32, RangedCoordu64, RangedCoordusize, 13 | }; 14 | 15 | mod slice; 16 | pub use slice::RangedSlice; 17 | -------------------------------------------------------------------------------- /plotters/src/coord/ranged1d/types/slice.rs: -------------------------------------------------------------------------------- 1 | use crate::coord::ranged1d::{ 2 | AsRangedCoord, DefaultFormatting, DiscreteRanged, KeyPointHint, Ranged, 3 | }; 4 | use std::ops::Range; 5 | 6 | /// A range that is defined by a slice of values. 7 | /// 8 | /// Please note: the behavior of constructing an empty range may cause panic 9 | #[derive(Clone)] 10 | pub struct RangedSlice<'a, T: PartialEq>(&'a [T]); 11 | 12 | impl<'a, T: PartialEq> Ranged for RangedSlice<'a, T> { 13 | type FormatOption = DefaultFormatting; 14 | type ValueType = &'a T; 15 | 16 | fn range(&self) -> Range<&'a T> { 17 | // If inner slice is empty, we should always panic 18 | &self.0[0]..&self.0[self.0.len() - 1] 19 | } 20 | 21 | fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 { 22 | match self.0.iter().position(|x| &x == value) { 23 | Some(pos) => { 24 | let pixel_span = limit.1 - limit.0; 25 | let value_span = self.0.len() - 1; 26 | (f64::from(limit.0) 27 | + f64::from(pixel_span) 28 | * (f64::from(pos as u32) / f64::from(value_span as u32))) 29 | .round() as i32 30 | } 31 | None => limit.0, 32 | } 33 | } 34 | 35 | fn key_points(&self, hint: Hint) -> Vec { 36 | let max_points = hint.max_num_points(); 37 | let mut ret = vec![]; 38 | let intervals = (self.0.len() - 1) as f64; 39 | let step = (intervals / max_points as f64 + 1.0) as usize; 40 | for idx in (0..self.0.len()).step_by(step) { 41 | ret.push(&self.0[idx]); 42 | } 43 | ret 44 | } 45 | } 46 | 47 | impl<'a, T: PartialEq> DiscreteRanged for RangedSlice<'a, T> { 48 | fn size(&self) -> usize { 49 | self.0.len() 50 | } 51 | 52 | fn index_of(&self, value: &&'a T) -> Option { 53 | self.0.iter().position(|x| &x == value) 54 | } 55 | 56 | fn from_index(&self, index: usize) -> Option<&'a T> { 57 | if self.0.len() <= index { 58 | return None; 59 | } 60 | Some(&self.0[index]) 61 | } 62 | } 63 | 64 | impl<'a, T: PartialEq> From<&'a [T]> for RangedSlice<'a, T> { 65 | fn from(range: &'a [T]) -> Self { 66 | RangedSlice(range) 67 | } 68 | } 69 | 70 | impl<'a, T: PartialEq> AsRangedCoord for &'a [T] { 71 | type CoordDescType = RangedSlice<'a, T>; 72 | type Value = &'a T; 73 | } 74 | 75 | #[cfg(test)] 76 | mod test { 77 | use super::*; 78 | #[test] 79 | fn test_slice_range() { 80 | let my_slice = [1, 2, 3, 0, -1, -2]; 81 | let slice_range: RangedSlice = my_slice[..].into(); 82 | 83 | assert_eq!(slice_range.range(), &1..&-2); 84 | assert_eq!( 85 | slice_range.key_points(6), 86 | my_slice.iter().collect::>() 87 | ); 88 | assert_eq!(slice_range.map(&&0, (0, 50)), 30); 89 | } 90 | 91 | #[test] 92 | fn test_slice_range_discrete() { 93 | let my_slice = [1, 2, 3, 0, -1, -2]; 94 | let slice_range: RangedSlice = my_slice[..].into(); 95 | 96 | assert_eq!(slice_range.size(), 6); 97 | assert_eq!(slice_range.index_of(&&3), Some(2)); 98 | assert_eq!(slice_range.from_index(2), Some(&3)); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /plotters/src/coord/ranged2d/cartesian.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | The 2-dimensional cartesian coordinate system. 3 | 4 | This module provides the 2D cartesian coordinate system, which is composed by two independent 5 | ranged 1D coordinate specification. 6 | 7 | This type of coordinate system is used by the chart constructed with [ChartBuilder::build_cartesian_2d](../../chart/ChartBuilder.html#method.build_cartesian_2d). 8 | */ 9 | 10 | use crate::coord::ranged1d::{KeyPointHint, Ranged, ReversibleRanged}; 11 | use crate::coord::{CoordTranslate, ReverseCoordTranslate}; 12 | 13 | use crate::style::ShapeStyle; 14 | use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind}; 15 | 16 | use std::ops::Range; 17 | 18 | /// A 2D Cartesian coordinate system described by two 1D ranged coordinate specs. 19 | #[derive(Clone)] 20 | pub struct Cartesian2d { 21 | logic_x: X, 22 | logic_y: Y, 23 | back_x: (i32, i32), 24 | back_y: (i32, i32), 25 | } 26 | 27 | impl Cartesian2d { 28 | /// Create a new 2D cartesian coordinate system 29 | /// - `logic_x` and `logic_y` : The description for the 1D coordinate system 30 | /// - `actual`: The pixel range on the screen for this coordinate system 31 | pub fn new, IntoY: Into>( 32 | logic_x: IntoX, 33 | logic_y: IntoY, 34 | actual: (Range, Range), 35 | ) -> Self { 36 | Self { 37 | logic_x: logic_x.into(), 38 | logic_y: logic_y.into(), 39 | back_x: (actual.0.start, actual.0.end), 40 | back_y: (actual.1.start, actual.1.end), 41 | } 42 | } 43 | 44 | /// Draw the mesh for the coordinate system 45 | pub fn draw_mesh< 46 | E, 47 | DrawMesh: FnMut(MeshLine) -> Result<(), E>, 48 | XH: KeyPointHint, 49 | YH: KeyPointHint, 50 | >( 51 | &self, 52 | h_limit: YH, 53 | v_limit: XH, 54 | mut draw_mesh: DrawMesh, 55 | ) -> Result<(), E> { 56 | let (xkp, ykp) = ( 57 | self.logic_x.key_points(v_limit), 58 | self.logic_y.key_points(h_limit), 59 | ); 60 | 61 | for logic_x in xkp { 62 | let x = self.logic_x.map(&logic_x, self.back_x); 63 | draw_mesh(MeshLine::XMesh( 64 | (x, self.back_y.0), 65 | (x, self.back_y.1), 66 | &logic_x, 67 | ))?; 68 | } 69 | 70 | for logic_y in ykp { 71 | let y = self.logic_y.map(&logic_y, self.back_y); 72 | draw_mesh(MeshLine::YMesh( 73 | (self.back_x.0, y), 74 | (self.back_x.1, y), 75 | &logic_y, 76 | ))?; 77 | } 78 | 79 | Ok(()) 80 | } 81 | 82 | /// Get the range of X axis 83 | pub fn get_x_range(&self) -> Range { 84 | self.logic_x.range() 85 | } 86 | 87 | /// Get the range of Y axis 88 | pub fn get_y_range(&self) -> Range { 89 | self.logic_y.range() 90 | } 91 | 92 | /// Get the horizental backend coordinate range where X axis should be drawn 93 | pub fn get_x_axis_pixel_range(&self) -> Range { 94 | self.logic_x.axis_pixel_range(self.back_x) 95 | } 96 | 97 | /// Get the vertical backend coordinate range where Y axis should be drawn 98 | pub fn get_y_axis_pixel_range(&self) -> Range { 99 | self.logic_y.axis_pixel_range(self.back_y) 100 | } 101 | 102 | /// Get the 1D coordinate spec for X axis 103 | pub fn x_spec(&self) -> &X { 104 | &self.logic_x 105 | } 106 | 107 | /// Get the 1D coordinate spec for Y axis 108 | pub fn y_spec(&self) -> &Y { 109 | &self.logic_y 110 | } 111 | } 112 | 113 | impl CoordTranslate for Cartesian2d { 114 | type From = (X::ValueType, Y::ValueType); 115 | 116 | fn translate(&self, from: &Self::From) -> BackendCoord { 117 | ( 118 | self.logic_x.map(&from.0, self.back_x), 119 | self.logic_y.map(&from.1, self.back_y), 120 | ) 121 | } 122 | } 123 | 124 | impl ReverseCoordTranslate for Cartesian2d { 125 | fn reverse_translate(&self, input: BackendCoord) -> Option { 126 | Some(( 127 | self.logic_x.unmap(input.0, self.back_x)?, 128 | self.logic_y.unmap(input.1, self.back_y)?, 129 | )) 130 | } 131 | } 132 | 133 | /// Represent a coordinate mesh for the two ranged value coordinate system 134 | pub enum MeshLine<'a, X: Ranged, Y: Ranged> { 135 | /// Used to plot the horizontal lines of the mesh 136 | XMesh(BackendCoord, BackendCoord, &'a X::ValueType), 137 | /// Used to plot the vertical lines of the mesh 138 | YMesh(BackendCoord, BackendCoord, &'a Y::ValueType), 139 | } 140 | 141 | impl<'a, X: Ranged, Y: Ranged> MeshLine<'a, X, Y> { 142 | /// Draw a single mesh line onto the backend 143 | pub fn draw( 144 | &self, 145 | backend: &mut DB, 146 | style: &ShapeStyle, 147 | ) -> Result<(), DrawingErrorKind> { 148 | let (&left, &right) = match self { 149 | MeshLine::XMesh(a, b, _) => (a, b), 150 | MeshLine::YMesh(a, b, _) => (a, b), 151 | }; 152 | backend.draw_line(left, right, style) 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /plotters/src/coord/ranged2d/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod cartesian; 2 | -------------------------------------------------------------------------------- /plotters/src/coord/ranged3d/cartesian3d.rs: -------------------------------------------------------------------------------- 1 | use super::{ProjectionMatrix, ProjectionMatrixBuilder}; 2 | use crate::coord::ranged1d::Ranged; 3 | use crate::coord::CoordTranslate; 4 | use plotters_backend::BackendCoord; 5 | 6 | use std::ops::Range; 7 | 8 | /// A 3D cartesian coordinate system 9 | #[derive(Clone)] 10 | pub struct Cartesian3d { 11 | pub(crate) logic_x: X, 12 | pub(crate) logic_y: Y, 13 | pub(crate) logic_z: Z, 14 | coord_size: (i32, i32, i32), 15 | projection: ProjectionMatrix, 16 | } 17 | 18 | impl Cartesian3d { 19 | fn compute_default_size(actual_x: Range, actual_y: Range) -> i32 { 20 | (actual_x.end - actual_x.start).min(actual_y.end - actual_y.start) * 4 / 5 21 | } 22 | fn create_projection ProjectionMatrix>( 23 | actual_x: Range, 24 | actual_y: Range, 25 | coord_size: (i32, i32, i32), 26 | f: F, 27 | ) -> ProjectionMatrix { 28 | let center_3d = (coord_size.0 / 2, coord_size.1 / 2, coord_size.2 / 2); 29 | let center_2d = ( 30 | (actual_x.end + actual_x.start) / 2, 31 | (actual_y.end + actual_y.start) / 2, 32 | ); 33 | let mut pb = ProjectionMatrixBuilder::new(); 34 | pb.set_pivot(center_3d, center_2d); 35 | f(pb) 36 | } 37 | /// Creates a Cartesian3d object with the given projection. 38 | pub fn with_projection< 39 | SX: Into, 40 | SY: Into, 41 | SZ: Into, 42 | F: FnOnce(ProjectionMatrixBuilder) -> ProjectionMatrix, 43 | >( 44 | logic_x: SX, 45 | logic_y: SY, 46 | logic_z: SZ, 47 | (actual_x, actual_y): (Range, Range), 48 | build_projection_matrix: F, 49 | ) -> Self { 50 | let default_size = Self::compute_default_size(actual_x.clone(), actual_y.clone()); 51 | let coord_size = (default_size, default_size, default_size); 52 | Self { 53 | logic_x: logic_x.into(), 54 | logic_y: logic_y.into(), 55 | logic_z: logic_z.into(), 56 | coord_size, 57 | projection: Self::create_projection( 58 | actual_x, 59 | actual_y, 60 | coord_size, 61 | build_projection_matrix, 62 | ), 63 | } 64 | } 65 | 66 | /// Sets the pixel sizes and projections according to the given ranges. 67 | pub fn set_coord_pixel_range( 68 | &mut self, 69 | actual_x: Range, 70 | actual_y: Range, 71 | coord_size: (i32, i32, i32), 72 | ) -> &mut Self { 73 | self.coord_size = coord_size; 74 | self.projection = 75 | Self::create_projection(actual_x, actual_y, coord_size, |pb| pb.into_matrix()); 76 | self 77 | } 78 | 79 | /// Set the projection matrix 80 | pub fn set_projection ProjectionMatrix>( 81 | &mut self, 82 | actual_x: Range, 83 | actual_y: Range, 84 | f: F, 85 | ) -> &mut Self { 86 | self.projection = Self::create_projection(actual_x, actual_y, self.coord_size, f); 87 | self 88 | } 89 | 90 | /// Create a new coordinate 91 | pub fn new, SY: Into, SZ: Into>( 92 | logic_x: SX, 93 | logic_y: SY, 94 | logic_z: SZ, 95 | (actual_x, actual_y): (Range, Range), 96 | ) -> Self { 97 | Self::with_projection(logic_x, logic_y, logic_z, (actual_x, actual_y), |pb| { 98 | pb.into_matrix() 99 | }) 100 | } 101 | /// Get the projection matrix 102 | pub fn projection(&self) -> &ProjectionMatrix { 103 | &self.projection 104 | } 105 | 106 | /// Do not project, only transform the guest coordinate system 107 | pub fn map_3d(&self, x: &X::ValueType, y: &Y::ValueType, z: &Z::ValueType) -> (i32, i32, i32) { 108 | ( 109 | self.logic_x.map(x, (0, self.coord_size.0)), 110 | self.logic_y.map(y, (0, self.coord_size.1)), 111 | self.logic_z.map(z, (0, self.coord_size.2)), 112 | ) 113 | } 114 | 115 | /// Get the depth of the projection 116 | pub fn projected_depth(&self, x: &X::ValueType, y: &Y::ValueType, z: &Z::ValueType) -> i32 { 117 | self.projection.projected_depth(self.map_3d(x, y, z)) 118 | } 119 | } 120 | 121 | impl CoordTranslate for Cartesian3d { 122 | type From = (X::ValueType, Y::ValueType, Z::ValueType); 123 | fn translate(&self, coord: &Self::From) -> BackendCoord { 124 | let pixel_coord_3d = self.map_3d(&coord.0, &coord.1, &coord.2); 125 | self.projection * pixel_coord_3d 126 | } 127 | 128 | fn depth(&self, coord: &Self::From) -> i32 { 129 | self.projected_depth(&coord.0, &coord.1, &coord.2) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /plotters/src/coord/ranged3d/mod.rs: -------------------------------------------------------------------------------- 1 | mod projection; 2 | pub use projection::{ProjectionMatrix, ProjectionMatrixBuilder}; 3 | 4 | mod cartesian3d; 5 | pub use cartesian3d::Cartesian3d; 6 | -------------------------------------------------------------------------------- /plotters/src/coord/translate.rs: -------------------------------------------------------------------------------- 1 | use plotters_backend::BackendCoord; 2 | use std::ops::Deref; 3 | 4 | /// The trait that translates some customized object to the backend coordinate 5 | pub trait CoordTranslate { 6 | /// Specifies the object to be translated from 7 | type From; 8 | 9 | /// Translate the guest coordinate to the guest coordinate 10 | fn translate(&self, from: &Self::From) -> BackendCoord; 11 | 12 | /// Get the Z-value of current coordinate 13 | fn depth(&self, _from: &Self::From) -> i32 { 14 | 0 15 | } 16 | } 17 | 18 | impl CoordTranslate for T 19 | where 20 | C: CoordTranslate, 21 | T: Deref, 22 | { 23 | type From = C::From; 24 | fn translate(&self, from: &Self::From) -> BackendCoord { 25 | self.deref().translate(from) 26 | } 27 | } 28 | 29 | /// The trait indicates that the coordinate system supports reverse transform 30 | /// This is useful when we need an interactive plot, thus we need to map the event 31 | /// from the backend coordinate to the logical coordinate 32 | pub trait ReverseCoordTranslate: CoordTranslate { 33 | /// Reverse translate the coordinate from the drawing coordinate to the 34 | /// logic coordinate. 35 | /// Note: the return value is an option, because it's possible that the drawing 36 | /// coordinate isn't able to be represented in te guest coordinate system 37 | fn reverse_translate(&self, input: BackendCoord) -> Option; 38 | } 39 | -------------------------------------------------------------------------------- /plotters/src/data/data_range.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::{Ordering, PartialOrd}; 2 | use std::iter::IntoIterator; 3 | use std::ops::Range; 4 | 5 | use num_traits::{One, Zero}; 6 | 7 | /// Build a range that fits the data 8 | /// 9 | /// - `iter`: the iterator over the data 10 | /// - **returns** The resulting range 11 | /// 12 | /// ```rust 13 | /// use plotters::data::fitting_range; 14 | /// 15 | /// let data = [4, 14, -2, 2, 5]; 16 | /// let range = fitting_range(&data); 17 | /// assert_eq!(range, std::ops::Range { start: -2, end: 14 }); 18 | /// ``` 19 | pub fn fitting_range<'a, T, I: IntoIterator>(iter: I) -> Range 20 | where 21 | T: 'a + Zero + One + PartialOrd + Clone, 22 | { 23 | let (mut lb, mut ub) = (None, None); 24 | 25 | for value in iter.into_iter() { 26 | if let Some(Ordering::Greater) = lb 27 | .as_ref() 28 | .map_or(Some(Ordering::Greater), |lbv: &T| lbv.partial_cmp(value)) 29 | { 30 | lb = Some(value.clone()); 31 | } 32 | 33 | if let Some(Ordering::Less) = ub 34 | .as_ref() 35 | .map_or(Some(Ordering::Less), |ubv: &T| ubv.partial_cmp(value)) 36 | { 37 | ub = Some(value.clone()); 38 | } 39 | } 40 | 41 | lb.unwrap_or_else(Zero::zero)..ub.unwrap_or_else(One::one) 42 | } 43 | -------------------------------------------------------------------------------- /plotters/src/data/float.rs: -------------------------------------------------------------------------------- 1 | // The code that is related to float number handling 2 | fn find_minimal_repr(n: f64, eps: f64) -> (f64, usize) { 3 | if eps >= 1.0 { 4 | return (n, 0); 5 | } 6 | if n - n.floor() < eps { 7 | (n.floor(), 0) 8 | } else if n.ceil() - n < eps { 9 | (n.ceil(), 0) 10 | } else { 11 | let (rem, pre) = find_minimal_repr((n - n.floor()) * 10.0, eps * 10.0); 12 | (n.floor() + rem / 10.0, pre + 1) 13 | } 14 | } 15 | 16 | #[allow(clippy::never_loop)] 17 | fn float_to_string(n: f64, max_precision: usize, min_decimal: usize) -> String { 18 | let (mut result, mut count) = loop { 19 | let (sign, n) = if n < 0.0 { ("-", -n) } else { ("", n) }; 20 | let int_part = n.floor(); 21 | 22 | let dec_part = 23 | ((n.abs() - int_part.abs()) * (10.0f64).powi(max_precision as i32)).round() as u64; 24 | 25 | if dec_part == 0 || max_precision == 0 { 26 | break (format!("{}{:.0}", sign, int_part), 0); 27 | } 28 | 29 | let mut leading = "".to_string(); 30 | let mut dec_result = format!("{}", dec_part); 31 | 32 | for _ in 0..(max_precision - dec_result.len()) { 33 | leading.push('0'); 34 | } 35 | 36 | while let Some(c) = dec_result.pop() { 37 | if c != '0' { 38 | dec_result.push(c); 39 | break; 40 | } 41 | } 42 | 43 | break ( 44 | format!("{}{:.0}.{}{}", sign, int_part, leading, dec_result), 45 | leading.len() + dec_result.len(), 46 | ); 47 | }; 48 | 49 | if count == 0 && min_decimal > 0 { 50 | result.push('.'); 51 | } 52 | 53 | while count < min_decimal { 54 | result.push('0'); 55 | count += 1; 56 | } 57 | result 58 | } 59 | 60 | /// Handles printing of floating point numbers 61 | pub struct FloatPrettyPrinter { 62 | /// Whether scientific notation is allowed 63 | pub allow_scientific: bool, 64 | /// Minimum allowed number of decimal digits 65 | pub min_decimal: i32, 66 | /// Maximum allowed number of decimal digits 67 | pub max_decimal: i32, 68 | } 69 | 70 | impl FloatPrettyPrinter { 71 | /// Handles printing of floating point numbers 72 | pub fn print(&self, n: f64) -> String { 73 | let (tn, p) = find_minimal_repr(n, (10f64).powi(-self.max_decimal)); 74 | let d_repr = float_to_string(tn, p, self.min_decimal as usize); 75 | if !self.allow_scientific { 76 | d_repr 77 | } else { 78 | if n == 0.0 { 79 | return "0".to_string(); 80 | } 81 | 82 | let mut idx = n.abs().log10().floor(); 83 | let mut exp = (10.0f64).powf(idx); 84 | 85 | if n.abs() / exp + 1e-5 >= 10.0 { 86 | idx += 1.0; 87 | exp *= 10.0; 88 | } 89 | 90 | if idx.abs() < 3.0 { 91 | return d_repr; 92 | } 93 | 94 | let (sn, sp) = find_minimal_repr(n / exp, 1e-5); 95 | let s_repr = format!( 96 | "{}e{}", 97 | float_to_string(sn, sp, self.min_decimal as usize), 98 | float_to_string(idx, 0, 0) 99 | ); 100 | if s_repr.len() + 1 < d_repr.len() || (tn == 0.0 && n != 0.0) { 101 | s_repr 102 | } else { 103 | d_repr 104 | } 105 | } 106 | } 107 | } 108 | 109 | /// The function that pretty prints the floating number 110 | /// Since rust doesn't have anything that can format a float with out appearance, so we just 111 | /// implement a float pretty printing function, which finds the shortest representation of a 112 | /// floating point number within the allowed error range. 113 | /// 114 | /// - `n`: The float number to pretty-print 115 | /// - `allow_sn`: Should we use scientific notation when possible 116 | /// - **returns**: The pretty printed string 117 | pub fn pretty_print_float(n: f64, allow_sn: bool) -> String { 118 | (FloatPrettyPrinter { 119 | allow_scientific: allow_sn, 120 | min_decimal: 0, 121 | max_decimal: 10, 122 | }) 123 | .print(n) 124 | } 125 | 126 | #[cfg(test)] 127 | mod test { 128 | use super::*; 129 | #[test] 130 | fn test_pretty_printing() { 131 | assert_eq!(pretty_print_float(0.99999999999999999999, false), "1"); 132 | assert_eq!(pretty_print_float(0.9999, false), "0.9999"); 133 | assert_eq!( 134 | pretty_print_float(-1e-5 - 0.00000000000000001, true), 135 | "-1e-5" 136 | ); 137 | assert_eq!( 138 | pretty_print_float(-1e-5 - 0.00000000000000001, false), 139 | "-0.00001" 140 | ); 141 | assert_eq!(pretty_print_float(1e100, true), "1e100"); 142 | assert_eq!(pretty_print_float(1234567890f64, true), "1234567890"); 143 | assert_eq!(pretty_print_float(1000000001f64, true), "1e9"); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /plotters/src/data/mod.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | The data processing module, which implements algorithms related to visualization of data. 3 | Such as, down-sampling, etc. 4 | */ 5 | 6 | mod data_range; 7 | pub use data_range::fitting_range; 8 | 9 | mod quartiles; 10 | pub use quartiles::Quartiles; 11 | 12 | /// Handles the printing of floating-point numbers. 13 | pub mod float; 14 | -------------------------------------------------------------------------------- /plotters/src/data/quartiles.rs: -------------------------------------------------------------------------------- 1 | /// The quartiles 2 | #[derive(Clone, Debug)] 3 | pub struct Quartiles { 4 | lower_fence: f64, 5 | lower: f64, 6 | median: f64, 7 | upper: f64, 8 | upper_fence: f64, 9 | } 10 | 11 | impl Quartiles { 12 | // Extract a value representing the `pct` percentile of a 13 | // sorted `s`, using linear interpolation. 14 | fn percentile_of_sorted + Copy>(s: &[T], pct: f64) -> f64 { 15 | assert!(!s.is_empty()); 16 | if s.len() == 1 { 17 | return s[0].into(); 18 | } 19 | assert!(0_f64 <= pct); 20 | let hundred = 100_f64; 21 | assert!(pct <= hundred); 22 | if (pct - hundred).abs() < f64::EPSILON { 23 | return s[s.len() - 1].into(); 24 | } 25 | let length = (s.len() - 1) as f64; 26 | let rank = (pct / hundred) * length; 27 | let lower_rank = rank.floor(); 28 | let d = rank - lower_rank; 29 | let n = lower_rank as usize; 30 | let lo = s[n].into(); 31 | let hi = s[n + 1].into(); 32 | lo + (hi - lo) * d 33 | } 34 | 35 | /// Create a new quartiles struct with the values calculated from the argument. 36 | /// 37 | /// - `s`: The array of the original values 38 | /// - **returns** The newly created quartiles 39 | /// 40 | /// ```rust 41 | /// use plotters::prelude::*; 42 | /// 43 | /// let quartiles = Quartiles::new(&[7, 15, 36, 39, 40, 41]); 44 | /// assert_eq!(quartiles.median(), 37.5); 45 | /// ``` 46 | pub fn new + Copy + PartialOrd>(s: &[T]) -> Self { 47 | let mut s = s.to_owned(); 48 | s.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap()); 49 | 50 | let lower = Quartiles::percentile_of_sorted(&s, 25_f64); 51 | let median = Quartiles::percentile_of_sorted(&s, 50_f64); 52 | let upper = Quartiles::percentile_of_sorted(&s, 75_f64); 53 | let iqr = upper - lower; 54 | let lower_fence = lower - 1.5 * iqr; 55 | let upper_fence = upper + 1.5 * iqr; 56 | Self { 57 | lower_fence, 58 | lower, 59 | median, 60 | upper, 61 | upper_fence, 62 | } 63 | } 64 | 65 | /// Get the quartiles values. 66 | /// 67 | /// - **returns** The array [lower fence, lower quartile, median, upper quartile, upper fence] 68 | /// 69 | /// ```rust 70 | /// use plotters::prelude::*; 71 | /// 72 | /// let quartiles = Quartiles::new(&[7, 15, 36, 39, 40, 41]); 73 | /// let values = quartiles.values(); 74 | /// assert_eq!(values, [-9.0, 20.25, 37.5, 39.75, 69.0]); 75 | /// ``` 76 | pub fn values(&self) -> [f32; 5] { 77 | [ 78 | self.lower_fence as f32, 79 | self.lower as f32, 80 | self.median as f32, 81 | self.upper as f32, 82 | self.upper_fence as f32, 83 | ] 84 | } 85 | 86 | /// Get the quartiles median. 87 | /// 88 | /// - **returns** The median 89 | /// 90 | /// ```rust 91 | /// use plotters::prelude::*; 92 | /// 93 | /// let quartiles = Quartiles::new(&[7, 15, 36, 39, 40, 41]); 94 | /// assert_eq!(quartiles.median(), 37.5); 95 | /// ``` 96 | pub fn median(&self) -> f64 { 97 | self.median 98 | } 99 | } 100 | 101 | #[cfg(test)] 102 | mod test { 103 | use super::*; 104 | 105 | #[test] 106 | #[should_panic] 107 | fn test_empty_input() { 108 | let empty_array: [i32; 0] = []; 109 | Quartiles::new(&empty_array); 110 | } 111 | 112 | #[test] 113 | fn test_low_inputs() { 114 | assert_eq!( 115 | Quartiles::new(&[15.0]).values(), 116 | [15.0, 15.0, 15.0, 15.0, 15.0] 117 | ); 118 | assert_eq!( 119 | Quartiles::new(&[10, 20]).values(), 120 | [5.0, 12.5, 15.0, 17.5, 25.0] 121 | ); 122 | assert_eq!( 123 | Quartiles::new(&[10, 20, 30]).values(), 124 | [0.0, 15.0, 20.0, 25.0, 40.0] 125 | ); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /plotters/src/drawing/backend_impl/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod mocked; 3 | #[cfg(test)] 4 | pub use mocked::{check_color, create_mocked_drawing_area, MockedBackend}; 5 | 6 | /// This is the dummy backend placeholder for the backend that never fails 7 | #[derive(Debug)] 8 | pub struct DummyBackendError; 9 | 10 | impl std::fmt::Display for DummyBackendError { 11 | fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { 12 | write!(fmt, "{:?}", self) 13 | } 14 | } 15 | 16 | impl std::error::Error for DummyBackendError {} 17 | -------------------------------------------------------------------------------- /plotters/src/drawing/mod.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | The drawing utils for Plotters. In Plotters, we have two set of drawing APIs: low-level API and 3 | high-level API. 4 | 5 | The low-level drawing abstraction, the module defines the `DrawingBackend` trait from the `plotters-backend` create. 6 | It exposes a set of functions which allows basic shape, such as pixels, lines, rectangles, circles, to be drawn on the screen. 7 | The low-level API uses the pixel based coordinate. 8 | 9 | The high-level API is built on the top of high-level API. The `DrawingArea` type exposes the high-level drawing API to the remaining part 10 | of Plotters. The basic drawing blocks are composable elements, which can be defined in logic coordinate. To learn more details 11 | about the [coordinate abstraction](../coord/index.html) and [element system](../element/index.html). 12 | */ 13 | mod area; 14 | mod backend_impl; 15 | 16 | pub use area::{DrawingArea, DrawingAreaErrorKind, IntoDrawingArea, Rect}; 17 | 18 | pub use backend_impl::*; 19 | -------------------------------------------------------------------------------- /plotters/src/element/basic_shapes_3d.rs: -------------------------------------------------------------------------------- 1 | use super::{BackendCoordAndZ, Drawable, PointCollection}; 2 | use crate::style::ShapeStyle; 3 | use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind}; 4 | 5 | /** 6 | Represents a cuboid, a six-faced solid. 7 | 8 | # Examples 9 | 10 | ``` 11 | use plotters::prelude::*; 12 | let drawing_area = SVGBackend::new("cuboid.svg", (300, 200)).into_drawing_area(); 13 | drawing_area.fill(&WHITE).unwrap(); 14 | let mut chart_builder = ChartBuilder::on(&drawing_area); 15 | let mut chart_context = chart_builder.margin(20).build_cartesian_3d(0.0..3.5, 0.0..2.5, 0.0..1.5).unwrap(); 16 | chart_context.configure_axes().x_labels(4).y_labels(3).z_labels(2).draw().unwrap(); 17 | let cubiod = Cubiod::new([(0.,0.,0.), (3.,2.,1.)], BLUE.mix(0.2), BLUE); 18 | chart_context.draw_series(std::iter::once(cubiod)).unwrap(); 19 | ``` 20 | 21 | The result is a semi-transparent cuboid with blue edges: 22 | 23 | ![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@b6703f7/apidoc/cuboid.svg) 24 | */ 25 | pub struct Cubiod { 26 | face_style: ShapeStyle, 27 | edge_style: ShapeStyle, 28 | vert: [(X, Y, Z); 8], 29 | } 30 | 31 | impl Cubiod { 32 | /** 33 | Creates a cuboid. 34 | 35 | See [`Cubiod`] for more information and examples. 36 | */ 37 | #[allow(clippy::redundant_clone)] 38 | pub fn new, ES: Into>( 39 | [(x0, y0, z0), (x1, y1, z1)]: [(X, Y, Z); 2], 40 | face_style: FS, 41 | edge_style: ES, 42 | ) -> Self { 43 | Self { 44 | face_style: face_style.into(), 45 | edge_style: edge_style.into(), 46 | vert: [ 47 | (x0.clone(), y0.clone(), z0.clone()), 48 | (x0.clone(), y0.clone(), z1.clone()), 49 | (x0.clone(), y1.clone(), z0.clone()), 50 | (x0.clone(), y1.clone(), z1.clone()), 51 | (x1.clone(), y0.clone(), z0.clone()), 52 | (x1.clone(), y0.clone(), z1.clone()), 53 | (x1.clone(), y1.clone(), z0.clone()), 54 | (x1.clone(), y1.clone(), z1.clone()), 55 | ], 56 | } 57 | } 58 | } 59 | 60 | impl<'a, X: 'a, Y: 'a, Z: 'a> PointCollection<'a, (X, Y, Z), BackendCoordAndZ> 61 | for &'a Cubiod 62 | { 63 | type Point = &'a (X, Y, Z); 64 | type IntoIter = &'a [(X, Y, Z)]; 65 | fn point_iter(self) -> Self::IntoIter { 66 | &self.vert 67 | } 68 | } 69 | 70 | impl Drawable for Cubiod { 71 | fn draw>( 72 | &self, 73 | points: I, 74 | backend: &mut DB, 75 | _: (u32, u32), 76 | ) -> Result<(), DrawingErrorKind> { 77 | let vert: Vec<_> = points.collect(); 78 | let mut polygon = vec![]; 79 | for mask in [1, 2, 4].iter().cloned() { 80 | let mask_a = if mask == 4 { 1 } else { mask * 2 }; 81 | let mask_b = if mask == 1 { 4 } else { mask / 2 }; 82 | let a = 0; 83 | let b = a | mask_a; 84 | let c = a | mask_a | mask_b; 85 | let d = a | mask_b; 86 | polygon.push([vert[a], vert[b], vert[c], vert[d]]); 87 | polygon.push([ 88 | vert[a | mask], 89 | vert[b | mask], 90 | vert[c | mask], 91 | vert[d | mask], 92 | ]); 93 | } 94 | polygon.sort_by_cached_key(|t| std::cmp::Reverse(t[0].1 + t[1].1 + t[2].1 + t[3].1)); 95 | 96 | for p in polygon { 97 | backend.fill_polygon(p.iter().map(|(coord, _)| *coord), &self.face_style)?; 98 | backend.draw_path( 99 | p.iter() 100 | .map(|(coord, _)| *coord) 101 | .chain(std::iter::once(p[0].0)), 102 | &self.edge_style, 103 | )?; 104 | } 105 | 106 | Ok(()) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /plotters/src/element/candlestick.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | The candlestick element, which showing the high/low/open/close price 3 | */ 4 | 5 | use std::cmp::Ordering; 6 | 7 | use crate::element::{Drawable, PointCollection}; 8 | use crate::style::ShapeStyle; 9 | use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind}; 10 | 11 | /// The candlestick data point element 12 | pub struct CandleStick { 13 | style: ShapeStyle, 14 | width: u32, 15 | points: [(X, Y); 4], 16 | } 17 | 18 | impl CandleStick { 19 | /// Create a new candlestick element, which requires the Y coordinate can be compared 20 | /// 21 | /// - `x`: The x coordinate 22 | /// - `open`: The open value 23 | /// - `high`: The high value 24 | /// - `low`: The low value 25 | /// - `close`: The close value 26 | /// - `gain_style`: The style for gain 27 | /// - `loss_style`: The style for loss 28 | /// - `width`: The width 29 | /// - **returns** The newly created candlestick element 30 | /// 31 | /// ```rust 32 | /// use chrono::prelude::*; 33 | /// use plotters::prelude::*; 34 | /// 35 | /// let candlestick = CandleStick::new(Local::now(), 130.0600, 131.3700, 128.8300, 129.1500, &GREEN, &RED, 15); 36 | /// ``` 37 | #[allow(clippy::too_many_arguments)] 38 | pub fn new, LS: Into>( 39 | x: X, 40 | open: Y, 41 | high: Y, 42 | low: Y, 43 | close: Y, 44 | gain_style: GS, 45 | loss_style: LS, 46 | width: u32, 47 | ) -> Self { 48 | Self { 49 | style: match open.partial_cmp(&close) { 50 | Some(Ordering::Less) => gain_style.into(), 51 | _ => loss_style.into(), 52 | }, 53 | width, 54 | points: [ 55 | (x.clone(), open), 56 | (x.clone(), high), 57 | (x.clone(), low), 58 | (x, close), 59 | ], 60 | } 61 | } 62 | } 63 | 64 | impl<'a, X: 'a, Y: PartialOrd + 'a> PointCollection<'a, (X, Y)> for &'a CandleStick { 65 | type Point = &'a (X, Y); 66 | type IntoIter = &'a [(X, Y)]; 67 | fn point_iter(self) -> &'a [(X, Y)] { 68 | &self.points 69 | } 70 | } 71 | 72 | impl Drawable for CandleStick { 73 | fn draw>( 74 | &self, 75 | points: I, 76 | backend: &mut DB, 77 | _: (u32, u32), 78 | ) -> Result<(), DrawingErrorKind> { 79 | let mut points: Vec<_> = points.take(4).collect(); 80 | if points.len() == 4 { 81 | let fill = self.style.filled; 82 | if points[0].1 > points[3].1 { 83 | points.swap(0, 3); 84 | } 85 | let (l, r) = ( 86 | self.width as i32 / 2, 87 | self.width as i32 - self.width as i32 / 2, 88 | ); 89 | 90 | backend.draw_line(points[0], points[1], &self.style)?; 91 | backend.draw_line(points[2], points[3], &self.style)?; 92 | 93 | points[0].0 -= l; 94 | points[3].0 += r; 95 | 96 | backend.draw_rect(points[0], points[3], &self.style, fill)?; 97 | } 98 | Ok(()) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /plotters/src/element/dynelem.rs: -------------------------------------------------------------------------------- 1 | use super::{Drawable, PointCollection}; 2 | use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind}; 3 | 4 | use std::borrow::Borrow; 5 | 6 | trait DynDrawable { 7 | fn draw_dyn( 8 | &self, 9 | points: &mut dyn Iterator, 10 | backend: &mut DB, 11 | parent_dim: (u32, u32), 12 | ) -> Result<(), DrawingErrorKind>; 13 | } 14 | 15 | impl> DynDrawable for T { 16 | fn draw_dyn( 17 | &self, 18 | points: &mut dyn Iterator, 19 | backend: &mut DB, 20 | parent_dim: (u32, u32), 21 | ) -> Result<(), DrawingErrorKind> { 22 | T::draw(self, points, backend, parent_dim) 23 | } 24 | } 25 | 26 | /// The container for a dynamically dispatched element 27 | pub struct DynElement<'a, DB, Coord> 28 | where 29 | DB: DrawingBackend, 30 | Coord: Clone, 31 | { 32 | points: Vec, 33 | drawable: Box + 'a>, 34 | } 35 | 36 | impl<'a, 'b: 'a, DB: DrawingBackend, Coord: Clone> PointCollection<'a, Coord> 37 | for &'a DynElement<'b, DB, Coord> 38 | { 39 | type Point = &'a Coord; 40 | type IntoIter = &'a Vec; 41 | fn point_iter(self) -> Self::IntoIter { 42 | &self.points 43 | } 44 | } 45 | 46 | impl<'a, DB: DrawingBackend, Coord: Clone> Drawable for DynElement<'a, DB, Coord> { 47 | fn draw>( 48 | &self, 49 | mut pos: I, 50 | backend: &mut DB, 51 | parent_dim: (u32, u32), 52 | ) -> Result<(), DrawingErrorKind> { 53 | self.drawable.draw_dyn(&mut pos, backend, parent_dim) 54 | } 55 | } 56 | 57 | /// The trait that makes the conversion from the statically dispatched element 58 | /// to the dynamically dispatched element 59 | pub trait IntoDynElement<'a, DB: DrawingBackend, Coord: Clone> 60 | where 61 | Self: 'a, 62 | { 63 | /// Make the conversion 64 | fn into_dyn(self) -> DynElement<'a, DB, Coord>; 65 | } 66 | 67 | impl<'b, T, DB, Coord> IntoDynElement<'b, DB, Coord> for T 68 | where 69 | T: Drawable + 'b, 70 | for<'a> &'a T: PointCollection<'a, Coord>, 71 | Coord: Clone, 72 | DB: DrawingBackend, 73 | { 74 | fn into_dyn(self) -> DynElement<'b, DB, Coord> { 75 | DynElement { 76 | points: self 77 | .point_iter() 78 | .into_iter() 79 | .map(|x| x.borrow().clone()) 80 | .collect(), 81 | drawable: Box::new(self), 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /plotters/src/element/points.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use super::{Drawable, PointCollection}; 3 | use crate::style::{Color, ShapeStyle, SizeDesc}; 4 | use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind}; 5 | 6 | /** 7 | A common trait for elements that can be interpreted as points: A cross, a circle, a triangle marker... 8 | 9 | This is used internally by Plotters and should probably not be included in user code. 10 | See [`EmptyElement`] for more information and examples. 11 | */ 12 | pub trait PointElement { 13 | /** 14 | Point creator. 15 | 16 | This is used internally by Plotters and should probably not be included in user code. 17 | See [`EmptyElement`] for more information and examples. 18 | */ 19 | fn make_point(pos: Coord, size: Size, style: ShapeStyle) -> Self; 20 | } 21 | 22 | /** 23 | A cross marker for visualizing data series. 24 | 25 | See [`EmptyElement`] for more information and examples. 26 | */ 27 | pub struct Cross { 28 | center: Coord, 29 | size: Size, 30 | style: ShapeStyle, 31 | } 32 | 33 | impl Cross { 34 | /** 35 | Creates a cross marker. 36 | 37 | See [`EmptyElement`] for more information and examples. 38 | */ 39 | pub fn new>(coord: Coord, size: Size, style: T) -> Self { 40 | Self { 41 | center: coord, 42 | size, 43 | style: style.into(), 44 | } 45 | } 46 | } 47 | 48 | impl<'a, Coord: 'a, Size: SizeDesc> PointCollection<'a, Coord> for &'a Cross { 49 | type Point = &'a Coord; 50 | type IntoIter = std::iter::Once<&'a Coord>; 51 | fn point_iter(self) -> std::iter::Once<&'a Coord> { 52 | std::iter::once(&self.center) 53 | } 54 | } 55 | 56 | impl Drawable for Cross { 57 | fn draw>( 58 | &self, 59 | mut points: I, 60 | backend: &mut DB, 61 | ps: (u32, u32), 62 | ) -> Result<(), DrawingErrorKind> { 63 | if let Some((x, y)) = points.next() { 64 | let size = self.size.in_pixels(&ps); 65 | let (x0, y0) = (x - size, y - size); 66 | let (x1, y1) = (x + size, y + size); 67 | backend.draw_line((x0, y0), (x1, y1), &self.style)?; 68 | backend.draw_line((x0, y1), (x1, y0), &self.style)?; 69 | } 70 | Ok(()) 71 | } 72 | } 73 | 74 | /** 75 | A triangle marker for visualizing data series. 76 | 77 | See [`EmptyElement`] for more information and examples. 78 | */ 79 | pub struct TriangleMarker { 80 | center: Coord, 81 | size: Size, 82 | style: ShapeStyle, 83 | } 84 | 85 | impl TriangleMarker { 86 | /** 87 | Creates a triangle marker. 88 | 89 | See [`EmptyElement`] for more information and examples. 90 | */ 91 | pub fn new>(coord: Coord, size: Size, style: T) -> Self { 92 | Self { 93 | center: coord, 94 | size, 95 | style: style.into(), 96 | } 97 | } 98 | } 99 | 100 | impl<'a, Coord: 'a, Size: SizeDesc> PointCollection<'a, Coord> for &'a TriangleMarker { 101 | type Point = &'a Coord; 102 | type IntoIter = std::iter::Once<&'a Coord>; 103 | fn point_iter(self) -> std::iter::Once<&'a Coord> { 104 | std::iter::once(&self.center) 105 | } 106 | } 107 | 108 | impl Drawable for TriangleMarker { 109 | fn draw>( 110 | &self, 111 | mut points: I, 112 | backend: &mut DB, 113 | ps: (u32, u32), 114 | ) -> Result<(), DrawingErrorKind> { 115 | if let Some((x, y)) = points.next() { 116 | let size = self.size.in_pixels(&ps); 117 | let points = [-90, -210, -330] 118 | .iter() 119 | .map(|deg| f64::from(*deg) * std::f64::consts::PI / 180.0) 120 | .map(|rad| { 121 | ( 122 | (rad.cos() * f64::from(size) + f64::from(x)).ceil() as i32, 123 | (rad.sin() * f64::from(size) + f64::from(y)).ceil() as i32, 124 | ) 125 | }); 126 | backend.fill_polygon(points, &self.style.color.to_backend_color())?; 127 | } 128 | Ok(()) 129 | } 130 | } 131 | 132 | impl PointElement for Cross { 133 | fn make_point(pos: Coord, size: Size, style: ShapeStyle) -> Self { 134 | Self::new(pos, size, style) 135 | } 136 | } 137 | 138 | impl PointElement for TriangleMarker { 139 | fn make_point(pos: Coord, size: Size, style: ShapeStyle) -> Self { 140 | Self::new(pos, size, style) 141 | } 142 | } 143 | 144 | impl PointElement for Circle { 145 | fn make_point(pos: Coord, size: Size, style: ShapeStyle) -> Self { 146 | Self::new(pos, size, style) 147 | } 148 | } 149 | 150 | impl PointElement for Pixel { 151 | fn make_point(pos: Coord, _: Size, style: ShapeStyle) -> Self { 152 | Self::new(pos, style) 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /plotters/src/evcxr.rs: -------------------------------------------------------------------------------- 1 | use crate::coord::Shift; 2 | use crate::drawing::{DrawingArea, IntoDrawingArea}; 3 | use plotters_backend::DrawingBackend; 4 | use plotters_svg::SVGBackend; 5 | use std::fs::File; 6 | use std::io::Write; 7 | 8 | #[cfg(feature = "evcxr_bitmap")] 9 | #[cfg_attr(doc_cfg, doc(cfg(feature = "evcxr_bitmap")))] 10 | use plotters_bitmap::BitMapBackend; 11 | 12 | /// The wrapper for the generated SVG 13 | pub struct SVGWrapper(String, String); 14 | 15 | impl SVGWrapper { 16 | /// Displays the contents of the `SVGWrapper` struct. 17 | pub fn evcxr_display(&self) { 18 | println!("{:?}", self); 19 | } 20 | /// Sets the style of the `SVGWrapper` struct. 21 | pub fn style>(mut self, style: S) -> Self { 22 | self.1 = style.into(); 23 | self 24 | } 25 | } 26 | 27 | impl std::fmt::Debug for SVGWrapper { 28 | fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 29 | let svg = self.0.as_str(); 30 | write!( 31 | formatter, 32 | "EVCXR_BEGIN_CONTENT text/html\n
{}
\nEVCXR_END_CONTENT", 33 | self.1, svg 34 | ) 35 | } 36 | } 37 | 38 | /// Start drawing an evcxr figure 39 | pub fn evcxr_figure< 40 | Draw: FnOnce(DrawingArea) -> Result<(), Box>, 41 | >( 42 | size: (u32, u32), 43 | draw: Draw, 44 | ) -> SVGWrapper { 45 | let mut buffer = "".to_string(); 46 | let root = SVGBackend::with_string(&mut buffer, size).into_drawing_area(); 47 | draw(root).expect("Drawing failure"); 48 | SVGWrapper(buffer, "".to_string()) 49 | } 50 | 51 | /// An evcxr figure that can save to the local file system and render in a notebook. 52 | pub fn evcxr_figure_with_saving< 53 | Draw: FnOnce(DrawingArea) -> Result<(), Box>, 54 | >( 55 | filename: &str, 56 | size: (u32, u32), 57 | draw: Draw, 58 | ) -> SVGWrapper { 59 | let mut buffer = "".to_string(); 60 | let root = SVGBackend::with_string(&mut buffer, size).into_drawing_area(); 61 | draw(root).expect("Drawing failure"); 62 | 63 | let mut file = File::create(filename).expect("Unable to create file"); 64 | file.write_all(buffer.as_bytes()) 65 | .expect("Unable to write data"); 66 | 67 | SVGWrapper(buffer, "".to_string()) 68 | } 69 | /// Start drawing an evcxr figure 70 | #[cfg(feature = "evcxr_bitmap")] 71 | #[cfg_attr(doc_cfg, doc(cfg(feature = "evcxr_bitmap")))] 72 | pub fn evcxr_bitmap_figure< 73 | Draw: FnOnce(DrawingArea) -> Result<(), Box>, 74 | >( 75 | size: (u32, u32), 76 | draw: Draw, 77 | ) -> SVGWrapper { 78 | const PIXEL_SIZE: usize = 3; 79 | 80 | let mut buf = vec![0; (size.0 as usize) * (size.1 as usize) * PIXEL_SIZE]; 81 | 82 | let root = BitMapBackend::with_buffer(&mut buf, size).into_drawing_area(); 83 | draw(root).expect("Drawing failure"); 84 | let mut buffer = "".to_string(); 85 | { 86 | let mut svg_root = SVGBackend::with_string(&mut buffer, size); 87 | svg_root 88 | .blit_bitmap((0, 0), size, &buf) 89 | .expect("Failure converting to SVG"); 90 | } 91 | SVGWrapper(buffer, "".to_string()) 92 | } 93 | -------------------------------------------------------------------------------- /plotters/src/series/area_series.rs: -------------------------------------------------------------------------------- 1 | use crate::element::{DynElement, IntoDynElement, PathElement, Polygon}; 2 | use crate::style::colors::TRANSPARENT; 3 | use crate::style::ShapeStyle; 4 | use plotters_backend::DrawingBackend; 5 | 6 | /** 7 | An area series is similar to a line series but uses a filled polygon. 8 | It takes an iterator of data points in guest coordinate system 9 | and creates appropriate lines and points with the given style. 10 | 11 | # Example 12 | 13 | ``` 14 | use plotters::prelude::*; 15 | let x_values = [0.0f64, 1., 2., 3., 4.]; 16 | let drawing_area = SVGBackend::new("area_series.svg", (300, 200)).into_drawing_area(); 17 | drawing_area.fill(&WHITE).unwrap(); 18 | let mut chart_builder = ChartBuilder::on(&drawing_area); 19 | chart_builder.margin(10).set_left_and_bottom_label_area_size(20); 20 | let mut chart_context = chart_builder.build_cartesian_2d(0.0..4.0, 0.0..3.0).unwrap(); 21 | chart_context.configure_mesh().draw().unwrap(); 22 | chart_context.draw_series(AreaSeries::new(x_values.map(|x| (x, 0.3 * x)), 0., BLACK.mix(0.2))).unwrap(); 23 | chart_context.draw_series(AreaSeries::new(x_values.map(|x| (x, 2.5 - 0.05 * x * x)), 0., RED.mix(0.2))).unwrap(); 24 | chart_context.draw_series(AreaSeries::new(x_values.map(|x| (x, 2. - 0.1 * x * x)), 0., BLUE.mix(0.2)).border_style(BLUE)).unwrap(); 25 | ``` 26 | 27 | The result is a chart with three line series; one of them has a highlighted blue border: 28 | 29 | ![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@b6703f7/apidoc/area_series.svg) 30 | */ 31 | pub struct AreaSeries { 32 | area_style: ShapeStyle, 33 | border_style: ShapeStyle, 34 | baseline: Y, 35 | data: Vec<(X, Y)>, 36 | state: u32, 37 | _p: std::marker::PhantomData, 38 | } 39 | 40 | impl AreaSeries { 41 | /** 42 | Creates an area series with transparent border. 43 | 44 | See [`AreaSeries`] for more information and examples. 45 | */ 46 | pub fn new, I: IntoIterator>( 47 | iter: I, 48 | baseline: Y, 49 | area_style: S, 50 | ) -> Self { 51 | Self { 52 | area_style: area_style.into(), 53 | baseline, 54 | data: iter.into_iter().collect(), 55 | state: 0, 56 | border_style: (&TRANSPARENT).into(), 57 | _p: std::marker::PhantomData, 58 | } 59 | } 60 | 61 | /** 62 | Sets the border style of the area series. 63 | 64 | See [`AreaSeries`] for more information and examples. 65 | */ 66 | pub fn border_style>(mut self, style: S) -> Self { 67 | self.border_style = style.into(); 68 | self 69 | } 70 | } 71 | 72 | impl Iterator for AreaSeries { 73 | type Item = DynElement<'static, DB, (X, Y)>; 74 | fn next(&mut self) -> Option { 75 | if self.state == 0 { 76 | let mut data: Vec<_> = self.data.clone(); 77 | 78 | if !data.is_empty() { 79 | data.push((data[data.len() - 1].0.clone(), self.baseline.clone())); 80 | data.push((data[0].0.clone(), self.baseline.clone())); 81 | } 82 | 83 | self.state = 1; 84 | 85 | Some(Polygon::new(data, self.area_style).into_dyn()) 86 | } else if self.state == 1 { 87 | let data: Vec<_> = self.data.clone(); 88 | 89 | self.state = 2; 90 | 91 | Some(PathElement::new(data, self.border_style).into_dyn()) 92 | } else { 93 | None 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /plotters/src/series/mod.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | This module contains predefined types of series. 3 | The series in Plotters is actually an iterator of elements, which 4 | can be taken by `ChartContext::draw_series` function. 5 | 6 | This module defines some "iterator transformer", which transform the data 7 | iterator to the element iterator. 8 | 9 | Any type that implements iterator emitting drawable elements are acceptable series. 10 | So iterator combinator such as `map`, `zip`, etc can also be used. 11 | */ 12 | 13 | #[cfg(feature = "area_series")] 14 | mod area_series; 15 | #[cfg(feature = "histogram")] 16 | mod histogram; 17 | #[cfg(feature = "line_series")] 18 | mod line_series; 19 | #[cfg(feature = "point_series")] 20 | mod point_series; 21 | #[cfg(feature = "surface_series")] 22 | mod surface; 23 | 24 | #[cfg(feature = "area_series")] 25 | #[cfg_attr(doc_cfg, doc(cfg(feature = "area_series")))] 26 | pub use area_series::AreaSeries; 27 | #[cfg(feature = "histogram")] 28 | #[cfg_attr(doc_cfg, doc(cfg(feature = "histogram")))] 29 | pub use histogram::Histogram; 30 | #[cfg(feature = "line_series")] 31 | #[cfg_attr(doc_cfg, doc(cfg(feature = "line_series")))] 32 | pub use line_series::{DashedLineSeries, DottedLineSeries, LineSeries}; 33 | #[cfg(feature = "point_series")] 34 | #[cfg_attr(doc_cfg, doc(cfg(feature = "point_series")))] 35 | pub use point_series::PointSeries; 36 | #[cfg(feature = "surface_series")] 37 | #[cfg_attr(doc_cfg, doc(cfg(feature = "surface_series")))] 38 | pub use surface::SurfaceSeries; 39 | -------------------------------------------------------------------------------- /plotters/src/series/point_series.rs: -------------------------------------------------------------------------------- 1 | use crate::element::PointElement; 2 | use crate::style::{ShapeStyle, SizeDesc}; 3 | 4 | /// The point plot object, which takes an iterator of points in guest coordinate system 5 | /// and create an element for each point 6 | pub struct PointSeries<'a, Coord, I: IntoIterator, E, Size: SizeDesc + Clone> { 7 | style: ShapeStyle, 8 | size: Size, 9 | data_iter: I::IntoIter, 10 | make_point: &'a dyn Fn(Coord, Size, ShapeStyle) -> E, 11 | } 12 | 13 | impl<'a, Coord, I: IntoIterator, E, Size: SizeDesc + Clone> Iterator 14 | for PointSeries<'a, Coord, I, E, Size> 15 | { 16 | type Item = E; 17 | fn next(&mut self) -> Option { 18 | self.data_iter 19 | .next() 20 | .map(|x| (self.make_point)(x, self.size.clone(), self.style)) 21 | } 22 | } 23 | 24 | impl<'a, Coord, I: IntoIterator, E, Size: SizeDesc + Clone> 25 | PointSeries<'a, Coord, I, E, Size> 26 | where 27 | E: PointElement, 28 | { 29 | /// Create a new point series with the element that implements point trait. 30 | /// You may also use a more general way to create a point series with `of_element` 31 | /// function which allows a customized element construction function 32 | pub fn new>(iter: I, size: Size, style: S) -> Self { 33 | Self { 34 | data_iter: iter.into_iter(), 35 | size, 36 | style: style.into(), 37 | make_point: &|a, b, c| E::make_point(a, b, c), 38 | } 39 | } 40 | } 41 | 42 | impl<'a, Coord, I: IntoIterator, E, Size: SizeDesc + Clone> 43 | PointSeries<'a, Coord, I, E, Size> 44 | { 45 | /// Create a new point series. Similar to `PointSeries::new` but it doesn't 46 | /// requires the element implements point trait. So instead of using the point 47 | /// constructor, it uses the customized function for element creation 48 | pub fn of_element, F: Fn(Coord, Size, ShapeStyle) -> E>( 49 | iter: I, 50 | size: Size, 51 | style: S, 52 | cons: &'a F, 53 | ) -> Self { 54 | Self { 55 | data_iter: iter.into_iter(), 56 | size, 57 | style: style.into(), 58 | make_point: cons, 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /plotters/src/style/colors/mod.rs: -------------------------------------------------------------------------------- 1 | //! Basic predefined colors. 2 | use super::{RGBAColor, RGBColor}; 3 | 4 | // Taken from https://stackoverflow.com/questions/60905060/prevent-line-break-in-doc-test 5 | /// Macro for allowing dynamic creation of doc attributes. 6 | #[macro_export] 7 | macro_rules! doc { 8 | { 9 | $(#[$m:meta])* 10 | $( 11 | [$doc:expr] 12 | $(#[$n:meta])* 13 | )* 14 | @ $thing:item 15 | } => { 16 | $(#[$m])* 17 | $( 18 | #[doc = $doc] 19 | $(#[$n])* 20 | )* 21 | $thing 22 | } 23 | } 24 | 25 | /// Defines and names a color based on its R, G, B, A values. 26 | #[macro_export] 27 | macro_rules! define_color { 28 | ($name:ident, $r:expr, $g:expr, $b:expr, $doc:expr) => { 29 | doc! { 30 | [$doc] 31 | // Format a colored box that will show up in the docs 32 | [concat!("(" )] 33 | [concat!("*rgb = (", $r,", ", $g, ", ", $b, ")*)")] 34 | @pub const $name: RGBColor = RGBColor($r, $g, $b); 35 | } 36 | }; 37 | 38 | ($name:ident, $r:expr, $g:expr, $b:expr, $a: expr, $doc:expr) => { 39 | doc! { 40 | [$doc] 41 | // Format a colored box that will show up in the docs 42 | [concat!("(" )] 43 | [concat!("*rgba = (", $r,", ", $g, ", ", $b, ", ", $a, ")*)")] 44 | @pub const $name: RGBAColor = RGBAColor($r, $g, $b, $a); 45 | } 46 | }; 47 | } 48 | 49 | define_color!(WHITE, 255, 255, 255, "White"); 50 | define_color!(BLACK, 0, 0, 0, "Black"); 51 | define_color!(RED, 255, 0, 0, "Red"); 52 | define_color!(GREEN, 0, 255, 0, "Green"); 53 | define_color!(BLUE, 0, 0, 255, "Blue"); 54 | define_color!(YELLOW, 255, 255, 0, "Yellow"); 55 | define_color!(CYAN, 0, 255, 255, "Cyan"); 56 | define_color!(MAGENTA, 255, 0, 255, "Magenta"); 57 | define_color!(TRANSPARENT, 0, 0, 0, 0.0, "Transparent"); 58 | 59 | #[cfg(feature = "colormaps")] 60 | #[cfg_attr(doc_cfg, doc(cfg(feature = "colormaps")))] 61 | /// Colormaps can be used to simply go from a scalar value to a color value which will be more/less 62 | /// intense corresponding to the value of the supplied scalar. 63 | /// These colormaps can also be defined by the user and be used with lower and upper bounds. 64 | pub mod colormaps; 65 | #[cfg(feature = "full_palette")] 66 | #[cfg_attr(doc_cfg, doc(cfg(feature = "full_palette")))] 67 | pub mod full_palette; 68 | -------------------------------------------------------------------------------- /plotters/src/style/font/mod.rs: -------------------------------------------------------------------------------- 1 | /// The implementation of an actual font implementation 2 | /// 3 | /// This exists since for the image rendering task, we want to use 4 | /// the system font. But in wasm application, we want the browser 5 | /// to handle all the font issue. 6 | /// 7 | /// Thus we need different mechanism for the font implementation 8 | 9 | #[cfg(all( 10 | not(all(target_arch = "wasm32", not(target_os = "wasi"))), 11 | feature = "ttf" 12 | ))] 13 | mod ttf; 14 | #[cfg(all( 15 | not(all(target_arch = "wasm32", not(target_os = "wasi"))), 16 | feature = "ttf" 17 | ))] 18 | use ttf::FontDataInternal; 19 | 20 | #[cfg(all( 21 | not(target_arch = "wasm32"), 22 | not(target_os = "wasi"), 23 | feature = "ab_glyph" 24 | ))] 25 | mod ab_glyph; 26 | #[cfg(all( 27 | not(target_arch = "wasm32"), 28 | not(target_os = "wasi"), 29 | feature = "ab_glyph" 30 | ))] 31 | pub use self::ab_glyph::register_font; 32 | #[cfg(all( 33 | not(target_arch = "wasm32"), 34 | not(target_os = "wasi"), 35 | feature = "ab_glyph", 36 | not(feature = "ttf") 37 | ))] 38 | use self::ab_glyph::FontDataInternal; 39 | 40 | #[cfg(all( 41 | not(all(target_arch = "wasm32", not(target_os = "wasi"))), 42 | not(feature = "ttf"), 43 | not(feature = "ab_glyph") 44 | ))] 45 | mod naive; 46 | #[cfg(all( 47 | not(all(target_arch = "wasm32", not(target_os = "wasi"))), 48 | not(feature = "ttf"), 49 | not(feature = "ab_glyph") 50 | ))] 51 | use naive::FontDataInternal; 52 | 53 | #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] 54 | mod web; 55 | #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] 56 | use web::FontDataInternal; 57 | 58 | mod font_desc; 59 | pub use font_desc::*; 60 | 61 | /// Represents a box where a text label can be fit 62 | pub type LayoutBox = ((i32, i32), (i32, i32)); 63 | 64 | pub trait FontData: Clone { 65 | type ErrorType: Sized + std::error::Error + Clone; 66 | fn new(family: FontFamily, style: FontStyle) -> Result; 67 | fn estimate_layout(&self, size: f64, text: &str) -> Result; 68 | fn draw Result<(), E>>( 69 | &self, 70 | _pos: (i32, i32), 71 | _size: f64, 72 | _text: &str, 73 | _draw: DrawFunc, 74 | ) -> Result, Self::ErrorType> { 75 | panic!("The font implementation is unable to draw text"); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /plotters/src/style/font/naive.rs: -------------------------------------------------------------------------------- 1 | use super::{FontData, FontFamily, FontStyle, LayoutBox}; 2 | 3 | #[derive(Debug, Clone)] 4 | pub struct FontError; 5 | 6 | impl std::fmt::Display for FontError { 7 | fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { 8 | write!(fmt, "General Error")?; 9 | Ok(()) 10 | } 11 | } 12 | 13 | impl std::error::Error for FontError {} 14 | 15 | #[derive(Clone)] 16 | pub struct FontDataInternal(String, String); 17 | 18 | impl FontData for FontDataInternal { 19 | type ErrorType = FontError; 20 | fn new(family: FontFamily, style: FontStyle) -> Result { 21 | Ok(FontDataInternal( 22 | family.as_str().into(), 23 | style.as_str().into(), 24 | )) 25 | } 26 | 27 | /// Note: This is only a crude estimatation, since for some backend such as SVG, we have no way to 28 | /// know the real size of the text anyway. Thus using font-kit is an overkill and doesn't helps 29 | /// the layout. 30 | fn estimate_layout(&self, size: f64, text: &str) -> Result { 31 | let em = size / 1.24 / 1.24; 32 | Ok(( 33 | (0, -em.round() as i32), 34 | ( 35 | (em * 0.7 * text.len() as f64).round() as i32, 36 | (em * 0.24).round() as i32, 37 | ), 38 | )) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /plotters/src/style/font/web.rs: -------------------------------------------------------------------------------- 1 | use super::{FontData, FontFamily, FontStyle, LayoutBox}; 2 | use wasm_bindgen::JsCast; 3 | use web_sys::{window, HtmlElement}; 4 | 5 | #[derive(Debug, Clone)] 6 | pub enum FontError { 7 | UnknownError, 8 | } 9 | 10 | impl std::fmt::Display for FontError { 11 | fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { 12 | match self { 13 | _ => write!(fmt, "Unknown error"), 14 | } 15 | } 16 | } 17 | 18 | impl std::error::Error for FontError {} 19 | 20 | #[derive(Clone)] 21 | pub struct FontDataInternal(String, String); 22 | 23 | impl FontData for FontDataInternal { 24 | type ErrorType = FontError; 25 | fn new(family: FontFamily, style: FontStyle) -> Result { 26 | Ok(FontDataInternal( 27 | family.as_str().into(), 28 | style.as_str().into(), 29 | )) 30 | } 31 | fn estimate_layout(&self, size: f64, text: &str) -> Result { 32 | let window = window().unwrap(); 33 | let document = window.document().unwrap(); 34 | let body = document.body().unwrap(); 35 | let span = document.create_element("span").unwrap(); 36 | span.set_text_content(Some(text)); 37 | span.set_attribute("style", &format!("display: inline-block; font-family:{}; font-style:{}; font-size: {}px; position: fixed; top: 100%", self.0, self.1, size)).unwrap(); 38 | let span = span.into(); 39 | body.append_with_node_1(&span).unwrap(); 40 | let elem = JsCast::dyn_into::(span).unwrap(); 41 | let height = elem.offset_height() as i32; 42 | let width = elem.offset_width() as i32; 43 | elem.remove(); 44 | Ok(((0, 0), (width, height))) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /plotters/src/style/mod.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | The style for shapes and text, font, color, etc. 3 | */ 4 | mod color; 5 | pub mod colors; 6 | mod font; 7 | mod palette; 8 | mod shape; 9 | mod size; 10 | mod text; 11 | 12 | /// Definitions of palettes of accessibility 13 | pub use self::palette::*; 14 | pub use color::{Color, HSLColor, PaletteColor, RGBAColor, RGBColor}; 15 | pub use colors::{BLACK, BLUE, CYAN, GREEN, MAGENTA, RED, TRANSPARENT, WHITE, YELLOW}; 16 | 17 | #[cfg(feature = "full_palette")] 18 | #[cfg_attr(doc_cfg, doc(cfg(feature = "full_palette")))] 19 | pub use colors::full_palette; 20 | 21 | #[cfg(all(not(target_arch = "wasm32"), feature = "ab_glyph"))] 22 | pub use font::register_font; 23 | pub use font::{ 24 | FontDesc, FontError, FontFamily, FontResult, FontStyle, FontTransform, IntoFont, LayoutBox, 25 | }; 26 | 27 | pub use shape::ShapeStyle; 28 | pub use size::{AsRelative, RelativeSize, SizeDesc}; 29 | pub use text::text_anchor; 30 | pub use text::{IntoTextStyle, TextStyle}; 31 | -------------------------------------------------------------------------------- /plotters/src/style/palette.rs: -------------------------------------------------------------------------------- 1 | use super::color::PaletteColor; 2 | 3 | /// Represents a color palette 4 | pub trait Palette { 5 | /// Array of colors 6 | const COLORS: &'static [(u8, u8, u8)]; 7 | /// Returns a color from the palette 8 | fn pick(idx: usize) -> PaletteColor 9 | where 10 | Self: Sized, 11 | { 12 | PaletteColor::::pick(idx) 13 | } 14 | } 15 | 16 | /// The palette of 99% accessibility 17 | pub struct Palette99; 18 | /// The palette of 99.99% accessibility 19 | pub struct Palette9999; 20 | /// The palette of 100% accessibility 21 | pub struct Palette100; 22 | 23 | impl Palette for Palette99 { 24 | const COLORS: &'static [(u8, u8, u8)] = &[ 25 | (230, 25, 75), 26 | (60, 180, 75), 27 | (255, 225, 25), 28 | (0, 130, 200), 29 | (245, 130, 48), 30 | (145, 30, 180), 31 | (70, 240, 240), 32 | (240, 50, 230), 33 | (210, 245, 60), 34 | (250, 190, 190), 35 | (0, 128, 128), 36 | (230, 190, 255), 37 | (170, 110, 40), 38 | (255, 250, 200), 39 | (128, 0, 0), 40 | (170, 255, 195), 41 | (128, 128, 0), 42 | (255, 215, 180), 43 | (0, 0, 128), 44 | (128, 128, 128), 45 | (0, 0, 0), 46 | ]; 47 | } 48 | 49 | impl Palette for Palette9999 { 50 | const COLORS: &'static [(u8, u8, u8)] = &[ 51 | (255, 225, 25), 52 | (0, 130, 200), 53 | (245, 130, 48), 54 | (250, 190, 190), 55 | (230, 190, 255), 56 | (128, 0, 0), 57 | (0, 0, 128), 58 | (128, 128, 128), 59 | (0, 0, 0), 60 | ]; 61 | } 62 | 63 | impl Palette for Palette100 { 64 | const COLORS: &'static [(u8, u8, u8)] = 65 | &[(255, 225, 25), (0, 130, 200), (128, 128, 128), (0, 0, 0)]; 66 | } 67 | -------------------------------------------------------------------------------- /plotters/src/style/shape.rs: -------------------------------------------------------------------------------- 1 | use super::color::{Color, RGBAColor}; 2 | use plotters_backend::{BackendColor, BackendStyle}; 3 | 4 | /// Style for any shape 5 | #[derive(Copy, Clone, Debug, PartialEq)] 6 | pub struct ShapeStyle { 7 | /// Specification of the color. 8 | pub color: RGBAColor, 9 | /// Whether the style is filled with color. 10 | pub filled: bool, 11 | /// Stroke width. 12 | pub stroke_width: u32, 13 | } 14 | 15 | impl ShapeStyle { 16 | /** 17 | Returns a filled style with the same color and stroke width. 18 | 19 | # Example 20 | 21 | ``` 22 | use plotters::prelude::*; 23 | let original_style = ShapeStyle { 24 | color: BLUE.mix(0.6), 25 | filled: false, 26 | stroke_width: 2, 27 | }; 28 | let filled_style = original_style.filled(); 29 | let drawing_area = SVGBackend::new("shape_style_filled.svg", (400, 200)).into_drawing_area(); 30 | drawing_area.fill(&WHITE).unwrap(); 31 | drawing_area.draw(&Circle::new((150, 100), 90, original_style)); 32 | drawing_area.draw(&Circle::new((250, 100), 90, filled_style)); 33 | ``` 34 | 35 | The result is a figure with two circles, one of them filled: 36 | 37 | ![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@b0b94d5/apidoc/shape_style_filled.svg) 38 | */ 39 | pub fn filled(&self) -> Self { 40 | Self { 41 | color: self.color.to_rgba(), 42 | filled: true, 43 | stroke_width: self.stroke_width, 44 | } 45 | } 46 | 47 | /** 48 | Returns a new style with the same color and the specified stroke width. 49 | 50 | # Example 51 | 52 | ``` 53 | use plotters::prelude::*; 54 | let original_style = ShapeStyle { 55 | color: BLUE.mix(0.6), 56 | filled: false, 57 | stroke_width: 2, 58 | }; 59 | let new_style = original_style.stroke_width(5); 60 | let drawing_area = SVGBackend::new("shape_style_stroke_width.svg", (400, 200)).into_drawing_area(); 61 | drawing_area.fill(&WHITE).unwrap(); 62 | drawing_area.draw(&Circle::new((150, 100), 90, original_style)); 63 | drawing_area.draw(&Circle::new((250, 100), 90, new_style)); 64 | ``` 65 | 66 | The result is a figure with two circles, one of them thicker than the other: 67 | 68 | ![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@b0b94d5/apidoc/shape_style_stroke_width.svg) 69 | */ 70 | pub fn stroke_width(&self, width: u32) -> Self { 71 | Self { 72 | color: self.color.to_rgba(), 73 | filled: self.filled, 74 | stroke_width: width, 75 | } 76 | } 77 | } 78 | 79 | impl From for ShapeStyle { 80 | fn from(f: T) -> Self { 81 | ShapeStyle { 82 | color: f.to_rgba(), 83 | filled: false, 84 | stroke_width: 1, 85 | } 86 | } 87 | } 88 | 89 | impl BackendStyle for ShapeStyle { 90 | /// Returns the color as interpreted by the backend. 91 | fn color(&self) -> BackendColor { 92 | self.color.to_backend_color() 93 | } 94 | /// Returns the stroke width. 95 | fn stroke_width(&self) -> u32 { 96 | self.stroke_width 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /plotters/src/test.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | 3 | #[cfg(feature = "svg_backend")] 4 | #[test] 5 | fn regression_test_issue_267() { 6 | let p1 = (338, 122); 7 | let p2 = (365, 122); 8 | 9 | let mut backend = SVGBackend::new("blub.png", (800, 600)); 10 | 11 | backend 12 | .draw_line(p1, p2, &RGBColor(0, 0, 0).stroke_width(0)) 13 | .unwrap(); 14 | } 15 | 16 | #[test] 17 | fn from_trait_impl_rgba_color() { 18 | let rgb = RGBColor(1, 2, 3); 19 | let c = RGBAColor::from(rgb); 20 | 21 | assert_eq!(c.rgb(), rgb.rgb()); 22 | } 23 | --------------------------------------------------------------------------------