Repository: tarkah/tickrs (language: rust)

Issue:
Is it possible to show the volumes too?



Discussion / hints:
I have volume data so this should be doable. I'm afraid out of the box, it won't work well to graph volumes along w/ prices since the y-axis range will be completely different. So I have 2 options I think:

1. Add a keyboard shortcut to "toggle" between prices being graphed and volumes. So only 1 is graphed at a time, so the y-axis can adjust accordlingly.
2. Normalize volumes against the min / max of the prices so I can graph it alongside the price graph. There won't be any axis labels to reference for volumes however.

The other thing is I'd prefer to do bar graphs for volumes, which the TUI library I'm using supports, but it'd have to be a separate graph from the price movement. I'll play around with all 3 of these ideas and see what works best, might take me a little while to tackle.
Great! Thank you for the quick response :)
If I can express a preference I'd vote for option 3.
Here are a few rough mockups, I still need to add some logic to make sure the bars extend the entire width perfectly. There's a bunch of caveats, but it's at least a good indicator of volume trends. Since I need to draw distinct bars, the "timeframe" of each bar is dependent on how wide your terminal is.

Do you like it better when the bars are clipped by the price lines? Or where the bars overlap with price lines?

![Code_BgA5vAh5UW](https://user-images.githubusercontent.com/10239377/106824710-3c409800-6638-11eb-806d-b4776a6611c9.png)
![Code_OMDI66KNlo](https://user-images.githubusercontent.com/10239377/106824708-3ba80180-6638-11eb-89ff-cdc629644015.png)

Do you want to see an axis legend on the right side, similar to the one for prices on the left? Or is having the days total volume in the header along w/ current / high / low price fine?
Ok, I think I found the better layout... And I figured out the logic to ensure it'll always fit the width. I'll try to add some y-axis legend just like w/ price and clean up the format a little. How do you like this?

![image](https://user-images.githubusercontent.com/10239377/106831513-e7efe500-6644-11eb-82c1-5ee3ed6fb024.png)

![image](https://user-images.githubusercontent.com/10239377/106831649-1b327400-6645-11eb-88f9-68952ed5e8d4.png)

![image](https://user-images.githubusercontent.com/10239377/106831671-25ed0900-6645-11eb-8d3e-977b2ac8a2f1.png)




Looks great! Well done! 🔝 

Fix patch:
diff --git a/.github/workflows/lints.yml b/.github/workflows/lints.yml
index 3fef1ba..f1d4ad5 100644
--- a/.github/workflows/lints.yml
+++ b/.github/workflows/lints.yml
@@ -45,11 +45,12 @@ jobs:
             target
           key: ${{ runner.os }}-lints-${{ hashFiles('**/Cargo.lock') }}
 
-      - name: Install stable toolchain
+      # use nightly toolchain for rustfmt
+      - name: Install nightly toolchain
         uses: actions-rs/toolchain@v1
         with:
           profile: minimal
-          toolchain: stable
+          toolchain: nightly
           override: true
           components: rustfmt, clippy
 
diff --git a/api/src/client.rs b/api/src/client.rs
index 1ceabfe..7d3a074 100644
--- a/api/src/client.rs
+++ b/api/src/client.rs
@@ -1,12 +1,12 @@
-use crate::{
-    model::{ChartData, CompanyData, OptionsHeader, Response, ResponseType},
-    Interval, Range,
-};
+use std::collections::HashMap;
+
 use anyhow::{bail, Context, Result};
 use futures::AsyncReadExt;
 use http::Uri;
 use isahc::HttpClient;
-use std::collections::HashMap;
+
+use crate::model::{ChartData, CompanyData, OptionsHeader, Response, ResponseType};
+use crate::{Interval, Range};
 
 #[derive(Debug)]
 pub struct Client {
diff --git a/api/src/lib.rs b/api/src/lib.rs
index ea9f4e7..431d6dd 100644
--- a/api/src/lib.rs
+++ b/api/src/lib.rs
@@ -1,8 +1,8 @@
+pub use self::client::Client;
+
 mod client;
 pub mod model;
 
-pub use client::Client;
-
 #[derive(Copy, Clone)]
 pub enum Interval {
     Minute1,
diff --git a/api/src/model.rs b/api/src/model.rs
index 7699e3a..e973662 100644
--- a/api/src/model.rs
+++ b/api/src/model.rs
@@ -1,11 +1,10 @@
-use anyhow::{bail, Result};
-use serde::{
-    de::{SeqAccess, Visitor},
-    Deserialize, Deserializer,
-};
 use std::fmt;
 use std::marker::PhantomData;
 
+use anyhow::{bail, Result};
+use serde::de::{SeqAccess, Visitor};
+use serde::{Deserialize, Deserializer};
+
 pub(crate) enum ResponseType {
     Chart,
     Company,
@@ -160,6 +159,7 @@ pub struct CompanyPrice {
     pub regular_market_price: CompanyMarketPrice,
     pub regular_market_previous_close: CompanyMarketPrice,
     pub post_market_price: CompanyPostMarketPrice,
+    pub regular_market_volume: CompanyMarketPrice,
     pub currency: Option<String>,
 }
 
@@ -168,6 +168,7 @@ pub struct CompanyPrice {
 pub struct CompanyMarketPrice {
     #[serde(rename = "raw")]
     pub price: f64,
+    pub fmt: String,
 }
 
 #[serde(rename_all = "camelCase")]
diff --git a/rustfmt.toml b/rustfmt.toml
new file mode 100644
index 0000000..4c35310
--- /dev/null
+++ b/rustfmt.toml
@@ -0,0 +1,3 @@
+edition = "2018"
+imports_granularity = "Module"
+group_imports = "StdExternalCrate"
\ No newline at end of file
diff --git a/src/app.rs b/src/app.rs
index b848003..bcd6dd6 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -1,8 +1,8 @@
+use crossterm::event::Event;
+
 use crate::common::TimeFrame;
 use crate::widget;
 
-use crossterm::event::Event;
-
 #[derive(PartialEq, Clone, Copy, Debug)]
 pub enum Mode {
     AddStock,
diff --git a/src/cli.rs b/src/cli.rs
index 7090e2c..35ade47 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -1,7 +1,7 @@
-use crate::common::TimeFrame;
-
 use structopt::StructOpt;
 
+use crate::common::TimeFrame;
+
 #[derive(Debug, StructOpt, Clone)]
 #[structopt(
     name = "tickrs",
@@ -39,6 +39,9 @@ pub struct Opt {
     #[structopt(long)]
     /// Truncate pre market graphing to only 30 minutes prior to markets opening
     pub trunc_pre: bool,
+    #[structopt(long)]
+    /// Show volumes graph
+    pub show_volumes: bool,
 }
 
 pub fn get_opts() -> Opt {
diff --git a/src/common.rs b/src/common.rs
index 754839b..3b32a94 100644
--- a/src/common.rs
+++ b/src/common.rs
@@ -1,9 +1,11 @@
-use api::{model::ChartData, Range};
+use std::str::FromStr;
+use std::time::Duration;
+
 use chrono::{Local, TimeZone, Utc};
 use itertools::izip;
 
-use std::str::FromStr;
-use std::time::Duration;
+use crate::api::model::ChartData;
+use crate::api::Range;
 
 #[derive(PartialEq, Clone, Copy, PartialOrd, Debug)]
 pub enum TimeFrame {
diff --git a/src/draw.rs b/src/draw.rs
index e3bce5b..08f5f20 100644
--- a/src/draw.rs
+++ b/src/draw.rs
@@ -1,15 +1,16 @@
-use crate::app::{App, Mode};
-use crate::common::TimeFrame;
-use crate::widget::{
-    block, AddStockWidget, OptionsWidget, StockSummaryWidget, StockWidget, HELP_HEIGHT, HELP_WIDTH,
-};
-
 use tui::backend::Backend;
 use tui::layout::{Alignment, Constraint, Direction, Layout, Rect};
 use tui::style::{Color, Style};
 use tui::widgets::{Block, Borders, Paragraph, Tabs, Text};
 use tui::{Frame, Terminal};
 
+use crate::app::{App, Mode};
+use crate::common::TimeFrame;
+use crate::widget::{
+    block, AddStockWidget, OptionsWidget, StockSummaryWidget, StockWidget, HELP_HEIGHT, HELP_WIDTH,
+};
+use crate::SHOW_VOLUMES;
+
 pub fn draw<B: Backend>(terminal: &mut Terminal<B>, app: &mut App) {
     let current_size = terminal.size().unwrap_or_default();
 
@@ -172,8 +173,11 @@ fn draw_summary<B: Backend>(frame: &mut Frame<B>, app: &mut App, area: Rect) {
     let border = block::new(" Summary ", None);
     frame.render_widget(border, area);
 
+    let show_volumes = *SHOW_VOLUMES.read().unwrap();
+    let stock_widget_height = if show_volumes { 7 } else { 6 };
+
     let height = area.height;
-    let num_to_render = (((height - 5) / 6) as usize).min(app.stocks.len());
+    let num_to_render = (((height - 5) / stock_widget_height) as usize).min(app.stocks.len());
 
     // layouy[0] - Header
     // layouy[1] - Summary window
@@ -182,7 +186,7 @@ fn draw_summary<B: Backend>(frame: &mut Frame<B>, app: &mut App, area: Rect) {
         .constraints(
             [
                 Constraint::Length(2),
-                Constraint::Length((num_to_render * 6) as u16),
+                Constraint::Length((num_to_render * stock_widget_height as usize) as u16),
                 Constraint::Min(0),
             ]
             .as_ref(),
@@ -221,7 +225,7 @@ fn draw_summary<B: Backend>(frame: &mut Frame<B>, app: &mut App, area: Rect) {
 
     let contraints = app.stocks[..num_to_render]
         .iter()
-        .map(|_| Constraint::Length(6))
+        .map(|_| Constraint::Length(stock_widget_height))
         .collect::<Vec<_>>();
 
     let stock_layout = Layout::default().constraints(contraints).split(layout[1]);
@@ -232,12 +236,20 @@ fn draw_summary<B: Backend>(frame: &mut Frame<B>, app: &mut App, area: Rect) {
 
     // Draw time frame
     {
-        let offset = layout[2].height - 3;
-
-        layout[2] = add_padding(layout[2], offset, PaddingDirection::Top);
         layout[2] = add_padding(layout[2], 2, PaddingDirection::Left);
         layout[2] = add_padding(layout[2], 2, PaddingDirection::Right);
 
+        // Clear out empty area
+        frame.render_widget(
+            Block::default()
+                .borders(Borders::TOP)
+                .border_style(Style::default().bg(Color::Black).fg(Color::Black)),
+            layout[2],
+        );
+
+        let offset = layout[2].height - 3;
+        layout[2] = add_padding(layout[2], offset, PaddingDirection::Top);
+
         frame.render_widget(Block::default().borders(Borders::TOP), layout[2]);
 
         layout[2] = add_padding(layout[2], 1, PaddingDirection::Top);
diff --git a/src/event.rs b/src/event.rs
index bbf6533..e347058 100644
--- a/src/event.rs
+++ b/src/event.rs
@@ -1,10 +1,9 @@
-use crate::cleanup_terminal;
-use crate::widget::options;
-use crate::{app, ENABLE_PRE_POST, SHOW_X_LABELS};
-
 use crossbeam_channel::Sender;
 use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
 
+use crate::widget::options;
+use crate::{app, cleanup_terminal, ENABLE_PRE_POST, SHOW_VOLUMES, SHOW_X_LABELS};
+
 pub fn handle_keys_display_stock(
     key_event: KeyEvent,
     mut app: &mut app::App,
@@ -18,6 +17,7 @@ pub fn handle_keys_display_stock(
             }
             KeyCode::Right => {
                 app.stocks[app.current_tab].time_frame_up();
+
                 let _ = request_redraw.try_send(());
             }
             KeyCode::Char('/') => {
@@ -40,8 +40,8 @@ pub fn handle_keys_display_stock(
             }
             KeyCode::Char('p') => {
                 let mut guard = ENABLE_PRE_POST.write().unwrap();
-
                 *guard = !*guard;
+                let _ = request_redraw.try_send(());
             }
             KeyCode::Char('q') => {
                 cleanup_terminal();
@@ -73,6 +73,11 @@ pub fn handle_keys_display_stock(
                 *show_x_labels = !*show_x_labels;
                 let _ = request_redraw.try_send(());
             }
+            KeyCode::Char('v') => {
+                let mut show_volumes = SHOW_VOLUMES.write().unwrap();
+                *show_volumes = !*show_volumes;
+                let _ = request_redraw.try_send(());
+            }
             KeyCode::Tab => {
                 if app.current_tab == app.stocks.len() - 1 {
                     app.current_tab = 0;
@@ -176,8 +181,8 @@ pub fn handle_keys_display_summary(
             }
             KeyCode::Char('p') => {
                 let mut guard = ENABLE_PRE_POST.write().unwrap();
-
                 *guard = !*guard;
+                let _ = request_redraw.try_send(());
             }
             KeyCode::Char('q') => {
                 cleanup_terminal();
@@ -187,6 +192,11 @@ pub fn handle_keys_display_summary(
                 app.mode = app::Mode::DisplayStock;
                 let _ = request_redraw.try_send(());
             }
+            KeyCode::Char('v') => {
+                let mut show_volumes = SHOW_VOLUMES.write().unwrap();
+                *show_volumes = !*show_volumes;
+                let _ = request_redraw.try_send(());
+            }
             KeyCode::Char('?') => {
                 app.previous_mode = app.mode;
                 app.mode = app::Mode::Help;
diff --git a/src/main.rs b/src/main.rs
index 93b1f0f..a1bc78c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,20 +1,19 @@
 extern crate tickrs_api as api;
 
-use crossbeam_channel::{bounded, select, unbounded, Receiver, Sender};
+use std::io::{self, Write};
+use std::sync::{Arc, Mutex, RwLock};
+use std::time::Duration;
+use std::{panic, thread};
 
+use crossbeam_channel::{bounded, select, unbounded, Receiver, Sender};
 use crossterm::event::{Event, MouseEvent};
 use crossterm::{cursor, execute, terminal, write_ansi_code};
-
 use lazy_static::lazy_static;
-
 use tui::backend::CrosstermBackend;
 use tui::Terminal;
 
-use std::io::{self, Write};
-use std::panic;
-use std::sync::{Arc, Mutex, RwLock};
-use std::thread;
-use std::time::Duration;
+use crate::app::DebugInfo;
+use crate::common::TimeFrame;
 
 mod app;
 mod cli;
@@ -25,9 +24,6 @@ mod service;
 mod task;
 mod widget;
 
-use crate::app::DebugInfo;
-use crate::common::TimeFrame;
-
 lazy_static! {
     pub static ref OPTS: cli::Opt = cli::get_opts();
     pub static ref UPDATE_INTERVAL: u64 = OPTS.update_interval;
@@ -39,6 +35,7 @@ lazy_static! {
     pub static ref SHOW_X_LABELS: RwLock<bool> = RwLock::new(OPTS.show_x_labels);
     pub static ref ENABLE_PRE_POST: RwLock<bool> = RwLock::new(OPTS.enable_pre_post);
     pub static ref TRUNC_PRE: bool = OPTS.trunc_pre;
+    pub static ref SHOW_VOLUMES: RwLock<bool> = RwLock::new(OPTS.show_volumes);
 }
 
 fn main() {
diff --git a/src/service/options.rs b/src/service/options.rs
index f92dc93..5f86c7d 100644
--- a/src/service/options.rs
+++ b/src/service/options.rs
@@ -1,8 +1,7 @@
 use super::*;
+use crate::api::model;
 use crate::task::*;
 
-use api::model;
-
 pub struct OptionsService {
     symbol: String,
     expiration_dates_handle: AsyncTaskHandle<Vec<i64>>,
diff --git a/src/service/stock.rs b/src/service/stock.rs
index bbf303f..2be8e0c 100644
--- a/src/service/stock.rs
+++ b/src/service/stock.rs
@@ -1,12 +1,11 @@
 use super::*;
+use crate::api::model::{ChartMeta, CompanyData};
 use crate::common::*;
 use crate::task::*;
 
-use api::model::{ChartMeta, CompanyData};
-
 pub struct StockService {
     symbol: String,
-    current_price_handle: AsyncTaskHandle<(f64, Option<f64>)>,
+    current_price_handle: AsyncTaskHandle<(f64, Option<f64>, String)>,
     prices_handle: AsyncTaskHandle<(TimeFrame, ChartMeta, Vec<Price>)>,
     company_handle: AsyncTaskHandle<CompanyData>,
 }
@@ -40,7 +39,7 @@ impl StockService {
 
 #[derive(Debug)]
 pub enum Update {
-    NewPrice((f64, Option<f64>)),
+    NewPrice((f64, Option<f64>, String)),
     Prices((TimeFrame, ChartMeta, Vec<Price>)),
     CompanyData(CompanyData),
 }
diff --git a/src/task.rs b/src/task.rs
index d642eea..82ed7f5 100644
--- a/src/task.rs
+++ b/src/task.rs
@@ -1,11 +1,16 @@
-use crate::{DATA_RECEIVED, UPDATE_INTERVAL};
+use std::time::{Duration, Instant};
 
 use async_std::sync::Arc;
 use async_std::task;
 use crossbeam_channel::{bounded, select, unbounded, Receiver, Sender};
 use futures::future::BoxFuture;
 
-use std::time::{Duration, Instant};
+pub use self::company::Company;
+pub use self::current_price::CurrentPrice;
+pub use self::options_data::OptionsData;
+pub use self::options_dates::OptionsDates;
+pub use self::prices::Prices;
+use crate::{DATA_RECEIVED, UPDATE_INTERVAL};
 
 mod company;
 mod current_price;
@@ -13,12 +18,6 @@ mod options_data;
 mod options_dates;
 mod prices;
 
-pub use company::Company;
-pub use current_price::CurrentPrice;
-pub use options_data::OptionsData;
-pub use options_dates::OptionsDates;
-pub use prices::Prices;
-
 /// Trait to define a type that spawns an Async Task to complete background
 /// work.
 pub trait AsyncTask: 'static {
diff --git a/src/task/company.rs b/src/task/company.rs
index 849f53e..224269c 100644
--- a/src/task/company.rs
+++ b/src/task/company.rs
@@ -1,9 +1,9 @@
-use super::*;
-
-use api::model::CompanyData;
 use async_std::sync::Arc;
 use futures::future::BoxFuture;
 
+use super::*;
+use crate::api::model::CompanyData;
+
 /// Returns a companies profile information. Only needs to be returned once.
 pub struct Company {
     symbol: String,
diff --git a/src/task/current_price.rs b/src/task/current_price.rs
index f4afd7c..9a7c4ff 100644
--- a/src/task/current_price.rs
+++ b/src/task/current_price.rs
@@ -1,8 +1,8 @@
-use super::*;
-
 use async_std::sync::Arc;
 use futures::future::BoxFuture;
 
+use super::*;
+
 /// Returns the current price, only if it has changed
 pub struct CurrentPrice {
     symbol: String,
@@ -16,7 +16,7 @@ impl CurrentPrice {
 
 impl AsyncTask for CurrentPrice {
     type Input = (String, api::Client);
-    type Response = (f64, Option<f64>);
+    type Response = (f64, Option<f64>, String);
 
     fn update_interval(&self) -> Option<Duration> {
         Some(Duration::from_secs(1))
@@ -36,7 +36,9 @@ impl AsyncTask for CurrentPrice {
 
                 let post_price = response.price.post_market_price.price;
 
-                Some((regular_price, post_price))
+                let volume = response.price.regular_market_volume.fmt;
+
+                Some((regular_price, post_price, volume))
             } else {
                 None
             }
diff --git a/src/task/options_data.rs b/src/task/options_data.rs
index 316fc42..405ce76 100644
--- a/src/task/options_data.rs
+++ b/src/task/options_data.rs
@@ -1,10 +1,10 @@
-use super::*;
+use std::time::Duration;
 
-use api::model;
 use async_std::sync::Arc;
 use futures::future::BoxFuture;
 
-use std::time::Duration;
+use super::*;
+use crate::api::model;
 
 /// Returns options data for a company
 pub struct OptionsData {
diff --git a/src/task/options_dates.rs b/src/task/options_dates.rs
index 5790616..6fbfaa5 100644
--- a/src/task/options_dates.rs
+++ b/src/task/options_dates.rs
@@ -1,9 +1,9 @@
-use super::*;
+use std::time::Duration;
 
 use async_std::sync::Arc;
 use futures::future::BoxFuture;
 
-use std::time::Duration;
+use super::*;
 
 /// Returns options expiration dates for a company
 pub struct OptionsDates {
diff --git a/src/task/prices.rs b/src/task/prices.rs
index 1e44895..921ef04 100644
--- a/src/task/prices.rs
+++ b/src/task/prices.rs
@@ -1,10 +1,11 @@
-use super::*;
-use crate::common::{chart_data_to_prices, Price, TimeFrame};
-
-use api::{model::ChartMeta, Interval};
 use async_std::sync::Arc;
 use futures::future::BoxFuture;
 
+use super::*;
+use crate::api::model::ChartMeta;
+use crate::api::Interval;
+use crate::common::{chart_data_to_prices, Price, TimeFrame};
+
 /// Returns an array of prices, depending on the TimeFrame chosen
 pub struct Prices {
     symbol: String,
diff --git a/src/widget.rs b/src/widget.rs
index de50055..fe581bc 100644
--- a/src/widget.rs
+++ b/src/widget.rs
@@ -1,12 +1,12 @@
+pub use add_stock::{AddStockState, AddStockWidget};
+pub use help::{HelpWidget, HELP_HEIGHT, HELP_WIDTH};
+pub use options::{OptionsState, OptionsWidget};
+pub use stock::{StockState, StockWidget};
+pub use stock_summary::StockSummaryWidget;
+
 mod add_stock;
 pub mod block;
 mod help;
 pub mod options;
 mod stock;
 mod stock_summary;
-
-pub use add_stock::{AddStockState, AddStockWidget};
-pub use help::{HelpWidget, HELP_HEIGHT, HELP_WIDTH};
-pub use options::{OptionsState, OptionsWidget};
-pub use stock::{StockState, StockWidget};
-pub use stock_summary::StockSummaryWidget;
diff --git a/src/widget/add_stock.rs b/src/widget/add_stock.rs
index 186eae3..79409f1 100644
--- a/src/widget/add_stock.rs
+++ b/src/widget/add_stock.rs
@@ -1,10 +1,10 @@
-use super::block;
-
 use tui::buffer::Buffer;
 use tui::layout::{Alignment, Rect};
 use tui::style::{Color, Modifier, Style};
 use tui::widgets::{Paragraph, StatefulWidget, Text, Widget};
 
+use super::block;
+
 pub struct AddStockState {
     search_string: String,
     has_user_input: bool,
diff --git a/src/widget/help.rs b/src/widget/help.rs
index 548fb4e..a17f248 100644
--- a/src/widget/help.rs
+++ b/src/widget/help.rs
@@ -1,11 +1,11 @@
-use super::block;
-use crate::draw::{add_padding, PaddingDirection};
-
 use tui::buffer::Buffer;
 use tui::layout::Rect;
 use tui::style::Style;
 use tui::widgets::{Paragraph, Text, Widget};
 
+use super::block;
+use crate::draw::{add_padding, PaddingDirection};
+
 const TEXT: &str = r#"
 Quit: q or <Ctrl+c>
 Add Stock:
@@ -30,11 +30,12 @@ Toggle Summary Pane:
   - s: toggle pane
 Graphing Display:
   - p: toggle pre / post market
+  - v: toggle volumes graph
   - x: toggle labels
 "#;
 
 pub const HELP_WIDTH: u16 = 35;
-pub const HELP_HEIGHT: u16 = 27;
+pub const HELP_HEIGHT: u16 = 28;
 
 #[derive(Copy, Clone)]
 pub struct HelpWidget {}
diff --git a/src/widget/options.rs b/src/widget/options.rs
index ec935d1..18fce2e 100644
--- a/src/widget/options.rs
+++ b/src/widget/options.rs
@@ -1,10 +1,6 @@
-use super::block;
-use crate::draw::{add_padding, PaddingDirection};
-use crate::service::{self, Service};
+use std::collections::HashMap;
 
-use api::model::{OptionsData, OptionsQuote};
 use chrono::NaiveDateTime;
-use std::collections::HashMap;
 use tui::buffer::Buffer;
 use tui::layout::{Alignment, Constraint, Direction, Layout, Rect};
 use tui::style::{Color, Modifier, Style};
@@ -13,6 +9,11 @@ use tui::widgets::{
     Widget,
 };
 
+use super::block;
+use crate::api::model::{OptionsData, OptionsQuote};
+use crate::draw::{add_padding, PaddingDirection};
+use crate::service::{self, Service};
+
 #[derive(Clone, Copy, PartialEq)]
 enum OptionType {
     Call,
diff --git a/src/widget/stock.rs b/src/widget/stock.rs
index 64885d0..f739a38 100644
--- a/src/widget/stock.rs
+++ b/src/widget/stock.rs
@@ -1,16 +1,21 @@
-use super::{block, OptionsState};
-use crate::common::*;
-use crate::draw::{add_padding, PaddingDirection};
-use crate::service::{self, Service};
-use crate::{ENABLE_PRE_POST, HIDE_PREV_CLOSE, HIDE_TOGGLE, SHOW_X_LABELS, TIME_FRAME, TRUNC_PRE};
-
-use api::model::{ChartMeta, CompanyData};
+use itertools::Itertools;
 use tui::buffer::Buffer;
 use tui::layout::{Alignment, Constraint, Direction, Layout, Rect};
 use tui::style::{Color, Modifier, Style};
-use tui::symbols::Marker;
+use tui::symbols::{bar, Marker};
 use tui::widgets::{
-    Axis, Block, Borders, Chart, Dataset, GraphType, Paragraph, StatefulWidget, Tabs, Text, Widget,
+    Axis, BarChart, Block, Borders, Chart, Dataset, GraphType, Paragraph, StatefulWidget, Tabs,
+    Text, Widget,
+};
+
+use super::{block, OptionsState};
+use crate::api::model::{ChartMeta, CompanyData};
+use crate::common::*;
+use crate::draw::{add_padding, PaddingDirection};
+use crate::service::{self, Service};
+use crate::{
+    ENABLE_PRE_POST, HIDE_PREV_CLOSE, HIDE_TOGGLE, SHOW_VOLUMES, SHOW_X_LABELS, TIME_FRAME,
+    TRUNC_PRE,
 };
 
 const NUM_LOADING_TICKS: usize = 4;
@@ -22,6 +27,7 @@ pub struct StockState {
     pub current_regular_price: f64,
     pub current_post_price: Option<f64>,
     pub prev_close_price: Option<f64>,
+    pub reg_mkt_volume: Option<String>,
     pub prices: [Vec<Price>; 7],
     pub time_frame: TimeFrame,
     pub show_options: bool,
@@ -44,6 +50,7 @@ impl StockState {
             current_regular_price: 0.0,
             current_post_price: None,
             prev_close_price: None,
+            reg_mkt_volume: None,
             prices: [vec![], vec![], vec![], vec![], vec![], vec![], vec![]],
             time_frame,
             show_options: false,
@@ -84,6 +91,28 @@ impl StockState {
         })
     }
 
+    pub fn volumes(&self) -> Vec<u64> {
+        let (start, end) = self.start_end();
+
+        let mut prices = self.prices();
+
+        if self.time_frame == TimeFrame::Day1 {
+            let times = MarketHours(start, end);
+
+            times
+                .map(|t| {
+                    if let Some(p) = prices.find(|p| p.date == t) {
+                        p.volume
+                    } else {
+                        0
+                    }
+                })
+                .collect()
+        } else {
+            prices.map(|p| p.volume).collect()
+        }
+    }
+
     pub fn current_price(&self) -> f64 {
         let enable_pre_post = { *ENABLE_PRE_POST.read().unwrap() };
 
@@ -99,9 +128,10 @@ impl StockState {
 
         for update in updates {
             match update {
-                service::stock::Update::NewPrice((regular, post)) => {
+                service::stock::Update::NewPrice((regular, post, vol)) => {
                     self.current_regular_price = regular;
                     self.current_post_price = post;
+                    self.reg_mkt_volume = Some(vol);
                 }
                 service::stock::Update::Prices((time_frame, chart_meta, prices)) => {
                     self.prices[time_frame.idx()] = prices;
@@ -398,8 +428,9 @@ impl StatefulWidget for StockWidget {
     fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
         let pct_change = state.pct_change();
 
-        let show_x_labes = SHOW_X_LABELS.read().map_or(false, |l| *l);
+        let show_x_labels = SHOW_X_LABELS.read().map_or(false, |l| *l);
         let enable_pre_post = *ENABLE_PRE_POST.read().unwrap();
+        let show_volumes = *SHOW_VOLUMES.read().unwrap();
 
         let loaded = state.loaded();
         state.loading_tick();
@@ -442,7 +473,7 @@ impl StatefulWidget for StockWidget {
             .constraints(
                 [
                     Constraint::Length(2),
-                    Constraint::Length(5),
+                    Constraint::Length(6),
                     Constraint::Min(0),
                     Constraint::Length(2),
                     Constraint::Length(1),
@@ -470,6 +501,7 @@ impl StatefulWidget for StockWidget {
             info_chunks[1].height += 1;
 
             let (high, low) = state.high_low();
+            let vol = state.reg_mkt_volume.clone().unwrap_or_default();
 
             let company_info = [
                 Text::styled("c: ", Style::default()),
@@ -483,9 +515,9 @@ impl StatefulWidget for StockWidget {
                 ),
                 Text::styled(
                     if loaded {
-                        format!("  {:.2}%\n\n", pct_change * 100.0)
+                        format!("  {:.2}%\n", pct_change * 100.0)
                     } else {
-                        "\n\n".to_string()
+                        "\n".to_string()
                     },
                     Style::default()
                         .modifier(Modifier::BOLD)
@@ -507,12 +539,17 @@ impl StatefulWidget for StockWidget {
                 Text::styled("l: ", Style::default()),
                 Text::styled(
                     if loaded {
-                        format!("{:.2}", low)
+                        format!("{:.2}\n\n", low)
                     } else {
-                        "".to_string()
+                        "\n\n".to_string()
                     },
                     Style::default().fg(Color::LightCyan),
                 ),
+                Text::styled("v: ", Style::default()),
+                Text::styled(
+                    if loaded { vol } else { "".to_string() },
+                    Style::default().fg(Color::LightCyan),
+                ),
             ];
 
             Paragraph::new(company_info.iter())
@@ -532,9 +569,18 @@ impl StatefulWidget for StockWidget {
                 let mut toggle_info = vec![Text::styled("Summary  's'", Style::default())];
 
                 if loaded {
+                    toggle_info.push(Text::styled(
+                        "\nVolumes  'v'",
+                        Style::default().bg(if show_volumes {
+                            Color::DarkGray
+                        } else {
+                            Color::Reset
+                        }),
+                    ));
+
                     toggle_info.push(Text::styled(
                         "\nX Labels 'x'",
-                        Style::default().bg(if show_x_labes {
+                        Style::default().bg(if show_x_labels {
                             Color::DarkGray
                         } else {
                             Color::Reset
@@ -588,7 +634,7 @@ impl StatefulWidget for StockWidget {
                 GraphType::Line
             };
 
-            let x_labels = if show_x_labes {
+            let x_labels = if show_x_labels {
                 state.x_labels(chunks[2].width, start, end)
             } else {
                 vec![]
@@ -755,6 +801,70 @@ impl StatefulWidget for StockWidget {
                 );
             }
 
+            // graph_chunks[0] = prices
+            // graph_chunks[1] = volume
+            let graph_chunks = if show_volumes {
+                Layout::default()
+                    .constraints([Constraint::Min(6), Constraint::Length(5)].as_ref())
+                    .split(chunks[2])
+            } else {
+                Layout::default()
+                    .constraints([Constraint::Min(0)].as_ref())
+                    .split(chunks[2])
+            };
+
+            if show_volumes {
+                let mut volume_chunks = graph_chunks[1];
+                volume_chunks.height += 1;
+
+                let x_offset = if !loaded {
+                    8
+                } else if show_x_labels {
+                    match state.time_frame {
+                        TimeFrame::Day1 => 9,
+                        TimeFrame::Week1 => 12,
+                        _ => 11,
+                    }
+                } else {
+                    9
+                };
+                volume_chunks.x += x_offset;
+                volume_chunks.width -= x_offset + 1;
+
+                let width = volume_chunks.width;
+                let num_bars = width as usize;
+
+                let volumes = state.volumes();
+                let vol_count = volumes.len();
+
+                if vol_count > 0 {
+                    let volumes = state
+                        .prices()
+                        .map(|p| [p.volume].repeat(num_bars))
+                        .flatten()
+                        .chunks(vol_count)
+                        .into_iter()
+                        .map(|c| ("", c.into_iter().sum::<u64>() / vol_count as u64))
+                        .collect::<Vec<_>>();
+
+                    volume_chunks.x -= 1;
+
+                    Block::default()
+                        .borders(Borders::LEFT)
+                        .border_style(Style::default().fg(Color::Blue))
+                        .render(volume_chunks, buf);
+
+                    volume_chunks.x += 1;
+
+                    BarChart::default()
+                        .bar_gap(0)
+                        .bar_set(bar::NINE_LEVELS)
+                        .style(Style::default().fg(Color::DarkGray))
+                        .data(&volumes)
+                        .render(volume_chunks, buf);
+                }
+            }
+
             Chart::<String, String>::default()
                 .block(
                     Block::default()
@@ -764,7 +874,7 @@ impl StatefulWidget for StockWidget {
                 .x_axis({
                     let axis = Axis::default().bounds(state.x_bounds(start, end));
 
-                    if show_x_labes && loaded {
+                    if show_x_labels && loaded {
                         axis.labels(&x_labels)
                             .style(Style::default().fg(Color::LightBlue))
                     } else {
@@ -778,7 +888,7 @@ impl StatefulWidget for StockWidget {
                         .style(Style::default().fg(Color::LightBlue)),
                 )
                 .datasets(&datasets)
-                .render(chunks[2], buf);
+                .render(graph_chunks[0], buf);
         }
 
         // Draw time frame tabs
diff --git a/src/widget/stock_summary.rs b/src/widget/stock_summary.rs
index 55cff0f..5c9e621 100644
--- a/src/widget/stock_summary.rs
+++ b/src/widget/stock_summary.rs
@@ -1,16 +1,18 @@
-use super::stock::StockState;
-use crate::common::*;
-use crate::draw::{add_padding, PaddingDirection};
-use crate::{ENABLE_PRE_POST, HIDE_PREV_CLOSE};
-
+use itertools::Itertools;
 use tui::buffer::Buffer;
 use tui::layout::{Alignment, Constraint, Direction, Layout, Rect};
 use tui::style::{Color, Modifier, Style};
-use tui::symbols::Marker;
+use tui::symbols::{bar, Marker};
 use tui::widgets::{
-    Axis, Block, Borders, Chart, Dataset, GraphType, Paragraph, StatefulWidget, Text, Widget,
+    Axis, BarChart, Block, Borders, Chart, Dataset, GraphType, Paragraph, StatefulWidget, Text,
+    Widget,
 };
 
+use super::stock::StockState;
+use crate::common::*;
+use crate::draw::{add_padding, PaddingDirection};
+use crate::{ENABLE_PRE_POST, HIDE_PREV_CLOSE, SHOW_VOLUMES};
+
 pub struct StockSummaryWidget {}
 
 impl StatefulWidget for StockSummaryWidget {
@@ -20,6 +22,7 @@ impl StatefulWidget for StockSummaryWidget {
         let pct_change = state.pct_change();
 
         let enable_pre_post = *ENABLE_PRE_POST.read().unwrap();
+        let show_volumes = *SHOW_VOLUMES.read().unwrap();
 
         let loaded = state.loaded();
         state.loading_tick();
@@ -62,11 +65,12 @@ impl StatefulWidget for StockSummaryWidget {
             .split(area);
 
         {
-            layout[0] = add_padding(layout[0], 2, PaddingDirection::Top);
+            layout[0] = add_padding(layout[0], 1, PaddingDirection::Top);
             layout[0] = add_padding(layout[0], 1, PaddingDirection::Left);
             layout[0] = add_padding(layout[0], 2, PaddingDirection::Right);
 
             let (high, low) = state.high_low();
+            let vol = state.reg_mkt_volume.clone().unwrap_or_default();
 
             let prices = [
                 Text::styled("c: ", Style::default()),
@@ -90,12 +94,17 @@ impl StatefulWidget for StockSummaryWidget {
                 Text::styled("l: ", Style::default()),
                 Text::styled(
                     if loaded {
-                        format!("{:.2}", low)
+                        format!("{:.2}\n\n", low)
                     } else {
-                        "".to_string()
+                        "\n\n".to_string()
                     },
                     Style::default().fg(Color::LightCyan),
                 ),
+                Text::styled("v: ", Style::default()),
+                Text::styled(
+                    if loaded { vol } else { "".to_string() },
+                    Style::default().fg(Color::LightCyan),
+                ),
             ];
 
             let pct = [Text::styled(
@@ -306,6 +315,61 @@ impl StatefulWidget for StockSummaryWidget {
                 );
             }
 
+            // graph_chunks[0] = prices
+            // graph_chunks[1] = volume
+            let graph_chunks = if show_volumes {
+                Layout::default()
+                    .constraints([Constraint::Length(5), Constraint::Length(1)].as_ref())
+                    .split(layout[1])
+            } else {
+                vec![layout[1]]
+            };
+
+            if show_volumes {
+                let mut volume_chunks = graph_chunks[1];
+                volume_chunks.height += 1;
+
+                let x_offset = if !loaded { 8 } else { 9 };
+                volume_chunks.x += x_offset;
+
+                if volume_chunks.width > x_offset + 1 {
+                    volume_chunks.width -= x_offset + 1;
+
+                    let width = volume_chunks.width;
+                    let num_bars = width as usize;
+
+                    let volumes = state.volumes();
+                    let vol_count = volumes.len();
+
+                    if vol_count > 0 {
+                        let volumes = state
+                            .prices()
+                            .map(|p| [p.volume].repeat(num_bars))
+                            .flatten()
+                            .chunks(vol_count)
+                            .into_iter()
+                            .map(|c| ("", c.into_iter().sum::<u64>() / vol_count as u64))
+                            .collect::<Vec<_>>();
+
+                        volume_chunks.x -= 1;
+
+                        Block::default()
+                            .borders(Borders::LEFT)
+                            .border_style(Style::default().fg(Color::Blue))
+                            .render(volume_chunks, buf);
+
+                        volume_chunks.x += 1;
+
+                        BarChart::default()
+                            .bar_gap(0)
+                            .bar_set(bar::NINE_LEVELS)
+                            .style(Style::default().fg(Color::DarkGray))
+                            .data(&volumes)
+                            .render(volume_chunks, buf);
+                    }
+                }
+            }
+
             Chart::<String, String>::default()
                 .block(Block::default().border_style(Style::default()))
                 .x_axis(Axis::default().bounds(state.x_bounds(start, end)))
@@ -316,7 +380,7 @@ impl StatefulWidget for StockSummaryWidget {
                         .style(Style::default().fg(Color::LightBlue)),
                 )
                 .datasets(&datasets)
-                .render(layout[1], buf);
+                .render(graph_chunks[0], buf);
         }
     }
 }


Test patch: