~neon/activity-graph

cbfc0e0a37171fc5985ac24394eb0d4e1cef4427 — Jens Pitkanen 2 years ago f9b5be1
Switch from iso weeks to normal ones, add "ghost days"
4 files changed, 110 insertions(+), 42 deletions(-)

M src/activity-graph.css
M src/main.rs
M src/render.rs
M src/server.rs
M src/activity-graph.css => src/activity-graph.css +9 -4
@@ 14,7 14,6 @@ html {
}

.blob {
    background-color: #fff;
    width: 8px;
    height: 8px;
    border: solid #888 1px;


@@ 25,12 24,16 @@ html {
    border-color: #222;
}

.lvl0 { background-color: #fff; }
.lvl0 { background-color: #fefefe; }
.lvl1 { background-color: #e1e1f7; }
.lvl2 { background-color: #c3c3ef; }
.lvl3 { background-color: #a5a5e7; }
.lvl4 { background-color: #8888de; }

.filler-day {
    opacity: 0.3;
}

@media (prefers-reduced-motion: reduce) {
    .activity-blob {
	transition: border-color 0s;


@@ 44,7 47,6 @@ html {
    }

    .blob {
	background-color: #1b1b1b;
	border-color: #444;
    }



@@ 57,6 59,10 @@ html {
    .lvl2 { background-color: #404752; }
    .lvl3 { background-color: #525e74; }
    .lvl4 { background-color: #62789a; }

    .filler-day {
	opacity: 0.3;
    }
}

@media (max-width: 760px) {


@@ 73,4 79,3 @@ html {
}

@media (max-width: 400px) { .blob { border-width: 0.75px; } }
@media (max-width: 200px) { .blob { border-width: 0px; } }

M src/main.rs => src/main.rs +10 -19
@@ 1,4 1,5 @@
use chrono::{DateTime, Datelike, Utc};
// TODO: Run clippy, and add a #warn for it

use structopt::StructOpt;

use std::fs::File;


@@ 23,6 24,7 @@ pub struct ProjectMetadata {

#[derive(Clone, Default)]
pub struct Day {
    filler: bool,
    commits: Vec<ProjectMetadata>,
}



@@ 116,7 118,7 @@ fn main() {
    let args = Args::from_args();
    log::set_verbosity(args.verbose);

    let mut stdout_years: Option<Vec<Year>> = None;
    let stdout_years;

    if let Some(command) = &args.command {
        match command {


@@ 151,7 153,7 @@ fn main() {
                    write_to_file(&css, output_css, "css");
                }

                stdout_years = Some(years);
                stdout_years = years;
            }

            #[cfg(feature = "server")]


@@ 160,16 162,15 @@ fn main() {
                cache_lifetime,
            } => {
                server::run(&args, *host, *cache_lifetime);
                return;
            }
        }
    } else {
        stdout_years = Some(generate_years(&args.gen));
        stdout_years = generate_years(&args.gen);
    }

    if args.stdout {
        if let Some(years) = stdout_years {
            println!("{}", render::ascii(&years));
        }
        println!("{}", render::ascii(&stdout_years));
    }

    log::verbose_println(


@@ 183,16 184,6 @@ fn main() {

pub fn generate_years(gen: &GenerationData) -> Vec<Year> {
    let repos = find_repositories::from_paths(&gen.input, gen.depth);

    let mut commit_dates = commits::find_dates(gen.author.as_ref(), &repos);
    commit_dates.sort_by(|(a, _), (b, _)| a.cmp(b));

    if commit_dates.len() > 0 {
        let get_year = |date: DateTime<Utc>| date.date().iso_week().year();
        let first_year = get_year(commit_dates[0].0);
        let last_year = get_year(commit_dates[commit_dates.len() - 1].0);
        render::gather_years(commit_dates, first_year, last_year)
    } else {
        Vec::new()
    }
    let commit_dates = commits::find_dates(gen.author.as_ref(), &repos);
    render::gather_years(commit_dates)
}

M src/render.rs => src/render.rs +90 -18
@@ 1,5 1,6 @@
//! Contains the functionality to render the visualizations out of
//! dated commit data.
use chrono::naive::NaiveDate;
use chrono::{DateTime, Datelike, Utc};

use std::fs::File;


@@ 12,11 13,17 @@ static HTML_HEAD: &str = include_str!("head.html");
static CSS: &str = include_str!("activity-graph.css");
static WEEKS: usize = 53;

pub fn gather_years(
    commit_dates: Vec<(DateTime<Utc>, ProjectMetadata)>,
    first_year: i32,
    last_year: i32,
) -> Vec<Year> {
pub fn gather_years(mut commit_dates: Vec<(DateTime<Utc>, ProjectMetadata)>) -> Vec<Year> {
    if commit_dates.is_empty() {
        return Vec::new();
    }

    commit_dates.sort_by(|(a, _), (b, _)| a.cmp(b));

    let get_year = |date: DateTime<Utc>| date.date().year();
    let first_year = get_year(commit_dates[0].0);
    let last_year = get_year(commit_dates[commit_dates.len() - 1].0);

    // Years is a vec containing vecs of years, which consist
    // of weekday-major grids of days: eg. the first row
    // represents all of the mondays in the year, in order.


@@ 28,36 35,96 @@ pub fn gather_years(
        });
    }

    let mut commit_dates = commit_dates.into_iter();
    let mut commit_dates = commit_dates.into_iter().peekable();
    let mut counted_commits = 0;
    for year in first_year..=last_year {
        // Loop through the years

        let days = &mut years[(year - first_year) as usize].days;
        while let Some((date, metadata)) = commit_dates.next() {
        let weekday_offset = NaiveDate::from_ymd(year, 1, 1)
            .weekday()
            .num_days_from_monday() as usize;
        let last_day =
            weekday_offset + NaiveDate::from_ymd(year + 1, 1, 1).pred().ordinal() as usize;
        let last_week = (last_day - (last_day % 7)) / 7;

        let (before, after) = years.split_at_mut((year + 1 - first_year) as usize);
        let (before, current) = before.split_at_mut(before.len() - 1);
        let days = &mut current[0].days;
        let mut last_year_days = if year > first_year {
            Some(&mut before[before.len() - 1].days)
        } else {
            None
        };
        let mut next_year_days = if year < last_year {
            Some(&mut after[0].days)
        } else {
            None
        };
        while let Some((date, _)) = commit_dates.peek() {
            // Loop through the days until the commit is from
            // next year or commits run out

            if date.iso_week().year() != year {
            if date.year() != year {
                break;
            }
            let weekday_index = date.weekday().num_days_from_monday() as usize;
            let week_index = date.iso_week().week0() as usize;

            let ordinal_with_offset = (date.ordinal0()) as usize + weekday_offset;
            let weekday_index = ordinal_with_offset % 7;
            let week_index = ordinal_with_offset / 7;
            if week_index < WEEKS {
                let day = &mut days[weekday_index * WEEKS + week_index];
                day.commits.push(metadata);
                counted_commits += 1;
                // This branch should always be taken because of the peek()
                if let Some((_, metadata)) = commit_dates.next() {
                    // Add the commit to the next/last year as well,
                    // to achieve consistency in the duplicated days
                    if week_index == last_week {
                        if let Some(days) = &mut next_year_days {
                            let next_year_today = &mut days[weekday_index * WEEKS];
                            next_year_today.commits.push(metadata.clone());
                        }
                    }
                    if week_index == 0 {
                        if let Some(days) = &mut last_year_days {
                            let last_year_today = &mut days[weekday_index * WEEKS + WEEKS - 1];
                            last_year_today.commits.push(metadata.clone());
                        }
                    }
                    day.commits.push(metadata);
                    counted_commits += 1;
                }
            }
        }

        // Set the first and last days as filler
        let first_day = weekday_offset;
        for ordinal_with_offset in (0..first_day).chain(last_day..days.len()) {
            let weekday_index = ordinal_with_offset % 7;
            let week_index = ordinal_with_offset / 7;
            days[weekday_index * WEEKS + week_index].filler = true;
        }

        log::verbose_println(
            &format!(
                "prepared year {} for rendering, {} commits processed so far",
                year, counted_commits
            ),
            false,
            true,
        );
    }

    let year_range = if first_year == last_year {
        format!(" {}", first_year)
    } else {
        format!("s {}-{}", first_year, last_year)
    };
    log::verbose_println(
        &format!(
            "prepared year{} for rendering, {} commits processed",
            year_range, counted_commits
        ),
        false,
    );

    years
}



@@ 115,9 182,10 @@ pub fn html(
                } else {
                    format!("{} commits", commit_count)
                };
                let filler = if metadata.filler { "filler-day" } else { "" };
                result += &format!(
                    "<td class=\"blob lvl{}\" title=\"{}\"></td>",
                    shade, tooltip
                    "<td class=\"blob lvl{} {}\" title=\"{}\"></td>",
                    shade, filler, tooltip
                );
            }
            result += "</tr>\n";


@@ 144,8 212,12 @@ pub fn ascii(years: &[Year]) -> String {
        for day in 0..7 {
            for week in 0..WEEKS {
                let metadata = &year.days[day * WEEKS + week];
                let shade = metadata.commits.len() as f32 / max_count as f32;
                result.push(get_shaded_char(shade));
                if metadata.filler {
                    result.push(' ');
                } else {
                    let shade = metadata.commits.len() as f32 / max_count as f32;
                    result.push(get_shaded_char(shade));
                }
            }
            result.push('\n');
        }

M src/server.rs => src/server.rs +1 -1
@@ 99,7 99,7 @@ async fn refresh_caches() {
        if Instant::now() >= refresh_time
            && !REFRESHING_CACHE.compare_and_swap(false, true, Ordering::Relaxed)
        {
            eprintln!("refreshing cache...");
            log::verbose_println("refreshing cache...", false);
            let start = Instant::now();
            if let (Ok(gen), Ok(ext)) = (GENERATION_DATA.read(), EXTERNAL_HTML.read()) {
                let years = generate_years(&gen);