20 Years of Processing: Visualizing Two Decades of Development in 30 Seconds

Visualization of the Processing twenty years of development.

2021 marks the 20th anniversary of Processing. Twenty years is an incredible span of time; a child born when Case and Fry began this project would now be a full-grown adult.

I found myself wondering: what did the developers feel during those two decades? Through the highs and lows, the changing seasons, and twenty years of iteration—what story did the data tell? I wanted to see if I could capture the heartbeat of that development by visualizing twenty years of GitHub logs using Processing itself.

[Japanese version / 日本語版はこちら]

 

Data as Art: Moving Beyond Graphs

My initial plan was to create a standard graph, animating values across the timeline.

The image of the Processing twenty years of development.

However, I soon realized that my interest lay not in pure "data visualization" (finding objective meaning in numbers), but in data-driven animation—using the records of human effort to create something visual and rhythmic.

 

This is What I Made

Here is the resulting data-driven animation. (Note: The video includes sound.)


The version numbers and durations reflect the actual development cycle for each release.

The displayed version number of Processing and the duration.

I realized that focusing purely on raw numbers might miss the "bigger picture" of the creative journey, which is why I chose a more layered visual approach.

To capture the multi-faceted nature of the project, I broke the visualization into several key components:

 

Duration

 

Version

The developing version on the duration.

The version number and the duration.
The version number and the duration of the whole duration.

 

Mod/Ins/Del

Files changed, insertions and deletions numbers that were fetched from GitHub commit log.



 

Abstract Decorations

These components were added to give the piece a more organic, artistic feel while still being rooted in the data.



 

The Pulse of Development: Past and Future

At a pace of eight months per second, the animation flys by. Watching it, I felt the long, steady march toward the 1.0 release, and the energetic bursts of the 2.0 and 3.0 eras. It reminded me that software isn't just code; it's the result of countless hours of human dedication.

I haven't always been conscious of it, but I feel incredibly fortunate to create my own work today thanks to the fruits of those twenty years of effort. That realization is empowering.

What will the next twenty years look like? As the community continues to grow and new ways of expression emerge, the future of Processing is in our hands. We, the users, are the ones who will shape the next two decades.

 

How I Made It?

I'll explain how did I make this animation briefly. I hope it will provide a hint to make something from some data, and it will be applied to your works.

 

Overview

Overview of making of this animation.

  1. Fetch the GitHub commit log data with the git command and save it to the text file.
  2. Merge and format the text file with the Perl script and save it to the tab-separated-value(tsv) file.
  3. Read the tab-separated-value file and draw some animation frames with Processing code and save these to the image(png) files.
  4. Compile the image frames into an MP4 video with the FFmpeg command.

The sound was taken separately and synchronized with the animation in a video editing application (kdenlive).

 

How to Fetch the Data from GitHub

I fetched the data from the Processing repository on GitHub. The target data is the number of 'files changed', 'insertion', and 'deletion' on the commit log.

I used the git command like this.


git log --shortstat --reverse --date=short --no-merges

The date range of fetched data was from 2001-07-26 to 2021-07-06.

GitHub - processing/processing: Source code for the Processing Core and Development Environment (PDE)
https://github.com/processing/processing
Processing 4.0 is in new repository.
https://github.com/processing/processing4/

 

How to Handle the Fetched Data in Processing Code

I structured the fetched data into a "one record per day" format. In the Processing code, I created an Activity class to store each day's metrics, managing them within an ArrayList called acts for easy access and iteration.

 

Date in Processing Code

I hold the date as Unix time in this code besides the date text for display.

It is easy to calculate the date span because the Unix time is the number of seconds elapsed since 00:00:00 UTC on 1 January 1970.

 

How to Draw the Plural Animation Parts

The frame of this animation is made with the parts like date-banner, version arcs, etc.

I drew the parts on individual PGraphics with transparent background. And I put these parts on the canvas with the image() function. So I can easily tune the position of these parts.

 

Processing Code of This Animation

As they say, "source code is the best documentation." I believe the logic behind the animation speaks for itself.

Please feel free to use this example code under the terms of the GPL.


/**
 * The Processing 20 years in 30 seconds.
 * data visualized animation of the Processing commit log data on GitHub.
 *
 * Processing 3.5.3
 * @author @deconbatch
 * @version 0.1
 * @license GNU GPL v3
 * created 0.1 2021.11.21
 */

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.Instant;
import java.time.format.DateTimeFormatter;

/**
 * there is only setup(), no draw().
 */
void setup() {
  size(960, 540);
  colorMode(HSB, 360.0, 100.0, 100.0, 100.0);
  smooth();
  noLoop();
  imageMode(CENTER);

  // constants
  int frmRate   = 30;
  int secTotal  = 27; // animation duration seconds
  int frmMax    = frmRate * secTotal;
  int daysRange = 360 * 2;
  int utPerDay  = 60 * 60 * 24; // unix time length of one day
  int cW        = width;
  int cH        = height;

  // colors
  Colour cBlueDark     = new BlueDark();
  Colour cBlueBright   = new BlueBright();
  Colour cCyan         = new Cyan();
  Colour cRed          = new Red();
  Colour cVioletDark   = new VioletDark();
  Colour cVioletBright = new VioletBright();
  Colour[] colours     = {cCyan, cBlueBright, cBlueDark, cVioletDark, cVioletBright, cRed};
  
  // read develop activities from the file
  Development dev = new Development("./data/data.log", utPerDay);
  int daysStep = floor((dev.daysDev + daysRange) / (frmMax - 1));

  // read released version from the file
  ArrayList<Version> vers = getVersions("./data/version.log");

  // title decoration image
  PImage theSun = modCircle(getActsRange(dev.acts, dev.dailyIndex, 0, dev.daysDev), dev.utTimeFrom, dev.daysDev * utPerDay, cW, floor(cH * 1.7));

  // opening
  noStroke();
  textFont(createFont("Waree Bold",24,true));
  textAlign(CENTER, CENTER);
  textSize(cW * 0.03);
  for (int frmCnt = 0; frmCnt < frmRate * 4; frmCnt++) {
    background(0.0, 0.0, 90.0, 100.0);
    // decoration
    pushMatrix();
    translate(cW * 0.5, cH * 0.5);
    rotate(frmCnt * 0.01);
    image(theSun, 0.0, 0.0);
    popMatrix();
    // mainTitle
    image(
          mainTitle(cW, cH, cBlueDark),
          cW * 0.5,
          cH * 0.5
          );
    // count down
    fill(0.0, 0.0, 50.0, 100.0);
    ellipse(cW * 0.5, cH * 0.85, cW * 0.08, cW * 0.08);
    fill(0.0, 0.0, 90.0, 100.0);
    text(3 - floor(frmCnt / frmRate), cW * 0.5, cH * 0.85);
    saveFrame("frames/00." + String.format("%04d", frmCnt) + ".png");
  }

  // draw data visualization
  // background image
  PImage bd = drawBack(cW, cH);
  // for the image of building-up data
  PGraphics pgDecoGraph = null;
  PGraphics pgDecoBubble = null;

  for (int frmCnt = 0; frmCnt < frmMax; frmCnt++) {

    float frmRatio = map(frmCnt, 0, frmMax, 0.0, 1.0);
    int   dayStart = frmCnt * daysStep - daysRange; // days count of the start
    int   utStart  = dev.utTimeFrom + dayStart * utPerDay; // unix time of the start date
    int   utRange  = daysRange * utPerDay; // days range in unix time to draw

    List<Activity> actsDraw = getActsRange(dev.acts, dev.dailyIndex, dayStart, daysRange); // activities to draw

    // background
    background(0.0, 0.0, 90.0, 100.0);
    image(bd, cW * 0.5, cH * 0.5);

    // center column
    image(
          dateBanner(utStart, utStart + utRange, cW, floor(cH * 0.25), cBlueDark),
          cW * 0.5,
          cH * 0.1
          );
    image(
          versionArc(vers, utStart, utRange, cW, cH, colours),
          cW * 0.5,
          cH * 0.5
          );

    image(
          modCircle(actsDraw, utStart, utRange, cW, cH),
          cW * 0.5,
          cH * 0.5
          );
    image(
          versionGraph(vers, dev.utTimeFrom, dev.utTimeTo, utStart, utRange, floor(cW * 0.8), floor(cH * 0.1), cBlueDark, cVioletBright),
          cW * 0.5,
          cH * 0.9
          );

    // left column
    image(
          insdelCircle(actsDraw, utStart, utRange, floor(cW * 0.5), floor(cH * 0.5), cBlueBright, cRed),
          cW * 0.175,
          cH * 0.385
          );
    image(
          logBar(actsDraw, floor(cW * 0.25), floor(cH * 0.25), cVioletDark, cBlueBright, cRed),
          cW * 0.225,
          cH * 0.75
          );

    
    // right column
    pgDecoGraph = decoGraph(pgDecoGraph, actsDraw, floor(cW * 0.20), floor(cH * 0.15), frmRatio, frmMax, cVioletDark);
    image(
          pgDecoGraph,
          cW * 0.8,
          cH * 0.265
          );
    pgDecoBubble = decoBubble(pgDecoBubble, actsDraw, floor(cW * 0.23), floor(cH * 0.5), frmRatio, cBlueBright);
    image(
          pgDecoBubble,
          cW * 0.8,
          cH * 0.56
          );
    
    saveFrame("frames/01." + String.format("%04d", frmCnt) + ".png");
  }

  // stop motion of the last frame
  for (int frmCnt = frmMax; frmCnt < frmMax + frmRate; frmCnt++) {
    saveFrame("frames/01." + String.format("%04d", frmCnt) + ".png");
  }  

  // ending
  noStroke();
  textFont(createFont("Source Code Pro Bold",24,true));
  textAlign(LEFT, CENTER);
  for (int frmCnt = 0; frmCnt < frmRate * 5; frmCnt++) {
    float circleRate = constrain(map(frmCnt, 0, frmRate * 2, 0.0, 1.0), 0.0, 1.0);
    
    background(0.0, 0.0, 90.0, 100.0);
    pushMatrix();

    // moving
    if (frmCnt < frmRate * 1) {
      translate(cW * 0.5, cH * 0.5);
    } else if  (frmCnt < frmRate * 2) {
      translate(cW * 0.5, cH * map(frmCnt, frmRate, frmRate * 2, 0.5, 0.33));
    } else { 
      translate(cW * 0.5, cH * 0.325);
    }
    
    // decoration fade out
    pushMatrix();
    rotate(frmCnt * 0.01);
    tint(0.0, 0.0, 100.0, sin(circleRate * PI) * 100.0);
    image(theSun, 0.0, 0.0);
    popMatrix();
    // mainTitle fade in
    tint(0.0, 0.0, 100.0, constrain(map(frmCnt, 0, frmRate * 1, -50.0, 100.0), 0.0, 100.0));
    image(
          mainTitle(cW, cH, cBlueDark),
          0.0,
          0.0
          );
    popMatrix();
    // message fade in
    pushMatrix();
    translate(cW * 0.26, cH * 0.635);
    fill(0.0, 0.0, 0.0, constrain(map(frmCnt, 0, frmRate * 3, -200.0, 100.0), 0.0, 100.0));
    textSize(cW * 0.03);
    text("I'll dedicate this video to", 0.0, 0.0);
    text("Processing developers and", 0.0, cH * 0.08);
    text("Processing lovers all over", 0.0, cH * 0.16);
    text("the world.", 0.0, cH * 0.24);
    textSize(cW * 0.02);
    text("–deconbatch", cW * 0.35, cH * 0.25);
    popMatrix();

    saveFrame("frames/02." + String.format("%04d", frmCnt) + ".png");
  }

  exit();

}

/**
 * mainTitle : draw main title image.
 */
public PGraphics mainTitle(int _w, int _h, Colour _c) {
  
  int   margin = 20;
  float radius = (min(_w, _h) - margin) * 0.6;
  float blindR = (min(_w, _h) - margin) * 0.5;

  PGraphics g = createGraphics(_w, _h);
  g.beginDraw();
  g.colorMode(HSB, 360, 100, 100, 100);
  g.smooth();
  g.blendMode(REPLACE);
  g.textFont(createFont("Waree Bold",240,true));
  g.background(0.0, 0.0, 0.0, 0.0);
  g.translate(_w * 0.5, _h * 0.5);

  // plate
  g.blendMode(BLEND);
  g.noStroke();
  g.rectMode(CENTER);
  g.fill(0.0, 0.0, 90.0, 80.0);
  g.rect(0.0, 0.0, _w, _h * 0.25);
  g.fill(_c.h, _c.s, _c.b, 40.0);
  g.rect(0.0, 0.0, _w, _h * 0.25);
  
  g.blendMode(REPLACE);
  g.fill(0.0, 0.0, 0.0, 0.0);
  g.ellipse(0.0, 0.0, radius, radius);

  g.stroke(0.0, 0.0, 0.0, 0.0);
  g.strokeWeight(10.0);
  g.noFill();
  g.rect(0.0, 0.0, _w * 2.0, _h * 0.15);
  
  g.noStroke();
  g.fill(_c.h, _c.s, _c.b, 60.0);
  g.ellipse(0.0, 0.0, blindR, blindR);

  // title
  g.textAlign(CENTER, CENTER);
  g.noStroke();
  g.fill(0.0, 0.0, 0.0, 100.0);

  g.textSize(_w * 0.018);
  g.text("The Processing", -_w * 0.33, 0.0);
  g.text("in 30 seconds.", _w * 0.33, 0.0);

  g.fill(0.0, 0.0, 100.0, 100.0);
  g.textSize(_w * 0.065);
  g.text("20", 0.0, -_h * 0.02);
  g.textSize(_w * 0.026);
  g.text("years", 0.0, _h * 0.1);
  
  g.endDraw();
  return g;
}


/**
 * drawBack : draw background decoration image.
 */
public PGraphics drawBack(int _w, int _h) {
  
  PGraphics g = createGraphics(_w, _h);
  g.beginDraw();
  g.colorMode(HSB, 360, 100, 100, 100);
  g.smooth();
  g.blendMode(REPLACE);
  g.textFont(createFont("Waree Bold",240,true));
  g.background(0.0, 0.0, 0.0, 0.0);
  //  g.translate(_w * 0.5, _h * 0.5);

  // node garden
  float div = min(_w, _h) * 0.01;
  ArrayList<PVector> nodes = new ArrayList<PVector>();
  for (float x = 0.0; x < _w; x += div) {
    for (float y = 0.0; y < _w; y += div) {
      if (random(1.0) < 0.3) {
        nodes.add(new PVector(x, y));
      }
    }
  }

  g.strokeWeight(div * 0.08);
  for (PVector n : nodes) {
    for (PVector m : nodes) {
      float d = dist(n.x, n.y, m.x, m.y);
      if (d > div && d < div * 2.0) {
        float v = noise(n.x, n.y);
        g.stroke(0.0, 0.0, 90.0 + v * 10.0, 100.0);
        g.line(n.x, n.y, m.x, m.y);
        break;
      }
    }
  }
  g.noStroke();
  for (PVector n : nodes) {
    float v = noise(n.x, n.y);
    float s = (0.5 + v) * div * 0.5;
    g.fill(0.0, 0.0, 90.0 + v * 10.0, 100.0);
    g.ellipse(n.x, n.y, s, s);
  }
  
  g.endDraw();
  return g;
}


/**
 * dateBanner : draw from/to date.
 */
public PGraphics dateBanner(int _start, int _end, int _w, int _h, Colour _c) {

  PGraphics g = createGraphics(_w, _h);
  g.beginDraw();
  g.colorMode(HSB, 360, 100, 100, 100);
  g.smooth();
  g.blendMode(REPLACE);
  g.background(0.0, 0.0, 0.0, 0.0);
  g.textFont(createFont("Waree Bold",240,true));
  g.textAlign(CENTER, CENTER);
  g.textSize(_w * 0.03);

  DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd");
  String start = fmt.format(ZonedDateTime.ofInstant(Instant.ofEpochSecond(_start), ZoneId.systemDefault()));
  String end   = fmt.format(ZonedDateTime.ofInstant(Instant.ofEpochSecond(_end), ZoneId.systemDefault()));

  // date string
  g.translate(_w * 0.5, _h * 0.5);
  g.noStroke();
  g.fill(_c.h, _c.s, _c.b, _c.a);
  g.text(start + "–" + end, 0.0, 0.0);

  g.endDraw();
  return g;
}


/**
 * versionArc : draw arcs of version.
 */
public PGraphics versionArc(List<Version> _vers, int _start, int _range, int _w, int _h, Colour[] _cs) {
  
  int   margin = 20;
  float divT   = TWO_PI / _range;
  float radius = (min(_w, _h) - margin) * 0.3 * 1.6;

  PGraphics g = createGraphics(_w, _h);
  g.beginDraw();
  g.colorMode(HSB, 360, 100, 100, 100);
  g.smooth();
  g.blendMode(REPLACE);
  g.background(0.0, 0.0, 0.0, 0.0);
  g.textFont(createFont("URWGothic-Demi",240,true));

  g.translate(_w * 0.5, _h * 0.5);
  g.stroke(0.0, 0.0, 0.0, 100.0);
  g.strokeWeight(2.0);
  g.textSize(_w * 0.03);
  g.textAlign(CENTER, CENTER);
  Version pVer = _vers.get(0);
  int sizeVers = _vers.size();
  int sizeClrs = _cs.length;
  for (int i = 0; i < sizeVers; i++) {
    Colour clr = _cs[i % sizeClrs];
    Version cVer = _vers.get(i);
    if (cVer.unixTime < _start + _range) {
      float thetaP = constrain((pVer.unixTime - _start) * divT, 0.0, TWO_PI);
      float thetaC = constrain((cVer.unixTime - _start) * divT, 0.0, TWO_PI);
      float drawS = TWO_PI * 1.75 - thetaP;
      float drawE = TWO_PI * 1.75 - thetaC;
      float drawT = TWO_PI * 1.75 - (thetaP + thetaC) * 0.5;
      g.fill(clr.h, clr.s, clr.b, clr.a);
      g.arc(0.0, 0.0, radius, radius, drawS, TWO_PI * 1.75, PIE);
      g.fill(0.0, 0.0, 0.0, 100.0);
      g.text(pVer.version, radius * 0.35 * cos(drawT), radius * 0.35 * sin(drawT));
      if (cVer.unixTime < _start) {
        break;
      }
    }

    pVer = cVer;
  }
  g.endDraw();
  return g;
}


/**
 * modCircle : draw circles of bar graph of the files changed number.
 */
public PGraphics modCircle(List<Activity> _acts, int _start, int _range, int _w, int _h) {
  
  int   margin = 20;
  float divT   = TWO_PI / _range;
  float radius = (min(_w, _h) - margin) * 0.3;

  PGraphics g = createGraphics(_w, _h);
  g.beginDraw();
  g.colorMode(HSB, 360, 100, 100, 100);
  g.smooth();
  g.blendMode(REPLACE);
  g.background(0.0, 0.0, 0.0, 0.0);
  g.translate(_w * 0.5, _h * 0.5);

  // radially lines
  g.noFill();
  g.strokeWeight(2.0);
  for (Activity act : _acts) {
    float theta = (act.unixTime - _start) * divT;
    g.pushMatrix();
    g.rotate(-theta);
    g.stroke(0.0, 0.0, 0.0, 100.0 * constrain(theta, 0.0, PI) / TWO_PI);
    g.translate(0.0, -radius);
    g.line(0.0, 0.0, 0.0, -act.change * _w * 0.002);
    g.popMatrix();
  }

  // decoration of broken line circle
  g.fill(0.0, 0.0, 90.0, 30.0);
  g.strokeWeight(5.0);
  g.stroke(0.0, 0.0, 50.0, 100.0);
  g.ellipse(0.0, 0.0, radius * 1.8, radius * 1.8);
  for (int i = 0; i < 12; i++) {
    float theta = i * TWO_PI / 12.0;
    g.noStroke();
    g.fill(0.0, 0.0, 90.0, 60.0);
    g.ellipse(
              radius * 0.9 * cos(theta),
              radius * 0.9 * sin(theta),
              radius * 0.05,
              radius * 0.05
              );
  }

  g.endDraw();
  return g;
}


/**
 * versionGraph : draw bar graph of the day x version.
 */
public  PGraphics versionGraph(List<Version> _vers, int _from, int _to, int _start, int _range, int _w, int _h, Colour _cGrh, Colour _cTime) {

  float step = _w * 1.0 / (_to - _from);
  
  PGraphics g;
  g = createGraphics(_w, _h);
  g.beginDraw();
  g.colorMode(HSB, 360, 100, 100, 100);
  g.smooth();
  g.blendMode(REPLACE);
  g.background(0.0, 0.0, 0.0, 0.0);

  // draw bar graph
  g.fill(_cGrh.h, _cGrh.s, _cGrh.b, _cGrh.a);
  g.stroke(_cGrh.h, _cGrh.s, _cGrh.b, _cGrh.a);
  int utPrev = _from;
  for (int i = _vers.size() - 2; i >= 0 ; i--) {
    Version cVer = _vers.get(i);
    float gX = (utPrev - _from) * step;
    float gW = (cVer.unixTime - utPrev) * step;
    float gH = map(Float.parseFloat(cVer.version), 1.0, 4.0, 0.01, 1.0) * _h;
    g.rect(gX, _h - gH, gW, gH);
    utPrev = cVer.unixTime;
  }

  // draw days range
  float rW = _range * step;
  float rX = (_start - _from) * step;
  g.noFill();
  g.stroke(_cTime.h, _cTime.s, _cTime.b, _cTime.a);
  g.strokeWeight(3.0);
  g.rect(rX, 0.0, rW, _h);

  g.endDraw();
  return g;
}


/**
 * insdelCircle : draw circle graph of the insertions and deletions data.
 */
public PGraphics insdelCircle(List<Activity> _acts, int _start, int _range, int _w, int _h, Colour _cIns, Colour _cDel) {

  float divT    = TWO_PI / _range;
  float baseRad = _h * 0.1;

  PGraphics g = createGraphics(_w, _h);
  g.beginDraw();
  g.colorMode(HSB, 360, 100, 100, 100);
  g.smooth();
  g.blendMode(REPLACE);
  g.background(0.0, 0.0, 0.0, 0.0);

  // ins
  g.pushMatrix();
  g.translate(_w * 0.5, _h * 0.35);
  g.stroke(_cIns.h, _cIns.s, _cIns.b, _cIns.a);
  g.strokeWeight(2.0);
  g.noFill();
  g.beginShape();
  g.vertex(
           0.0 * cos(HALF_PI),
           0.0 * sin(HALF_PI)
           );
  for (Activity act : _acts) {
    float theta = (act.unixTime - _start) * divT;
    float radius = baseRad + log(act.insert) * _w * 0.005;
    g.vertex(
             radius * cos(+theta + HALF_PI),
             radius * sin(+theta + HALF_PI)
             );
  }
  g.vertex(
           0.0 * cos(HALF_PI),
           0.0 * sin(HALF_PI)
           );
  g.endShape(CLOSE);
  g.fill(0.0, 0.0, 90.0, 60.0);
  g.stroke(_cIns.h, _cIns.s, _cIns.b, _cIns.a);
  g.strokeWeight(6.0);
  g.ellipse(0.0, 0.0, baseRad * 1.8, baseRad * 1.8);
  g.popMatrix();

  // del
  g.pushMatrix();
  g.translate(_w * 0.5, _h * 0.65);
  g.stroke(_cDel.h, _cDel.s, _cDel.b, _cDel.a);
  g.strokeWeight(2.0);
  g.noFill();
  g.beginShape();
  g.vertex(
           0.0 * cos(-HALF_PI),
           0.0 * sin(-HALF_PI)
           );
  for (Activity act : _acts) {
    float theta = (act.unixTime - _start) * divT;
    float radius = baseRad + log(act.delete) * _w * 0.005;
    g.vertex(
             radius * cos(-theta - HALF_PI),
             radius * sin(-theta - HALF_PI)
             );
  }
  g.vertex(
           0.0 * cos(-HALF_PI),
           0.0 * sin(-HALF_PI)
           );
  g.endShape(CLOSE);
  g.fill(0.0, 0.0, 90.0, 60.0);
  g.stroke(_cDel.h, _cDel.s, _cDel.b, _cDel.a);
  g.strokeWeight(6.0);
  g.ellipse(0.0, 0.0, baseRad * 1.8, baseRad * 1.8);
  g.popMatrix();

  g.endDraw();
  return g;
}


/**
 * logBar : draw bar graph of the files changed, insertions and deletions data.
 */
public PGraphics logBar(List<Activity> _acts, int _w, int _h, Colour _cMod, Colour _cIns, Colour _cDel) {

  float tSize = _w * 0.1;
  int   last  = _acts.size() - 1;
  
  PGraphics g = createGraphics(_w, _h);
  g.beginDraw();
  g.colorMode(HSB, 360, 100, 100, 100);
  g.smooth();
  g.blendMode(REPLACE);
  g.background(0.0, 0.0, 0.0, 0.0);
  g.rectMode(CORNER);
  g.textFont(createFont("URWGothic-Demi",240,true));
  g.textAlign(LEFT, CENTER);
  g.textSize(tSize);

  float rX = tSize * 3.0;
  float rY = -tSize * 0.25;
  float rH = tSize * 0.8;
  
  // mod
  g.pushMatrix();
  g.translate(0.0, _h * 0.1);
  g.noStroke();
  g.fill(_cMod.h, _cMod.s, _cMod.b, _cMod.a);
  g.text("FILE", 0.0, 0.0);
  g.rect(rX, rY, log(_acts.get(last).change) * 15.0, rH);
  g.popMatrix();

  // ins
  g.pushMatrix();
  g.translate(0.0, _h * 0.1 + tSize * 1.1);
  g.noStroke();
  g.fill(_cIns.h, _cIns.s, _cIns.b, _cIns.a);
  g.text("INS", 0.0, 0.0);
  g.rect(rX, rY, log(_acts.get(last).insert) * 15.0, rH);
  g.popMatrix();

  // del
  g.pushMatrix();
  g.translate(0.0, _h * 0.1 + tSize * 2.2);
  g.noStroke();
  g.fill(_cDel.h, _cDel.s, _cDel.b, _cDel.a);
  g.text("DEL", 0.0, 0.0);
  g.rect(rX, rY, log(_acts.get(last).delete) * 15.0, rH);
  g.popMatrix();

  g.endDraw();
  return g;
}


/**
 * decoGraph : draw decoration image of the files changed.
 */
public  PGraphics decoGraph(PGraphics _g, List<Activity> _acts, int _w, int _h, float _pathRatio, int _plotNum, Colour _c) {

  int   last  = _acts.size() - 1;

  PGraphics g;
  if (_g == null) {
    g = createGraphics(_w, _h);
    g.beginDraw();
    g.colorMode(HSB, 360, 100, 100, 100);
    g.smooth();
    g.blendMode(REPLACE);
    g.background(0.0, 0.0, 0.0, 0.0);
  } else {
    g = _g;
    g.beginDraw();
  }

  float rH = log(_acts.get(last).change) * _h * 0.1;
  g.stroke(_c.h, _c.s, _c.b, _c.a);
  g.fill(_c.h, _c.s, _c.b, _c.a);
  g.translate(0, _h * 0.5);
  g.rect(
         _w * _pathRatio,
         -rH * 0.5,
         _w / _plotNum,
         rH
         );

  g.endDraw();
  return g;
}


/**
 * decoBubble : draw decoration image of the files changed, insertions and deletions data.
 */
public  PGraphics decoBubble(PGraphics _g, List<Activity> _acts, int _w, int _h, float _pathRatio, Colour _c) {

  int last  = _acts.size() - 1;

  PGraphics g;
  if (_g == null) {
    g = createGraphics(_w, _h);
    g.beginDraw();
    g.colorMode(HSB, 360, 100, 100, 100);
    g.smooth();
    g.blendMode(REPLACE);
    g.background(0.0, 0.0, 0.0, 0.0);
  } else {
    g = _g;
    g.beginDraw();
  }

  float eSize = log(_acts.get(last).change) * _w * 0.01;
  g.noFill();
  g.stroke(_c.h, _c.s, _c.b, _c.a);
  g.ellipse(
            _w * (0.45 + (log(_acts.get(last).insert) - log(_acts.get(last).delete)) * 0.065),
            _h * (0.1 + _pathRatio * 0.8),
            eSize,
            eSize
            );

  g.endDraw();
  return g;
}


/**
 * getActsRange : get acitivity data in days range.
 */
public List<Activity> getActsRange(ArrayList<Activity> _acts, int[] _dailyIndex, int _start, int _range) {

  // get start index
  int start = _start;
  if (_start < 0) {
    start = 0;
  } else if (_start >= _dailyIndex.length){
    start = _acts.size() - 1;
  } else {
    for (int i = _start; i <= _start + _range; i++) {
      if (_dailyIndex[i] != -1) {
        start = _dailyIndex[i];
        break;
      }
    }
  }

  // get activities
  int end = _start + _range;
  if (_start + _range < 0) {
    end = 0;
  } else if (_start + _range >= _dailyIndex.length){
    end = _acts.size() - 1;
  } else {
    for (int i = _start + _range; i >= _start; i--) {
      if (_dailyIndex[i] != -1) {
        end = _dailyIndex[i];
        break;
      }
    }
  }

  return _acts.subList(start, end + 1);
  
}


/**
 * getVersions : make day x version array from version data file.
 */
public ArrayList<Version> getVersions(String _fileName) {
  
  ArrayList<Version> vers = new ArrayList<Version>();
  
  // read version data file
  String fileData = null;
  ArrayList<ArrayList<String>> recs = new ArrayList<ArrayList<String>>();
  try {
    File f = new File(_fileName);
    byte[] b = new byte[(int) f.length()];
    FileInputStream fi = new FileInputStream(f);
    fi.read(b);
    fileData = new String(b);
  } catch (Exception e) {
    println("exception");
  }

  if (fileData == null) {
    println("no data");
  }

  String[] rows = fileData.split("\n");
  for (int i = 0; i < rows.length; i++) {
    //    println(rows[i]);
    String[] cols = rows[i].split("\t");
    vers.add(new Version(
                         cols[0],
                         Integer.parseInt(cols[1]),
                         cols[2]
                         ));
  }
  return vers;
}


/**
 * Version : hold version info.
 */
public class Version {

  public String date;
  public int    unixTime;
  public String version;

  Version(String _s, int _u, String _v) {
    date = _s;
    unixTime = _u;
    version = _v;
  }
}


/**
 * Development : make day x develop acitivities array from activity data file.
 */
public class Development {

  public int utTimeFrom;           // develop start unix time
  public int utTimeTo;             // develop end
  public int daysDev;              // during days of development
  public ArrayList<Activity> acts; // development activity
  public int dailyIndex[];         // holds activity index of every day of daysDev

  Development(String _fileName, int _utPerDay) {

    utTimeFrom = 0;
    utTimeTo   = 0;

    // read activity file
    String fileData = null;
    ArrayList<ArrayList<String>> recs = new ArrayList<ArrayList<String>>();
    try {
      File f = new File(_fileName);
      byte[] b = new byte[(int) f.length()];
      FileInputStream fi = new FileInputStream(f);
      fi.read(b);
      fileData = new String(b);
    } catch (Exception e) {
      println("exception");
    }

    if (fileData == null) {
      println("no data");
    }

    String[] rows = fileData.split("\n");
    for (int i = 0; i < rows.length; i++) {
      String[] cols = rows[i].split("\t");
      ArrayList<String> colAry = new ArrayList<String>();
      for (int j = 0; j < cols.length; j++) {
        colAry.add(cols[j]);
      }
      recs.add(colAry);
    
      if (i == 0) {
        utTimeFrom = Integer.parseInt(cols[1]);
      } else if (i == rows.length - 1) {
        utTimeTo = Integer.parseInt(cols[1]);
      }
    }

    // Activity data array set
    // some day has no activity
    // daysDev = 7286, recs.size() = 2798
    daysDev = floor((utTimeTo - utTimeFrom) / _utPerDay) + 1; // days of development
    acts = new ArrayList<Activity>(); // Activity data array

    // initialize with -1
    dailyIndex = new int[daysDev];
    for (int i = 0; i < daysDev; i++) {
      dailyIndex[i] = -1;
    }

    for (int i = 0; i < recs.size(); i++) {
      ArrayList<String> rec = recs.get(i);
      acts.add(new Activity(
                            rec.get(0),
                            Integer.parseInt(rec.get(1)),
                            Integer.parseInt(rec.get(2)),
                            Integer.parseInt(rec.get(3)),
                            Integer.parseInt(rec.get(4))
                            ));
      dailyIndex[floor((acts.get(i).unixTime - utTimeFrom) / _utPerDay)] = i;
    }

  }
}


/**
 * Activity : hold activity info.
 */
public class Activity {

  public String date;     // date of the activity
  public int    unixTime; // unix time of the date
  public int    change;   // changed files count
  public int    insert;   // insert count
  public int    delete;   // delete count

  Activity(String _s, int _u, int _c, int _i, int _d) {
    date = _s;
    unixTime = _u;
    change = _c;
    insert = _i;
    delete = _d;
  }
}


/**
 * Colour : define the theme color
 * you know this theme ;-)
 */
abstract class Colour {
  public int h, s, b, a; // hue, saturation, brightness, alpha
}
  
public class BlueDark extends Colour {
  BlueDark() {
    h = 232;
    s = 83;
    b = 35;
    a = 100;
  }
}
public class BlueBright extends Colour {
  BlueBright() {
    h = 231;
    s = 82;
    b = 67;
    a = 100;
  }
}
public class Cyan extends Colour {
  Cyan() {
    h = 218;
    s = 49;
    b = 100;
    a = 100;
  }
}
public class Red extends Colour {
  Red() {
    h = 343;
    s = 86;
    b = 93;
    a = 100;
  }
}
public class VioletDark extends Colour {
  VioletDark() {
    h = 271;
    s = 99;
    b = 64;
    a = 100;
  }
}
public class VioletBright extends Colour {
  VioletBright() {
    h = 267;
    s = 71;
    b = 100;
    a = 100;
  }
}


/*
Copyright (C) 2021- deconbatch

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>
*/


http://www.gnu.org/licenses/

 

Data Format


GitHub Activity

Display date  Unix time  Files change  Insertion  Deletion


ex.
2001-07-26	996073200	19	2376	114
2001-07-27	996159600	3	18	7

 

Version History

Display date  Unix time  Version


ex.
2018-07-22	1532185200	3.3
2017-01-29	1485615600	3.2

 

Processing Community Catalog

I applied the work featuring the still image of this animation for the Processing Community Catalog.

Processing Community Catalog
https://processingfoundation.org/advocacy/community-catalog

Is this adopted? I hope so.

 

Next Post Previous Post
No Comment
Add Comment
comment url