|
@@ -3,8 +3,12 @@ use std::collections::BTreeMap;
|
|
|
use chrono::{DateTime, Utc, Duration, TimeZone, LocalResult};
|
|
use chrono::{DateTime, Utc, Duration, TimeZone, LocalResult};
|
|
|
use iced::{
|
|
use iced::{
|
|
|
executor, widget::{
|
|
executor, widget::{
|
|
|
- button, canvas::{path::lyon_path::geom::euclid::num::Round, Cache, Frame, Geometry}, pick_list, Column, Container, Row, Text
|
|
|
|
|
- }, Alignment, Application, Command, Element, Font, Length, Settings, Size, Subscription, Theme
|
|
|
|
|
|
|
+ button, canvas::{path::lyon_path::geom::euclid::num::Round, Cache, Frame, Geometry}, pick_list, Column, Container, Row, Space, Text
|
|
|
|
|
+ }, Alignment, Application, Color, Command, Element, Font, Length, Settings, Size, Subscription, Theme
|
|
|
|
|
+};
|
|
|
|
|
+use iced::widget::pane_grid::{self, PaneGrid};
|
|
|
|
|
+use iced::widget::{
|
|
|
|
|
+ column, container, row, scrollable, text, responsive
|
|
|
};
|
|
};
|
|
|
use futures::TryFutureExt;
|
|
use futures::TryFutureExt;
|
|
|
use plotters::prelude::ChartBuilder;
|
|
use plotters::prelude::ChartBuilder;
|
|
@@ -61,6 +65,20 @@ impl Default for WsState {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+#[derive(Clone, Copy)]
|
|
|
|
|
+struct Pane {
|
|
|
|
|
+ id: usize,
|
|
|
|
|
+ pub is_pinned: bool,
|
|
|
|
|
+}
|
|
|
|
|
+impl Pane {
|
|
|
|
|
+ fn new(id: usize) -> Self {
|
|
|
|
|
+ Self {
|
|
|
|
|
+ id,
|
|
|
|
|
+ is_pinned: false,
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
fn main() {
|
|
fn main() {
|
|
|
State::run(Settings {
|
|
State::run(Settings {
|
|
|
antialiasing: true,
|
|
antialiasing: true,
|
|
@@ -77,6 +95,16 @@ enum Message {
|
|
|
WsEvent(ws_binance::Event),
|
|
WsEvent(ws_binance::Event),
|
|
|
WsToggle(),
|
|
WsToggle(),
|
|
|
FetchEvent(Result<Vec<ws_binance::Kline>, std::string::String>),
|
|
FetchEvent(Result<Vec<ws_binance::Kline>, std::string::String>),
|
|
|
|
|
+ Split(pane_grid::Axis, pane_grid::Pane),
|
|
|
|
|
+ Clicked(pane_grid::Pane),
|
|
|
|
|
+ Dragged(pane_grid::DragEvent),
|
|
|
|
|
+ Resized(pane_grid::ResizeEvent),
|
|
|
|
|
+ TogglePin(pane_grid::Pane),
|
|
|
|
|
+ Maximize(pane_grid::Pane),
|
|
|
|
|
+ Restore,
|
|
|
|
|
+ Close(pane_grid::Pane),
|
|
|
|
|
+ CloseFocused,
|
|
|
|
|
+ ToggleLayoutLock,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
struct State {
|
|
struct State {
|
|
@@ -86,6 +114,11 @@ struct State {
|
|
|
selected_timeframe: Option<&'static str>,
|
|
selected_timeframe: Option<&'static str>,
|
|
|
ws_state: WsState,
|
|
ws_state: WsState,
|
|
|
ws_running: bool,
|
|
ws_running: bool,
|
|
|
|
|
+ panes: pane_grid::State<Pane>,
|
|
|
|
|
+ panes_created: usize,
|
|
|
|
|
+ focus: Option<pane_grid::Pane>,
|
|
|
|
|
+ first_pane: pane_grid::Pane,
|
|
|
|
|
+ pane_lock: bool,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
impl Application for State {
|
|
impl Application for State {
|
|
@@ -95,6 +128,7 @@ impl Application for State {
|
|
|
type Theme = Theme;
|
|
type Theme = Theme;
|
|
|
|
|
|
|
|
fn new(_flags: Self::Flags) -> (Self, Command<Self::Message>) {
|
|
fn new(_flags: Self::Flags) -> (Self, Command<Self::Message>) {
|
|
|
|
|
+ let (panes, first_pane) = pane_grid::State::new(Pane::new(0));
|
|
|
(
|
|
(
|
|
|
Self {
|
|
Self {
|
|
|
trades_chart: None,
|
|
trades_chart: None,
|
|
@@ -103,13 +137,13 @@ impl Application for State {
|
|
|
selected_timeframe: Some("1m"),
|
|
selected_timeframe: Some("1m"),
|
|
|
ws_state: WsState::Disconnected,
|
|
ws_state: WsState::Disconnected,
|
|
|
ws_running: false,
|
|
ws_running: false,
|
|
|
|
|
+ panes,
|
|
|
|
|
+ panes_created: 1,
|
|
|
|
|
+ focus: None,
|
|
|
|
|
+ first_pane,
|
|
|
|
|
+ pane_lock: false,
|
|
|
},
|
|
},
|
|
|
-
|
|
|
|
|
- Command::batch([
|
|
|
|
|
- //Command::perform(tokio::task::spawn_blocking(generate_data), |data| {
|
|
|
|
|
- // Message::DataLoaded(data.unwrap())
|
|
|
|
|
- //}),
|
|
|
|
|
- ]),
|
|
|
|
|
|
|
+ Command::none(),
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -132,19 +166,34 @@ impl Application for State {
|
|
|
dbg!(&self.ws_running);
|
|
dbg!(&self.ws_running);
|
|
|
if self.ws_running {
|
|
if self.ws_running {
|
|
|
self.trades_chart = Some(LineChart::new());
|
|
self.trades_chart = Some(LineChart::new());
|
|
|
- Command::perform(
|
|
|
|
|
|
|
+ let fetch_klines = Command::perform(
|
|
|
ws_binance::fetch_klines(self.selected_ticker.unwrap().to_string(), self.selected_timeframe.unwrap().to_string())
|
|
ws_binance::fetch_klines(self.selected_ticker.unwrap().to_string(), self.selected_timeframe.unwrap().to_string())
|
|
|
.map_err(|err| format!("{}", err)),
|
|
.map_err(|err| format!("{}", err)),
|
|
|
|klines| {
|
|
|klines| {
|
|
|
Message::FetchEvent(klines)
|
|
Message::FetchEvent(klines)
|
|
|
}
|
|
}
|
|
|
- )
|
|
|
|
|
|
|
+ );
|
|
|
|
|
+ if self.panes.len() == 1 {
|
|
|
|
|
+ let first_pane = self.first_pane;
|
|
|
|
|
+ let split_pane = Command::perform(
|
|
|
|
|
+ async move {
|
|
|
|
|
+ tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
|
|
|
|
+ (pane_grid::Axis::Horizontal, first_pane)
|
|
|
|
|
+ },
|
|
|
|
|
+ |(axis, pane)| {
|
|
|
|
|
+ Message::Split(axis, pane)
|
|
|
|
|
+ }
|
|
|
|
|
+ );
|
|
|
|
|
+ Command::batch(vec![fetch_klines, split_pane])
|
|
|
|
|
+ } else {
|
|
|
|
|
+ fetch_klines
|
|
|
|
|
+ }
|
|
|
} else {
|
|
} else {
|
|
|
self.trades_chart = None;
|
|
self.trades_chart = None;
|
|
|
self.candlestick_chart = None;
|
|
self.candlestick_chart = None;
|
|
|
Command::none()
|
|
Command::none()
|
|
|
}
|
|
}
|
|
|
- },
|
|
|
|
|
|
|
+ },
|
|
|
Message::FetchEvent(klines) => {
|
|
Message::FetchEvent(klines) => {
|
|
|
match klines {
|
|
match klines {
|
|
|
Ok(klines) => {
|
|
Ok(klines) => {
|
|
@@ -187,14 +236,130 @@ impl Application for State {
|
|
|
Command::none()
|
|
Command::none()
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
|
|
+ Message::Split(axis, pane) => {
|
|
|
|
|
+ let result =
|
|
|
|
|
+ self.panes.split(axis, pane, Pane::new(self.panes_created));
|
|
|
|
|
+
|
|
|
|
|
+ if let Some((pane, _)) = result {
|
|
|
|
|
+ self.focus = Some(pane);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ self.panes_created += 1;
|
|
|
|
|
+ Command::none()
|
|
|
|
|
+ }
|
|
|
|
|
+ Message::Clicked(pane) => {
|
|
|
|
|
+ self.focus = Some(pane);
|
|
|
|
|
+ Command::none()
|
|
|
|
|
+ }
|
|
|
|
|
+ Message::Resized(pane_grid::ResizeEvent { split, ratio }) => {
|
|
|
|
|
+ self.panes.resize(split, ratio);
|
|
|
|
|
+ Command::none()
|
|
|
|
|
+ }
|
|
|
|
|
+ Message::Dragged(pane_grid::DragEvent::Dropped {
|
|
|
|
|
+ pane,
|
|
|
|
|
+ target,
|
|
|
|
|
+ }) => {
|
|
|
|
|
+ self.panes.drop(pane, target);
|
|
|
|
|
+ Command::none()
|
|
|
|
|
+ }
|
|
|
|
|
+ Message::Dragged(_) => {
|
|
|
|
|
+ Command::none()
|
|
|
|
|
+ }
|
|
|
|
|
+ Message::TogglePin(pane) => {
|
|
|
|
|
+ if let Some(Pane { is_pinned, .. }) = self.panes.get_mut(pane) {
|
|
|
|
|
+ *is_pinned = !*is_pinned;
|
|
|
|
|
+ }
|
|
|
|
|
+ Command::none()
|
|
|
|
|
+ }
|
|
|
|
|
+ Message::Maximize(pane) => {
|
|
|
|
|
+ self.panes.maximize(pane);
|
|
|
|
|
+ Command::none()
|
|
|
|
|
+ },
|
|
|
|
|
+ Message::Restore => {
|
|
|
|
|
+ self.panes.restore();
|
|
|
|
|
+ Command::none()
|
|
|
|
|
+ }
|
|
|
|
|
+ Message::Close(pane) => {
|
|
|
|
|
+ if let Some((_, sibling)) = self.panes.close(pane) {
|
|
|
|
|
+ self.focus = Some(sibling);
|
|
|
|
|
+ }
|
|
|
|
|
+ Command::none()
|
|
|
|
|
+ }
|
|
|
|
|
+ Message::CloseFocused => {
|
|
|
|
|
+ if let Some(pane) = self.focus {
|
|
|
|
|
+ if let Some(Pane { is_pinned, .. }) = self.panes.get(pane) {
|
|
|
|
|
+ if !is_pinned {
|
|
|
|
|
+ if let Some((_, sibling)) = self.panes.close(pane) {
|
|
|
|
|
+ self.focus = Some(sibling);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ Command::none()
|
|
|
|
|
+ }
|
|
|
|
|
+ Message::ToggleLayoutLock => {
|
|
|
|
|
+ self.focus = None;
|
|
|
|
|
+ self.pane_lock = !self.pane_lock;
|
|
|
|
|
+ Command::none()
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
fn view(&self) -> Element<'_, Self::Message> {
|
|
fn view(&self) -> Element<'_, Self::Message> {
|
|
|
- let button_text = if self.ws_running { "Disconnect" } else { "Connect" };
|
|
|
|
|
- let ws_button = button(button_text).on_press(Message::WsToggle());
|
|
|
|
|
-
|
|
|
|
|
- let mut controls = Row::new()
|
|
|
|
|
|
|
+ let focus = self.focus;
|
|
|
|
|
+ let total_panes = self.panes.len();
|
|
|
|
|
+
|
|
|
|
|
+ let pane_grid = PaneGrid::new(&self.panes, |id, pane, is_maximized| {
|
|
|
|
|
+ let is_focused = focus == Some(id);
|
|
|
|
|
+
|
|
|
|
|
+ let content = pane_grid::Content::new(responsive(move |size| {
|
|
|
|
|
+ view_content(id, total_panes, pane.is_pinned, size, pane.id.to_string(), &self.trades_chart, &self.candlestick_chart)
|
|
|
|
|
+ }));
|
|
|
|
|
+
|
|
|
|
|
+ if self.pane_lock {
|
|
|
|
|
+ return content.style(style::pane_active);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let mut content = content.style(if is_focused {
|
|
|
|
|
+ style::pane_focused
|
|
|
|
|
+ } else {
|
|
|
|
|
+ style::pane_active
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ let title = if pane.id == 0 {
|
|
|
|
|
+ "Heatmap"
|
|
|
|
|
+ } else {
|
|
|
|
|
+ "Candlesticks"
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ if is_focused {
|
|
|
|
|
+ let title_bar = pane_grid::TitleBar::new(title)
|
|
|
|
|
+ .controls(view_controls(
|
|
|
|
|
+ id,
|
|
|
|
|
+ total_panes,
|
|
|
|
|
+ pane.is_pinned,
|
|
|
|
|
+ is_maximized,
|
|
|
|
|
+ ))
|
|
|
|
|
+ .padding(4)
|
|
|
|
|
+ .style(style::title_bar_focused);
|
|
|
|
|
+
|
|
|
|
|
+ content = content.title_bar(title_bar);
|
|
|
|
|
+ }
|
|
|
|
|
+ content
|
|
|
|
|
+ })
|
|
|
|
|
+ .width(Length::Fill)
|
|
|
|
|
+ .height(Length::Fill)
|
|
|
|
|
+ .spacing(10)
|
|
|
|
|
+ .on_click(Message::Clicked)
|
|
|
|
|
+ .on_drag(Message::Dragged)
|
|
|
|
|
+ .on_resize(10, Message::Resized);
|
|
|
|
|
+
|
|
|
|
|
+ let ws_button = button(if self.ws_running { "Disconnect" } else { "Connect" })
|
|
|
|
|
+ .on_press(Message::WsToggle());
|
|
|
|
|
+ let layout_lock = button(if self.pane_lock { "Unlock Layout" } else { "Lock Layout" })
|
|
|
|
|
+ .on_press(Message::ToggleLayoutLock);
|
|
|
|
|
+
|
|
|
|
|
+ let mut ws_controls = Row::new()
|
|
|
.spacing(20)
|
|
.spacing(20)
|
|
|
.align_items(Alignment::Center)
|
|
.align_items(Alignment::Center)
|
|
|
.push(ws_button);
|
|
.push(ws_button);
|
|
@@ -213,34 +378,29 @@ impl Application for State {
|
|
|
Message::TimeframeSelected,
|
|
Message::TimeframeSelected,
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
- controls = controls.push(timeframe_pick_list)
|
|
|
|
|
|
|
+ ws_controls = ws_controls.push(timeframe_pick_list)
|
|
|
.push(symbol_pick_list);
|
|
.push(symbol_pick_list);
|
|
|
} else {
|
|
} else {
|
|
|
- controls = controls.push(Text::new(self.selected_ticker.unwrap().to_string()).size(20));
|
|
|
|
|
|
|
+ ws_controls = ws_controls.push(Text::new(self.selected_ticker.unwrap().to_string()).size(20));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- let trades_chart = match self.trades_chart {
|
|
|
|
|
- Some(ref trades_chart) => trades_chart.view(),
|
|
|
|
|
- None => Text::new("").into(),
|
|
|
|
|
- };
|
|
|
|
|
- let candlestick_chart = match self.candlestick_chart {
|
|
|
|
|
- Some(ref candlestick_chart) => candlestick_chart.view(),
|
|
|
|
|
- None => Text::new("").into(),
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
let content = Column::new()
|
|
let content = Column::new()
|
|
|
- .spacing(20)
|
|
|
|
|
|
|
+ .spacing(10)
|
|
|
.align_items(Alignment::Start)
|
|
.align_items(Alignment::Start)
|
|
|
.width(Length::Fill)
|
|
.width(Length::Fill)
|
|
|
.height(Length::Fill)
|
|
.height(Length::Fill)
|
|
|
- .push(controls)
|
|
|
|
|
- .push(trades_chart)
|
|
|
|
|
- .push(candlestick_chart);
|
|
|
|
|
|
|
+ .push(
|
|
|
|
|
+ Row::new()
|
|
|
|
|
+ .push(ws_controls)
|
|
|
|
|
+ .push(Space::with_width(Length::Fill))
|
|
|
|
|
+ .push(layout_lock)
|
|
|
|
|
+ )
|
|
|
|
|
+ .push(pane_grid);
|
|
|
|
|
|
|
|
Container::new(content)
|
|
Container::new(content)
|
|
|
.width(Length::Fill)
|
|
.width(Length::Fill)
|
|
|
.height(Length::Fill)
|
|
.height(Length::Fill)
|
|
|
- .padding(20)
|
|
|
|
|
|
|
+ .padding(10)
|
|
|
.center_x()
|
|
.center_x()
|
|
|
.center_y()
|
|
.center_y()
|
|
|
.into()
|
|
.into()
|
|
@@ -258,6 +418,121 @@ impl Application for State {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+fn view_content<'a>(
|
|
|
|
|
+ _pane: pane_grid::Pane,
|
|
|
|
|
+ _total_panes: usize,
|
|
|
|
|
+ _is_pinned: bool,
|
|
|
|
|
+ _size: Size,
|
|
|
|
|
+ pane_id: String,
|
|
|
|
|
+ trades_chart: &'a Option<LineChart>,
|
|
|
|
|
+ candlestick_chart: &'a Option<CandlestickChart>,
|
|
|
|
|
+) -> Element<'a, Message> {
|
|
|
|
|
+
|
|
|
|
|
+ let chart = match pane_id.as_str() {
|
|
|
|
|
+ "0" => trades_chart.as_ref().map(LineChart::view).unwrap_or_else(|| Text::new("No data").into()),
|
|
|
|
|
+ "1" => candlestick_chart.as_ref().map(CandlestickChart::view).unwrap_or_else(|| Text::new("No data").into()),
|
|
|
|
|
+ _ => Text::new("No data").into(),
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ container(chart)
|
|
|
|
|
+ .width(Length::Fill)
|
|
|
|
|
+ .height(Length::Fill)
|
|
|
|
|
+ .into()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+fn view_controls<'a>(
|
|
|
|
|
+ pane: pane_grid::Pane,
|
|
|
|
|
+ total_panes: usize,
|
|
|
|
|
+ _is_pinned: bool,
|
|
|
|
|
+ is_maximized: bool,
|
|
|
|
|
+) -> Element<'a, Message> {
|
|
|
|
|
+ let mut row = row![].spacing(5);
|
|
|
|
|
+
|
|
|
|
|
+ if total_panes > 1 {
|
|
|
|
|
+ let buttons = if is_maximized {
|
|
|
|
|
+ vec![
|
|
|
|
|
+ ("Restore", Message::Restore),
|
|
|
|
|
+ //("Split Horizontally", Message::Split(pane_grid::Axis::Horizontal, pane)),
|
|
|
|
|
+ //("Split Vertically", Message::Split(pane_grid::Axis::Vertical, pane))
|
|
|
|
|
+ ]
|
|
|
|
|
+ } else {
|
|
|
|
|
+ vec![
|
|
|
|
|
+ ("Maximize", Message::Maximize(pane)),
|
|
|
|
|
+ //("Split Horizontally", Message::Split(pane_grid::Axis::Horizontal, pane)),
|
|
|
|
|
+ //("Split Vertically", Message::Split(pane_grid::Axis::Vertical, pane))
|
|
|
|
|
+ ]
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ for (content, message) in buttons {
|
|
|
|
|
+ row = row.push(
|
|
|
|
|
+ button(text(content).size(14))
|
|
|
|
|
+ .padding(3)
|
|
|
|
|
+ .on_press(message),
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ //let close = button(text("Close").size(14))
|
|
|
|
|
+ // .padding(3)
|
|
|
|
|
+ // .on_press_maybe(if total_panes > 1 && !is_pinned {
|
|
|
|
|
+ // Some(Message::Close(pane))
|
|
|
|
|
+ // } else {
|
|
|
|
|
+ // None
|
|
|
|
|
+ // });
|
|
|
|
|
+ //row.push(close).into()
|
|
|
|
|
+ row.into()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+mod style {
|
|
|
|
|
+ use iced::widget::container;
|
|
|
|
|
+ use iced::{Border, Theme};
|
|
|
|
|
+
|
|
|
|
|
+ pub fn title_bar_active(theme: &Theme) -> container::Appearance {
|
|
|
|
|
+ let palette = theme.extended_palette();
|
|
|
|
|
+
|
|
|
|
|
+ container::Appearance {
|
|
|
|
|
+ text_color: Some(palette.background.strong.text),
|
|
|
|
|
+ background: Some(palette.background.strong.color.into()),
|
|
|
|
|
+ ..Default::default()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ pub fn title_bar_focused(theme: &Theme) -> container::Appearance {
|
|
|
|
|
+ let palette = theme.extended_palette();
|
|
|
|
|
+
|
|
|
|
|
+ container::Appearance {
|
|
|
|
|
+ text_color: Some(palette.primary.strong.text),
|
|
|
|
|
+ background: Some(palette.primary.strong.color.into()),
|
|
|
|
|
+ ..Default::default()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ pub fn pane_active(theme: &Theme) -> container::Appearance {
|
|
|
|
|
+ let palette = theme.extended_palette();
|
|
|
|
|
+
|
|
|
|
|
+ container::Appearance {
|
|
|
|
|
+ //background: Some(palette.background.weak.color.into()),
|
|
|
|
|
+ border: Border {
|
|
|
|
|
+ width: 2.0,
|
|
|
|
|
+ color: palette.background.strong.color,
|
|
|
|
|
+ ..Border::default()
|
|
|
|
|
+ },
|
|
|
|
|
+ ..Default::default()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ pub fn pane_focused(theme: &Theme) -> container::Appearance {
|
|
|
|
|
+ let palette = theme.extended_palette();
|
|
|
|
|
+
|
|
|
|
|
+ container::Appearance {
|
|
|
|
|
+ //background: Some(palette.background.weak.color.into()),
|
|
|
|
|
+ border: Border {
|
|
|
|
|
+ width: 2.0,
|
|
|
|
|
+ color: palette.primary.strong.color,
|
|
|
|
|
+ ..Border::default()
|
|
|
|
|
+ },
|
|
|
|
|
+ ..Default::default()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
struct CandlestickChart {
|
|
struct CandlestickChart {
|
|
|
cache: Cache,
|
|
cache: Cache,
|
|
|
data_points: BTreeMap<DateTime<Utc>, (f32, f32, f32, f32)>,
|
|
data_points: BTreeMap<DateTime<Utc>, (f32, f32, f32, f32)>,
|
|
@@ -346,8 +621,8 @@ impl Chart<Message> for CandlestickChart {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
let mut chart = chart
|
|
let mut chart = chart
|
|
|
- .x_label_area_size(28)
|
|
|
|
|
- .y_label_area_size(28)
|
|
|
|
|
|
|
+ .x_label_area_size(20)
|
|
|
|
|
+ .y_label_area_size(32)
|
|
|
.margin(20)
|
|
.margin(20)
|
|
|
.build_cartesian_2d(oldest_time..newest_time, y_min..y_max)
|
|
.build_cartesian_2d(oldest_time..newest_time, y_min..y_max)
|
|
|
.expect("failed to build chart");
|
|
.expect("failed to build chart");
|
|
@@ -384,7 +659,6 @@ impl Chart<Message> for CandlestickChart {
|
|
|
).expect("failed to draw chart data");
|
|
).expect("failed to draw chart data");
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
struct LineChart {
|
|
struct LineChart {
|
|
|
cache: Cache,
|
|
cache: Cache,
|
|
|
data_points: VecDeque<(DateTime<Utc>, f32, f32, bool)>,
|
|
data_points: VecDeque<(DateTime<Utc>, f32, f32, bool)>,
|
|
@@ -490,8 +764,8 @@ impl Chart<Message> for LineChart {
|
|
|
}).collect();
|
|
}).collect();
|
|
|
|
|
|
|
|
let mut chart = chart
|
|
let mut chart = chart
|
|
|
- .x_label_area_size(28)
|
|
|
|
|
- .y_label_area_size(28)
|
|
|
|
|
|
|
+ .x_label_area_size(20)
|
|
|
|
|
+ .y_label_area_size(32)
|
|
|
.margin(20)
|
|
.margin(20)
|
|
|
.build_cartesian_2d(oldest_time..newest_time, y_min..y_max)
|
|
.build_cartesian_2d(oldest_time..newest_time, y_min..y_max)
|
|
|
.expect("failed to build chart");
|
|
.expect("failed to build chart");
|
|
@@ -554,7 +828,7 @@ impl Chart<Message> for LineChart {
|
|
|
chart
|
|
chart
|
|
|
.draw_series(
|
|
.draw_series(
|
|
|
recent_data_points.iter().map(|&(time, price, qty, is_sell)| {
|
|
recent_data_points.iter().map(|&(time, price, qty, is_sell)| {
|
|
|
- let radius = 1.0 + (qty - qty_min) * (30.0 - 1.0) / (qty_max - qty_min);
|
|
|
|
|
|
|
+ let radius = 1.0 + (qty - qty_min) * (35.0 - 1.0) / (qty_max - qty_min);
|
|
|
let color = if is_sell { RGBColor(192, 80, 77) } else { RGBColor(81, 205, 160)};
|
|
let color = if is_sell { RGBColor(192, 80, 77) } else { RGBColor(81, 205, 160)};
|
|
|
Circle::new(
|
|
Circle::new(
|
|
|
(time, price),
|
|
(time, price),
|