Full Code of Joylei/plotters-iced for AI

master 3c5877de843a cached
21 files
66.7 KB
17.6k tokens
164 symbols
1 requests
Download .txt
Repository: Joylei/plotters-iced
Branch: master
Commit: 3c5877de843a
Files: 21
Total size: 66.7 KB

Directory structure:
gitextract_ofu0l0m2/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       └── test.yml
├── .gitignore
├── Cargo.toml
├── LICENSE
├── README.md
├── examples/
│   ├── cpu-monitor.rs
│   ├── large-data.rs
│   ├── mouse_events.rs
│   └── split-chart/
│       ├── Cargo.toml
│       ├── index.html
│       └── src/
│           └── main.rs
└── src/
    ├── backend/
    │   └── mod.rs
    ├── chart.rs
    ├── error.rs
    ├── lib.rs
    ├── renderer.rs
    ├── sample/
    │   └── lttb.rs
    ├── sample.rs
    ├── utils.rs
    └── widget.rs

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/dependabot.yml
================================================
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates

version: 2
updates:
- package-ecosystem: cargo
  directory: "/"
  schedule:
    interval: "daily"
  target-branch: "master"


================================================
FILE: .github/workflows/test.yml
================================================
name: Test and Build

on:
  push:
    branches: [master]
  pull_request:
    branches: [master]

env:
  CARGO_TERM_COLOR: always

jobs:
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macOS-latest]
        rust: [stable]
    steps:
      - uses: actions/checkout@master
      - name: Install Rust toolchain
        uses: actions-rs/toolchain@v1
        with:
          toolchain: ${{ matrix.rust }}
          components: rustfmt
          override: true
      - name: Install dependencies
        run: |
          if [ "$RUNNER_OS" == "Linux" ]; then
              sudo apt-get -qq update
              sudo apt-get install -y libxkbcommon-dev
          fi
        shell: bash
      - name: Verify versions
        run: rustc --version && rustup --version && cargo --version
      - name: Cargo Build
        run: cargo build --verbose
      - name: Build example
        run: cargo build --examples
        continue-on-error: true
      - name: Run tests
        run: cargo test --verbose
      - name: Check code style
        run: cargo fmt -- --check


================================================
FILE: .gitignore
================================================
target/*
Cargo.lock
.cargo/*
examples/split-chart/dist/*
.idea
.DS_store
.history

================================================
FILE: Cargo.toml
================================================
[package]
name = "plotters-iced"
version = "0.11.0"
description = "Iced backend for Plotters"
readme = "README.md"
license = "MIT"
edition = "2021"
resolver = "2"
homepage = "https://github.com/Joylei/plotters-iced"
repository = "https://github.com/Joylei/plotters-iced.git"
documentation = "https://docs.rs/crate/plotters-iced/"
keywords = ["plotters", "chart", "plot", "iced", "backend"]
categories = ["visualization"]
authors = ["Joylei <leingliu@gmail.com>"]

[workspace]
members = [".", "examples/split-chart"]

[dependencies]
plotters = { version = "0.3", default-features = false }
plotters-backend = "0.3"
iced_widget = { version = "0.13", features = ["canvas"] }
iced_graphics = "0.13"
once_cell = "1"

[dev-dependencies]
plotters = { version = "0.3", default-features = false, features = [
    "chrono",
    "area_series",
    "line_series",
    "point_series",
] }
iced = { version = "0.13", features = ["canvas", "tokio"] }
chrono = { version = "0.4", default-features = false }
rand = "0.8"
tokio = { version = "1", features = ["rt"], default-features = false }

[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
sysinfo = { version = "0.30", default-features = false }


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2022, Joylei <leingliu@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# plotters-iced
[![Test and Build](https://github.com/joylei/plotters-iced/workflows/Test%20and%20Build/badge.svg?branch=master)](https://github.com/joylei/plotters-iced/actions?query=workflow%3A%22Test+and+Build%22)
[![Documentation](https://docs.rs/plotters-iced/badge.svg)](https://docs.rs/plotters-iced)
[![Crates.io](https://img.shields.io/crates/v/plotters-iced.svg)](https://crates.io/crates/plotters-iced)
[![License](https://img.shields.io/crates/l/plotters-iced.svg)](https://github.com/joylei/plotters-iced/blob/master/LICENSE)

This is an implementation of an Iced backend for Plotters, for both native and wasm applications.

This backend has been optimized as for speed. Note that some specific plotting features supported in the Bitmap backend may not be implemented there, though.

## Showcase

![CPU Monitor Example](./images/plotter_iced_demo.png)

![WASM Example](./images/split-chart-web.png)

## What is Plotters?

Plotters is an extensible Rust drawing library that can be used to plot data on nice-looking graphs, rendering them through a plotting backend (eg. to a Bitmap image raw buffer, to your GUI backend, to an SVG file, etc.).

**For more details on Plotters, please check the following links:**

- For an introduction of Plotters, see: [Plotters on Crates.io](https://crates.io/crates/plotters);
- Check the main repository on [GitHub](https://github.com/38/plotters);
- You can also visit the Plotters [homepage](https://plotters-rs.github.io/);

## How to install?

Include `plotters-iced` in your `Cargo.toml` dependencies:

```toml
[dependencies]
plotters-iced = "0.11"
iced = { version = "0.13", features = ["canvas", "tokio"] }
plotters="0.3"
```

## How to use?

First, import `Chart` and `ChartWidget`:

```rust,ignore
use plotters_iced::{Chart, ChartWidget, DrawingBackend, ChartBuilder};
```

Then, derive `Chart` trait and build your chart, and let `plotters-iced` takes care the rest:

```rust,ignore
struct MyChart;
impl Chart<Message> for MyChart {
    type State = ();
    fn build_chart<DB:DrawingBackend>(&self, state: &Self::State, builder: ChartBuilder<DB>) {
        //build your chart here, please refer to plotters for more details
    }
}
```

Finally, render your chart view:

```rust,ignore
impl MyChart {
    fn view(&mut self)->Element<Message> {
        ChartWidget::new(self)
            .width(Length::Fixed(200))
            .height(Length::Fixed(200))
            .into()
    }
}
```

_If you are looking for a full example of an implementation, please check [cpu-monitor.rs](./examples/cpu-monitor.rs)._

## How to run the examples?

### Example #1: `cpu-monitor`

This example samples your CPU load every second, and renders it in a real-time chart:

```sh
cargo run --release --example cpu-monitor
```

From this example, you'll learn:

- how to build charts by `plotters-iced`
- how to feed data to charts
- how to make layouts of charts responsive
- how to use fonts with charts

### Example #2: `split-chart`

This example shows you how to split drawing area.

- run the native version

```sh
cargo run --release --example split-chart
```

- run the web version

```sh
cd examples/split-chart
trunk serve
```

## Are there any limitations?

### Limitation #1: No image rendering

No image rendering for native and wasm applications.

### Limitation #2: Limited text rendering for native applications

Only TTF font family are supported for text rendering, which is a limitation of `Iced`, please look at  [cpu-monitor.rs](./examples/cpu-monitor.rs). As well, font transforms are not supported,which is also a limitation of `Iced`.

## Credits

- [plotters-conrod](https://github.com/valeriansaliou/plotters-conrod)

================================================
FILE: examples/cpu-monitor.rs
================================================
// plotters-iced
//
// Iced backend for Plotters
// Copyright: 2022, Joylei <leingliu@gmail.com>
// License: MIT

extern crate iced;
extern crate plotters;
extern crate sysinfo;

use chrono::{DateTime, Utc};
use iced::{
    alignment::{Horizontal, Vertical},
    font,
    widget::{
        canvas::{Cache, Frame, Geometry},
        Column, Container, Row, Scrollable, Space, Text,
    },
    Alignment, Element, Font, Length, Size, Task,
};
use plotters::prelude::ChartBuilder;
use plotters_backend::DrawingBackend;
use plotters_iced::{Chart, ChartWidget, Renderer};
use std::{
    collections::VecDeque,
    time::{Duration, Instant},
};
use sysinfo::{CpuRefreshKind, RefreshKind, System};

const PLOT_SECONDS: usize = 60; //1 min
const TITLE_FONT_SIZE: u16 = 22;
const SAMPLE_EVERY: Duration = Duration::from_millis(1000);

const FONT_BOLD: Font = Font {
    family: font::Family::Name("Noto Sans"),
    weight: font::Weight::Bold,
    ..Font::DEFAULT
};

fn main() {
    iced::application("CPU Monitor Example", State::update, State::view)
        .antialiasing(true)
        .default_font(Font::with_name("Noto Sans"))
        .subscription(|_| {
            const FPS: u64 = 50;
            iced::time::every(Duration::from_millis(1000 / FPS)).map(|_| Message::Tick)
        })
        .run_with(State::new)
        .unwrap();
}

#[derive(Debug)]
enum Message {
    /// message that cause charts' data lazily updated
    Tick,
    FontLoaded(Result<(), font::Error>),
}

struct State {
    chart: SystemChart,
}

impl State {
    fn new() -> (Self, Task<Message>) {
        (
            Self {
                chart: Default::default(),
            },
            Task::batch([
                font::load(include_bytes!("./fonts/notosans-regular.ttf").as_slice())
                    .map(Message::FontLoaded),
                font::load(include_bytes!("./fonts/notosans-bold.ttf").as_slice())
                    .map(Message::FontLoaded),
            ]),
        )
    }

    fn update(&mut self, message: Message) {
        match message {
            Message::Tick => {
                self.chart.update();
            }
            _ => {}
        }
    }

    fn view(&self) -> Element<'_, Message> {
        let content = Column::new()
            .spacing(20)
            .align_x(Alignment::Start)
            .width(Length::Fill)
            .height(Length::Fill)
            .push(
                Text::new("Iced test chart")
                    .size(TITLE_FONT_SIZE)
                    .font(FONT_BOLD),
            )
            .push(self.chart.view());

        Container::new(content)
            //.style(style::Container)
            .padding(5)
            .center_x(Length::Fill)
            .center_y(Length::Fill)
            .into()
    }
}

struct SystemChart {
    sys: System,
    last_sample_time: Instant,
    items_per_row: usize,
    processors: Vec<CpuUsageChart>,
    chart_height: f32,
}

impl Default for SystemChart {
    fn default() -> Self {
        Self {
            sys: System::new_with_specifics(
                RefreshKind::new().with_cpu(CpuRefreshKind::new().with_cpu_usage()),
            ),
            last_sample_time: Instant::now(),
            items_per_row: 3,
            processors: Default::default(),
            chart_height: 300.0,
        }
    }
}

impl SystemChart {
    #[inline]
    fn is_initialized(&self) -> bool {
        !self.processors.is_empty()
    }

    #[inline]
    fn should_update(&self) -> bool {
        !self.is_initialized() || self.last_sample_time.elapsed() > SAMPLE_EVERY
    }

    fn update(&mut self) {
        if !self.should_update() {
            return;
        }
        //eprintln!("refresh...");

        self.sys.refresh_cpu();
        self.last_sample_time = Instant::now();
        let now = Utc::now();
        let data = self.sys.cpus().iter().map(|v| v.cpu_usage() as i32);

        //check if initialized
        if !self.is_initialized() {
            eprintln!("init...");
            let mut processors: Vec<_> = data
                .map(|percent| CpuUsageChart::new(vec![(now, percent)].into_iter()))
                .collect();
            self.processors.append(&mut processors);
        } else {
            //eprintln!("update...");
            for (percent, p) in data.zip(self.processors.iter_mut()) {
                p.push_data(now, percent);
            }
        }
    }

    fn view(&self) -> Element<Message> {
        if !self.is_initialized() {
            Text::new("Loading...")
                .align_x(Horizontal::Center)
                .align_y(Vertical::Center)
                .into()
        } else {
            let mut col = Column::new()
                .width(Length::Fill)
                .height(Length::Shrink)
                .align_x(Alignment::Center);

            let chart_height = self.chart_height;
            let mut idx = 0;
            for chunk in self.processors.chunks(self.items_per_row) {
                let mut row = Row::new()
                    .spacing(15)
                    .padding(20)
                    .width(Length::Fill)
                    .height(Length::Shrink)
                    .align_y(Alignment::Center);
                for item in chunk {
                    row = row.push(item.view(idx, chart_height));
                    idx += 1;
                }
                while idx % self.items_per_row != 0 {
                    row = row.push(Space::new(Length::Fill, Length::Fixed(50.0)));
                    idx += 1;
                }
                col = col.push(row);
            }

            Scrollable::new(col).height(Length::Shrink).into()
        }
    }
}

struct CpuUsageChart {
    cache: Cache,
    data_points: VecDeque<(DateTime<Utc>, i32)>,
    limit: Duration,
}

impl CpuUsageChart {
    fn new(data: impl Iterator<Item = (DateTime<Utc>, i32)>) -> Self {
        let data_points: VecDeque<_> = data.collect();
        Self {
            cache: Cache::new(),
            data_points,
            limit: Duration::from_secs(PLOT_SECONDS as u64),
        }
    }

    fn push_data(&mut self, time: DateTime<Utc>, value: i32) {
        let cur_ms = time.timestamp_millis();
        self.data_points.push_front((time, value));
        loop {
            if let Some((time, _)) = self.data_points.back() {
                let diff = Duration::from_millis((cur_ms - time.timestamp_millis()) as u64);
                if diff > self.limit {
                    self.data_points.pop_back();
                    continue;
                }
            }
            break;
        }
        self.cache.clear();
    }

    fn view(&self, idx: usize, chart_height: f32) -> Element<Message> {
        Column::new()
            .width(Length::Fill)
            .height(Length::Shrink)
            .spacing(5)
            .align_x(Alignment::Center)
            .push(Text::new(format!("Processor {}", idx)))
            .push(ChartWidget::new(self).height(Length::Fixed(chart_height)))
            .into()
    }
}

impl Chart<Message> for CpuUsageChart {
    type State = ();
    // fn update(
    //     &mut self,
    //     event: Event,
    //     bounds: Rectangle,
    //     cursor: Cursor,
    // ) -> (event::Status, Option<Message>) {
    //     self.cache.clear();
    //     (event::Status::Ignored, None)
    // }

    #[inline]
    fn draw<R: Renderer, F: Fn(&mut Frame)>(
        &self,
        renderer: &R,
        bounds: Size,
        draw_fn: F,
    ) -> Geometry {
        renderer.draw_cache(&self.cache, bounds, draw_fn)
    }

    fn build_chart<DB: DrawingBackend>(&self, _state: &Self::State, mut chart: ChartBuilder<DB>) {
        use plotters::prelude::*;

        const PLOT_LINE_COLOR: RGBColor = RGBColor(0, 175, 255);

        // Acquire time range
        let newest_time = self
            .data_points
            .front()
            .unwrap_or(&(DateTime::from_timestamp(0, 0).unwrap(), 0))
            .0;
        let oldest_time = newest_time - chrono::Duration::seconds(PLOT_SECONDS as i64);
        let mut chart = chart
            .x_label_area_size(0)
            .y_label_area_size(28)
            .margin(20)
            .build_cartesian_2d(oldest_time..newest_time, 0..100)
            .expect("failed to build chart");

        chart
            .configure_mesh()
            .bold_line_style(plotters::style::colors::BLUE.mix(0.1))
            .light_line_style(plotters::style::colors::BLUE.mix(0.05))
            .axis_style(ShapeStyle::from(plotters::style::colors::BLUE.mix(0.45)).stroke_width(1))
            .y_labels(10)
            .y_label_style(
                ("sans-serif", 15)
                    .into_font()
                    .color(&plotters::style::colors::BLUE.mix(0.65))
                    .transform(FontTransform::Rotate90),
            )
            .y_label_formatter(&|y: &i32| format!("{}%", y))
            .draw()
            .expect("failed to draw chart mesh");

        chart
            .draw_series(
                AreaSeries::new(
                    self.data_points.iter().map(|x| (x.0, x.1)),
                    0,
                    PLOT_LINE_COLOR.mix(0.175),
                )
                .border_style(ShapeStyle::from(PLOT_LINE_COLOR).stroke_width(2)),
            )
            .expect("failed to draw chart data");
    }
}


================================================
FILE: examples/large-data.rs
================================================
// plotters-iced
//
// Iced backend for Plotters
// Copyright: 2022, Joylei <leingliu@gmail.com>
// License: MIT

extern crate iced;
extern crate plotters;
extern crate rand;
extern crate tokio;

use chrono::{DateTime, Utc};
use iced::{
    font,
    widget::{
        canvas::{Cache, Frame, Geometry},
        Column, Container, Text,
    },
    Alignment, Element, Font, Length, Size, Task,
};
use plotters::prelude::ChartBuilder;
use plotters_backend::DrawingBackend;
use plotters_iced::{
    sample::lttb::{DataPoint, LttbSource},
    Chart, ChartWidget, Renderer,
};
use rand::Rng;
use std::time::Duration;
use std::{collections::VecDeque, time::Instant};

const TITLE_FONT_SIZE: u16 = 22;

const FONT_BOLD: Font = Font {
    family: font::Family::Name("Noto Sans"),
    weight: font::Weight::Bold,
    ..Font::DEFAULT
};

fn main() {
    iced::application("Large Data Example", State::update, State::view)
        .antialiasing(true)
        .default_font(Font::with_name("Noto Sans"))
        .run_with(State::new)
        .unwrap();
}

struct Wrapper<'a>(&'a DateTime<Utc>, &'a f32);

impl DataPoint for Wrapper<'_> {
    #[inline]
    fn x(&self) -> f64 {
        self.0.timestamp() as f64
    }
    #[inline]
    fn y(&self) -> f64 {
        *self.1 as f64
    }
}

#[derive(Debug)]
enum Message {
    FontLoaded(Result<(), font::Error>),
    DataLoaded(Vec<(DateTime<Utc>, f32)>),
    Sampled(Vec<(DateTime<Utc>, f32)>),
}

struct State {
    chart: Option<ExampleChart>,
}

impl State {
    fn new() -> (Self, Task<Message>) {
        (
            Self { chart: None },
            Task::batch([
                font::load(include_bytes!("./fonts/notosans-regular.ttf").as_slice())
                    .map(Message::FontLoaded),
                font::load(include_bytes!("./fonts/notosans-bold.ttf").as_slice())
                    .map(Message::FontLoaded),
                Task::perform(tokio::task::spawn_blocking(generate_data), |data| {
                    Message::DataLoaded(data.unwrap())
                }),
            ]),
        )
    }

    fn update(&mut self, message: Message) -> Task<Message> {
        match message {
            Message::DataLoaded(data) => Task::perform(
                tokio::task::spawn_blocking(move || {
                    let now = Instant::now();
                    let sampled: Vec<_> = (&data[..])
                        .cast(|v| Wrapper(&v.0, &v.1))
                        .lttb(1000)
                        .map(|w| (*w.0, *w.1))
                        .collect();
                    dbg!(now.elapsed().as_millis());
                    sampled
                }),
                |data| Message::Sampled(data.unwrap()),
            ),
            Message::Sampled(sampled) => {
                self.chart = Some(ExampleChart::new(sampled.into_iter()));
                Task::none()
            }
            _ => Task::none(),
        }
    }

    fn view(&self) -> Element<'_, Message> {
        let content = Column::new()
            .spacing(20)
            .align_x(Alignment::Start)
            .width(Length::Fill)
            .height(Length::Fill)
            .push(
                Text::new("Iced test chart")
                    .size(TITLE_FONT_SIZE)
                    .font(FONT_BOLD),
            )
            .push(match self.chart {
                Some(ref chart) => chart.view(),
                None => Text::new("Loading...").into(),
            });

        Container::new(content)
            .padding(5)
            .center_x(Length::Fill)
            .center_y(Length::Fill)
            .into()
    }
}

struct ExampleChart {
    cache: Cache,
    data_points: VecDeque<(DateTime<Utc>, f32)>,
}

impl ExampleChart {
    fn new(data: impl Iterator<Item = (DateTime<Utc>, f32)>) -> Self {
        let data_points: VecDeque<_> = data.collect();
        Self {
            cache: Cache::new(),
            data_points,
        }
    }

    fn view(&self) -> Element<Message> {
        let chart = ChartWidget::new(self)
            .width(Length::Fill)
            .height(Length::Fill);

        chart.into()
    }
}

impl Chart<Message> for ExampleChart {
    type State = ();
    // fn update(
    //     &mut self,
    //     event: Event,
    //     bounds: Rectangle,
    //     cursor: Cursor,
    // ) -> (event::Status, Option<Message>) {
    //     self.cache.clear();
    //     (event::Status::Ignored, None)
    // }

    #[inline]
    fn draw<R: Renderer, F: Fn(&mut Frame)>(
        &self,
        renderer: &R,
        bounds: Size,
        draw_fn: F,
    ) -> Geometry {
        renderer.draw_cache(&self.cache, bounds, draw_fn)
    }

    fn build_chart<DB: DrawingBackend>(&self, _state: &Self::State, mut chart: ChartBuilder<DB>) {
        use plotters::prelude::*;

        const PLOT_LINE_COLOR: RGBColor = RGBColor(0, 175, 255);

        // Acquire time range
        let newest_time = self
            .data_points
            .back()
            .unwrap()
            .0
            .checked_add_signed(chrono::Duration::from_std(Duration::from_secs(10)).unwrap())
            .unwrap();
        //let oldest_time = newest_time - chrono::Duration::seconds(PLOT_SECONDS as i64);
        let oldest_time = self
            .data_points
            .front()
            .unwrap()
            .0
            .checked_sub_signed(chrono::Duration::from_std(Duration::from_secs(10)).unwrap())
            .unwrap();
        //dbg!(&newest_time);
        //dbg!(&oldest_time);
        let mut chart = chart
            .x_label_area_size(0)
            .y_label_area_size(28)
            .margin(20)
            .build_cartesian_2d(oldest_time..newest_time, -10.0_f32..110.0_f32)
            .expect("failed to build chart");

        chart
            .configure_mesh()
            .bold_line_style(plotters::style::colors::BLUE.mix(0.1))
            .light_line_style(plotters::style::colors::BLUE.mix(0.05))
            .axis_style(ShapeStyle::from(plotters::style::colors::BLUE.mix(0.45)).stroke_width(1))
            .y_labels(10)
            .y_label_style(
                ("Noto Sans", 15)
                    .into_font()
                    .color(&plotters::style::colors::BLUE.mix(0.65))
                    .transform(FontTransform::Rotate90),
            )
            .y_label_formatter(&|y| format!("{}", y))
            .draw()
            .expect("failed to draw chart mesh");

        chart
            .draw_series(
                AreaSeries::new(
                    self.data_points.iter().cloned(),
                    0_f32,
                    PLOT_LINE_COLOR.mix(0.175),
                )
                .border_style(ShapeStyle::from(PLOT_LINE_COLOR).stroke_width(2)),
            )
            .expect("failed to draw chart data");
    }
}

fn generate_data() -> Vec<(DateTime<Utc>, f32)> {
    let total = 10_000_000;
    let mut data = Vec::new();
    let mut rng = rand::thread_rng();
    let time_range = (24 * 3600 * 30) as f32;
    let interval = (3600 * 12) as f32;
    let start = Utc::now()
        .checked_sub_signed(
            chrono::Duration::from_std(Duration::from_secs_f32(time_range)).unwrap(),
        )
        .unwrap();
    while data.len() < total {
        let secs = rng.gen_range(0.1..time_range);
        let time = start
            .checked_sub_signed(chrono::Duration::from_std(Duration::from_secs_f32(secs)).unwrap())
            .unwrap();

        let value =
            (((secs % interval) - interval / 2.0) / (interval / 2.0) * std::f32::consts::PI).sin()
                * 50_f32
                + 50_f32;
        data.push((time, value));
    }
    data.sort_by_cached_key(|x| x.0);
    //dbg!(&data[..100]);
    data
}


================================================
FILE: examples/mouse_events.rs
================================================
// plotters-iced
//
// Iced backend for Plotters
// Copyright: 2022, Grey <grey@openrobotics.org>
// License: MIT

use iced::{
    event,
    mouse::Cursor,
    widget::{
        canvas::{self, Cache, Frame, Geometry},
        Column, Container, Text,
    },
    Alignment, Element, Length, Point, Size,
};
use plotters::{
    coord::{types::RangedCoordf32, ReverseCoordTranslate},
    prelude::*,
};
use plotters_iced::{Chart, ChartWidget, Renderer};
use std::cell::RefCell;

#[derive(Default)]
struct State {
    chart: ArtChart,
}

impl State {
    fn update(&mut self, message: Message) {
        match message {
            Message::MouseEvent(event, point) => {
                self.chart.set_current_position(point);
                match event {
                    iced::mouse::Event::ButtonPressed(iced::mouse::Button::Left) => {
                        self.chart.set_down(true);
                    }
                    iced::mouse::Event::ButtonReleased(iced::mouse::Button::Left) => {
                        self.chart.set_down(false);
                    }
                    _ => {
                        // Do nothing
                    }
                }
            }
        }
    }

    fn view(&self) -> Element<Message> {
        let content = Column::new()
            .spacing(20)
            .width(Length::Fill)
            .height(Length::Fill)
            .push(Text::new("Click below!").size(20))
            .push(self.chart.view())
            .align_x(Alignment::Center)
            .padding(15);

        Container::new(content)
            .padding(5)
            .center_x(Length::Fill)
            .center_y(Length::Fill)
            .into()
    }
}

#[derive(Default)]
struct ArtChart {
    cache: Cache,
    points: Vec<(f32, f32)>,
    lines: Vec<((f32, f32), (f32, f32))>,
    is_down: bool,
    current_position: Option<(f32, f32)>,
    initial_down_position: Option<(f32, f32)>,
    spec: RefCell<Option<Cartesian2d<RangedCoordf32, RangedCoordf32>>>,
}

impl ArtChart {
    fn view(&self) -> Element<Message> {
        let chart = ChartWidget::new(self)
            .width(Length::Fill)
            .height(Length::Fill);

        chart.into()
    }

    fn set_current_position(&mut self, p: Point) {
        if let Some(spec) = self.spec.borrow().as_ref() {
            self.current_position = spec.reverse_translate((p.x as i32, p.y as i32));
            self.cache.clear();
        }
    }

    fn nearby(p0: (f32, f32), p1: (f32, f32)) -> bool {
        let delta = (p1.0 - p0.0, p1.1 - p0.1);
        (delta.0 * delta.0 + delta.1 * delta.1).sqrt() <= 1.0
    }

    fn set_down(&mut self, new_is_down: bool) {
        if !self.is_down && new_is_down {
            self.initial_down_position = self.current_position;
        }

        if self.is_down && !new_is_down {
            if let Some((initial_p, current_p)) =
                self.initial_down_position.zip(self.current_position)
            {
                if Self::nearby(initial_p, current_p) {
                    self.points.push(current_p);
                } else {
                    self.lines.push((initial_p, current_p));
                }
            }
        }

        self.is_down = new_is_down;
    }
}

impl Chart<Message> for ArtChart {
    type State = ();
    fn draw<R: Renderer, F: Fn(&mut Frame)>(
        &self,
        renderer: &R,
        bounds: Size,
        draw_fn: F,
    ) -> Geometry {
        renderer.draw_cache(&self.cache, bounds, draw_fn)
    }

    fn build_chart<DB: DrawingBackend>(&self, _state: &Self::State, mut builder: ChartBuilder<DB>) {
        use plotters::style::colors;

        const POINT_COLOR: RGBColor = colors::RED;
        const LINE_COLOR: RGBColor = colors::BLUE;
        const HOVER_COLOR: RGBColor = colors::YELLOW;
        const PREVIEW_COLOR: RGBColor = colors::GREEN;

        let mut chart = builder
            .x_label_area_size(28_i32)
            .y_label_area_size(28_i32)
            .margin(20_i32)
            .build_cartesian_2d(0_f32..100_f32, 0_f32..100_f32)
            .expect("Failed to build chart");

        chart
            .configure_mesh()
            .bold_line_style(colors::BLACK.mix(0.1))
            .light_line_style(colors::BLACK.mix(0.05))
            .axis_style(ShapeStyle::from(colors::BLACK.mix(0.45)).stroke_width(1))
            .y_labels(10)
            .y_label_style(
                ("sans-serif", 15)
                    .into_font()
                    .color(&colors::BLACK.mix(0.65))
                    .transform(FontTransform::Rotate90),
            )
            .y_label_formatter(&|y| format!("{}", y))
            .draw()
            .expect("Failed to draw chart mesh");

        chart
            .draw_series(
                self.points
                    .iter()
                    .map(|p| Circle::new(*p, 5_i32, POINT_COLOR.filled())),
            )
            .expect("Failed to draw points");

        for line in &self.lines {
            chart
                .draw_series(LineSeries::new(
                    vec![line.0, line.1].into_iter(),
                    LINE_COLOR.filled(),
                ))
                .expect("Failed to draw line");
        }

        if self.is_down {
            if let Some((initial_p, current_p)) =
                self.initial_down_position.zip(self.current_position)
            {
                if Self::nearby(initial_p, current_p) {
                    chart
                        .draw_series(std::iter::once(Circle::new(
                            current_p,
                            5_i32,
                            PREVIEW_COLOR.filled(),
                        )))
                        .expect("Failed to draw preview point");
                } else {
                    chart
                        .draw_series(LineSeries::new(
                            vec![initial_p, current_p].into_iter(),
                            PREVIEW_COLOR.filled(),
                        ))
                        .expect("Failed to draw preview line");
                }
            }
        } else if let Some(current_p) = self.current_position {
            chart
                .draw_series(std::iter::once(Circle::new(
                    current_p,
                    5_i32,
                    HOVER_COLOR.filled(),
                )))
                .expect("Failed to draw hover point");
        }

        *self.spec.borrow_mut() = Some(chart.as_coord_spec().clone());
    }

    fn update(
        &self,
        _state: &mut Self::State,
        event: canvas::Event,
        bounds: iced::Rectangle,
        cursor: Cursor,
    ) -> (event::Status, Option<Message>) {
        if let Cursor::Available(point) = cursor {
            match event {
                canvas::Event::Mouse(evt) if bounds.contains(point) => {
                    let p_origin = bounds.position();
                    let p = point - p_origin;
                    return (
                        event::Status::Captured,
                        Some(Message::MouseEvent(evt, Point::new(p.x, p.y))),
                    );
                }
                _ => {}
            }
        }
        (event::Status::Ignored, None)
    }
}

#[derive(Debug)]
enum Message {
    MouseEvent(iced::mouse::Event, iced::Point),
}

fn main() -> iced::Result {
    iced::application("Art", State::update, State::view)
        .antialiasing(true)
        .run()
}


================================================
FILE: examples/split-chart/Cargo.toml
================================================
[package]
name = "split-chart"
version = "0.1.0"
authors = ["Joylei <leingliu@gmail.com>"]
edition = "2021"
publish = false

[dependencies]
iced = { version = "0.13", features = ["canvas"] }
plotters-iced = { path = "../../" }
plotters = { version = "0.3", default-features = false, features = [
    "chrono",
    "area_series",
    "line_series",
    "point_series",
] }

[target.'cfg(target_arch = "wasm32")'.dependencies]
iced.version = "0.13"
iced.features = ["canvas", "debug", "webgl"]

console_error_panic_hook = "0.1"
console_log = "1.0"


================================================
FILE: examples/split-chart/index.html
================================================
<!DOCTYPE html>
<html>

<head>
    <meta content="text/html; charset=utf-8" charset="utf-8" http-equiv="Content-Type">
    <meta name="description" content="">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>split chart example</title>
    <base data-trunk-public-url />
</head>

<body>
    <link data-trunk rel="rust" href="Cargo.toml" data-wasm-opt="z" data-bin="split-chart" />
    <!-- see split-chart.rs for details -->
</body>

</html>

================================================
FILE: examples/split-chart/src/main.rs
================================================
// plotters-iced
//
// Iced backend for Plotters
// Copyright: 2022, Joylei <leingliu@gmail.com>
// License: MIT

/*!
- run the native version

```sh
cargo run --release --example split-chart
```

- run the web version with [trunk](https://trunkrs.dev/)

```sh
cd examples
trunk serve
```

*/

extern crate iced;
extern crate plotters;

use iced::{
    widget::{Column, Container, Text},
    window, Alignment, Element, Length,
};
use plotters::{coord::Shift, prelude::*};
use plotters_backend::DrawingBackend;
use plotters_iced::{plotters_backend, Chart, ChartWidget, DrawingArea};

const TITLE_FONT_SIZE: u16 = 22;

// antialiasing issue: https://github.com/iced-rs/iced/issues/1159

fn main() {
    #[cfg(target_arch = "wasm32")]
    {
        console_log::init().expect("Initialize logger");
        std::panic::set_hook(Box::new(console_error_panic_hook::hook));
    }
    let app = iced::application("Split Chart Example", State::update, State::view)
        .antialiasing(cfg!(not(target_arch = "wasm32")))
        .subscription(|_| window::frames().map(|_| Message::Tick));
    app.run().unwrap();
}

#[allow(unused)]
#[derive(Debug)]
enum Message {
    Tick,
}

#[derive(Default)]
struct State {
    chart: MyChart,
}

impl State {
    fn update(&mut self, _message: Message) {}

    fn view(&self) -> Element<'_, Message> {
        let content = Column::new()
            .spacing(20)
            .align_x(Alignment::Start)
            .width(Length::Fill)
            .height(Length::Fill)
            .push(Text::new("Iced test chart").size(TITLE_FONT_SIZE))
            .push(self.chart.view());

        Container::new(content)
            .padding(5)
            .center_x(Length::Fill)
            .center_y(Length::Fill)
            .into()
    }
}

#[allow(unused)]
#[derive(Default)]
struct MyChart;

impl MyChart {
    fn view(&self) -> Element<Message> {
        let chart = ChartWidget::new(self)
            .width(Length::Fill)
            .height(Length::Fill);

        chart.into()
    }
}

impl Chart<Message> for MyChart {
    type State = ();
    // leave it empty
    fn build_chart<DB: DrawingBackend>(&self, _state: &Self::State, _builder: ChartBuilder<DB>) {}

    fn draw_chart<DB: DrawingBackend>(&self, _state: &Self::State, root: DrawingArea<DB, Shift>) {
        let children = root.split_evenly((2, 2));
        for (i, area) in children.iter().enumerate() {
            let builder = ChartBuilder::on(area);
            draw_chart(builder, i + 1);
        }
    }
}

fn draw_chart<DB: DrawingBackend>(mut chart: ChartBuilder<DB>, power: usize) {
    let mut chart = chart
        .margin(30)
        .caption(format!("y=x^{}", power), ("sans-serif", 22))
        .x_label_area_size(30)
        .y_label_area_size(30)
        .build_cartesian_2d(-1f32..1f32, -1.2f32..1.2f32)
        .unwrap();

    chart
        .configure_mesh()
        .x_labels(3)
        .y_labels(3)
        // .y_label_style(
        //     ("sans-serif", 15)
        //         .into_font()
        //         .color(&plotters::style::colors::BLACK.mix(0.8))
        //         .transform(FontTransform::RotateAngle(30.0)),
        // )
        .draw()
        .unwrap();

    chart
        .draw_series(LineSeries::new(
            (-50..=50)
                .map(|x| x as f32 / 50.0)
                .map(|x| (x, x.powf(power as f32))),
            &RED,
        ))
        .unwrap();
}


================================================
FILE: src/backend/mod.rs
================================================
// plotters-iced
//
// Iced backend for Plotters
// Copyright: 2022, Joylei <leingliu@gmail.com>
// License: MIT

use std::collections::HashSet;

use iced_graphics::core::text::Paragraph;
use iced_widget::{
    canvas,
    core::{
        alignment::{Horizontal, Vertical},
        font, text, Font, Size,
    },
    text::Shaping,
};
use once_cell::unsync::Lazy;
use plotters_backend::{
    text_anchor,
    //FontTransform,
    BackendColor,
    BackendCoord,
    BackendStyle,
    BackendTextStyle,
    DrawingBackend,
    DrawingErrorKind,
    FontFamily,
    FontStyle,
};

use crate::error::Error;
use crate::utils::{cvt_color, cvt_stroke, CvtPoint};

/// The Iced drawing backend
pub(crate) struct IcedChartBackend<'a, B> {
    frame: &'a mut canvas::Frame,
    backend: &'a B,
    shaping: Shaping,
}

impl<'a, B> IcedChartBackend<'a, B>
where
    B: text::Renderer<Font = Font>,
{
    pub fn new(frame: &'a mut canvas::Frame, backend: &'a B, shaping: Shaping) -> Self {
        Self {
            frame,
            backend,
            shaping,
        }
    }
}

impl<'a, B> DrawingBackend for IcedChartBackend<'a, B>
where
    B: text::Renderer<Font = Font>,
{
    type ErrorType = Error;

    fn get_size(&self) -> (u32, u32) {
        let Size { width, height } = self.frame.size();
        (width as u32, height as u32)
    }

    fn ensure_prepared(&mut self) -> Result<(), DrawingErrorKind<Error>> {
        Ok(())
    }

    fn present(&mut self) -> Result<(), DrawingErrorKind<Error>> {
        Ok(())
    }

    #[inline]
    fn draw_pixel(
        &mut self,
        point: BackendCoord,
        color: BackendColor,
    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
        if color.alpha == 0.0 {
            return Ok(());
        }
        self.frame
            .fill_rectangle(point.cvt_point(), Size::new(1.0, 1.0), cvt_color(&color));
        Ok(())
    }

    #[inline]
    fn draw_line<S: BackendStyle>(
        &mut self,
        from: BackendCoord,
        to: BackendCoord,
        style: &S,
    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
        if style.color().alpha == 0.0 {
            return Ok(());
        }
        let line = canvas::Path::line(from.cvt_point(), to.cvt_point());
        self.frame.stroke(&line, cvt_stroke(style));
        Ok(())
    }

    #[inline]
    fn draw_rect<S: BackendStyle>(
        &mut self,
        upper_left: BackendCoord,
        bottom_right: BackendCoord,
        style: &S,
        fill: bool,
    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
        if style.color().alpha == 0.0 {
            return Ok(());
        }
        let height = (bottom_right.1 - upper_left.1) as f32;
        let width = (bottom_right.0 - upper_left.0) as f32;
        let upper_left = upper_left.cvt_point();
        if fill {
            self.frame.fill_rectangle(
                upper_left,
                Size::new(width, height),
                cvt_color(&style.color()),
            );
        } else {
            let rect = canvas::Path::rectangle(upper_left, Size::new(width, height));
            self.frame.stroke(&rect, cvt_stroke(style));
        }

        Ok(())
    }

    #[inline]
    fn draw_path<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
        &mut self,
        path: I,
        style: &S,
    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
        if style.color().alpha == 0.0 {
            return Ok(());
        }
        let path = canvas::Path::new(move |builder| {
            for (i, point) in path.into_iter().enumerate() {
                if i > 0 {
                    builder.line_to(point.cvt_point());
                } else {
                    builder.move_to(point.cvt_point());
                }
            }
        });

        self.frame.stroke(&path, cvt_stroke(style));
        Ok(())
    }

    #[inline]
    fn draw_circle<S: BackendStyle>(
        &mut self,
        center: BackendCoord,
        radius: u32,
        style: &S,
        fill: bool,
    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
        if style.color().alpha == 0.0 {
            return Ok(());
        }

        let circle = canvas::Path::circle(center.cvt_point(), radius as f32);

        if fill {
            self.frame.fill(&circle, cvt_color(&style.color()));
        } else {
            self.frame.stroke(&circle, cvt_stroke(style));
        }

        Ok(())
    }

    #[inline]
    fn fill_polygon<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
        &mut self,
        vert: I,
        style: &S,
    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
        if style.color().alpha == 0.0 {
            return Ok(());
        }
        let path = canvas::Path::new(move |builder| {
            for (i, point) in vert.into_iter().enumerate() {
                if i > 0 {
                    builder.line_to(point.cvt_point());
                } else {
                    builder.move_to(point.cvt_point());
                }
            }
            builder.close();
        });
        self.frame.fill(&path, cvt_color(&style.color()));
        Ok(())
    }

    #[inline]
    fn draw_text<S: BackendTextStyle>(
        &mut self,
        text: &str,
        style: &S,
        pos: BackendCoord,
    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
        if style.color().alpha == 0.0 {
            return Ok(());
        }
        let horizontal_alignment = match style.anchor().h_pos {
            text_anchor::HPos::Left => Horizontal::Left,
            text_anchor::HPos::Right => Horizontal::Right,
            text_anchor::HPos::Center => Horizontal::Center,
        };
        let vertical_alignment = match style.anchor().v_pos {
            text_anchor::VPos::Top => Vertical::Top,
            text_anchor::VPos::Center => Vertical::Center,
            text_anchor::VPos::Bottom => Vertical::Bottom,
        };
        let font = style_to_font(style);
        let pos = pos.cvt_point();

        //let (w, h) = self.estimate_text_size(text, style)?;
        let text = canvas::Text {
            content: text.to_owned(),
            position: pos,
            color: cvt_color(&style.color()),
            size: (style.size() as f32).into(),
            line_height: Default::default(),
            font,
            horizontal_alignment,
            vertical_alignment,
            shaping: self.shaping,
        };
        //TODO: fix rotation until text rotation is supported by Iced
        // let rotate = match style.transform() {
        //     FontTransform::None => None,
        //     FontTransform::Rotate90 => Some(90.0),
        //     FontTransform::Rotate180 => Some(180.0),
        //     FontTransform::Rotate270 => Some(270.0),
        //     FontTransform::RotateAngle(angle) => Some(angle),
        // };
        // if let Some(rotate) = rotate {
        //     dbg!(rotate);
        //     self.frame.with_save(move |frame| {
        //         frame.fill_text(text);
        //         frame.translate(Vector::new(pos.x + w as f32 / 2.0, pos.y + h as f32 / 2.0));
        //         let angle = 2.0 * std::f32::consts::PI * rotate / 360.0;
        //         frame.rotate(angle);
        //     });
        // } else {
        //     self.frame.fill_text(text);
        // }
        self.frame.fill_text(text);

        Ok(())
    }

    #[inline]
    fn estimate_text_size<S: BackendTextStyle>(
        &self,
        text: &str,
        style: &S,
    ) -> Result<(u32, u32), DrawingErrorKind<Self::ErrorType>> {
        let font = style_to_font(style);
        let bounds = self.frame.size();
        let horizontal_alignment = match style.anchor().h_pos {
            text_anchor::HPos::Left => Horizontal::Left,
            text_anchor::HPos::Right => Horizontal::Right,
            text_anchor::HPos::Center => Horizontal::Center,
        };
        let vertical_alignment = match style.anchor().v_pos {
            text_anchor::VPos::Top => Vertical::Top,
            text_anchor::VPos::Center => Vertical::Center,
            text_anchor::VPos::Bottom => Vertical::Bottom,
        };

        let p = B::Paragraph::with_text(iced_widget::core::text::Text {
            content: text,
            bounds,
            size: self.backend.default_size(),
            line_height: Default::default(),
            font,
            horizontal_alignment,
            vertical_alignment,
            shaping: self.shaping,
            wrapping: iced_widget::core::text::Wrapping::Word,
        });
        let size = p.min_bounds();
        Ok((size.width as u32, size.height as u32))
    }

    #[inline]
    fn blit_bitmap(
        &mut self,
        _pos: BackendCoord,
        (_iw, _ih): (u32, u32),
        _src: &[u8],
    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
        // Not supported yet (rendering ignored)
        // Notice: currently Iced has limitations, because widgets are not rendered in the order of creation, and different primitives go to different render pipelines.

        Ok(())
    }
}

fn style_to_font<S: BackendTextStyle>(style: &S) -> Font {
    // iced font family requires static str
    static mut FONTS: Lazy<HashSet<String>> = Lazy::new(HashSet::new);

    Font {
        family: match style.family() {
            FontFamily::Serif => font::Family::Serif,
            FontFamily::SansSerif => font::Family::SansSerif,
            FontFamily::Monospace => font::Family::Monospace,
            FontFamily::Name(s) => {
                let s = unsafe {
                    if !FONTS.contains(s) {
                        FONTS.insert(String::from(s));
                    }
                    FONTS.get(s).unwrap().as_str()
                };
                font::Family::Name(s)
            }
        },
        weight: match style.style() {
            FontStyle::Bold => font::Weight::Bold,
            _ => font::Weight::Normal,
        },
        ..Font::DEFAULT
    }
}


================================================
FILE: src/chart.rs
================================================
// plotters-iced
//
// Iced backend for Plotters
// Copyright: 2022, Joylei <leingliu@gmail.com>
// License: MIT

use iced_widget::canvas::Cache;
use iced_widget::core::event::Status;
use iced_widget::core::mouse::Interaction;
use iced_widget::core::Rectangle;
use iced_widget::{
    canvas::{Event, Frame, Geometry},
    core::{mouse::Cursor, Size},
};
use plotters::{chart::ChartBuilder, coord::Shift, drawing::DrawingArea};
use plotters_backend::DrawingBackend;

/// graphics renderer
pub trait Renderer {
    /// draw frame
    fn draw<F: Fn(&mut Frame)>(&self, bounds: Size, draw_fn: F) -> Geometry;

    /// draw frame with cache
    fn draw_cache<F: Fn(&mut Frame)>(&self, cache: &Cache, bounds: Size, draw_fn: F) -> Geometry;
}

impl<Message, C: ?Sized> Chart<Message> for &C
where
    C: Chart<Message>,
{
    type State = C::State;
    #[inline]
    fn build_chart<DB: DrawingBackend>(&self, state: &Self::State, builder: ChartBuilder<DB>) {
        C::build_chart(self, state, builder);
    }
    #[inline]
    fn draw_chart<DB: DrawingBackend>(&self, state: &Self::State, root: DrawingArea<DB, Shift>) {
        C::draw_chart(self, state, root);
    }
    #[inline]
    fn draw<R: Renderer, F: Fn(&mut Frame)>(&self, renderer: &R, size: Size, f: F) -> Geometry {
        C::draw(self, renderer, size, f)
    }
    #[inline]
    fn update(
        &self,
        state: &mut Self::State,
        event: Event,
        bounds: Rectangle,
        cursor: Cursor,
    ) -> (Status, Option<Message>) {
        C::update(self, state, event, bounds, cursor)
    }
    #[inline]
    fn mouse_interaction(
        &self,
        state: &Self::State,
        bounds: Rectangle,
        cursor: Cursor,
    ) -> Interaction {
        C::mouse_interaction(self, state, bounds, cursor)
    }
}

/// Chart View Model
///
/// ## Example
/// ```rust,ignore
/// use plotters::prelude::*;
/// use plotters_iced::{Chart,ChartWidget};
/// struct MyChart;
/// impl Chart<Message> for MyChart {
///     type State = ();
///     fn build_chart<DB:DrawingBackend>(&self, state: &Self::State, builder: ChartBuilder<DB>) {
///         //build your chart here, please refer to plotters for more details
///     }
/// }
///
/// impl MyChart {
///     fn view(&mut self)->Element<Message> {
///         ChartWidget::new(self)
///             .width(Length::Fixed(200))
///             .height(Length::Fixed(200))
///             .into()
///     }
/// }
/// ```
pub trait Chart<Message> {
    /// state data of chart
    type State: Default + 'static;

    /// draw chart with [`ChartBuilder`]
    ///
    /// for simple chart, you impl this method
    fn build_chart<DB: DrawingBackend>(&self, state: &Self::State, builder: ChartBuilder<DB>);

    /// override this method if you want more freedom of drawing area
    ///
    /// ## Example
    /// ```rust,ignore
    /// use plotters::prelude::*;
    /// use plotters_iced::{Chart,ChartWidget};
    ///
    /// struct MyChart{}
    ///
    /// impl Chart<Message> for MyChart {
    ///     // leave it empty
    ///     fn build_chart<DB: DrawingBackend>(&self, state: &Self::State, builder: ChartBuilder<DB>){}
    ///     fn draw_chart<DB: DrawingBackend>(&self, state: &Self::State, root: DrawingArea<DB, Shift>){
    ///          let children = root.split_evenly((3,3));
    ///          for (area, color) in children.into_iter().zip(0..) {
    ///                area.fill(&Palette99::pick(color)).unwrap();
    ///          }
    ///      }
    /// }
    /// ```
    #[inline]
    fn draw_chart<DB: DrawingBackend>(&self, state: &Self::State, root: DrawingArea<DB, Shift>) {
        let builder = ChartBuilder::on(&root);
        self.build_chart(state, builder);
    }

    /// draw on [`iced_widget::canvas::Canvas`]
    ///
    /// override this method if you want to use [`iced_widget::canvas::Cache`]
    ///
    /// ## Example
    /// ```rust,ignore
    ///
    /// impl Chart<Message> for CpuUsageChart {
    ///
    ///       #[inline]
    ///       fn draw<R: Renderer, F: Fn(&mut Frame)>(&self, renderer: &R, bounds: Size, draw_fn: F) -> Geometry {
    ///            R::draw_cache(renderer, &self.cache, size, draw_fn)
    ///       }
    ///      //...
    /// }
    /// ```
    #[inline]
    fn draw<R: Renderer, F: Fn(&mut Frame)>(&self, renderer: &R, size: Size, f: F) -> Geometry {
        R::draw(renderer, size, f)
    }

    /// react on event
    #[allow(unused_variables)]
    #[inline]
    #[allow(unused)]
    fn update(
        &self,
        state: &mut Self::State,
        event: Event,
        bounds: Rectangle,
        cursor: Cursor,
    ) -> (Status, Option<Message>) {
        (Status::Ignored, None)
    }

    /// Returns the current mouse interaction of the [`Chart`]
    #[inline]
    #[allow(unused)]
    fn mouse_interaction(
        &self,
        state: &Self::State,
        bounds: Rectangle,
        cursor: Cursor,
    ) -> Interaction {
        Interaction::Idle
    }
}


================================================
FILE: src/error.rs
================================================
// plotters-iced
//
// Iced backend for Plotters
// Copyright: 2022, Joylei <leingliu@gmail.com>
// License: MIT

use std::error::Error as StdError;
use std::fmt;

#[derive(Debug)]
/// Indicates that some error occurred within the Iced backend
pub enum Error {}

impl fmt::Display for Error {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        write!(fmt, "{self:?}")
    }
}

impl StdError for Error {}


================================================
FILE: src/lib.rs
================================================
#![doc = include_str!("../README.md")]
#![warn(missing_docs)]

pub extern crate plotters_backend;

#[doc(no_inline)]
pub use plotters::{chart::ChartBuilder, drawing::DrawingArea};
#[doc(no_inline)]
pub use plotters_backend::DrawingBackend;

#[doc(inline)]
pub use chart::Chart;
#[doc(inline)]
pub use chart::Renderer;
#[doc(inline)]
pub use error::Error;
pub use widget::ChartWidget;

mod backend;
mod chart;
mod error;
mod renderer;
/// data point sampling
pub mod sample;
mod utils;
mod widget;


================================================
FILE: src/renderer.rs
================================================
// plotters-iced
//
// Iced backend for Plotters
// Copyright: 2022, Joylei <leingliu@gmail.com>
// License: MIT

use iced_widget::{
    canvas::{Cache, Frame, Geometry},
    core::{Layout, Size, Vector},
    text::Shaping,
};
use plotters::prelude::DrawingArea;

use crate::backend::IcedChartBackend;
use crate::Chart;

/// Graphics Renderer
pub trait Renderer:
    iced_widget::core::Renderer + iced_widget::core::text::Renderer + iced_graphics::geometry::Renderer
{
    /// draw a [Chart]
    fn draw_chart<Message, C>(
        &mut self,
        state: &C::State,
        chart: &C,
        layout: Layout<'_>,
        shaping: Shaping,
    ) where
        C: Chart<Message>;
}

impl crate::chart::Renderer for iced_widget::renderer::Renderer {
    fn draw<F: Fn(&mut Frame)>(&self, size: Size, f: F) -> Geometry {
        let mut frame = Frame::new(self, size);
        f(&mut frame);
        frame.into_geometry()
    }

    fn draw_cache<F: Fn(&mut Frame)>(&self, cache: &Cache, size: Size, f: F) -> Geometry {
        cache.draw(self, size, f)
    }
}

impl Renderer for iced_widget::renderer::Renderer {
    fn draw_chart<Message, C>(
        &mut self,
        state: &C::State,
        chart: &C,
        layout: Layout<'_>,
        shaping: Shaping,
    ) where
        C: Chart<Message>,
    {
        let bounds = layout.bounds();
        if bounds.width < 1.0 || bounds.height < 1.0 {
            return;
        }
        let geometry = chart.draw(self, bounds.size(), |frame| {
            let backend = IcedChartBackend::new(frame, self, shaping);
            let root: DrawingArea<_, _> = backend.into();
            chart.draw_chart(state, root);
        });
        let translation = Vector::new(bounds.x, bounds.y);
        iced_widget::core::Renderer::with_translation(self, translation, |renderer| {
            iced_graphics::geometry::Renderer::draw_geometry(renderer, geometry);
        });
    }
}


================================================
FILE: src/sample/lttb.rs
================================================
// plotters-iced
//
// Iced backend for Plotters
// Copyright: 2022, Joylei <leingliu@gmail.com>
// License: MIT
//

//! Largest-Triangle-Three-Buckets algorithm (LTTB)
//!
//! ## Known limitations
//! - X-values must be in a strictly increasing order

// original version: https://github.com/sveinn-steinarsson/flot-downsample
// modified based on https://github.com/jeromefroe/lttb-rs

/// data point for [`LttbSource`]
pub trait DataPoint {
    /// x value for sampling, must be in a strictly increasing order
    fn x(&self) -> f64;
    /// y value for sampling
    fn y(&self) -> f64;
}

impl<D: DataPoint> DataPoint for &D {
    #[inline]
    fn x(&self) -> f64 {
        (*self).x()
    }
    #[inline]
    fn y(&self) -> f64 {
        (*self).y()
    }
}

/// data source for lttb sampling
///
/// ## Known limitations
/// - X-values must be in a strictly increasing order
pub trait LttbSource {
    /// data item of [`LttbSource`]
    type Item;

    /// length of [`LttbSource`]
    fn len(&self) -> usize;

    /// is [`LttbSource`] empty
    #[inline]
    fn is_empty(&self) -> bool {
        self.len() == 0
    }

    /// data item at  index `i`
    fn item_at(&self, i: usize) -> Self::Item;

    /// map data item to another type.
    /// - if the data item type of [`LttbSource`] is not [`DataPoint`], lttb sampling can be used after casting
    fn cast<T, F>(self, f: F) -> Cast<Self, T, F>
    where
        Self: Sized,
        T: DataPoint,
        F: Fn(Self::Item) -> T,
    {
        Cast { s: self, f }
    }

    /// lttb sampling
    fn lttb(self, threshold: usize) -> LttbIterator<Self>
    where
        Self: Sized,
        Self::Item: DataPoint,
    {
        let is_sample = !(threshold >= self.len() || threshold < 3);
        let every = if is_sample {
            ((self.len() - 2) as f64) / ((threshold - 2) as f64)
        } else {
            0_f64
        };
        LttbIterator {
            source: self,
            is_sample,
            idx: 0,
            a: 0,
            threshold,
            every,
        }
    }
}

/// map data item to another type
pub struct Cast<S, T, F>
where
    S: LttbSource,
    T: DataPoint,
    F: Fn(S::Item) -> T,
{
    s: S,
    f: F,
}

impl<S, T, F> LttbSource for Cast<S, T, F>
where
    S: LttbSource,
    T: DataPoint,
    F: Fn(S::Item) -> T,
{
    type Item = T;
    #[inline]
    fn len(&self) -> usize {
        self.s.len()
    }

    #[inline]
    fn item_at(&self, i: usize) -> Self::Item {
        let item = self.s.item_at(i);
        (self.f)(item)
    }
}

impl<'a, S: LttbSource> LttbSource for &'a S {
    type Item = S::Item;
    #[inline]
    fn len(&self) -> usize {
        (*self).len()
    }

    #[inline]
    fn item_at(&self, i: usize) -> Self::Item {
        (*self).item_at(i)
    }
}

/// iterator for [`LttbSource`]
pub struct LttbIterator<S: LttbSource> {
    source: S,
    is_sample: bool,
    idx: usize,
    threshold: usize,
    every: f64,
    a: usize,
}

impl<S: LttbSource> LttbIterator<S>
where
    S::Item: DataPoint,
{
    fn next_no_sample(&mut self) -> Option<S::Item> {
        if self.idx < self.source.len() {
            let item = self.source.item_at(self.idx);
            self.idx += 1;
            Some(item)
        } else {
            None
        }
    }

    fn next_sample(&mut self) -> Option<S::Item> {
        if self.idx < self.threshold {
            if self.idx == 0 {
                self.idx += 1;
                Some(self.source.item_at(0))
            } else if self.idx + 1 == self.threshold {
                self.idx += 1;
                Some(self.source.item_at(self.source.len() - 1))
            } else {
                let every = self.every;
                let i = self.idx - 1;
                // Calculate point average for next bucket (containing c).
                let mut avg_x = 0f64;
                let mut avg_y = 0f64;

                let avg_range_start = (((i + 1) as f64) * every) as usize + 1;

                let mut end = (((i + 2) as f64) * every) as usize + 1;
                if end >= self.source.len() {
                    end = self.source.len();
                }
                let avg_range_end = end;

                let avg_range_length = (avg_range_end - avg_range_start) as f64;

                for i in 0..(avg_range_end - avg_range_start) {
                    let idx = avg_range_start + i;
                    let item = self.source.item_at(idx);
                    avg_x += item.x();
                    avg_y += item.y();
                }
                avg_x /= avg_range_length;
                avg_y /= avg_range_length;

                // Get the range for this bucket.
                let range_offs = ((i as f64) * every) as usize + 1;
                let range_to = (((i + 1) as f64) * every) as usize + 1;

                // Point a.
                let item = self.source.item_at(self.a);
                let point_a_x = item.x();
                let point_a_y = item.y();

                let mut max_area = -1f64;
                let mut next_a = range_offs;
                for i in 0..(range_to - range_offs) {
                    let idx = range_offs + i;

                    // Calculate triangle area over three buckets.
                    let item = self.source.item_at(idx);
                    let area = ((point_a_x - avg_x) * (item.y() - point_a_y)
                        - (point_a_x - item.x()) * (avg_y - point_a_y))
                        .abs()
                        * 0.5;
                    if area > max_area {
                        max_area = area;
                        next_a = idx; // Next a is this b.
                    }
                }

                let item = self.source.item_at(next_a); // Pick this point from the bucket.
                self.a = next_a; // This a is the next a (chosen b).
                self.idx += 1;
                Some(item)
            }
        } else {
            None
        }
    }

    #[inline]
    fn remaining(&self) -> usize {
        if self.is_sample {
            self.threshold - self.idx
        } else {
            self.source.len() - self.idx
        }
    }
}

impl<S: LttbSource> Iterator for LttbIterator<S>
where
    S::Item: DataPoint,
{
    type Item = S::Item;
    fn next(&mut self) -> Option<Self::Item> {
        if self.is_sample {
            self.next_sample()
        } else {
            self.next_no_sample()
        }
    }

    #[inline]
    fn size_hint(&self) -> (usize, Option<usize>) {
        let size = self.remaining();
        (size, Some(size))
    }
}

impl<S: LttbSource> ExactSizeIterator for LttbIterator<S>
where
    S::Item: DataPoint,
{
    #[inline]
    fn len(&self) -> usize {
        self.remaining()
    }
}

impl<'a, T> LttbSource for &'a [T] {
    type Item = &'a T;
    #[inline]
    fn len(&self) -> usize {
        (*self).len()
    }

    #[inline]
    fn item_at(&self, i: usize) -> Self::Item {
        &self[i]
    }
}

impl<T: Clone> LttbSource for [T] {
    type Item = T;
    #[inline]
    fn len(&self) -> usize {
        (*self).len()
    }

    #[inline]
    fn item_at(&self, i: usize) -> Self::Item {
        self[i].clone()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[derive(Debug, PartialEq, Clone)]
    pub struct DataPoint {
        pub x: f64,
        pub y: f64,
    }

    impl DataPoint {
        pub fn new(x: f64, y: f64) -> Self {
            DataPoint { x, y }
        }
    }

    impl super::DataPoint for DataPoint {
        fn x(&self) -> f64 {
            self.x
        }

        fn y(&self) -> f64 {
            self.y
        }
    }

    #[test]
    fn lttb_test() {
        let mut dps = vec![];
        dps.push(DataPoint::new(0.0, 10.0));
        dps.push(DataPoint::new(1.0, 12.0));
        dps.push(DataPoint::new(2.0, 8.0));
        dps.push(DataPoint::new(3.0, 10.0));
        dps.push(DataPoint::new(4.0, 12.0));

        let mut expected = vec![];
        expected.push(DataPoint::new(0.0, 10.0));
        expected.push(DataPoint::new(2.0, 8.0));
        expected.push(DataPoint::new(4.0, 12.0));

        let result: Vec<DataPoint> = dps.as_slice().lttb(3).cloned().collect();

        assert_eq!(expected, result);
    }
}


================================================
FILE: src/sample.rs
================================================
// plotters-iced
//
// Iced backend for Plotters
// Copyright: 2022, Joylei <leingliu@gmail.com>
// License: MIT
//

pub mod lttb;


================================================
FILE: src/utils.rs
================================================
// plotters-iced
//
// Iced backend for Plotters
// Copyright: 2022, Joylei <leingliu@gmail.com>
// License: MIT

use iced_widget::canvas;
use iced_widget::core::{Color, Point};
use plotters_backend::{BackendColor, BackendCoord, BackendStyle};

pub(crate) trait AndExt {
    fn and<F: Fn(Self) -> Self>(self, f: F) -> Self
    where
        Self: Sized;
}

impl<T> AndExt for T {
    #[inline(always)]
    fn and<F: Fn(Self) -> Self>(self, f: F) -> Self
    where
        Self: Sized,
    {
        f(self)
    }
}

#[inline]
pub(crate) fn cvt_color(color: &BackendColor) -> Color {
    let ((r, g, b), a) = (color.rgb, color.alpha);
    Color::from_rgba8(r, g, b, a as f32)
}

#[inline]
pub(crate) fn cvt_stroke<S: BackendStyle>(style: &S) -> canvas::Stroke {
    canvas::Stroke::default()
        .with_color(cvt_color(&style.color()))
        .with_width(style.stroke_width() as f32)
}

pub(crate) trait CvtPoint {
    fn cvt_point(self) -> Point;
}

impl CvtPoint for BackendCoord {
    #[inline]
    fn cvt_point(self) -> Point {
        Point::new(self.0 as f32, self.1 as f32)
    }
}

impl CvtPoint for [f64; 2] {
    #[inline]
    fn cvt_point(self) -> Point {
        Point::new(self[0] as f32, self[1] as f32)
    }
}


================================================
FILE: src/widget.rs
================================================
// plotters-iced
//
// Iced backend for Plotters
// Copyright: 2022, Joylei <leingliu@gmail.com>
// License: MIT

use core::marker::PhantomData;

use iced_widget::{
    canvas::Event,
    core::{
        event,
        mouse::Cursor,
        renderer::Style,
        widget::{tree, Tree},
        Element, Layout, Length, Rectangle, Shell, Size, Widget,
    },
    text::Shaping,
};

use crate::renderer::Renderer;

use super::Chart;

/// Chart container, turns [`Chart`]s to [`Widget`]s
pub struct ChartWidget<'a, Message, Theme, Renderer, C>
where
    C: Chart<Message>,
{
    chart: C,
    width: Length,
    height: Length,
    shaping: Shaping,
    _marker: PhantomData<&'a (Renderer, Theme, Message)>,
}

impl<'a, Message, Theme, Renderer, C> ChartWidget<'a, Message, Theme, Renderer, C>
where
    C: Chart<Message> + 'a,
{
    /// create a new [`ChartWidget`]
    pub fn new(chart: C) -> Self {
        Self {
            chart,
            width: Length::Fill,
            height: Length::Fill,
            shaping: Default::default(),
            _marker: Default::default(),
        }
    }

    /// set width
    pub fn width(mut self, width: Length) -> Self {
        self.width = width;
        self
    }

    /// set height
    pub fn height(mut self, height: Length) -> Self {
        self.height = height;
        self
    }

    /// set text shaping
    pub fn text_shaping(mut self, shaping: Shaping) -> Self {
        self.shaping = shaping;
        self
    }
}

impl<'a, Message, Theme, Renderer, C> Widget<Message, Theme, Renderer>
    for ChartWidget<'a, Message, Theme, Renderer, C>
where
    C: Chart<Message>,
    Renderer: self::Renderer,
{
    fn size(&self) -> Size<Length> {
        Size::new(self.width, self.height)
    }

    fn tag(&self) -> tree::Tag {
        struct Tag<T>(T);
        tree::Tag::of::<Tag<C::State>>()
    }

    fn state(&self) -> tree::State {
        tree::State::new(C::State::default())
    }

    #[inline]
    fn layout(
        &self,
        _tree: &mut Tree,
        _renderer: &Renderer,
        limits: &iced_widget::core::layout::Limits,
    ) -> iced_widget::core::layout::Node {
        let size = limits.resolve(self.width, self.height, Size::ZERO);
        iced_widget::core::layout::Node::new(size)
    }

    #[inline]
    fn draw(
        &self,
        tree: &Tree,
        renderer: &mut Renderer,
        _theme: &Theme,
        _style: &Style,
        layout: Layout<'_>,
        _cursor_position: Cursor,
        _viewport: &Rectangle,
    ) {
        let state = tree.state.downcast_ref::<C::State>();
        renderer.draw_chart(state, &self.chart, layout, self.shaping);
    }

    #[inline]
    fn on_event(
        &mut self,
        tree: &mut Tree,
        event: iced_widget::core::Event,
        layout: Layout<'_>,
        cursor: Cursor,
        _renderer: &Renderer,
        _clipboard: &mut dyn iced_widget::core::Clipboard,
        shell: &mut Shell<'_, Message>,
        _rectangle: &Rectangle,
    ) -> event::Status {
        let bounds = layout.bounds();
        let canvas_event = match event {
            iced_widget::core::Event::Mouse(mouse_event) => Some(Event::Mouse(mouse_event)),
            iced_widget::core::Event::Keyboard(keyboard_event) => {
                Some(Event::Keyboard(keyboard_event))
            }
            _ => None,
        };
        if let Some(canvas_event) = canvas_event {
            let state = tree.state.downcast_mut::<C::State>();

            let (event_status, message) = self.chart.update(state, canvas_event, bounds, cursor);

            if let Some(message) = message {
                shell.publish(message);
            }
            return event_status;
        }
        event::Status::Ignored
    }

    fn mouse_interaction(
        &self,
        tree: &Tree,
        layout: Layout<'_>,
        cursor: Cursor,
        _viewport: &Rectangle,
        _renderer: &Renderer,
    ) -> iced_widget::core::mouse::Interaction {
        let state = tree.state.downcast_ref::<C::State>();
        let bounds = layout.bounds();
        self.chart.mouse_interaction(state, bounds, cursor)
    }
}

impl<'a, Message, Theme, Renderer, C> From<ChartWidget<'a, Message, Theme, Renderer, C>>
    for Element<'a, Message, Theme, Renderer>
where
    Message: 'a,
    C: Chart<Message> + 'a,
    Renderer: self::Renderer,
{
    fn from(widget: ChartWidget<'a, Message, Theme, Renderer, C>) -> Self {
        Element::new(widget)
    }
}
Download .txt
gitextract_ofu0l0m2/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       └── test.yml
├── .gitignore
├── Cargo.toml
├── LICENSE
├── README.md
├── examples/
│   ├── cpu-monitor.rs
│   ├── large-data.rs
│   ├── mouse_events.rs
│   └── split-chart/
│       ├── Cargo.toml
│       ├── index.html
│       └── src/
│           └── main.rs
└── src/
    ├── backend/
    │   └── mod.rs
    ├── chart.rs
    ├── error.rs
    ├── lib.rs
    ├── renderer.rs
    ├── sample/
    │   └── lttb.rs
    ├── sample.rs
    ├── utils.rs
    └── widget.rs
Download .txt
SYMBOL INDEX (164 symbols across 11 files)

FILE: examples/cpu-monitor.rs
  constant PLOT_SECONDS (line 30) | const PLOT_SECONDS: usize = 60;
  constant TITLE_FONT_SIZE (line 31) | const TITLE_FONT_SIZE: u16 = 22;
  constant SAMPLE_EVERY (line 32) | const SAMPLE_EVERY: Duration = Duration::from_millis(1000);
  constant FONT_BOLD (line 34) | const FONT_BOLD: Font = Font {
  function main (line 40) | fn main() {
  type Message (line 53) | enum Message {
  type State (line 59) | struct State {
    method new (line 64) | fn new() -> (Self, Task<Message>) {
    method update (line 78) | fn update(&mut self, message: Message) {
    method view (line 87) | fn view(&self) -> Element<'_, Message> {
  type SystemChart (line 109) | struct SystemChart {
    method is_initialized (line 133) | fn is_initialized(&self) -> bool {
    method should_update (line 138) | fn should_update(&self) -> bool {
    method update (line 142) | fn update(&mut self) {
    method view (line 168) | fn view(&self) -> Element<Message> {
  method default (line 118) | fn default() -> Self {
  type CpuUsageChart (line 205) | struct CpuUsageChart {
    method new (line 212) | fn new(data: impl Iterator<Item = (DateTime<Utc>, i32)>) -> Self {
    method push_data (line 221) | fn push_data(&mut self, time: DateTime<Utc>, value: i32) {
    method view (line 237) | fn view(&self, idx: usize, chart_height: f32) -> Element<Message> {
    type State (line 250) | type State = ();
    method draw (line 262) | fn draw<R: Renderer, F: Fn(&mut Frame)>(
    method build_chart (line 271) | fn build_chart<DB: DrawingBackend>(&self, _state: &Self::State, mut ch...

FILE: examples/large-data.rs
  constant TITLE_FONT_SIZE (line 31) | const TITLE_FONT_SIZE: u16 = 22;
  constant FONT_BOLD (line 33) | const FONT_BOLD: Font = Font {
  function main (line 39) | fn main() {
  type Wrapper (line 47) | struct Wrapper<'a>(&'a DateTime<Utc>, &'a f32);
  method x (line 51) | fn x(&self) -> f64 {
  method y (line 55) | fn y(&self) -> f64 {
  type Message (line 61) | enum Message {
  type State (line 67) | struct State {
    method new (line 72) | fn new() -> (Self, Task<Message>) {
    method update (line 87) | fn update(&mut self, message: Message) -> Task<Message> {
    method view (line 110) | fn view(&self) -> Element<'_, Message> {
  type ExampleChart (line 134) | struct ExampleChart {
    method new (line 140) | fn new(data: impl Iterator<Item = (DateTime<Utc>, f32)>) -> Self {
    method view (line 148) | fn view(&self) -> Element<Message> {
    type State (line 158) | type State = ();
    method draw (line 170) | fn draw<R: Renderer, F: Fn(&mut Frame)>(
    method build_chart (line 179) | fn build_chart<DB: DrawingBackend>(&self, _state: &Self::State, mut ch...
  function generate_data (line 238) | fn generate_data() -> Vec<(DateTime<Utc>, f32)> {

FILE: examples/mouse_events.rs
  type State (line 24) | struct State {
    method update (line 29) | fn update(&mut self, message: Message) {
    method view (line 48) | fn view(&self) -> Element<Message> {
  type ArtChart (line 67) | struct ArtChart {
    method view (line 78) | fn view(&self) -> Element<Message> {
    method set_current_position (line 86) | fn set_current_position(&mut self, p: Point) {
    method nearby (line 93) | fn nearby(p0: (f32, f32), p1: (f32, f32)) -> bool {
    method set_down (line 98) | fn set_down(&mut self, new_is_down: bool) {
    type State (line 120) | type State = ();
    method draw (line 121) | fn draw<R: Renderer, F: Fn(&mut Frame)>(
    method build_chart (line 130) | fn build_chart<DB: DrawingBackend>(&self, _state: &Self::State, mut bu...
    method update (line 212) | fn update(
  type Message (line 237) | enum Message {
  function main (line 241) | fn main() -> iced::Result {

FILE: examples/split-chart/src/main.rs
  constant TITLE_FONT_SIZE (line 34) | const TITLE_FONT_SIZE: u16 = 22;
  function main (line 38) | fn main() {
  type Message (line 52) | enum Message {
  type State (line 57) | struct State {
    method update (line 62) | fn update(&mut self, _message: Message) {}
    method view (line 64) | fn view(&self) -> Element<'_, Message> {
  type MyChart (line 83) | struct MyChart;
    method view (line 86) | fn view(&self) -> Element<Message> {
    type State (line 96) | type State = ();
    method build_chart (line 98) | fn build_chart<DB: DrawingBackend>(&self, _state: &Self::State, _build...
    method draw_chart (line 100) | fn draw_chart<DB: DrawingBackend>(&self, _state: &Self::State, root: D...
  function draw_chart (line 109) | fn draw_chart<DB: DrawingBackend>(mut chart: ChartBuilder<DB>, power: us...

FILE: src/backend/mod.rs
  type IcedChartBackend (line 36) | pub(crate) struct IcedChartBackend<'a, B> {
  function new (line 46) | pub fn new(frame: &'a mut canvas::Frame, backend: &'a B, shaping: Shapin...
  type ErrorType (line 59) | type ErrorType = Error;
  method get_size (line 61) | fn get_size(&self) -> (u32, u32) {
  method ensure_prepared (line 66) | fn ensure_prepared(&mut self) -> Result<(), DrawingErrorKind<Error>> {
  method present (line 70) | fn present(&mut self) -> Result<(), DrawingErrorKind<Error>> {
  method draw_pixel (line 75) | fn draw_pixel(
  method draw_line (line 89) | fn draw_line<S: BackendStyle>(
  method draw_rect (line 104) | fn draw_rect<S: BackendStyle>(
  method draw_path (line 132) | fn draw_path<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
  method draw_circle (line 155) | fn draw_circle<S: BackendStyle>(
  method fill_polygon (line 178) | fn fill_polygon<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
  method draw_text (line 201) | fn draw_text<S: BackendTextStyle>(
  method estimate_text_size (line 260) | fn estimate_text_size<S: BackendTextStyle>(
  method blit_bitmap (line 294) | fn blit_bitmap(
  function style_to_font (line 307) | fn style_to_font<S: BackendTextStyle>(style: &S) -> Font {

FILE: src/chart.rs
  type Renderer (line 19) | pub trait Renderer {
    method draw (line 21) | fn draw<F: Fn(&mut Frame)>(&self, bounds: Size, draw_fn: F) -> Geometry;
    method draw_cache (line 24) | fn draw_cache<F: Fn(&mut Frame)>(&self, cache: &Cache, bounds: Size, d...
  type State (line 31) | type State = C::State;
  function build_chart (line 33) | fn build_chart<DB: DrawingBackend>(&self, state: &Self::State, builder: ...
  function draw_chart (line 37) | fn draw_chart<DB: DrawingBackend>(&self, state: &Self::State, root: Draw...
  function draw (line 41) | fn draw<R: Renderer, F: Fn(&mut Frame)>(&self, renderer: &R, size: Size,...
  function update (line 45) | fn update(
  function mouse_interaction (line 55) | fn mouse_interaction(
  type Chart (line 88) | pub trait Chart<Message> {
    method build_chart (line 95) | fn build_chart<DB: DrawingBackend>(&self, state: &Self::State, builder...
    method draw_chart (line 118) | fn draw_chart<DB: DrawingBackend>(&self, state: &Self::State, root: Dr...
    method draw (line 140) | fn draw<R: Renderer, F: Fn(&mut Frame)>(&self, renderer: &R, size: Siz...
    method update (line 148) | fn update(
    method mouse_interaction (line 161) | fn mouse_interaction(

FILE: src/error.rs
  type Error (line 12) | pub enum Error {}
    method fmt (line 15) | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {

FILE: src/renderer.rs
  type Renderer (line 18) | pub trait Renderer:
    method draw_chart (line 22) | fn draw_chart<Message, C>(
    method draw_chart (line 45) | fn draw_chart<Message, C>(
  function draw (line 33) | fn draw<F: Fn(&mut Frame)>(&self, size: Size, f: F) -> Geometry {
  function draw_cache (line 39) | fn draw_cache<F: Fn(&mut Frame)>(&self, cache: &Cache, size: Size, f: F)...

FILE: src/sample/lttb.rs
  type DataPoint (line 17) | pub trait DataPoint {
    method x (line 19) | fn x(&self) -> f64;
    method y (line 21) | fn y(&self) -> f64;
    method x (line 26) | fn x(&self) -> f64 {
    method y (line 30) | fn y(&self) -> f64 {
    method new (line 303) | pub fn new(x: f64, y: f64) -> Self {
    method x (line 309) | fn x(&self) -> f64 {
    method y (line 313) | fn y(&self) -> f64 {
  type LttbSource (line 39) | pub trait LttbSource {
    method len (line 44) | fn len(&self) -> usize;
    method is_empty (line 48) | fn is_empty(&self) -> bool {
    method item_at (line 53) | fn item_at(&self, i: usize) -> Self::Item;
    method cast (line 57) | fn cast<T, F>(self, f: F) -> Cast<Self, T, F>
    method lttb (line 67) | fn lttb(self, threshold: usize) -> LttbIterator<Self>
    type Item (line 106) | type Item = T;
    method len (line 108) | fn len(&self) -> usize {
    method item_at (line 113) | fn item_at(&self, i: usize) -> Self::Item {
    type Item (line 120) | type Item = S::Item;
    method len (line 122) | fn len(&self) -> usize {
    method item_at (line 127) | fn item_at(&self, i: usize) -> Self::Item {
    type Item (line 267) | type Item = &'a T;
    method len (line 269) | fn len(&self) -> usize {
    method item_at (line 274) | fn item_at(&self, i: usize) -> Self::Item {
    type Item (line 280) | type Item = T;
    method len (line 282) | fn len(&self) -> usize {
    method item_at (line 287) | fn item_at(&self, i: usize) -> Self::Item {
  type Cast (line 90) | pub struct Cast<S, T, F>
  type LttbIterator (line 133) | pub struct LttbIterator<S: LttbSource> {
  function next_no_sample (line 146) | fn next_no_sample(&mut self) -> Option<S::Item> {
  function next_sample (line 156) | fn next_sample(&mut self) -> Option<S::Item> {
  function remaining (line 227) | fn remaining(&self) -> usize {
  type Item (line 240) | type Item = S::Item;
  method next (line 241) | fn next(&mut self) -> Option<Self::Item> {
  method size_hint (line 250) | fn size_hint(&self) -> (usize, Option<usize>) {
  method len (line 261) | fn len(&self) -> usize {
  type DataPoint (line 297) | pub struct DataPoint {
    method x (line 19) | fn x(&self) -> f64;
    method y (line 21) | fn y(&self) -> f64;
    method x (line 26) | fn x(&self) -> f64 {
    method y (line 30) | fn y(&self) -> f64 {
    method new (line 303) | pub fn new(x: f64, y: f64) -> Self {
    method x (line 309) | fn x(&self) -> f64 {
    method y (line 313) | fn y(&self) -> f64 {
  function lttb_test (line 319) | fn lttb_test() {

FILE: src/utils.rs
  type AndExt (line 11) | pub(crate) trait AndExt {
    method and (line 12) | fn and<F: Fn(Self) -> Self>(self, f: F) -> Self
    method and (line 19) | fn and<F: Fn(Self) -> Self>(self, f: F) -> Self
  function cvt_color (line 28) | pub(crate) fn cvt_color(color: &BackendColor) -> Color {
  function cvt_stroke (line 34) | pub(crate) fn cvt_stroke<S: BackendStyle>(style: &S) -> canvas::Stroke {
  type CvtPoint (line 40) | pub(crate) trait CvtPoint {
    method cvt_point (line 41) | fn cvt_point(self) -> Point;
    method cvt_point (line 46) | fn cvt_point(self) -> Point {
    method cvt_point (line 53) | fn cvt_point(self) -> Point {

FILE: src/widget.rs
  type ChartWidget (line 26) | pub struct ChartWidget<'a, Message, Theme, Renderer, C>
  function new (line 42) | pub fn new(chart: C) -> Self {
  function width (line 53) | pub fn width(mut self, width: Length) -> Self {
  function height (line 59) | pub fn height(mut self, height: Length) -> Self {
  function text_shaping (line 65) | pub fn text_shaping(mut self, shaping: Shaping) -> Self {
  function size (line 77) | fn size(&self) -> Size<Length> {
  function tag (line 81) | fn tag(&self) -> tree::Tag {
  function state (line 86) | fn state(&self) -> tree::State {
  function layout (line 91) | fn layout(
  function draw (line 102) | fn draw(
  function on_event (line 117) | fn on_event(
  function mouse_interaction (line 149) | fn mouse_interaction(
  function from (line 170) | fn from(widget: ChartWidget<'a, Message, Theme, Renderer, C>) -> Self {
Condensed preview — 21 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (72K chars).
[
  {
    "path": ".github/dependabot.yml",
    "chars": 447,
    "preview": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where "
  },
  {
    "path": ".github/workflows/test.yml",
    "chars": 1118,
    "preview": "name: Test and Build\n\non:\n  push:\n    branches: [master]\n  pull_request:\n    branches: [master]\n\nenv:\n  CARGO_TERM_COLOR"
  },
  {
    "path": ".gitignore",
    "chars": 81,
    "preview": "target/*\nCargo.lock\n.cargo/*\nexamples/split-chart/dist/*\n.idea\n.DS_store\n.history"
  },
  {
    "path": "Cargo.toml",
    "chars": 1194,
    "preview": "[package]\nname = \"plotters-iced\"\nversion = \"0.11.0\"\ndescription = \"Iced backend for Plotters\"\nreadme = \"README.md\"\nlicen"
  },
  {
    "path": "LICENSE",
    "chars": 1085,
    "preview": "MIT License\n\nCopyright (c) 2022, Joylei <leingliu@gmail.com>\n\nPermission is hereby granted, free of charge, to any perso"
  },
  {
    "path": "README.md",
    "chars": 3684,
    "preview": "# plotters-iced\n[![Test and Build](https://github.com/joylei/plotters-iced/workflows/Test%20and%20Build/badge.svg?branch"
  },
  {
    "path": "examples/cpu-monitor.rs",
    "chars": 9328,
    "preview": "// plotters-iced\n//\n// Iced backend for Plotters\n// Copyright: 2022, Joylei <leingliu@gmail.com>\n// License: MIT\n\nextern"
  },
  {
    "path": "examples/large-data.rs",
    "chars": 7701,
    "preview": "// plotters-iced\n//\n// Iced backend for Plotters\n// Copyright: 2022, Joylei <leingliu@gmail.com>\n// License: MIT\n\nextern"
  },
  {
    "path": "examples/mouse_events.rs",
    "chars": 7443,
    "preview": "// plotters-iced\n//\n// Iced backend for Plotters\n// Copyright: 2022, Grey <grey@openrobotics.org>\n// License: MIT\n\nuse i"
  },
  {
    "path": "examples/split-chart/Cargo.toml",
    "chars": 546,
    "preview": "[package]\nname = \"split-chart\"\nversion = \"0.1.0\"\nauthors = [\"Joylei <leingliu@gmail.com>\"]\nedition = \"2021\"\npublish = fa"
  },
  {
    "path": "examples/split-chart/index.html",
    "chars": 477,
    "preview": "<!DOCTYPE html>\n<html>\n\n<head>\n    <meta content=\"text/html; charset=utf-8\" charset=\"utf-8\" http-equiv=\"Content-Type\">\n "
  },
  {
    "path": "examples/split-chart/src/main.rs",
    "chars": 3405,
    "preview": "// plotters-iced\n//\n// Iced backend for Plotters\n// Copyright: 2022, Joylei <leingliu@gmail.com>\n// License: MIT\n\n/*!\n- "
  },
  {
    "path": "src/backend/mod.rs",
    "chars": 9933,
    "preview": "// plotters-iced\n//\n// Iced backend for Plotters\n// Copyright: 2022, Joylei <leingliu@gmail.com>\n// License: MIT\n\nuse st"
  },
  {
    "path": "src/chart.rs",
    "chars": 4957,
    "preview": "// plotters-iced\n//\n// Iced backend for Plotters\n// Copyright: 2022, Joylei <leingliu@gmail.com>\n// License: MIT\n\nuse ic"
  },
  {
    "path": "src/error.rs",
    "chars": 422,
    "preview": "// plotters-iced\n//\n// Iced backend for Plotters\n// Copyright: 2022, Joylei <leingliu@gmail.com>\n// License: MIT\n\nuse st"
  },
  {
    "path": "src/lib.rs",
    "chars": 497,
    "preview": "#![doc = include_str!(\"../README.md\")]\n#![warn(missing_docs)]\n\npub extern crate plotters_backend;\n\n#[doc(no_inline)]\npub"
  },
  {
    "path": "src/renderer.rs",
    "chars": 1926,
    "preview": "// plotters-iced\n//\n// Iced backend for Plotters\n// Copyright: 2022, Joylei <leingliu@gmail.com>\n// License: MIT\n\nuse ic"
  },
  {
    "path": "src/sample/lttb.rs",
    "chars": 8241,
    "preview": "// plotters-iced\n//\n// Iced backend for Plotters\n// Copyright: 2022, Joylei <leingliu@gmail.com>\n// License: MIT\n//\n\n//!"
  },
  {
    "path": "src/sample.rs",
    "chars": 131,
    "preview": "// plotters-iced\n//\n// Iced backend for Plotters\n// Copyright: 2022, Joylei <leingliu@gmail.com>\n// License: MIT\n//\n\npub"
  },
  {
    "path": "src/utils.rs",
    "chars": 1229,
    "preview": "// plotters-iced\n//\n// Iced backend for Plotters\n// Copyright: 2022, Joylei <leingliu@gmail.com>\n// License: MIT\n\nuse ic"
  },
  {
    "path": "src/widget.rs",
    "chars": 4471,
    "preview": "// plotters-iced\n//\n// Iced backend for Plotters\n// Copyright: 2022, Joylei <leingliu@gmail.com>\n// License: MIT\n\nuse co"
  }
]

About this extraction

This page contains the full source code of the Joylei/plotters-iced GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 21 files (66.7 KB), approximately 17.6k tokens, and a symbol index with 164 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!