Visualization of the Processing twenty years of development.

It's the Processing's 20th anniversary this year. Twenty years is a long time. You can see the baby drinks spirits during that time. It's so long!

How did they feel about the development during such a long time? For whats? Good times bad times. Come rain or come shine. The summer ran by and the winter comes, twenty times.

I wondered if I could feel the developers feeling when I visualized the Processing twenty years of development with the Processing.

👉 この記事は日本語でも読めます。

 

Had I visualized it?

I planned to draw something like a graph with the data from the Processing GitHub and animate the graph value through the days of twenty years.

The image of the Processing twenty years of development.

At first, I wanted to make a "Data visualization work", but I could not. All the thing I can do was to draw something with the data. And I could not think about the essential thing of data visualization 'Find some meaning from the data'.

So this is not the data visualization, but just some animation with the data.

 

This is what I made.

This is that 'animation with the data'. Be aware of the sound.


The displaying version number of Processing and the duration are the duration of development for that version release.

The displaying version number of Processing and the duration.

Ain't that a shame to explain such a thing. I know it must be a result of lacking the point of data visualization.

The parts of the drawing express thing like these.

 

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.



 

Just a decoration.

These two is just a decoration.



 

The Processing twenty years. Past and the future.

This animation expresses about eight months in a second. When I was watching this, I felt like 'Long long time to the 1.0 release', 'It must be fun in 2.0 and 3.0 active development'. And I felt the feeling there are some precious things that were built up steadily by the hands of certainly existing people.

The twenty years of development is a serious thing. How long do you spend time making your own work? I've spent much time making this animation. But it's nothing compared to the twenty years. In the first place, I've been able to make this animation because there is the 'Processing' the result of the twenty years of development.

I've not been consciously thinking about it. But It's a very fortunate thing that I can make my own works with the benefit of the fruit of the twenty years' efforts, I feel so now. This fact encourages me!

There must happen many things for twenty years from now. The Processing development will continue, and the Processing community will be spread more and more. Of course, the brand new expression with the Processing will come out for next twenty years. Perhaps the new purpose or way to use the Processing may come out.

What can be done with the Processing in the future? We, users, should make the next twenty years. During the making of this animation, I thought about a thing like that.

 

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 the 'Processing' code and save these to the image(png) files.
  4. Make these image files to the movie(mp4) file 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 put the fetched data to the file as one-record = one-day data.

I made a class named 'Activity' that holds whole items of the record. And I made an ArrayList of the 'Activity' instance named 'acts'. So 'acts' holds whole records. Then you can access easily to the fetched data.

 

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.

 

The 'Processing' code of this animation.

It must be better to see the code than to read my poor description. (It's true with my poor coding?)

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  バージョン


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.