The Processing 20 years in 30 seconds.
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.
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.
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.
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.
- 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 the 'Processing' code and save these to the image(png) files.
- 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.