I attempted to recreate the github code frequency graph(example) with a daily granularity using perl and git log. How did I do, and what improvements can I make? I know that I should try to reduce the frequency of keys on the x-axis, but I have no idea how to do that and maintain scale.

Example of the output

It should be noted that the default application for open svg files should be set to a browser of some sort.

#!/usr/bin/env perl
# Henry J Schmale
# November 4, 2015
# Creates an insertion and delation graph per day graph for a git repo. It
# outputs an svg of the graph on standard output.
# This script can take the name of a directory to produce the graph for
# that directory if no param is given, then it does it in the current
# directory. 
# Requires SVG::TT:Graph::Line

use strict;
use warnings;

# Get the path to the stylesheet first
use File::Spec;
use File::Basename;
my $graphsty = dirname(File::Spec->rel2abs(__FILE__)) . '/svg-graph-ss.css';

# CD into the directory specified if specified 
if(-e $ARGV[0] and -d $ARGV[0]){
    chdir $ARGV[0];

# Indexed by date
my %commits;

# get the git log and preprocess it
my $gitlogOutput = qx(git log --numstat --pretty="%H %aI" | grep -v '^\$');

my @lines = split /\n/, $gitlogOutput;
my $date;
my $hash;
foreach (@lines) {
    my @fields = split /\s+/;
    # Length of sha1sum
    if(length($fields[0]) > 39){
        $hash = $fields[0];
        $date = substr($fields[1], 0, 10);
        $commits{$date}->{ins} += $fields[0];
        $commits{$date}->{del} += $fields[1];

use DateTime;
use Date::Parse;
use Data::Dumper;

my $firstDate = getDateTime(((sort keys %commits)[0]));
my $lastDate  = getDateTime(((sort keys %commits)[-1]));
# print "$firstDate\t".((sort keys %commits)[0])."\n";
# print "$lastDate\t".((sort keys %commits)[-1])."\n";

# print (scalar keys %commits)."\n";
while($firstDate->add(days => 1) < $lastDate){
    my $key = $firstDate->ymd('-');
    if(!defined $commits{$key}){
        $commits{$key}->{ins} = 0;
        $commits{$key}->{del} = 0;
# print scalar keys %commits;
# print "\n";

# Prepare data for graphing by converting them to arrays
my (@key, @ins, @del, @net);
my $i = 0;
foreach (sort keys %commits){
    $key[$i] = $_;
    $ins[$i] = $commits{$_}{ins};
    # del must be negitive in order for graph to look right, with delations
    # being below the x axis.
    $del[$i] = -$commits{$_}{del};
    $net[$i] = $commits{$_}{ins} - $commits{$_}{del};
    # printf("%s,%s,%s,%s\n", $_, $ins[$i], $del[$i], $net[$i]);

# Get the max value in the data sets
my @allpoints;
my ($min, $max);
push @allpoints, @ins;
push @allpoints, @del;
push @allpoints, @net;
    $min = $_ if !$min || $_ < $min;
    $max = $_ if !$max || $_ > $max;

# Graph it
use SVG::TT::Graph::Line;

my $graph = SVG::TT::Graph::Line->new({
        width => 1200,
        height => 800,
        fields => \@key,
        scale_integers => 1,
        rotate_x_labels => 1,
        show_data_values => 0,
        show_data_points => 0,
        min_scale_value => $min,
        max_scale_value => $max,
        style_sheet => $graphsty,
# Add the data
        'data' => \@ins,
        title => 'Inserts Per Day'
        'data' => \@del,
        title => 'Deletions Per Day'
        'data' => \@net,
        title => 'Net Insert/Del Per Day'

# Print file 
my $filepath = '/tmp/gitGraph.svg';
open my $FD,'>',$filepath or die $!;
print $FD $graph->burn();
close $FD;

# Open it up in the browser or prefered method for opening the file
$filepath = 'file://'.$filepath;
qx(xdg-open $filepath);

sub getDateTime {
    my ($ss,$mm,$hh,$day,$month,$year,$zone) = strptime($_[0]);
    # print "\t$_[0]\n";
    return DateTime->new(
        day => $day,
        month => $month + 1,
        # We have to add 1900 here inorder to make the date format work
        year  => ($year + 1900)
  • \$\begingroup\$ Empty string for every odd element on X axis? \$\endgroup\$ – mpapec Nov 16 '15 at 6:27
  • \$\begingroup\$ @mpapec It only happens on the first run for a repository, or if you have dozens of branches. \$\endgroup\$ – HSchmale Nov 16 '15 at 15:11
  • 3
    \$\begingroup\$ Just a style thing, but the use SVG::TT::Graph::Line; should be at the top. So should the other use statements that are in the middle. They will all be loaded at compile time anyway. \$\endgroup\$ – simbabque Nov 26 '15 at 16:18

A simple thing to add would be a Logarithmic Y-scaling function, so that significant bursts in either losses or gains don't get over-exaggerated perceptually. Essentially you want 100-commit days to only take up at most, 10x as much space as 1 commit days, so "low activity" wavering is still visible.

I'd also suggest breaking windows of about 7 days into single data points, by aggregating them and representing them as 5 lines each:

  • Max number of commits per day in 7-day-range,
  • Upper Quartile number of commits per day in 7-day-range
  • Median number of commits per day ...
  • Lower Quartile ....
  • Min...

And then render it with some shades like this ( half-assed mockup )

enter image description here

With this, you could afford to narrow the sample window to "per hour" say, but still represent the data accumulated in terms of weeks, so you'd have "max commits/hour this week", "min commits/hour this week", etc, all represented in a single vertical column.

  • \$\begingroup\$ IMO a CR post almost always should include some code (or at least a reference to original code) otherwise it's more about UX (which is good and desiderable but maybe off-topic) \$\endgroup\$ – Adriano Repetti Apr 29 '16 at 11:44
  • \$\begingroup\$ I mostly agree. But here it's not entirely obvious what the author is asking for, seems like they're wanting both. The code ... is ... well, more scary than I can begin to start on :/ \$\endgroup\$ – Kent Fredric Apr 29 '16 at 12:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.