20 Years of Processing: Visualizing Two Decades of Development in 30 Seconds
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.
Data as Art: Moving Beyond Graphs
My initial plan was to create a standard graph, animating values across the timeline.
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.
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.
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
- Fetch the GitHub commit log data with the git command and save it to the text file.
- Merge and format the text file with the Perl script and save it to the tab-separated-value(tsv) file.
- Read the tab-separated-value file and draw some animation frames with Processing code and save these to the image(png) files.
- 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.
