Skip to content

Commit d191c24

Browse files
committed
#22 Add absolute plot functionality for artists
1 parent 77e6b7c commit d191c24

File tree

10 files changed

+433
-20
lines changed

10 files changed

+433
-20
lines changed

Cargo.lock

+318-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+5
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,15 @@ chrono = "0.4"
1717
chrono-tz = "0.8"
1818
rustyline = { version = "11.*", features = ["derive"] }
1919
unicode-width = "0.1.10"
20+
plotly = "0.8"
2021

2122
[dev-dependencies]
2223
criterion = "0.4"
2324

25+
[profile.release]
26+
# for profiling with cargo-instruments/flamegraph-rs
27+
debug = true
28+
2429
[[bench]]
2530
name = "printing_top"
2631
harness = false

src/display.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use std::collections::HashMap;
1212

1313
/// Module containing [`display`](self) functions with
1414
/// date functionality
15-
mod date;
15+
pub mod date;
1616

1717
/// If set to true, it will sum up the plays of one song across multiple
1818
/// albums it may be in

src/display/date.rs

+14-14
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ pub fn print_aspect(
2828
art,
2929
start.date_naive(),
3030
end.date_naive(),
31-
gather_artist_date(entries, art, start, end)
31+
gather_artist(entries, art, start, end)
3232
);
3333
print_artist(
3434
entries,
@@ -43,7 +43,7 @@ pub fn print_aspect(
4343
alb,
4444
start.date_naive(),
4545
end.date_naive(),
46-
gather_album_date(entries, alb, start, end)
46+
gather_album(entries, alb, start, end)
4747
);
4848
print_album(&gather_songs_with_album_date(entries, alb, start, end));
4949
}
@@ -53,7 +53,7 @@ pub fn print_aspect(
5353
son,
5454
start.date_naive(),
5555
end.date_naive(),
56-
gather_song_date(entries, son, start, end)
56+
gather_song(entries, son, start, end)
5757
);
5858
}
5959
}
@@ -77,7 +77,7 @@ fn print_artist(
7777
println!(
7878
"--- {} | {} plays ---",
7979
alb,
80-
gather_album_date(entries, alb, start, end)
80+
gather_album(entries, alb, start, end)
8181
);
8282
print_album(&mus);
8383
}
@@ -86,7 +86,7 @@ fn print_artist(
8686
/// Counts up the plays of a single artist within a date frame
8787
///
8888
/// Basically [`super::gather_artist()`] but with date functionality
89-
fn gather_artist_date(
89+
pub fn gather_artist(
9090
entries: &Vec<SongEntry>,
9191
art: &Artist,
9292
start: &DateTime<Tz>,
@@ -95,7 +95,7 @@ fn gather_artist_date(
9595
let mut plays = 0;
9696

9797
for entry in entries {
98-
if entry.timestamp.ge(start) && entry.timestamp.le(end) && entry.artist.eq(&art.name) {
98+
if entry.artist.eq(&art.name) && entry.timestamp.ge(start) && entry.timestamp.le(end) {
9999
plays += 1;
100100
}
101101
}
@@ -106,7 +106,7 @@ fn gather_artist_date(
106106
/// Counts up the plays of a single album within a date frame
107107
///
108108
/// Basically [`super::gather_album()`] but with date functionality
109-
fn gather_album_date(
109+
pub fn gather_album(
110110
entries: &[SongEntry],
111111
alb: &Album,
112112
start: &DateTime<Tz>,
@@ -115,10 +115,10 @@ fn gather_album_date(
115115
let mut plays = 0;
116116

117117
for entry in entries {
118-
if entry.timestamp.ge(start)
119-
&& entry.timestamp.le(end)
120-
&& entry.artist.eq(&alb.artist.name)
118+
if entry.artist.eq(&alb.artist.name)
121119
&& entry.album.eq(&alb.name)
120+
&& entry.timestamp.ge(start)
121+
&& entry.timestamp.le(end)
122122
{
123123
plays += 1;
124124
}
@@ -130,7 +130,7 @@ fn gather_album_date(
130130
/// Counts up the plays of a single song within a date frame
131131
///
132132
/// Basically [`super::gather_song()`] but with date functionality
133-
fn gather_song_date(
133+
pub fn gather_song(
134134
entries: &[SongEntry],
135135
son: &Song,
136136
start: &DateTime<Tz>,
@@ -139,11 +139,11 @@ fn gather_song_date(
139139
let mut plays = 0;
140140

141141
for entry in entries {
142-
if entry.timestamp.ge(start)
143-
&& entry.timestamp.le(end)
144-
&& entry.artist.eq(&son.album.artist.name)
142+
if entry.artist.eq(&son.album.artist.name)
145143
&& entry.album.eq(&son.album.name)
146144
&& entry.track.eq(&son.name)
145+
&& entry.timestamp.ge(start)
146+
&& entry.timestamp.le(end)
147147
{
148148
plays += 1;
149149
}

src/main.rs

+8
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
mod display;
2121
mod parse;
22+
mod plot;
2223
mod types;
2324
mod ui;
2425

@@ -54,6 +55,7 @@ fn main() {
5455
let entries = SongEntries::new(paths).unwrap();
5556

5657
// test(&entries);
58+
// test_plot(&entries);
5759

5860
ui::start(&entries);
5961
}
@@ -113,3 +115,9 @@ fn test(entries: &SongEntries) {
113115
entries.print_aspect_date(&AspectFull::Album(&coat), &start_date, &end_date);
114116
entries.print_aspect_date(&AspectFull::Song(&final_solution), &start_date, &end_date);
115117
}
118+
119+
/// tests various [`plot`] functions
120+
#[allow(dead_code)]
121+
fn test_plot(entries: &SongEntries) {
122+
plot::absolute::artist(entries, &types::Artist::from_str("Sabaton"));
123+
}

src/plot.rs

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
//! Module responsible for plotting/charts
2+
3+
/// Responsible for plotting absolute plots
4+
pub mod absolute;

src/plot/absolute.rs

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
use crate::display::date;
2+
use crate::types::{Artist, SongEntries, SongEntry};
3+
use crate::ui::user_input_date_parser;
4+
5+
use chrono::DateTime;
6+
use chrono_tz::Tz;
7+
use plotly::{Layout, Plot, Scatter};
8+
9+
/// Creates a plot of the absolute amount of plays of an [`Artist`]
10+
///
11+
/// Opens the plot in the browser
12+
pub fn artist(entries: &SongEntries, art: &Artist) {
13+
let mut times = Vec::<i64>::new();
14+
let mut plays = Vec::<usize>::new();
15+
16+
let mut dates = find_artist_dates(entries, art);
17+
dates.sort();
18+
19+
let start = dates.first().unwrap();
20+
21+
for date in &dates {
22+
times.push(date.timestamp());
23+
plays.push(date::gather_artist(entries, art, start, date));
24+
}
25+
26+
let mut plot = Plot::new();
27+
// TODO: make it display actual dates instead of UNIX timestamps xd
28+
plot.add_trace(Scatter::new(times, plays).name(art.name.as_str()));
29+
30+
// sets the title of the plot the artist name
31+
let layout = Layout::new().title(format!("<b>{art}</b>").as_str().into());
32+
plot.set_layout(layout);
33+
34+
// opens the plot in the browser
35+
plot.show();
36+
}
37+
38+
/// Used by [`artist()`] to get the dates of all of its occurrence
39+
fn find_artist_dates(entries: &Vec<SongEntry>, art: &Artist) -> Vec<DateTime<Tz>> {
40+
let mut dates = Vec::<DateTime<Tz>>::new();
41+
42+
for entry in entries {
43+
let artist = Artist::new(entry.artist.clone());
44+
45+
if artist == *art {
46+
dates.push(entry.timestamp);
47+
}
48+
}
49+
50+
dates.push(user_input_date_parser("now").unwrap());
51+
52+
dates
53+
}

src/types.rs

+6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use chrono_tz::Tz;
88

99
use crate::display;
1010
use crate::parse;
11+
use crate::plot;
1112

1213
/// Algebraic data type similar to [Aspect]
1314
/// but used by functions such as [`display::print_aspect()`]
@@ -266,6 +267,11 @@ impl SongEntries {
266267
display::print_aspect_date(self, asp, start, end);
267268
}
268269

270+
/// Creates a plot of the artist
271+
pub fn plot_artist(&self, art: &Artist) {
272+
plot::absolute::artist(self, art);
273+
}
274+
269275
/// Adds search capability
270276
///
271277
/// Use with methods from [`Find`]: [`.artist()`](Find::artist()), [`.album()`](Find::album()),

src/ui.rs

+16-1
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ fn match_input(
173173
"print top artists" | "ptarts" => match_print_top(entries, rl, &Aspect::Artists)?,
174174
"print top albums" | "ptalbs" => match_print_top(entries, rl, &Aspect::Albums)?,
175175
"print top songs" | "ptsons" => match_print_top(entries, rl, &Aspect::Songs)?,
176+
"plot artist" | "gart" => match_plot_artist(entries, rl)?,
176177
// when you press ENTER -> nothing happens, new prompt
177178
"" => (),
178179
_ => {
@@ -425,11 +426,25 @@ fn match_print_top(
425426
Ok(())
426427
}
427428

429+
/// Used by [`match_input()`] for `plot artist` command
430+
fn match_plot_artist(
431+
entries: &SongEntries,
432+
rl: &mut Editor<ShellHelper, FileHistory>,
433+
) -> Result<(), Box<dyn Error>> {
434+
// 1st prompt: artist name
435+
println!("Artist name?");
436+
let usr_input_art = rl.readline(PROMPT_MAIN)?;
437+
let art = entries.find().artist(&usr_input_art)?;
438+
439+
entries.plot_artist(&art);
440+
Ok(())
441+
}
442+
428443
/// used by `*_date` functions in this module for when the user inputs a date
429444
///
430445
/// # Arguments
431446
/// * `usr_input` - in YYYY-MM-DD format or 'now' or 'start'
432-
fn user_input_date_parser(usr_input: &str) -> Result<DateTime<Tz>, chrono::format::ParseError> {
447+
pub fn user_input_date_parser(usr_input: &str) -> Result<DateTime<Tz>, chrono::format::ParseError> {
433448
let date_str = match usr_input {
434449
"now" => {
435450
return Ok(LOCATION_TZ

src/ui/help.rs

+8-3
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pub fn help() {
1717
print("print top", &print_top_commands());
1818

1919
// GRAPH COMMANDS
20-
print("graph", &graph_commands());
20+
print("graph/plot", &plot_commands());
2121
}
2222

2323
/// Prints the commands
@@ -194,8 +194,13 @@ fn print_top_commands<'a>() -> Vec<[&'a str; 3]> {
194194
}
195195

196196
/// Returns graph commands
197-
fn graph_commands<'a>() -> Vec<[&'a str; 3]> {
198-
vec![["graph placeholder", "gphd", "placeholder description"]]
197+
fn plot_commands<'a>() -> Vec<[&'a str; 3]> {
198+
vec![[
199+
"plot artist",
200+
"gart",
201+
"creates a plot of the absolute amount of plays of the given artist
202+
and opens it in the web browser",
203+
]]
199204
}
200205

201206
#[cfg(test)]

0 commit comments

Comments
 (0)