{"id":13631899,"url":"https://github.com/askanium/rustplotlib","last_synced_at":"2026-04-07T04:32:01.723Z","repository":{"id":44900775,"uuid":"250637403","full_name":"askanium/rustplotlib","owner":"askanium","description":"A pure Rust visualization library inspired by D3.js","archived":false,"fork":false,"pushed_at":"2023-08-14T19:43:50.000Z","size":208,"stargazers_count":293,"open_issues_count":14,"forks_count":24,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-12-14T01:17:20.824Z","etag":null,"topics":["dataviz","rust","rust-library","visualization"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/askanium.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2020-03-27T20:15:54.000Z","updated_at":"2025-11-14T07:01:44.000Z","dependencies_parsed_at":"2024-01-16T00:22:29.275Z","dependency_job_id":"d6c3931e-3ad4-45e6-87a0-e0246d7f22b0","html_url":"https://github.com/askanium/rustplotlib","commit_stats":{"total_commits":84,"total_committers":2,"mean_commits":42.0,"dds":"0.011904761904761862","last_synced_commit":"78a16e6a3cba633f83863e311c1fa3d03703d8ad"},"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/askanium/rustplotlib","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/askanium%2Frustplotlib","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/askanium%2Frustplotlib/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/askanium%2Frustplotlib/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/askanium%2Frustplotlib/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/askanium","download_url":"https://codeload.github.com/askanium/rustplotlib/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/askanium%2Frustplotlib/sbom","scorecard":{"id":212281,"data":{"date":"2025-08-11","repo":{"name":"github.com/askanium/rustplotlib","commit":"78a16e6a3cba633f83863e311c1fa3d03703d8ad"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.md:0","Info: FSF or OSI recognized license: MIT License: LICENSE.md:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}}]},"last_synced_at":"2025-08-17T00:57:57.192Z","repository_id":44900775,"created_at":"2025-08-17T00:57:57.192Z","updated_at":"2025-08-17T00:57:57.192Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31500397,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-07T03:10:19.677Z","status":"ssl_error","status_checked_at":"2026-04-07T03:10:13.982Z","response_time":105,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dataviz","rust","rust-library","visualization"],"created_at":"2024-08-01T22:02:43.216Z","updated_at":"2026-04-07T04:32:01.700Z","avatar_url":"https://github.com/askanium.png","language":"Rust","funding_links":[],"categories":["Rust","Data Science"],"sub_categories":["Visualization"],"readme":"# charts\n\nA pure Rust visualization library inspired by D3.js.\n\nSee [gallery](./gallery) and [examples](./examples) for code and more charts.\n\n![Frequency Of English Letters](./assets/img/gallery/letter-frequency.svg)\n\n![Revenue By Music Format](./assets/img/gallery/revenue-by-music-format.svg)\n\n## Install\n\nYou can add this as a dependency to your project in your `Cargo.toml` file:\n\n```toml\n[dependencies]\ncharts = \"0.3.0\"\n```\n\n## Chart Types\n\nThe library supports the following charts (more to be added soon):\n\n1. Vertical Bar Chart\n2. Vertical Stacked Bar Chart\n3. Horizontal Bar Chart\n4. Horizontal Stacked Bar Chart\n5. Scatter Chart\n6. Line Chart\n7. Area Chart\n7. Histogram (TBD)\n8. Box Plot (TBD)\n9. Other (TBD)\n\nAlso, **composite charts** are supported (see Composite Charts below)\n\n## Building Blocks\n\nHere are the components that allow you to create a chart:\n\n1. **Scales**\n2. **Views**\n3. Axes\n4. Size and Margins\n5. Legend\n \nThe first two are foundational components that offer a certain degree of flexibility in how data is\nrepresented and how it can be combined to achieve the desired outcome when the goal is to visualize\ndata beyond simple forms.\n\nLet's dive into each building block and define its structure and API, or you can go to Examples section\nand see what you can build using `charts`.\n\n### 1. Scales\n\nA scale is an entity that transforms data from one dimension into another. The dimension you are transforming\nfrom is called **domain** and the dimension you are transforming to is called **range**.\nCurrently, `charts` has implemented two types of scales:\n\n1. Linear Scale\n2. Band Scale\n\n#### Linear Scale\n\nA linear scale is an interpolation function that takes in a domain (e.g. [0, 10]) and a range (e.g.\n[0, 50]) and is able to transform a value from the domain into a corresponding value in the range\n(e.g. 5 -\u003e 25).\n\nWhen using a linear scale to create a chart, the **domain** represents the range of values that are\npresent in the dataset. For instance, if you have a simple dataset `vec![123, 48, 232, 99]`, the\ndomain is represented by `[48, 232]` (but it can also be represented by `[40, 240]` or `[0, 1000]`,\nmore on that in a minute).\n\nConversely, the **range** of a scale represents the diapason that is available for rendering.\nFor instance, if you have a chart that is 800px wide and 50px margin on the left and 50px margin\non the right (see Margins below), this means that the available range to render data is `[0, 700]`.\n\nThus, if to combine the **domain** and the **range** concepts, a scale with `domain[0, 10]` and a\n`range[0, 500]` will map all points from 0 to 10 onto a range of 0 to 500 pixels.\n\n#### Band Scale\n\nA band scale takes in a list of distinct **domain** values (e.g. categories, years) and a continuous\n**range** and interpolates the domain onto the range. It is often used in Bar Charts, where you need\nto map a categorical dataset onto a continuous axis of a specific size.\n\nFor instance, a band scale with `domain[\"Apples\", \"Oranges\", \"Pears\"]` and a `range[0, 100]` will\nmap the `\"Apples\"` domain to `0`, `\"Oranges\"` to `33.33` and `\"Pears\"` to `66.66`. The actual \nimplementation has an `inner_padding` value that will leave a gap between the categories, so the real\nmapped values are going to be a bit different.\n\n### 2. Views\n\nSince the same dataset can be represented in different forms, there is a concept of a **View**\nthat denotes a specific representation of the data.\n\nSince a view of the data is a representation of that data along 2 dimensions (in 2D charts), each view\nrequires a scale for its X dimension and a scale for its Y dimension.\n\nHere is the list of views that are available:\n\n1. VerticalBarView\n2. HorizontalBarView\n3. ScatterView\n4. LineSeriesView\n5. AreaSeriesView\n\n### 3. Axes\n\nAn axis is a representation of the range of the domain that is being visualized. This means that\nin order to properly display an axis, it should operate with the same scale as a view does.\n\nAs a user, you will not explicitly create axes, but rather where to draw the axis (top, right, bottom,\nleft) and what scale to use for that axis.\n\n### 4. Size and Margins\n\nWhen creating a chart, you can customize its layout to some degree.\n\nYou can set chart width and height as well as the margins (top, right, bottom, left). Chart margins\nare the padding from the chart borders that define how much space will be left for axes and other\nelements that are not related to a view of the data.\n\nFor instance, a chart with 800px wide and 600px tall and margins of `top: 100, right: 40, bottom: 50,\nleft: 60` will leave an area of 700px wide and 450px tall for actual data representation.\n\n```\n      width - 800\n+-------------------------------------------------------+\n|                        top                            |  h\n|                        100                            |  e\n|     +-------------------------------------------+     |  i\n|     |                                           |     |  g\n|left |                  Actual                   |right|  h\n| 60  |                   Data                    | 40  |  t\n|     |                   View                    |     |  \n|     |                700 x 450                  |     |  6\n|     |                                           |     |  0\n|     |                                           |     |  0\n|     +-------------------------------------------+     |\n|                      bottom - 50                      |\n+-------------------------------------------------------+\n```\n\n### 5. Legend\n\nThe legend is automatically populated with entries present in each view\nattached to a chart. In order to add a legend to a chart, use the\n`add_legend_at(position: AxisPosition)` method on a chart instance (don't\nforget to leave sufficient margin at the bottom for the legend to show up).\n\nThe dataset's `key` values are used as legend entry labels. In case the\ndataset doesn't have any key values (i.e. when the dataset represents a\nsingle type of data), you can specify a custom label on a `View` via the\n`.set_custom_data_label(label: String)` method. Check out the \n[Chart Composition](#chart-composition) section example of scatter plot\nwith two datasets.\n\n## Examples\n\nBelow you can find examples of charts that are currently supported.\n\n### Vertical Bar Chart\n\nHere is the sample code:\n\n```rust\nuse charts::{Chart, VerticalBarView, ScaleBand, ScaleLinear};\n\nfn main() {\n    // Define chart related sizes.\n    let width = 800;\n    let height = 600;\n    let (top, right, bottom, left) = (90, 40, 50, 60);\n\n    // Create a band scale that maps [\"A\", \"B\", \"C\"] categories to values in the [0, availableWidth]\n    // range (the width of the chart without the margins).\n    let x = ScaleBand::new()\n        .set_domain(vec![String::from(\"A\"), String::from(\"B\"), String::from(\"C\")])\n        .set_range(vec![0, width - left - right])\n        .set_inner_padding(0.1)\n        .set_outer_padding(0.1);\n\n    // Create a linear scale that will interpolate values in [0, 100] range to corresponding\n    // values in [availableHeight, 0] range (the height of the chart without the margins).\n    // The [availableHeight, 0] range is inverted because SVGs coordinate system's origin is\n    // in top left corner, while chart's origin is in bottom left corner, hence we need to invert\n    // the range on Y axis for the chart to display as though its origin is at bottom left.\n    let y = ScaleLinear::new()\n        .set_domain(vec![0, 100])\n        .set_range(vec![height - top - bottom, 0]);\n\n    // You can use your own iterable as data as long as its items implement the `BarDatum` trait.\n    let data = vec![(\"A\", 90), (\"B\", 10), (\"C\", 30)];\n\n    // Create VerticalBar view that is going to represent the data as vertical bars.\n    let view = VerticalBarView::new()\n        .set_x_scale(\u0026x)\n        .set_y_scale(\u0026y)\n        .load_data(\u0026data).unwrap();\n\n    // Generate and save the chart.\n    Chart::new()\n        .set_width(width)\n        .set_height(height)\n        .set_margins(top, right, bottom, left)\n        .add_title(String::from(\"Bar Chart\"))\n        .add_view(\u0026view)\n        .add_axis_bottom(\u0026x)\n        .add_axis_left(\u0026y)\n        .add_left_axis_label(\"Units of Measurement\")\n        .add_bottom_axis_label(\"Categories\")\n        .save(\"vertical-bar-chart.svg\").unwrap();\n}\n```\n\nHere is the result:\n\n![Vertical Bar Chart](./assets/img/vertical-bar-chart.svg)\n\nThe display order of categories is defined by the vector passed to `set_domain()` of a `ScaleBand`.\nIf you'll switch that order to `\"A\", \"C\", \"B\"`, the resulting order will adjust as well.\n\n![Vertical Bar Chart with changed domain order](./assets/img/vertical-bar-chart-domain-order.svg)\n\nYou can play with `set_inner_padding()` and `set_outer_padding()` values to customize the appearance of the chart.\n\n### Vertical Stacked Bar Chart\n\nA simple bar chart (as the one above) is a specific instance of a stacked bar chart where there\nis only one type of values. Under the hood, `charts` treats both types pretty much the same,\nthat's why the code is almost identical, except the input data must provide a `key` by which to group\nand stack values.\n\n```rust\nuse charts::{Chart, VerticalBarView, ScaleBand, ScaleLinear, BarLabelPosition};\n\nfn main() {\n    // Define chart related sizes.\n    let width = 800;\n    let height = 600;\n    let (top, right, bottom, left) = (90, 40, 50, 60);\n\n    // Create a band scale that maps [\"A\", \"B\", \"C\"] categories to values in [0, availableWidth]\n    // range (the width of the chart without the margins).\n    let x = ScaleBand::new()\n        .set_domain(vec![String::from(\"A\"), String::from(\"B\"), String::from(\"C\")])\n        .set_range(vec![0, width - left - right]);\n\n    // Create a linear scale that will interpolate values in [0, 100] range to corresponding\n    // values in [availableHeight, 0] range (the height of the chart without the margins).\n    // The [availableHeight, 0] range is inverted because SVGs coordinate system's origin is\n    // in the top left corner, while chart's origin is in bottom left corner, hence we need to\n    // invert the range on Y axis for the chart to display as though its origin is at bottom left.\n    let y = ScaleLinear::new()\n        .set_domain(vec![0, 100])\n        .set_range(vec![height - top - bottom, 0]);\n\n    // You can use your own iterable as data as long as its items implement the `BarDatum` trait.\n    let data = vec![(\"A\", 70, \"foo\"), (\"B\", 10, \"foo\"), (\"C\", 30, \"foo\"), (\"A\", 20, \"bar\"), (\"A\", 5, \"baz\")];\n\n    // Create VerticalBar view that is going to represent the data as vertical bars.\n    let view = VerticalBarView::new()\n        .set_x_scale(\u0026x)\n        .set_y_scale(\u0026y)\n        // .set_label_visibility(false)  // \u003c-- uncomment this line to hide bar value labels\n        .set_label_position(BarLabelPosition::Center)\n        .load_data(\u0026data).unwrap();\n\n    // Generate and save the chart.\n    Chart::new()\n        .set_width(width)\n        .set_height(height)\n        .set_margins(top, right, bottom, left)\n        .add_title(String::from(\"Stacked Bar Chart\"))\n        .add_view(\u0026view)\n        .add_axis_bottom(\u0026x)\n        .add_axis_left(\u0026y)\n        .add_left_axis_label(\"Units of Measurement\")\n        .add_bottom_axis_label(\"Categories\")\n        .save(\"stacked-vertical-bar-chart.svg\").unwrap();\n}\n```\n\nThe result:\n\n![Vertical Stacked Bar Chart](./assets/img/stacked-vertical-bar-chart.svg)\n\nBy default, the order of the keys in the input data dictates the stacking order. You can, however,\nadjust that by specifying a different order using the `set_keys()` method on the `VerticalBarDataset` \nstruct before calling `load_data()`:\n\n```rust\n// Create VerticalBar view that is going to represent the data as vertical bars.\nlet view = VerticalBarView::new()\n    .set_x_scale(\u0026x)\n    .set_y_scale(\u0026y)\n    .set_keys(vec![String::from(\"foo\"), String::from(\"baz\"), String::from(\"bar\")])\n    // .set_label_visibility(false)  // \u003c-- uncomment this line to hide bar value labels\n    .set_label_position(BarLabelPosition::Center)\n    .load_data(\u0026data).unwrap();\n```\n\nThe result of that is as follows (notice the order in the first bar has changed):\n\n![Vertical Stacked Bar Chart with custom keys order](./assets/img/stacked-vertical-bar-chart-key-order.svg)\n\n\n### Scatter Plot\n\n```rust\nuse charts::{Chart, ScaleLinear, ScatterView, MarkerType, PointLabelPosition};\n\nfn main() {\n    // Define chart related sizes.\n    let width = 800;\n    let height = 600;\n    let (top, right, bottom, left) = (90, 40, 50, 60);\n\n    // Create a band scale that will interpolate values in [0, 200] to values in the\n    // [0, availableWidth] range (the width of the chart without the margins).\n    let x = ScaleLinear::new()\n        .set_domain(vec![0, 200])\n        .set_range(vec![0, width - left - right]);\n\n    // Create a linear scale that will interpolate values in [0, 100] range to corresponding\n    // values in [availableHeight, 0] range (the height of the chart without the margins).\n    // The [availableHeight, 0] range is inverted because SVGs coordinate system's origin is\n    // in top left corner, while chart's origin is in bottom left corner, hence we need to invert\n    // the range on Y axis for the chart to display as though its origin is at bottom left.\n    let y = ScaleLinear::new()\n        .set_domain(vec![0, 100])\n        .set_range(vec![height - top - bottom, 0]);\n\n    // You can use your own iterable as data as long as its items implement the `PointDatum` trait.\n    let scatter_data = vec![(120, 90), (12, 54), (100, 40), (180, 10)];\n\n    // Create Scatter view that is going to represent the data as points.\n    let scatter_view = ScatterView::new()\n        .set_x_scale(\u0026x)\n        .set_y_scale(\u0026y)\n        .set_label_position(PointLabelPosition::E)\n        .set_marker_type(MarkerType::Square)\n        .load_data(\u0026scatter_data).unwrap();\n\n    // Generate and save the chart.\n    Chart::new()\n        .set_width(width)\n        .set_height(height)\n        .set_margins(top, right, bottom, left)\n        .add_title(String::from(\"Scatter Chart\"))\n        .add_view(\u0026scatter_view)\n        .add_axis_bottom(\u0026x)\n        .add_axis_left(\u0026y)\n        .add_left_axis_label(\"Custom X Axis Label\")\n        .add_bottom_axis_label(\"Custom Y Axis Label\")\n        .save(\"scatter-chart.svg\").unwrap();\n}\n```\n\nThe result:\n\n![Scatter plot](./assets/img/scatter-chart.svg)\n\nYou can customize the appearance and layout as follows:\n\nChoose how labels should display relative to the mark. Available options: _N, NE, E, SE, S, SW, W, NW_\n\nChoose the mark type for the points. Available options: _Square, Circle, X_\n\nIf the dataset has a `key` which differentiates the points you can also use a `ScatterView` to display\nit.\n\n```rust\nuse charts::{Chart, ScaleLinear, ScatterView, MarkerType, Color, PointLabelPosition};\n\nfn main() {\n    // Define chart related sizes.\n    let width = 800;\n    let height = 600;\n    let (top, right, bottom, left) = (90, 40, 50, 60);\n\n    // Create a band scale that will interpolate values in [0, 200] to values in the\n    // [0, availableWidth] range (the width of the chart without the margins).\n    let x = ScaleLinear::new()\n        .set_domain(vec![0, 200])\n        .set_range(vec![0, width - left - right]);\n\n    // Create a linear scale that will interpolate values in [0, 100] range to corresponding\n    // values in [availableHeight, 0] range (the height of the chart without the margins).\n    // The [availableHeight, 0] range is inverted because SVGs coordinate system's origin is\n    // in top left corner, while chart's origin is in bottom left corner, hence we need to invert\n    // the range on Y axis for the chart to display as though its origin is at bottom left.\n    let y = ScaleLinear::new()\n        .set_domain(vec![0, 100])\n        .set_range(vec![height - top - bottom, 0]);\n\n    // You can use your own iterable as data as long as its items implement the `PointDatum` trait.\n    let scatter_data = vec![(120, 90, \"foo\"), (12, 54, \"foo\"), (100, 40, \"bar\"), (180, 10, \"baz\")];\n\n    // Create Scatter view that is going to represent the data as points.\n    let scatter_view = ScatterView::new()\n        .set_x_scale(\u0026x)\n        .set_y_scale(\u0026y)\n        .set_label_position(PointLabelPosition::E)\n        .set_marker_type(MarkerType::Circle)\n        .set_colors(Color::color_scheme_dark())\n        .load_data(\u0026scatter_data).unwrap();\n\n    // Generate and save the chart.\n    Chart::new()\n        .set_width(width)\n        .set_height(height)\n        .set_margins(top, right, bottom, left)\n        .add_title(String::from(\"Scatter Chart\"))\n        .add_view(\u0026scatter_view)\n        .add_axis_bottom(\u0026x)\n        .add_axis_left(\u0026y)\n        .add_left_axis_label(\"Custom X Axis Label\")\n        .add_bottom_axis_label(\"Custom Y Axis Label\")\n        .save(\"scatter-chart-multiple-keys.svg\").unwrap();\n}\n```\n\n![Scatter Chart with data with different keys](./assets/img/scatter-chart-multiple-keys.svg)\n\n### Line Series\n\nLine series supports the same functionality os a Scatter plot.\n\n```rust\nuse charts::{Chart, ScaleLinear, MarkerType, PointLabelPosition, LineSeriesView};\n\nfn main() {\n    // Define chart related sizes.\n    let width = 800;\n    let height = 600;\n    let (top, right, bottom, left) = (90, 40, 50, 60);\n\n    // Create a band scale that will interpolate values in [0, 200] to values in the\n    // [0, availableWidth] range (the width of the chart without the margins).\n    let x = ScaleLinear::new()\n        .set_domain(vec![0_f32, 200_f32])\n        .set_range(vec![0, width - left - right]);\n\n    // Create a linear scale that will interpolate values in [0, 100] range to corresponding\n    // values in [availableHeight, 0] range (the height of the chart without the margins).\n    // The [availableHeight, 0] range is inverted because SVGs coordinate system's origin is\n    // in top left corner, while chart's origin is in bottom left corner, hence we need to invert\n    // the range on Y axis for the chart to display as though its origin is at bottom left.\n    let y = ScaleLinear::new()\n        .set_domain(vec![0_f32, 100_f32])\n        .set_range(vec![height - top - bottom, 0]);\n\n    // You can use your own iterable as data as long as its items implement the `PointDatum` trait.\n    let line_data = vec![(12, 54), (100, 40), (120, 50), (180, 70)];\n\n    // Create Line series view that is going to represent the data.\n    let line_view = LineSeriesView::new()\n        .set_x_scale(\u0026x)\n        .set_y_scale(\u0026y)\n        .set_marker_type(MarkerType::Circle)\n        .set_label_position(PointLabelPosition::N)\n        .load_data(\u0026line_data).unwrap();\n\n    // Generate and save the chart.\n    Chart::new()\n        .set_width(width)\n        .set_height(height)\n        .set_margins(top, right, bottom, left)\n        .add_title(String::from(\"Line Chart\"))\n        .add_view(\u0026line_view)\n        .add_axis_bottom(\u0026x)\n        .add_axis_left(\u0026y)\n        .add_left_axis_label(\"Custom Y Axis Label\")\n        .add_bottom_axis_label(\"Custom X Axis Label\")\n        .save(\"line-chart.svg\").unwrap();\n}\n```\n\n![Line Series](./assets/img/line-chart.svg)\n\n### Area Series\n\nCurrently, AreaSeriesView doesn't support datasets with multiple keys, thus\nit cannot display a stacked area chart.\n\n```rust\nuse charts::{Chart, ScaleLinear, MarkerType, PointLabelPosition, AreaSeriesView};\n\nfn main() {\n    // Define chart related sizes.\n    let width = 800;\n    let height = 600;\n    let (top, right, bottom, left) = (90, 40, 50, 60);\n\n    // Create a band scale that will interpolate values in [0, 200] to values in the\n    // [0, availableWidth] range (the width of the chart without the margins).\n    let x = ScaleLinear::new()\n        .set_domain(vec![12_f32, 180_f32])\n        .set_range(vec![0, width - left - right]);\n\n    // Create a linear scale that will interpolate values in [0, 100] range to corresponding\n    // values in [availableHeight, 0] range (the height of the chart without the margins).\n    // The [availableHeight, 0] range is inverted because SVGs coordinate system's origin is\n    // in top left corner, while chart's origin is in bottom left corner, hence we need to invert\n    // the range on Y axis for the chart to display as though its origin is at bottom left.\n    let y = ScaleLinear::new()\n        .set_domain(vec![0_f32, 100_f32])\n        .set_range(vec![height - top - bottom, 0]);\n\n    // You can use your own iterable as data as long as its items implement the `PointDatum` trait.\n    let area_data = vec![(12, 54), (100, 40), (120, 50), (180, 70)];\n\n    // Create Area series view that is going to represent the data.\n    let area_view = AreaSeriesView::new()\n        .set_x_scale(\u0026x)\n        .set_y_scale(\u0026y)\n        .set_marker_type(MarkerType::Circle)\n        .set_label_position(PointLabelPosition::N)\n        .load_data(\u0026area_data).unwrap();\n\n    // Generate and save the chart.\n    Chart::new()\n        .set_width(width)\n        .set_height(height)\n        .set_margins(top, right, bottom, left)\n        .add_title(String::from(\"Area Chart\"))\n        .add_view(\u0026area_view)\n        .add_axis_bottom(\u0026x)\n        .add_axis_left(\u0026y)\n        .add_left_axis_label(\"Custom Y Axis Label\")\n        .add_bottom_axis_label(\"Custom X Axis Label\")\n        .save(\"area-chart.svg\").unwrap();\n}\n```\n\n![Area Series Chart](./assets/img/area-chart.svg)\n\n## Chart Composition\n\nOnce you understand the basic building blocks (mainly *Scales* and *Views*), the sky is the limit \n(and available implemented views, for now (: ) on what you can achieve using `charts`.\n\nThe main idea is to use common scale instances that serve as a common context between different \ndatasets and views.\n\nOne example is combining a bar chart and a scatter plot:\n\n```rust\nuse charts::{Chart, VerticalBarView, ScaleBand, ScaleLinear, ScatterView, MarkerType, Color, PointLabelPosition};\n\nfn main() {\n    // Define chart related sizes.\n    let width = 800;\n    let height = 600;\n    let (top, right, bottom, left) = (90, 40, 50, 60);\n\n    // Create a band scale that maps [\"A\", \"B\", \"C\"] categories to values in the [0, availableWidth]\n    // range (the width of the chart without the margins).\n    let x = ScaleBand::new()\n        .set_domain(vec![String::from(\"A\"), String::from(\"B\"), String::from(\"C\")])\n        .set_range(vec![0, width - left - right]);\n\n    // Create a linear scale that will interpolate values in [0, 100] range to corresponding\n    // values in [availableHeight, 0] range (the height of the chart without the margins).\n    // The [availableHeight, 0] range is inverted because SVGs coordinate system's origin is\n    // in top left corner, while chart's origin is in bottom left corner, hence we need to invert\n    // the range on Y axis for the chart to display as though its origin is at bottom left.\n    let y = ScaleLinear::new()\n        .set_domain(vec![0, 100])\n        .set_range(vec![height - top - bottom, 0]);\n\n    // You can use your own iterable as data as long as its items implement the `BarDatum` trait.\n    let bar_data = vec![(\"A\", 70), (\"B\", 10), (\"C\", 30)];\n\n    // You can use your own iterable as data as long as its items implement the `PointDatum` trait.\n    let scatter_data = vec![(String::from(\"A\"), 90.3), (String::from(\"B\"), 20.1), (String::from(\"C\"), 10.8)];\n\n    // Create VerticalBar view that is going to represent the data as vertical bars.\n    let bar_view = VerticalBarView::new()\n        .set_x_scale(\u0026x)\n        .set_y_scale(\u0026y)\n        .load_data(\u0026bar_data).unwrap();\n\n    // Create Scatter view that is going to represent the data as points.\n    let scatter_view = ScatterView::new()\n        .set_x_scale(\u0026x)\n        .set_y_scale(\u0026y)\n        .set_label_position(PointLabelPosition::NE)\n        .set_marker_type(MarkerType::Circle)\n        .set_colors(Color::from_vec_of_hex_strings(vec![\"#FF4700\"]))\n        .load_data(\u0026scatter_data).unwrap();\n\n    // Generate and save the chart.\n    Chart::new()\n        .set_width(width)\n        .set_height(height)\n        .set_margins(top, right, bottom, left)\n        .add_title(String::from(\"Composite Bar + Scatter Chart\"))\n        .add_view(\u0026bar_view)                            // \u003c-- add bar view\n        .add_view(\u0026scatter_view)                        // \u003c-- add scatter view\n        .add_axis_bottom(\u0026x)\n        .add_axis_left(\u0026y)\n        .add_left_axis_label(\"Units of Measurement\")\n        .add_bottom_axis_label(\"Categories\")\n        .save(\"composite-bar-and-scatter-chart.svg\").unwrap();\n}\n```\n\nThe result looks as follows:\n\n![Composite Bar and Scatter Chart](./assets/img/composite-bar-and-scatter-chart.svg)\n\nAnother example is combining different datasets. For instance, we can define scales that cover\nthe domain from both datasets and see how they relate to each other.\n\n```rust\nuse charts::{Chart, ScaleLinear, ScatterView, MarkerType, PointLabelPosition, Color, AxisPosition};\n\nfn main() {\n    // Define chart related sizes.\n    let width = 800;\n    let height = 600;\n    let (top, right, bottom, left) = (90, 40, 80, 60);\n\n    // Create a band scale that will interpolate values in [0, 200] to values in the\n    // [0, availableWidth] range (the width of the chart without the margins).\n    let x = ScaleLinear::new()\n        .set_domain(vec![0_f32, 200_f32])\n        .set_range(vec![0, width - left - right]);\n\n    // Create a linear scale that will interpolate values in [0, 100] range to corresponding\n    // values in [availableHeight, 0] range (the height of the chart without the margins).\n    // The [availableHeight, 0] range is inverted because SVGs coordinate system's origin is\n    // in top left corner, while chart's origin is in bottom left corner, hence we need to invert\n    // the range on Y axis for the chart to display as though its origin is at bottom left.\n    let y = ScaleLinear::new()\n        .set_domain(vec![0_f32, 100_f32])\n        .set_range(vec![height - top - bottom, 0]);\n\n    // You can use your own iterable as data as long as its items implement the `PointDatum` trait.\n    let scatter_data_1 = vec![(20, 90), (12, 54), (25, 70), (33, 40)];\n    let scatter_data_2 = vec![(120, 10), (143, 34), (170, 14), (190, 13)];\n\n    // Create Scatter view that is going to represent the data as points.\n    let scatter_view_1 = ScatterView::new()\n        .set_x_scale(\u0026x)\n        .set_y_scale(\u0026y)\n        .set_marker_type(MarkerType::Circle)\n        .set_label_position(PointLabelPosition::N)\n        .set_custom_data_label(\"Apples\".to_owned())\n        .load_data(\u0026scatter_data_1).unwrap();\n\n    // Create Scatter view that is going to represent the data as points.\n    let scatter_view_2 = ScatterView::new()\n        .set_x_scale(\u0026x)\n        .set_y_scale(\u0026y)\n        .set_marker_type(MarkerType::Square)\n        .set_label_position(PointLabelPosition::N)\n        .set_custom_data_label(\"Oranges\".to_owned())\n        .set_colors(Color::from_vec_of_hex_strings(vec![\"#aa0000\"]))\n        .load_data(\u0026scatter_data_2).unwrap();\n\n    // Generate and save the chart.\n    Chart::new()\n        .set_width(width)\n        .set_height(height)\n        .set_margins(top, right, bottom, left)\n        .add_title(String::from(\"Scatter Chart\"))\n        .add_view(\u0026scatter_view_1)\n        .add_view(\u0026scatter_view_2)\n        .add_axis_bottom(\u0026x)\n        .add_axis_left(\u0026y)\n        .add_left_axis_label(\"Custom X Axis Label\")\n        .add_bottom_axis_label(\"Custom Y Axis Label\")\n        .add_legend_at(AxisPosition::Bottom)\n        .save(\"scatter-chart-two-datasets.svg\").unwrap();\n}\n```\n\n![Scatter Plot With Two Datasets](./assets/img/scatter-chart-two-datasets.svg)\n\n## Next Steps\n\nThis is still a work in progress, so the next steps are going to be implementing more views and\nimproving on existing functionality.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faskanium%2Frustplotlib","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faskanium%2Frustplotlib","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faskanium%2Frustplotlib/lists"}