├── src
├── chart
│ ├── index.spec.js
│ ├── renderer.js
│ └── index.js
├── store
│ └── index.js
├── timeline
│ ├── index.js
│ ├── index.semantical.js
│ └── index.presentational.js
├── reducers
│ ├── index.js
│ └── common.js
├── edge
│ ├── index.js
│ ├── index.semantical.js
│ ├── index.spec.js
│ ├── edge.js
│ └── index.presentational.js
├── snapshot
│ ├── index.js
│ ├── index.semantical.js
│ ├── snapshot.js
│ ├── index.presentational.js
│ └── index.spec.js
├── actions
│ └── index.js
├── walk
│ ├── index.js
│ └── index.spec.js
├── transducer
│ ├── index.js
│ └── index.spec.js
├── utils
│ ├── index.spec.js
│ └── index.js
├── git-history-flow.js
└── contribution-hunk
│ ├── index.spec.js
│ └── index.js
├── .eslintrc.js
├── .gitignore
├── webpack.config.js
├── README.md
├── package.json
├── docs
├── style.css
├── index.html
├── ghf-d3
│ ├── index.html
│ └── data.d3.js
├── ghf-npm
│ └── index.html
├── ghf-react
│ └── index.html
├── ghf-vscode
│ └── index.html
├── ghf-bootstrap
│ └── index.html
├── ghf-webpack
│ └── index.html
├── index-dev.html
├── main.js
└── out
│ └── main.js
└── scripts
└── diff-script.sh
/src/chart/index.spec.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/chart/renderer.js:
--------------------------------------------------------------------------------
1 | module.exports = Object.assign({},
2 | require('d3-selection'),
3 | require('d3-scale')
4 | );
5 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import { createStore } from 'redux';
2 | import { default as model } from '../reducers';
3 |
4 | const store = createStore(model);
5 |
6 | export { store as default };
7 |
--------------------------------------------------------------------------------
/src/timeline/index.js:
--------------------------------------------------------------------------------
1 | import { default as TimelineSemantics } from './index.semantical.js';
2 | import { default as TimelinePresentation } from './index.presentational.js';
3 |
4 | export { TimelineSemantics, TimelinePresentation };
5 |
--------------------------------------------------------------------------------
/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import { mode, xType, focus } from './common';
3 |
4 | const model = combineReducers({
5 | mode,
6 | xType,
7 | focus
8 | });
9 |
10 | export { model as default };
11 |
--------------------------------------------------------------------------------
/src/edge/index.js:
--------------------------------------------------------------------------------
1 | import { default as Edge } from './edge.js';
2 | import { default as EdgeSemantics } from './index.semantical.js';
3 | import { default as EdgePresentation } from './index.presentational.js';
4 |
5 | export { Edge, EdgeSemantics, EdgePresentation };
6 |
--------------------------------------------------------------------------------
/src/snapshot/index.js:
--------------------------------------------------------------------------------
1 | import { default as Snapshot } from './snapshot.js';
2 | import { default as SnapshotSemantics } from './index.semantical.js';
3 | import { default as SnapshotPresentation } from './index.presentational.js';
4 |
5 | export { Snapshot, SnapshotSemantics, SnapshotPresentation };
6 |
--------------------------------------------------------------------------------
/src/actions/index.js:
--------------------------------------------------------------------------------
1 | const
2 | all = {},
3 | changeMode = all.changeMode = (mode) => ({ type: 'CHANGE_MODE', payload: { mode: mode } }),
4 | changeXType = all.changeXType = (type) => ({ type: 'CHANGE_X', payload: { type: type } }),
5 | focus = all.focus = (index) => ({ type: 'FOCUS', payload: { index: index } });
6 |
7 | export { changeMode, changeXType, focus, all};
8 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "env": {
3 | "browser": true,
4 | "commonjs": true,
5 | "es6": true,
6 | "node": true
7 | },
8 | "extends": "eslint:recommended",
9 | "parserOptions": {
10 | "sourceType": "module"
11 | },
12 | "rules": {
13 | "indent": [
14 | "error",
15 | 4
16 | ],
17 | "linebreak-style": [
18 | "error",
19 | "unix"
20 | ],
21 | "quotes": [
22 | "error",
23 | "single"
24 | ],
25 | "semi": [
26 | "error",
27 | "always"
28 | ]
29 | }
30 | };
31 |
--------------------------------------------------------------------------------
/src/walk/index.js:
--------------------------------------------------------------------------------
1 | const it = function* (snapshots) {
2 | const len = snapshots.length;
3 | let i = 0;
4 |
5 | while (i < len) {
6 | yield snapshots[i++];
7 | }
8 | };
9 |
10 | const walk = (snapshot) => {
11 | let target,
12 | itr,
13 | chainLength = 0,
14 | snapshots = [];
15 |
16 | target = snapshot;
17 | while (!target.isRoot()) {
18 | snapshots.push(target);
19 | target = target.prev();
20 | chainLength++;
21 | }
22 |
23 | snapshots = snapshots.reverse();
24 | itr = it(snapshots);
25 | itr.chainLength = chainLength;
26 |
27 | return itr;
28 | };
29 |
30 | export { walk as default };
31 |
--------------------------------------------------------------------------------
/src/timeline/index.semantical.js:
--------------------------------------------------------------------------------
1 | const TimelineSemantics = class {
2 | constructor (data, store) {
3 | this.data = data;
4 | this.store = store;
5 |
6 | this.presentation = null;
7 | }
8 |
9 | connect (presentation) {
10 | this.presentation = presentation;
11 | this.presentation.setData(this.getData());
12 |
13 | return this;
14 | }
15 |
16 | getData () {
17 | return this.data;
18 | }
19 |
20 | render (group, position, depencencies) {
21 | this.presentation.setPosition(position);
22 | this.presentation.render(group, this.store.getState(), depencencies);
23 | return this;
24 | }
25 | };
26 |
27 | export { TimelineSemantics as default };
28 |
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # nyc test coverage
18 | .nyc_output
19 |
20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21 | .grunt
22 |
23 | # node-waf configuration
24 | .lock-wscript
25 |
26 | # Compiled binary addons (http://nodejs.org/api/addons.html)
27 | build/Release
28 |
29 | # Dependency directories
30 | node_modules
31 | jspm_packages
32 |
33 | # Optional npm cache directory
34 | .npm
35 |
36 | # Optional REPL history
37 | .node_repl_history
38 |
39 | # Vim swp files
40 | *.swp
41 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | entry: {
3 | 'git-history-flow': ['babel-polyfill', './src/git-history-flow.js'],
4 | 'main': ['./docs/main.js']
5 | },
6 | output: {
7 | path: __dirname + '/docs/out',
8 | filename: '[name].js',
9 | library: 'GitHistoryFlow',
10 | libraryTarget: 'umd',
11 | umdNamedDefine: true
12 | },
13 | module: {
14 | loaders: [{
15 | test: /\.css$/,
16 | loader: "style!css"
17 | }, {
18 | test: /\.js$/,
19 | loader: 'babel-loader',
20 | query: {
21 | presets: ['es2015']
22 | }
23 | }]
24 | },
25 | devServer: {
26 | inline: true,
27 | contentBase: './docs'
28 | }
29 | };
30 |
--------------------------------------------------------------------------------
/src/transducer/index.js:
--------------------------------------------------------------------------------
1 | import { Snapshot } from '../snapshot';
2 | import { default as utils } from '../utils';
3 |
4 | const userColorMap = {},
5 | palette = utils.palette(),
6 | getColorForUser = (id) => {
7 | return userColorMap[id] || (userColorMap[id] = palette.next());
8 | },
9 | transducer = (rawData) => {
10 | return rawData
11 | .map(d => {
12 | return Object.assign({ }, d, { color: getColorForUser(d.user.email) });
13 | })
14 | .reduce((acc, data) => {
15 | return Snapshot
16 | .with(acc, data)
17 | .transact(data.diff.changes);
18 | }, Snapshot.root());
19 | };
20 |
21 | export { transducer as default };
22 |
--------------------------------------------------------------------------------
/src/edge/index.semantical.js:
--------------------------------------------------------------------------------
1 | import watch from 'redux-watch';
2 |
3 | const EdgeSemantics = class {
4 | constructor (data, store) {
5 | this.data = data;
6 | this.store = store;
7 |
8 | this.presentation = null;
9 | this.unsubscribe = [];
10 | }
11 |
12 | connect (presentation) {
13 | this.presentation = presentation;
14 | this.presentation.setData(this.getData());
15 |
16 | this.unsubscribe = this.presentation.actions().map(action => {
17 | let watcher = watch(this.store.getState, action.path);
18 | return this.store.subscribe(() => requestAnimationFrame(watcher(action.executable)));
19 | });
20 |
21 | return this;
22 | }
23 |
24 | getData () {
25 | return this.data;
26 | }
27 |
28 | render (group, depencencies) {
29 | this.presentation.render(group, this.store.getState(), depencencies);
30 | return this;
31 | }
32 | };
33 |
34 | export { EdgeSemantics as default };
35 |
--------------------------------------------------------------------------------
/src/reducers/common.js:
--------------------------------------------------------------------------------
1 | const MODES_ENUM = {
2 | COMMUNITY_VIEW: 'COMMUNITY_VIEW',
3 | LATEST_COMMIT_VIEW: 'LATEST_COMMIT_VIEW'
4 | },
5 | X_TYPE_ENUM = {
6 | ORDINAL_X: 'ORDINAL_X',
7 | TIME_X: 'TIME_X'
8 | },
9 | mode = (state = MODES_ENUM.COMMUNITY_VIEW, action) => {
10 | switch (action.type) {
11 | case 'CHANGE_MODE':
12 | return action.payload.mode;
13 |
14 | default:
15 | return state;
16 | }
17 | },
18 | xType = (state = X_TYPE_ENUM.ORDINAL_X, action) => {
19 | switch (action.type) {
20 | case 'CHANGE_X':
21 | return action.payload.type;
22 |
23 | default:
24 | return state;
25 | }
26 | },
27 | focus = (state = null, action) => {
28 | switch (action.type) {
29 | case 'FOCUS':
30 | return action.payload.index;
31 |
32 | default:
33 | return state;
34 | }
35 | };
36 |
37 | export { mode, xType, focus };
38 |
--------------------------------------------------------------------------------
/src/snapshot/index.semantical.js:
--------------------------------------------------------------------------------
1 | import watch from 'redux-watch';
2 |
3 | const SnapshotSemantics = class {
4 | constructor (data, store) {
5 | this.data = data;
6 | this.store = store;
7 |
8 | this.presentation = null;
9 | this.unsubscribe = [];
10 | }
11 |
12 | connect (presentation) {
13 | this.presentation = presentation;
14 | this.presentation.setData(this.getData());
15 |
16 | this.unsubscribe = this.presentation.actions().map(action => {
17 | let watcher = watch(this.store.getState, action.path);
18 | return this.store.subscribe(() => requestAnimationFrame(watcher(action.executable)));
19 | });
20 |
21 | return this;
22 | }
23 |
24 | getData () {
25 | return this.data;
26 | }
27 |
28 | render (group, dependencies) {
29 | dependencies.store = this.store;
30 | this.presentation.render(group, this.store.getState(), dependencies);
31 | return this;
32 | }
33 | };
34 |
35 | export { SnapshotSemantics as default };
36 |
--------------------------------------------------------------------------------
/src/edge/index.spec.js:
--------------------------------------------------------------------------------
1 | /* global describe it before */
2 |
3 | import { expect } from 'chai';
4 | import { Edge } from '../edge';
5 | import { default as ContributionHunk } from '../contribution-hunk';
6 |
7 |
8 | describe('Edge', function () {
9 | let leftNode,
10 | rightNode,
11 | edge;
12 |
13 | before(function () {
14 | leftNode = ContributionHunk.of(10, 20, { color: '#ff0000' });
15 | rightNode = ContributionHunk.clone(leftNode).shift(5);
16 | edge = Edge.between(rightNode, { index: 0 }, { index: 1 });
17 | });
18 |
19 | describe('#between', function () {
20 | it('creates and returns instance of the edge', function () {
21 | expect(edge).to.be.instanceof(Edge);
22 | });
23 |
24 | it('has an attrubute which is equal to the movement', function () {
25 | expect(edge.attr).to.equal(5);
26 | });
27 | });
28 |
29 | describe('#boundary', function () {
30 | it('returns the boundary by value', function () {
31 | expect(edge.boundary(true)).to.be.deep.equal([[0, 20], [0, 10], [1, 15], [1, 25]]);
32 | });
33 | });
34 |
35 | });
36 |
--------------------------------------------------------------------------------
/src/edge/edge.js:
--------------------------------------------------------------------------------
1 | const Edge = class {
2 | constructor (right, leftBoundary, rightBoundary) {
3 | this.rightNode = right;
4 | this.leftBoundary = leftBoundary;
5 | this.rightBoundary = rightBoundary;
6 |
7 | this.attr = right.movement;
8 | }
9 |
10 | static between (right, leftBoundary, rightBoundary) {
11 | return new Edge(right, leftBoundary, rightBoundary);
12 | }
13 |
14 | boundary (equiDistant) {
15 | let lx,
16 | rx,
17 | bottom = this.rightNode.range[0],
18 | top = this.rightNode.range[1];
19 |
20 | if (equiDistant) {
21 | lx = this.leftBoundary.index;
22 | rx = this.rightBoundary.index;
23 | } else {
24 | lx = this.leftBoundary.snapshot.data.timestamp;
25 | rx = this.rightBoundary.snapshot.data.timestamp;
26 | }
27 |
28 |
29 | return [
30 | [ lx, top - this.attr ],
31 | [ lx, bottom - this.attr ],
32 | [ rx, bottom ],
33 | [ rx, top ]
34 | ];
35 | }
36 |
37 | meta () {
38 | return this.rightNode.meta;
39 | }
40 | };
41 |
42 | export { Edge as default };
43 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [git-history-flow][1]
2 | Visualize the evolution of a file tracked by git
3 |
4 | ## Run existing examples
5 | In order to run the examples, do the following steps
6 | ```
7 | npm install
8 | ```
9 | followed by
10 | ```
11 | npm start
12 | ```
13 | ## Run your own example
14 | 1. Clone the git-history-flow repo
15 | 2. Copy the script file `./scripts/diff-script.sh`
16 | 3. Go to the project (tracked by git) root and paste the copied script file
17 | 4. Run the command from the project root `./diff-script.sh package.json output.json`. This command will diff `package.json` and save the output to `output.json` file. You can optionally pass the output file name. If not passed, the output stream is redirected to terminal / console (standard output)
18 | 5. Feed the generated content (either from console / file) it to the `git-history-flow/dev/index-dev.html` file.
19 | 6. Once done follow the steps mentioned in *Run existing examples* section.
20 |
21 |
22 | ## Known issues
23 |
24 | - Not performance optimized in terms of memory usage, nodes creation, memory leaks.
25 | - Large dataset might cause the browser to slow down.
26 | - Performance is significantly slower in firefox.
27 |
28 | [1]: https://adotg.github.io/git-history-flow/
29 |
--------------------------------------------------------------------------------
/src/transducer/index.spec.js:
--------------------------------------------------------------------------------
1 | /* global describe it before */
2 |
3 | import { expect } from 'chai';
4 | import { default as transducer } from '../transducer';
5 | import { Snapshot } from '../snapshot';
6 |
7 | const data = [
8 | { diff: { changes: [0, 0, 1, 24] }, user: { email: 'a@domain.com' }, 'timestamp': '2017-03-01 20:54:47 +0530' },
9 | { diff: { changes: [19, 1, 16, 1] }, user: { email: 'b@domain.com' }, 'timestamp': '2017-03-01 23:54:47 +0530' },
10 | { diff: { changes: [12, 0, 13, 5] }, user: { email: 'c@domain.com' }, 'timestamp': '2017-03-02 23:54:47 +0530' },
11 | { diff: { changes: [12, 0, 13, 3] }, user: { email: 'd@domain.com' }, 'timestamp': '2017-03-03 23:54:47 +0530' }
12 | ];
13 |
14 |
15 | describe('Transducer', function () {
16 | let res;
17 |
18 | before(function() {
19 | res = transducer(data);
20 | });
21 |
22 | describe('transducer', function () {
23 | it('reduces to a type of Snapshot', function () {
24 | expect(res).to.be.instanceof(Snapshot);
25 | });
26 |
27 | it('creates a snapshot chain returning the last item in the chain', function () {
28 | expect(res.prevSnapshot).to.be.instanceof(Snapshot);
29 | });
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/src/utils/index.spec.js:
--------------------------------------------------------------------------------
1 | /* global describe it before */
2 |
3 | import { expect } from 'chai';
4 | import { default as utils } from '../utils';
5 |
6 | describe('utils', function () {
7 | describe('palette', function () {
8 | let palette;
9 |
10 | before(function () {
11 | palette = utils.palette();
12 | });
13 |
14 | it('is of size 24', function () {
15 | expect(palette.size()).to.equal(24);
16 | });
17 |
18 | it('has an iterator which rotates', function () {
19 | let last,
20 | i = palette.size(),
21 | first = palette.next();
22 |
23 | while (i--) {
24 | last = palette.next();
25 | }
26 | expect(first).to.equal(last);
27 | });
28 |
29 | it('can reset the counter', function () {
30 | let resetVal,
31 | first,
32 | i = 4;
33 |
34 | palette.reset();
35 | first = palette.next();
36 | while (i--) {
37 | palette.next();
38 | }
39 | palette.reset();
40 | resetVal = palette.next();
41 |
42 | expect(first).to.equal(resetVal);
43 | });
44 | });
45 | });
46 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "git-history-flow",
3 | "version": "1.0.0",
4 | "description": "Visualize the evolution of a file tracked by git",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "mocha-webpack --webpack-config webpack.config-test.js \"src/**/*.spec.js\"",
8 | "build": "webpack",
9 | "lint": "eslint ./src/**/*.js",
10 | "start": "webpack-dev-server --port 8181"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/akash-goswami/git-history-flow.git"
15 | },
16 | "author": "Akash Goswami",
17 | "license": "MIT",
18 | "bugs": {
19 | "url": "https://github.com/akash-goswami/git-history-flow/issues"
20 | },
21 | "homepage": "https://github.com/akash-goswami/git-history-flow#readme",
22 | "devDependencies": {
23 | "babel-core": "^6.24.1",
24 | "babel-eslint": "^7.2.3",
25 | "babel-loader": "^7.0.0",
26 | "babel-polyfill": "^6.23.0",
27 | "babel-preset-es2015": "^6.24.1",
28 | "chai": "^3.5.0",
29 | "eslint": "^3.19.0",
30 | "eslint-config-standard": "^10.2.0",
31 | "eslint-plugin-import": "^2.2.0",
32 | "eslint-plugin-node": "^4.2.2",
33 | "eslint-plugin-promise": "^3.5.0",
34 | "eslint-plugin-standard": "^3.0.1",
35 | "mocha": "^3.2.0",
36 | "mocha-webpack": "^1.0.0-beta.1",
37 | "webpack": "^2.3.3",
38 | "webpack-dev-server": "^2.4.2"
39 | },
40 | "dependencies": {
41 | "color": "^1.0.3",
42 | "d3-scale": "^1.0.5",
43 | "d3-selection": "^1.0.5",
44 | "d3-transition": "^1.0.4",
45 | "fusioncharts-smartlabel": "^1.0.2",
46 | "is-equal": "^1.5.5",
47 | "redux": "^3.6.0",
48 | "redux-watch": "^1.1.1"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/git-history-flow.js:
--------------------------------------------------------------------------------
1 | import { default as transducer } from './transducer';
2 | import { default as walk } from './walk';
3 | import { default as chart } from './chart';
4 | import { default as store } from './store';
5 | import { Edge, EdgeSemantics, EdgePresentation } from './edge';
6 | import { SnapshotSemantics, SnapshotPresentation } from './snapshot';
7 | import { TimelineSemantics, TimelinePresentation } from './timeline';
8 | import watch from 'redux-watch';
9 | import { all } from './actions';
10 |
11 | const render = (conf, data) => {
12 | let walkable,
13 | res,
14 | i,
15 | l,
16 | transducedRes,
17 | snapshots = [],
18 | edges = [];
19 |
20 | transducedRes = transducer(data);
21 | walkable = walk(transducedRes);
22 | res = walkable.next();
23 |
24 | while (!res.done) {
25 | snapshots.push(res.value);
26 | res = walkable.next();
27 | }
28 |
29 | for ( i = 1, l = snapshots.length; i < l; i++) {
30 | edges.push(snapshots[i].hunks
31 | .filter(hunk => !!hunk._clonedFrom)
32 | .map(hunk => Edge.between(
33 | hunk,
34 | { index: i - 1, snapshot: snapshots[i -1] },
35 | { index: i, snapshot: snapshots[i] }
36 | )));
37 | }
38 |
39 | chart(
40 | conf,
41 | new SnapshotSemantics(snapshots, store).connect(new SnapshotPresentation()),
42 | new EdgeSemantics(edges, store).connect(new EdgePresentation()),
43 | new TimelineSemantics(snapshots, store).connect(new TimelinePresentation()),
44 | { store: store }
45 | );
46 |
47 | return {
48 | store: store,
49 | watch: watch,
50 | actions: all,
51 | snapshots: snapshots
52 | };
53 | }
54 |
55 | export { render as render };
56 |
--------------------------------------------------------------------------------
/docs/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | background: #e7e7e7;
3 | color: #515151;
4 | position: absolute;
5 | width: 100%;
6 | height: 100%;
7 | }
8 |
9 | #contributor-mount {
10 | max-height: 500px;
11 | overflow: scroll;
12 | }
13 |
14 | .hf-font-md {
15 | font-weight: 300;
16 | line-height: 1.4;
17 | font-size: 12px;
18 | }
19 |
20 | .hf-font-lg{
21 | font-weight: 300;
22 | line-height: 1.4;
23 | font-size: 14px;
24 | }
25 |
26 | .hf-font-xs {
27 | font-weight: 300;
28 | line-height: 1.4;
29 | font-size: 10px;
30 | }
31 |
32 | .hf-font-xxs {
33 | font-weight: 300;
34 | line-height: 1.4;
35 | font-size: 8px;
36 | }
37 |
38 | .bs-callout {
39 | margin: 20px 0;
40 | padding: 5px 15px 5px 15px;
41 | border-left: 5px solid #eee;
42 | overflow: auto;
43 | }
44 |
45 | .bs-callout h4 {
46 | margin-top: 0;
47 | }
48 |
49 | .bs-callout p:last-child {
50 | margin-bottom: 0;
51 | }
52 |
53 | .bs-callout code,
54 | .bs-callout .highlight {
55 | background-color: #fff;
56 | }
57 |
58 | .hf-svg {
59 | border: none;
60 | background: transparent;
61 | }
62 |
63 | .hf-axislines-group {
64 | stroke: #c1c1c1;
65 | stroke-width: 0.5;
66 | stroke-opacity: .65;
67 | fill: aliceblue;
68 | font-family: monospace;
69 | font-size: 10px;
70 | }
71 |
72 | .hf-timeline-text {
73 | fill: #8b8b8b;
74 | stroke: #8b8b8b;
75 | stroke-width: 0.5px;
76 | }
77 |
78 | .hf-axisline-g {
79 | stroke-width: 0;
80 | fill: #dddddd;
81 | }
82 |
83 | .hf-ilayer {
84 | opacity: 0;
85 | }
86 |
87 | .hf-ui-restriction-layer {
88 | position: absolute;
89 | z-index: 999999;
90 | /*background: rgba(175, 175, 175, 0.5);*/
91 | background: #ffffff;
92 | }
93 |
94 | .hf-uirl-full {
95 | width: 100%;
96 | height: 100%
97 | }
98 |
99 | .hf-uirl-none {
100 | width: 0%;
101 | height: 0%
102 | }
103 |
--------------------------------------------------------------------------------
/src/walk/index.spec.js:
--------------------------------------------------------------------------------
1 | /* global describe it before */
2 |
3 | import { expect } from 'chai';
4 | import { default as walk } from '../walk';
5 |
6 | class MockList {
7 | constructor (id) {
8 | this._prev = null;
9 | this.id = id;
10 | }
11 |
12 | isRoot () {
13 | return !this._prev;
14 | }
15 |
16 | prev () {
17 | return this._prev;
18 | }
19 |
20 | setPrev (inst) {
21 | this._prev = inst;
22 | return this;
23 | }
24 | }
25 |
26 | describe('Walk', function () {
27 | let list,
28 | walkable;
29 |
30 | before(function () {
31 | list = [1, 2, 3, 4, 5]
32 | .map(d => new MockList(d))
33 | .reduce((acc, item) => {
34 | return item.setPrev(acc);
35 | }, new MockList(null));
36 | });
37 |
38 | describe('MockList', function () {
39 | it('generates the mocklist correctly', function () {
40 | let target,
41 | res = [];
42 |
43 | target = list;
44 | while(!target.isRoot()) {
45 | res.push(target.id);
46 | target = target.prev();
47 | }
48 |
49 | expect(res).to.deep.equal([5, 4, 3, 2, 1]);
50 | });
51 | });
52 |
53 | before(function () {
54 | walkable = walk(list);
55 | });
56 |
57 | describe('#chainLength', function () {
58 | it('returns length of the chain', function () {
59 | expect(typeof walkable.chainLength).to.equal('number');
60 | });
61 |
62 | it('evaluates chain length correctly', function () {
63 | expect(walkable.chainLength).to.equal(5);
64 | });
65 | });
66 |
67 | describe('#next', function () {
68 | it('returns an iteraterable', function () {
69 | expect(typeof walkable.next).to.equal('function');
70 | });
71 |
72 | it('iterates the list from start to end', function () {
73 | let next,
74 | res = [];
75 |
76 | next = walkable.next();
77 | while (!next.done) {
78 | res.push(next.value.id);
79 | next = walkable.next();
80 | }
81 |
82 | expect(res).to.deep.equal([1, 2, 3, 4, 5]);
83 | });
84 | });
85 | });
86 |
--------------------------------------------------------------------------------
/src/timeline/index.presentational.js:
--------------------------------------------------------------------------------
1 | import { default as SmartLabelManager } from 'fusioncharts-smartlabel';
2 | import { default as utils } from '../utils';
3 |
4 | const TimelinePresentation = class {
5 | constructor () {
6 | this._graphics = {};
7 | this._group = null;
8 | this._dependencies = null;
9 | this._data = null;
10 |
11 | this._smartlabel = (new SmartLabelManager(0))
12 | .setStyle({
13 | 'font-size': '10px',
14 | 'font-family': 'monospace'
15 | });
16 | }
17 |
18 | setData (data) {
19 | this._data = data;
20 | return this;
21 | }
22 |
23 | setPosition (pos) {
24 | this._pos = pos;
25 | return this;
26 | }
27 |
28 | render (group, state, depencencies) {
29 | this._group = group;
30 | this._dependencies = depencencies;
31 |
32 | this._draw(state);
33 | }
34 |
35 | _draw () {
36 | let pos = this._pos;
37 | this._drawText(pos.y);
38 |
39 | return this;
40 | }
41 |
42 | _drawText (y) {
43 | let xAxis = this._dependencies.x,
44 | smartlabel = this._smartlabel,
45 | data = this._data,
46 | group = this._graphics.textGroup = this._graphics.textGroup ||
47 | (this._group
48 | .append('g')
49 | .attr('class', 'hf-timeline-text'));
50 |
51 | group
52 | .selectAll('text')
53 | .data(utils.niceDateTicks(data[0].data.timestamp, data[data.length - 1].data.timestamp))
54 | .enter('text')
55 | .append('text')
56 | .text(d => d)
57 | .attr('text-anchor', 'middle')
58 | .attr('x', (d, i) => {
59 | let dim = smartlabel.getOriSize(d);
60 | if (i) {
61 | return xAxis(data.length - 1) - dim.width * 1 / 4;
62 | } else {
63 | return xAxis(0) + dim.width * 1 / 4;
64 | }
65 | })
66 | .attr('y', () => {
67 | return y - smartlabel.getOriSize('W').height;
68 | })
69 | .style('font-family', 'monospace')
70 | .style('font-size', '10px');
71 |
72 | return this;
73 | }
74 | };
75 |
76 | export { TimelinePresentation as default };
77 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | const utils = { };
2 |
3 | utils.palette = () => {
4 | let colors = [
5 | '#023fa5', '#7d87b9', '#bec1d4',
6 | '#d6bcc0', '#bb7784', '#8e063b',
7 | '#4a6fe3', '#8595e1', '#b5bbe3',
8 | '#e6afb9', '#e07b91', '#d33f6a',
9 | '#11c638', '#8dd593', '#c6dec7',
10 | '#ead3c6', '#f0b98d', '#ef9708',
11 | '#0fcfc0', '#9cded6', '#d5eae7',
12 | '#f3e1eb', '#f6c4e1', '#f79cd4'
13 | ],
14 | i = 0,
15 | len = colors.length,
16 | size = () => len,
17 | next = () => colors[i++ % len],
18 | reset = () => (i = 0);
19 |
20 | return { size, next, reset };
21 | };
22 |
23 | utils.ISO8601toNativeDate = (isoString) => {
24 | let isoDateArr = isoString.split(/\s+/),
25 | dateCompArr = isoDateArr[0].split('-').map(d => parseInt(d)),
26 | timeCompArr = isoDateArr[1].split(':').map(d => parseInt(d));
27 |
28 | return new Date(dateCompArr[0], dateCompArr[1] - 1, dateCompArr[2],
29 | timeCompArr[0], timeCompArr[1], timeCompArr[2]);
30 | };
31 |
32 | utils.search = (arr, item, start, end) => {
33 | let middle;
34 |
35 | start = start || 0;
36 | end = end || arr.length - 1;
37 |
38 | if (end - start === 1) {
39 | return ((arr[end] + arr[start]) / 2) > item ? start : end;
40 | }
41 |
42 | middle = (start + end) / 2;
43 | middle = Math.floor(middle);
44 |
45 | if (arr[middle] === item) {
46 | return middle;
47 | } else if (arr[middle] < item) {
48 | return utils.search(arr, item, middle, end);
49 | } else {
50 | return utils.search(arr, item, start, middle);
51 | }
52 | };
53 |
54 | utils.getNiceDate = (date, towards = 0) => {
55 | return new Date(date.getFullYear(), date.getMonth(), date.getDate() + towards);
56 | };
57 |
58 | utils.niceDateTicks = (d1, d2) => {
59 | let dayFraction,
60 | diffInMS = d2.getTime() - d1.getTime();
61 |
62 | dayFraction = diffInMS / (1000 * 60 * 60 * 24);
63 |
64 | if (dayFraction > 1) {
65 | return [
66 | d1.getFullYear() + '-' + (d1.getMonth() + 1) + '-' + d1.getDate(),
67 | d2.getFullYear() + '-' + (d2.getMonth() + 1) + '-' + d2.getDate()
68 | ];
69 | } else {
70 | return [
71 | d1.getFullYear() + '-' + (d1.getMonth() + 1) + '-' + d1.getDate() + ' ' +
72 | d1.getHours() + ':' + d1.getMinutes(),
73 | d2.getFullYear() + '-' + (d2.getMonth() + 1) + '-' + d2.getDate() + ' ' +
74 | d2.getHours() + ':' + d2.getMinutes()
75 | ];
76 | }
77 | };
78 |
79 | export { utils as default };
80 |
--------------------------------------------------------------------------------
/src/contribution-hunk/index.spec.js:
--------------------------------------------------------------------------------
1 | /* global describe it before */
2 |
3 | import { expect } from 'chai';
4 | import { default as ContributionHunk } from '../contribution-hunk';
5 |
6 |
7 | describe('ContributionHunk ', function() {
8 | let hunk;
9 |
10 | before(function () {
11 | hunk = new ContributionHunk(0, 7);
12 | });
13 |
14 | describe('#of', function() {
15 | it('returns a new instance of the class', function() {
16 | expect(hunk).to.be.an.instanceof(ContributionHunk);
17 | });
18 |
19 | it('creates an instance with range which is as same as the input', function() {
20 | expect(hunk.range).to.deep.equal([0, 7]);
21 | });
22 | });
23 |
24 |
25 | describe('#updateRange', function() {
26 | it('happens with multiple parameters', function() {
27 | expect(hunk.updateRange(3, 6).range).to.deep.equal([3, 6]);
28 | });
29 |
30 | it('happens with single parameter keeping the other range untouched', function() {
31 | expect(hunk.updateRange(undefined, 11).range).to.deep.equal([3, 11]);
32 | expect(hunk.updateRange(1, undefined).range).to.deep.equal([1, 11]);
33 | });
34 | });
35 |
36 | describe('#shift', function() {
37 | it('shifts the start and end', function() {
38 | expect(hunk.updateRange(3, 6).shift(5).range).to.deep.equal([8, 11]);
39 | });
40 |
41 | it('adds the shift to the movement', function() {
42 | expect(hunk.shift(4).movement).to.equal(9);
43 | });
44 | });
45 |
46 | describe('#clone', function() {
47 | let clone;
48 |
49 | before(function () {
50 | clone = ContributionHunk.clone(hunk);
51 | });
52 |
53 | it('clones an instance and which is a different instance from which it was cloned', function() {
54 | expect(clone).to.not.equal(hunk);
55 | });
56 |
57 | it('creates a clone with a range which is same as the original', function() {
58 | expect(clone.range).to.deep.equal(hunk.range);
59 | });
60 |
61 | it('keeps track of the clones created', function() {
62 | expect(hunk._clones.length).to.equal(1);
63 | });
64 |
65 | it('saves the source reference to the cloned object', function() {
66 | expect(clone._clonedFrom).to.equal(true);
67 | });
68 | });
69 |
70 | describe('#cloneWithRange', function() {
71 | let clone;
72 |
73 | before(function () {
74 | clone = ContributionHunk.cloneWithRange(hunk, 3, 6);
75 | });
76 |
77 | it('creates a clone with a specified range', function() {
78 | expect(clone.range).to.deep.equal([3, 6]);
79 | });
80 | });
81 | });
82 |
--------------------------------------------------------------------------------
/src/contribution-hunk/index.js:
--------------------------------------------------------------------------------
1 | const ContributionHunk = class {
2 | constructor (rangeStart, rangeEnd, meta) {
3 | this.range = [rangeStart, rangeEnd];
4 | this.meta = Object.assign({ }, meta);
5 | this.recent = true;
6 |
7 | this._clones = [];
8 | this._clonedFrom = null;
9 | this.movement = 0;
10 | this.removeFlag = rangeStart > rangeEnd ? true : false;
11 | }
12 |
13 | static of (rangeStart, rangeEnd, meta) {
14 | return new ContributionHunk(rangeStart, rangeEnd, meta);
15 | }
16 |
17 | static clone (instance) {
18 | let newHunk = new ContributionHunk(instance.range[0], instance.range[1], instance.meta);
19 |
20 | instance._clones.push(newHunk);
21 | newHunk._clonedFrom = true;
22 | newHunk.recent = false;
23 |
24 | return newHunk;
25 | }
26 |
27 | static cloneWithRange (instance, rangeStart, rangeEnd) {
28 | let newHunk = new ContributionHunk(
29 | rangeStart || instance.range[0],
30 | rangeEnd || instance.range[1],
31 | instance.meta);
32 |
33 | instance._clones.push(newHunk);
34 | newHunk._clonedFrom = true;
35 | newHunk.movement = instance.movement;
36 | newHunk.recent = false;
37 | return newHunk;
38 | }
39 |
40 | static merge(hunks) {
41 | let newItem;
42 |
43 | return hunks.reduce((acc, item, i, hunks) => {
44 | if (!i) {
45 | newItem = this.cloneWithRange(item, item.range[0], item.range[1]);
46 | newItem._clonedFrom = item._clonedFrom;
47 | newItem.recent = item.recent;
48 | return (acc.push(newItem), acc);
49 | }
50 |
51 | if(item.meta.user.email === hunks[i - 1].meta.user.email && item.movement === hunks[i - 1].movement &&
52 | item._clonedFrom === hunks[i - 1]._clonedFrom && !item.recent) {
53 | acc[acc.length - 1].updateRange(undefined, item.range[1]);
54 | } else {
55 | newItem = this.cloneWithRange(item, item.range[0], item.range[1]);
56 | newItem._clonedFrom = item._clonedFrom;
57 | newItem.recent = item.recent;
58 | acc.push(newItem);
59 | }
60 | return acc;
61 | }, []);
62 | }
63 |
64 | updateRange (rangeStart, rangeEnd) {
65 | rangeStart = isFinite(rangeStart) ? rangeStart : this.range[0];
66 | rangeEnd = isFinite(rangeEnd) ? rangeEnd : this.range[1];
67 |
68 | this.range[0] = rangeStart;
69 | this.range[1] = rangeEnd;
70 | this.removeFlag = rangeStart > rangeEnd ? true : false;
71 |
72 | return this;
73 | }
74 |
75 | shift (delta) {
76 | this.range[0] += delta;
77 | this.range[1] += delta;
78 | this.movement += delta;
79 | return this;
80 | }
81 |
82 | removable (flag) {
83 | this.removeFlag = flag;
84 | return this;
85 | }
86 | };
87 |
88 | export { ContributionHunk as default };
89 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
Git History Flow
16 |
Visualize the evolution of a file tracked by git.
17 |
18 |
Examples:
19 |
20 | D3
21 | Webpack
22 | React
23 | NPM
24 | VSCode
25 | Bootstrap
26 |
27 |
28 |
29 |
30 |
31 | According to IBM History Flow Visualization
32 |
33 |
34 | History flow is a tool for visualizing dynamic, evolving documents and the interactions of multiple collaborating authors.
35 |
36 |
37 | Git-History-Flow is an implementation of the same concept by taking subset of functionality which are suggested by the author(s). The implementation assumes that the subject is tracked by git. The input to the implementation is generated by a script and is fed to the renderer. The logic to generate the input is fairly simple. For a particular file, all the commits are listed from the beginning of the time. Following this, git log is applied on the first commit and git diff is applied for two subsequent commits.
38 |
39 |
40 | The implementation supports two display modes and two spacing mode. Two display modes are Community Mode and Latest Commit Mode which is nothing more than Community View and Recent Changes View explained in the document
41 |
42 |
43 |
44 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/src/edge/index.presentational.js:
--------------------------------------------------------------------------------
1 | import { default as color } from 'color';
2 |
3 | const EdgePresentation = class {
4 | constructor () {
5 | this._model = null;
6 | this._graphics = null;
7 | this._group = null;
8 | this._dependencies = null;
9 | this._data = null;
10 | }
11 |
12 | setData (data) {
13 | this._data = data;
14 | return this;
15 | }
16 |
17 | render (group, state, depencencies) {
18 | this._group = group;
19 | this._dependencies = depencencies;
20 |
21 | this._draw(state);
22 | }
23 |
24 | _draw (state) {
25 | this.actions().map(action => action.executable(state[action.path]));
26 | return this;
27 |
28 | }
29 |
30 |
31 | _modelToGraphics (x, boundaryFn) {
32 | let rootGraphics,
33 | nestedGraphics,
34 | graphics,
35 | y = this._dependencies.y;
36 |
37 | rootGraphics = this._group
38 | .selectAll('.hf-atomic-flow-g')
39 | .data(this._data);
40 |
41 | nestedGraphics = rootGraphics
42 | .enter()
43 | .append('g')
44 | .attr('class', 'hf-atomic-flow-g')
45 | .style('opacity', 0.5)
46 | .merge(rootGraphics)
47 | .selectAll('path')
48 | .data(d => d);
49 |
50 | graphics = nestedGraphics
51 | .enter()
52 | .append('path')
53 | .merge(nestedGraphics);
54 |
55 | graphics
56 | .attr('d', d => {
57 | let boundary = boundaryFn(d);
58 | return 'M' + x(boundary[0][0]) + ',' + y(boundary[0][1]) +
59 | 'L' + x(boundary[1][0]) + ',' + y(boundary[1][1] - 1) +
60 | 'L' + x(boundary[2][0]) + ',' + y(boundary[2][1] - 1) +
61 | 'L' + x(boundary[3][0]) + ',' + y(boundary[3][1]) +
62 | 'Z';
63 | });
64 |
65 | return graphics;
66 | }
67 |
68 | actions () {
69 | return [
70 | {
71 | path: 'xType',
72 | executable: (newVal, oldVal) => {
73 | if (newVal === oldVal) {
74 | return;
75 | }
76 |
77 | switch(newVal) {
78 | case 'ORDINAL_X':
79 | this._graphics = this._modelToGraphics(this._dependencies.x, d => d.boundary(true));
80 | break;
81 |
82 | case 'TIME_X':
83 | default:
84 | this._graphics = this._modelToGraphics(this._dependencies.timeX, d => d.boundary());
85 | break;
86 | }
87 | }
88 | },
89 | {
90 | path: 'mode',
91 | executable: (newVal, oldVal) => {
92 | if (newVal === oldVal) {
93 | return;
94 | }
95 |
96 | switch(newVal) {
97 | case 'COMMUNITY_VIEW':
98 | this._graphics
99 | .style('fill', d => d.meta().color);
100 | break;
101 |
102 | case 'LATEST_COMMIT_VIEW':
103 | default:
104 | this._graphics
105 | .style('fill', d => color(d.meta().color).fade(0.9));
106 | break;
107 | }
108 | }
109 | }
110 | ];
111 | }
112 | };
113 |
114 | export { EdgePresentation as default };
115 |
--------------------------------------------------------------------------------
/docs/ghf-d3/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Development Test Page
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
D3 Git History flow
18 |
19 |
20 |
25 |
26 |
27 |
28 |
29 |
X Axis
30 |
31 | Ordinal
32 | Time
33 |
34 |
35 |
36 |
Mode
37 |
38 | Community
39 | Commit
40 |
41 |
42 |
43 |
Commit details
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
72 |
83 |
84 |
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/docs/ghf-npm/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Development Test Page
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
NPM Git History flow
18 |
19 |
20 |
25 |
26 |
27 |
28 |
29 |
X Axis
30 |
31 | Ordinal
32 | Time
33 |
34 |
35 |
36 |
Mode
37 |
38 | Community
39 | Commit
40 |
41 |
42 |
43 |
Commit details
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
72 |
83 |
84 |
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/docs/ghf-react/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Development Test Page
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
React Git History flow
18 |
19 |
20 |
25 |
26 |
27 |
28 |
29 |
X Axis
30 |
31 | Ordinal
32 | Time
33 |
34 |
35 |
36 |
Mode
37 |
38 | Community
39 | Commit
40 |
41 |
42 |
43 |
Commit details
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
72 |
83 |
84 |
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/docs/ghf-vscode/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Development Test Page
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
VSCode Git History flow
18 |
19 |
20 |
25 |
26 |
27 |
28 |
29 |
X Axis
30 |
31 | Ordinal
32 | Time
33 |
34 |
35 |
36 |
Mode
37 |
38 | Community
39 | Commit
40 |
41 |
42 |
43 |
Commit details
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
72 |
83 |
84 |
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/docs/ghf-bootstrap/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Development Test Page
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
Bootstrap Git History flow
18 |
19 |
20 |
25 |
26 |
27 |
28 |
29 |
X Axis
30 |
31 | Ordinal
32 | Time
33 |
34 |
35 |
36 |
Mode
37 |
38 | Community
39 | Commit
40 |
41 |
42 |
43 |
Commit details
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
71 |
82 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/docs/ghf-webpack/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Development Test Page
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
Webpack Git History flow
18 |
19 |
20 |
25 |
26 |
27 |
28 |
29 |
X Axis
30 |
31 | Ordinal
32 | Time
33 |
34 |
35 |
36 |
Mode
37 |
38 | Community
39 | Commit
40 |
41 |
42 |
43 |
Commit details
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
72 |
83 |
84 |
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/docs/index-dev.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Development Test Page
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
Git History flow
18 |
19 |
20 |
25 |
26 |
27 |
28 |
29 |
X Axis
30 |
31 | Ordinal
32 | Time
33 |
34 |
35 |
36 |
Mode
37 |
38 | Community
39 | Commit
40 |
41 |
42 |
43 |
Commit details
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
73 |
84 |
85 |
86 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/src/chart/index.js:
--------------------------------------------------------------------------------
1 | import { focus } from '../actions';
2 | import { default as utils } from '../utils';
3 |
4 | const d3 = require('./renderer');
5 |
6 | function chart (conf, snapshot, edge, timeline, dependencies) {
7 | let chartSVG,
8 | rootG,
9 | timelineG,
10 | historyFlowG,
11 | snapshotG,
12 | flowG,
13 | focusMockerG,
14 | height,
15 | width,
16 | x,
17 | y,
18 | timeX,
19 | yMax,
20 | padding,
21 | params,
22 | allMS,
23 | iLayer,
24 | store = dependencies.store,
25 | data = snapshot.getData();
26 |
27 | conf.width = conf.mountPoint.clientWidth;
28 | conf.height = conf.mountPoint.clientHeight;
29 |
30 | chartSVG =
31 | d3.select(conf.mountPoint)
32 | .append('svg')
33 | .attr('class', 'hf-svg')
34 | .attr('version', '1.1')
35 | .attr('xmlns', 'http://www.w3.org/2000/svg')
36 | .attr('height', height = conf.height)
37 | .attr('width', width = conf.width);
38 |
39 | padding = {
40 | h: height * 0.05,
41 | w: width * 0.05
42 | };
43 |
44 | rootG = chartSVG
45 | .append('g')
46 | .attr('class', 'hf-root-group')
47 | .attr('transform', 'translate(' + padding.w + ', ' + 0 + ')');
48 |
49 | timelineG = rootG
50 | .append('g')
51 | .attr('class', 'hf-timeline-group');
52 |
53 | historyFlowG = rootG
54 | .append('g')
55 | .attr('class', 'hf-chart-group');
56 |
57 | snapshotG = historyFlowG
58 | .append('g')
59 | .attr('class', 'hf-snapshot-group');
60 |
61 | flowG = historyFlowG
62 | .append('g')
63 | .attr('class', 'hf-flow-group');
64 |
65 | focusMockerG = rootG
66 | .append('g')
67 | .attr('class', 'hf-mocker-group');
68 |
69 | iLayer = rootG
70 | .append('rect')
71 | .attr('class', 'hf-ilayer')
72 | .attr('x', 0)
73 | .attr('y', 0)
74 | .attr('width', width - 2 * padding.w)
75 | .attr('height', height - 2 * padding.h);
76 |
77 | x = d3
78 | .scaleLinear()
79 | .domain([0, data.length - 1])
80 | .range([0, width - 2 * padding.w]);
81 |
82 | timeX = d3
83 | .scaleTime()
84 | .domain([data[0].data.timestamp, data[data.length - 1].data.timestamp, 1])
85 | .range([0, width - 2 * padding.w]);
86 |
87 | yMax = data
88 | .reduce((acc, snapshot) => {
89 | let max = snapshot.getMax();
90 | if (acc < max) {
91 | return max;
92 | }
93 | return acc;
94 | }, Number.NEGATIVE_INFINITY);
95 |
96 | y = d3
97 | .scaleLinear()
98 | .domain([0, yMax])
99 | .range([0, height - padding.h]);
100 |
101 |
102 | params = {
103 | x: x,
104 | timeX: timeX,
105 | y: y,
106 | yMax: yMax,
107 | focusMocker: focusMockerG
108 | };
109 |
110 | timeline.render(timelineG, {
111 | y: height
112 | }, params);
113 | snapshot.render(snapshotG, params);
114 | edge.render(flowG, params);
115 |
116 | requestAnimationFrame(() => {
117 | allMS = snapshot.data.map(s => s.data.timestamp.getTime());
118 | iLayer
119 | .on('mousemove', function () {
120 | let index,
121 | state = store.getState();
122 |
123 | if (state.xType === 'ORDINAL_X') {
124 | index = Math.round(x.invert(d3.mouse(this)[0]));
125 | } else if (state.xType === 'TIME_X') {
126 | index = utils.search(allMS, Math.round(timeX.invert(d3.mouse(this)[0])));
127 | } else {
128 | index = null;
129 | }
130 |
131 | store.dispatch(focus(index));
132 | })
133 | .on('mouseout', function () {
134 | store.dispatch(focus(null));
135 | });
136 | });
137 |
138 | }
139 |
140 | export { chart as default };
141 |
--------------------------------------------------------------------------------
/scripts/diff-script.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | TARGET_FILE="$1"
4 | # Saves function output
5 | FN_OP=
6 | # Contains final output
7 | JSON_AS_STRING=
8 |
9 | # Make a json of one depth
10 | maker() {
11 | local new_obj=
12 | local i=
13 | local list=
14 |
15 | new_obj="{ "
16 | list=$1[@]
17 | list=("${!list}")
18 |
19 | for (( i = 0; i < ${#list[*]}; i+=2 ))
20 | do
21 | new_obj+=\"${list[i]}\"
22 | new_obj+=": "
23 | new_obj+=${list[i+1]}
24 |
25 | new_obj+=","
26 | done
27 |
28 | new_obj=`echo "$new_obj" | sed 's/.$//'`
29 | new_obj+=" }"
30 | echo $new_obj
31 | }
32 |
33 | # Insert a key valur pair at the end of the json at the top most level
34 | insert_at_last() {
35 | local open_obj=
36 |
37 | open_obj=`echo "$1" | sed 's/.$//'`
38 | open_obj+=", "
39 | open_obj+=\"$2\"
40 | open_obj+=": "
41 | open_obj+=$3
42 | open_obj+=" }"
43 |
44 | echo $open_obj
45 | }
46 |
47 | normalize_change() {
48 | local op=
49 | if [[ $1 == *","* ]]; then
50 | op=$1
51 | else
52 | op="$1,1"
53 | fi
54 |
55 | echo $op
56 | }
57 |
58 | extract_changes() {
59 | local res=
60 | local deletion=
61 | local addition=
62 | local op="["
63 |
64 | # Extract the text between @@ and @@
65 | res=`echo "$1" | sed -e 's/@@ \(.*\) @@\(.*\)/\1/'`
66 | deletion=`echo "$res" | sed -e 's/\-\(.*\) \(.*\)/\1/'`
67 | addition=`echo "$res" | sed -e 's/\(.*\)\+\(.*\)$/\2/'`
68 |
69 | op+=`normalize_change $deletion`
70 | op+=","
71 | op+=`normalize_change $addition`
72 | op+="]"
73 |
74 | echo $op
75 | }
76 |
77 | # Extracts the addition and deleton inf form the commit log
78 | process() {
79 | local i=
80 | local line_list=
81 | local line=
82 | local op=
83 | local op_obj="["
84 | local user=
85 | local list_len=
86 | local args=
87 |
88 | IFS=$'\n' read -rd '' -a line_list <<<"$1"
89 | list_len=${#line_list[*]}
90 |
91 | for (( i = 0; i < list_len; ++ i ))
92 | do
93 | line=${line_list[$i]}
94 | if [[ $line =~ ^@@+ ]]; then
95 | op_obj+=`extract_changes "$line"`
96 | op_obj+=","
97 | if [ "$2" = true ] ; then
98 | break
99 | fi
100 | fi
101 | done
102 |
103 | op_obj=`echo "$op_obj" | sed 's/.$//'`
104 | op_obj+="]"
105 |
106 | args=("changes" "$op_obj")
107 | op=`maker args`
108 |
109 | FN_OP=$op
110 | }
111 |
112 | loggable() {
113 | local res=
114 | # Apply git log on any commit and sends it for processing with a flag which says to stop execution whenever the
115 | # match is found
116 | res=`git log -U0 $1 $TARGET_FILE`
117 | process "$res" $true
118 | }
119 |
120 | diffable() {
121 | local res=
122 | # Apply git log on any commit and sends it for processing until the end of the string is encountered
123 | res=`git diff -U0 $1 $2 $TARGET_FILE`
124 | process "$res" $false
125 | }
126 |
127 | JSON_AS_STRING="[ "
128 |
129 | # Pretty print the log in the json format
130 | DIFF_COMMITS=`git log --pretty="{ \"commitId\": \"%h\", \"desc\": \"%f\", \"timestamp\": \"%ci\", \"user\": { \"name\": \"%cn\", \"email\": \"%ce\" } }" --reverse $1`
131 | # Split the string sepatated by new line character in list
132 | IFS=$'\n' read -rd '' -a COMMITS_LIST <<<"$DIFF_COMMITS"
133 |
134 | for (( i = 0; i < ${#COMMITS_LIST[*]}; ++ i ))
135 | do
136 | item=${COMMITS_LIST[$i]}
137 | COMMIT_ID=`echo "$item" | sed -e 's/^{[[:blank:]]\"commitId\":[[:blank:]]\"\(.*\)\",[[:blank:]]\"desc\":.*/\1/'`
138 |
139 | if ((i==0))
140 | then
141 | # For the first commit, apply git log as there is nothing against which it would be diffed to
142 | loggable $COMMIT_ID
143 | else
144 | # For for subsequent commits, apply git diff between the previous and current commit
145 | diffable $PREV_COMMIT_ID $COMMIT_ID
146 | fi
147 |
148 | OP=`insert_at_last "$item" "diff" "$FN_OP"`
149 | JSON_AS_STRING+=$OP;
150 | JSON_AS_STRING+=","
151 |
152 | PREV_COMMIT_ID=$COMMIT_ID
153 | done
154 |
155 | JSON_AS_STRING=`echo "$JSON_AS_STRING" | sed 's/.$//'`
156 | JSON_AS_STRING+=" ]"
157 |
158 | # Redirect the output to console or to a file
159 | if [ $# -eq 1 ]
160 | then
161 | echo "$JSON_AS_STRING"
162 | else
163 | echo $JSON_AS_STRING > $2
164 | fi
165 |
--------------------------------------------------------------------------------
/docs/main.js:
--------------------------------------------------------------------------------
1 | (function (win) {
2 | var api,
3 | axisType,
4 | mode,
5 | watcher,
6 | populateContributors,
7 | doc = win.document;
8 |
9 | function editCls (elm, clsName, ops) {
10 | var newList,
11 | clsList = elm.getAttribute('class'),
12 | arr = clsList.split(/\s+/),
13 | found = arr.reduce((cache, item) => {
14 | if (cache) {
15 | return cache;
16 | }
17 | return item === clsName;
18 | }, false);
19 |
20 | switch (ops) {
21 | case 'add':
22 | if (!found) {
23 | arr.push(clsName);
24 | newList = arr.join(' ');
25 | elm.setAttribute('class', newList);
26 | }
27 | break;
28 |
29 | case 'remove':
30 | if (found) {
31 | newList = arr.filter(item => item !== clsName).join(' ');
32 | elm.setAttribute('class', newList);
33 | }
34 | break;
35 | }
36 |
37 | return elm;
38 | }
39 |
40 | api = win.GitHistoryFlow.render({
41 | mountPoint: doc.getElementById('viz-container'),
42 | }, win.data);
43 |
44 | axisType = doc.getElementById('axis-type');
45 | axisType.addEventListener('click', function (e) {
46 | var target = e.target,
47 | val = target.getAttribute('_value_');
48 |
49 | Array.prototype.forEach.call(this.getElementsByTagName('button'), function (btn) {
50 | if (btn === target) {
51 | editCls(btn, 'active', 'add');
52 | } else {
53 | editCls(btn, 'active', 'remove');
54 | }
55 | });
56 |
57 | if (val === 'ordinal') {
58 | api.store.dispatch(api.actions.changeXType('ORDINAL_X'));
59 | } else if (val === 'time') {
60 | api.store.dispatch(api.actions.changeXType('TIME_X'));
61 | }
62 |
63 | mode.getElementsByTagName('button')[0].click();
64 |
65 | });
66 |
67 |
68 | mode = doc.getElementById('mode');
69 | mode.addEventListener('click', function (e) {
70 | var target = e.target,
71 | val = target.getAttribute('_value_');
72 |
73 | Array.prototype.forEach.call(this.getElementsByTagName('button'), function (btn) {
74 | if (btn === target) {
75 | editCls(btn, 'active', 'add');
76 | } else {
77 | editCls(btn, 'active', 'remove');
78 | }
79 | });
80 |
81 | if (val === 'community') {
82 | api.store.dispatch(api.actions.changeMode('COMMUNITY_VIEW'));
83 | } else if (val === 'commit') {
84 | api.store.dispatch(api.actions.changeMode('LATEST_COMMIT_VIEW'));
85 | }
86 | });
87 |
88 | populateContributors = (mode) => {
89 | var contributorBase,
90 | key,
91 | hbSrc,
92 | hbTemplate,
93 | genHtml,
94 | contributorBaseArr = [];
95 |
96 | contributorBase = api.snapshots.reduce((store, item) => {
97 | var user = item.data.user;
98 |
99 | if (user.email in store) {
100 | store[user.email].count++;
101 | } else {
102 | store[user.email] = {
103 | user: user,
104 | count: 1,
105 | color: mode === 'COMMUNITY_VIEW' ? item.data.flowColor : item.data.color
106 | };
107 | }
108 |
109 | return store;
110 | }, {});
111 |
112 | for (key in contributorBase) {
113 | contributorBaseArr.push(contributorBase[key]);
114 | }
115 |
116 | contributorBaseArr = contributorBaseArr.sort((m, n) => n.count - m.count);
117 |
118 | hbSrc = doc.getElementById('contributor-template').innerHTML;
119 | hbTemplate = win.Handlebars.compile(hbSrc);
120 | genHtml = hbTemplate({ contributor: contributorBaseArr, count: contributorBaseArr.length,
121 | commits: api.snapshots.length });
122 | doc.getElementById('contributor-mount').innerHTML = genHtml;
123 | };
124 |
125 | populateContributors('COMMUNITY_VIEW');
126 | watcher = api.watch(api.store.getState, 'mode');
127 | api.store.subscribe(watcher((newVal, oldVal) => {
128 | if (oldVal === newVal) {
129 | return;
130 | }
131 | populateContributors(newVal);
132 | }));
133 |
134 |
135 | watcher = api.watch(api.store.getState, 'focus');
136 | api.store.subscribe(watcher((newVal, oldVal) => {
137 | let snapshot,
138 | hbSrc,
139 | hbTemplate,
140 | genHtml,
141 | data,
142 | changes,
143 | context = {};
144 |
145 |
146 | if (oldVal === newVal) {
147 | return;
148 | }
149 |
150 | if (newVal === null) {
151 | doc.getElementById('summary-mount').innerHTML = '';
152 | return;
153 | }
154 |
155 | snapshot = api.snapshots[newVal];
156 | data = snapshot.data;
157 | context.desc = data.desc;
158 | context.commitId = data.commitId;
159 | context.timestamp = data.timestamp;
160 | context.user= data.user;
161 | changes = data.diff.changes.reduce((store, status) => {
162 | store.add += status[3];
163 | store.del += status[1];
164 |
165 | return store;
166 | }, { add: 0, del: 0 });
167 | context.addition = changes.add + ' ++ ';
168 | context.deletion = changes.del + ' -- ';
169 |
170 | hbSrc = doc.getElementById('summary-template').innerHTML;
171 | hbTemplate = win.Handlebars.compile(hbSrc);
172 | genHtml = hbTemplate(context);
173 | doc.getElementById('summary-mount').innerHTML = genHtml;
174 | }));
175 |
176 | populateContributors('COMMUNITY_VIEW');
177 | watcher = api.watch(api.store.getState, 'mode');
178 | api.store.subscribe(watcher((newVal, oldVal) => {
179 | if (oldVal === newVal) {
180 | return;
181 | }
182 | populateContributors(newVal);
183 | }));
184 |
185 | })(window);
186 |
--------------------------------------------------------------------------------
/src/snapshot/snapshot.js:
--------------------------------------------------------------------------------
1 | import { default as ContributionHunk } from '../contribution-hunk';
2 | import { default as utils } from '../utils';
3 | import { default as Color } from 'color';
4 |
5 | const Snapshot = class {
6 | constructor (prevSnapshot = Snapshot.root(), data = null) {
7 | if (prevSnapshot === null) {
8 | this.prevSnapshot = null;
9 | this.tracker = [null];
10 | this.hunks = [];
11 | } else {
12 | this.prevSnapshot = prevSnapshot;
13 | this.tracker = prevSnapshot.tracker.slice(0);
14 | this.hunks = prevSnapshot.hunks.map(d => ContributionHunk.clone(d));
15 | }
16 | this.data = this._parse(data);
17 | }
18 |
19 | static with (prevSnapshot, data) {
20 | return new Snapshot(prevSnapshot, data);
21 | }
22 |
23 | static root () {
24 | return new Snapshot(null);
25 | }
26 |
27 | isRoot () {
28 | return !this.prevSnapshot;
29 | }
30 |
31 | prev () {
32 | return this.prevSnapshot;
33 | }
34 |
35 | _parse (data) {
36 | let parsedData,
37 | color;
38 |
39 | if (!data) {
40 | return null;
41 | }
42 |
43 | parsedData = Object.assign({}, data);
44 | parsedData.timestamp = utils.ISO8601toNativeDate(parsedData.timestamp);
45 | color = Color(parsedData.color);
46 | parsedData.flowColor = color.rgb().opaquer(-0.5);
47 |
48 | return parsedData;
49 | }
50 |
51 | transact (commands) {
52 | let deleted,
53 | added,
54 | delta = 0;
55 |
56 | commands.forEach(command => {
57 | deleted = command[1];
58 | added = command[3];
59 |
60 | this
61 | ._atomicRemoveTransaction(command[0], deleted, delta)
62 | ._atomicAdditionTransaction(command[2], added, delta);
63 |
64 | delta += (-deleted + added);
65 | });
66 |
67 | this.hunks = ContributionHunk.merge(this.hunks);
68 | this.tracker = this.hunks.reduce((acc, hunk, i) => {
69 | return acc.concat(Array(hunk.range[1] - hunk.range[0] + 1).fill(i));
70 | }, [null]);
71 |
72 | return this;
73 | }
74 |
75 | _atomicRemoveTransaction (start, change, delta) {
76 | let startHunkIndex,
77 | endHunkIndex,
78 | hunk,
79 | hunkTop,
80 | hunkBottom,
81 | end;
82 |
83 | if (!change) {
84 | return this;
85 | }
86 |
87 | start += delta;
88 |
89 | startHunkIndex = this.tracker[start];
90 | endHunkIndex = this.tracker[end = start + change - 1];
91 |
92 | if (startHunkIndex === endHunkIndex) {
93 | hunk = this.hunks[startHunkIndex];
94 |
95 | this.hunks = this.hunks
96 | .slice(0, startHunkIndex + 1)
97 | .concat(ContributionHunk.of(start, end).removable(true))
98 | .concat(ContributionHunk.cloneWithRange(hunk, end + 1))
99 | .concat(this.hunks.slice(startHunkIndex + 1));
100 |
101 | hunk.updateRange(undefined, start - 1);
102 | } else {
103 | hunkTop = this.hunks[startHunkIndex];
104 | hunkBottom = this.hunks[endHunkIndex];
105 |
106 | this.hunks = this.hunks
107 | .slice(0, startHunkIndex + 1)
108 | .concat(ContributionHunk.of(start, hunkTop.range[1]).removable(true))
109 | .concat(this.hunks.slice(startHunkIndex + 1, endHunkIndex).map(h => h.removable(true)))
110 | .concat(ContributionHunk.cloneWithRange(hunkBottom, undefined, end).removable(true))
111 | .concat(ContributionHunk.cloneWithRange(hunkBottom, end + 1))
112 | .concat(this.hunks.slice(endHunkIndex + 1));
113 |
114 | hunkTop.updateRange(undefined, start - 1);
115 | hunkBottom.updateRange(end + 1);
116 | }
117 |
118 | this.hunks.reduce((acc, _hunk, i) => {
119 | if (!_hunk.removeFlag) {
120 | _hunk.shift(-acc.delta);
121 | } else {
122 | acc.delta += _hunk.range[1] - _hunk.range[0] + 1;
123 | acc.removables.push(i);
124 | }
125 |
126 | return acc;
127 | }, { delta: 0, removables: [] });
128 |
129 | this.hunks = this.hunks.filter(_hunk => !_hunk.removeFlag);
130 | this.tracker = this.hunks.reduce((acc, _hunk, i) => {
131 | return acc.concat(Array(_hunk.range[1] - _hunk.range[0] + 1).fill(i));
132 | }, [null]);
133 |
134 | return this;
135 | }
136 |
137 | _atomicAdditionTransaction (start, change) {
138 | let index,
139 | hunk,
140 | indexToHunk,
141 | end;
142 |
143 | if (!change) {
144 | return this;
145 | }
146 |
147 | if (this.tracker.length - 1 < start) {
148 | index = this.hunks.push(ContributionHunk.of(start, start + change - 1, this.data));
149 | this.tracker = this.tracker.concat(Array(change).fill(index - 1));
150 | } else {
151 | indexToHunk = this.tracker[start];
152 | hunk = this.hunks[indexToHunk];
153 |
154 | if(hunk.range[0] !== start) {
155 | end = hunk.range[1];
156 | hunk.updateRange(undefined, start - 1);
157 |
158 | this.hunks = this.hunks
159 | .slice(0, indexToHunk + 1)
160 | .concat(ContributionHunk.cloneWithRange(hunk, start, end))
161 | .concat(this.hunks.slice(indexToHunk + 1));
162 |
163 | this.tracker = this.tracker
164 | .slice(0, start)
165 | .concat(this.tracker.slice(start).map(d => d + 1));
166 |
167 | indexToHunk++;
168 | }
169 |
170 | this.hunks = this.hunks
171 | .slice(0, indexToHunk)
172 | .concat(ContributionHunk.of(start, start + change - 1, this.data))
173 | .concat(
174 | this.hunks
175 | .slice(indexToHunk)
176 | .map((_hunk) => _hunk.shift(change))
177 | );
178 |
179 | this.tracker = this.tracker
180 | .slice(0, start)
181 | .concat(Array(change).fill(indexToHunk))
182 | .concat(
183 | this.tracker
184 | .slice(start)
185 | .map(d => d + 1)
186 | );
187 | }
188 |
189 | return this;
190 | }
191 |
192 | getMax () {
193 | return this.hunks[this.hunks.length - 1].range[1];
194 | }
195 | };
196 |
197 | export { Snapshot as default };
198 |
--------------------------------------------------------------------------------
/src/snapshot/index.presentational.js:
--------------------------------------------------------------------------------
1 | import { default as color } from 'color';
2 |
3 | const SnapshotPresentation = class {
4 | constructor () {
5 | this._model = null;
6 | this._graphics = null;
7 | this._group = null;
8 | this._dependencies = null;
9 | this._data = null;
10 | }
11 |
12 | render (group, state, depencencies) {
13 | this._group = group;
14 | this._dependencies = depencencies;
15 |
16 | this._draw(state);
17 | }
18 |
19 | setData (data) {
20 | this._data = data;
21 | return this;
22 | }
23 |
24 | _draw (state) {
25 | this.actions().map(action => action.executable(state[action.path]));
26 | return this;
27 | }
28 |
29 | _modelToGraphics (x, xValFn) {
30 | let rootGraphics,
31 | nestedGraphics,
32 | axislineGraphics,
33 | nestedAxisLineGraphics,
34 | graphics = [],
35 | y = this._dependencies.y;
36 |
37 | rootGraphics = this._group
38 | .selectAll('.hf-atomic-snapshot-g')
39 | .data(this._data);
40 |
41 | axislineGraphics = this._group
42 | .selectAll('.hf-axisline-g')
43 | .data([this._data]);
44 |
45 | nestedAxisLineGraphics = axislineGraphics
46 | .enter()
47 | .append('g')
48 | .attr('class', 'hf-axisline-g')
49 | .merge(axislineGraphics)
50 | .selectAll('rect')
51 | .data(d =>
52 | d.map((datum, i) =>
53 | ({ groupIndex: i, parentData: datum })));
54 |
55 | graphics.push(nestedAxisLineGraphics
56 | .enter()
57 | .append('rect')
58 | .attr('y', 0)
59 | .attr('height', y(this._dependencies.yMax))
60 | .attr('width', 0.5)
61 | .merge(nestedAxisLineGraphics));
62 |
63 | nestedGraphics = rootGraphics
64 | .enter()
65 | .append('g')
66 | .attr('class', 'hf-atomic-snapshot-g')
67 | .merge(rootGraphics)
68 | .selectAll('rect')
69 | .data((d, i) =>
70 | d.hunks.map(hunk =>
71 | ({ hunk: hunk, groupIndex: i, parentData: d })));
72 |
73 | graphics.push(nestedGraphics
74 | .enter()
75 | .append('rect')
76 | .attr('y', d => d._plotYStartPos = y(d.hunk.range[0] - 1))
77 | .attr('height', d => y(d.hunk.range[1]) - d._plotYStartPos)
78 | .merge(nestedGraphics)
79 | .attr('width', d => d.__width = d.__width || 0.5));
80 |
81 | graphics.map(graph => graph
82 | .attr('x', d => d._plotXStartPos = x(xValFn(d, d.parentData))));
83 |
84 | return graphics;
85 | }
86 |
87 | actions () {
88 | return [
89 | {
90 | path: 'xType',
91 | executable: (newVal, oldVal) => {
92 | if (newVal === oldVal) {
93 | return;
94 | }
95 |
96 | switch(newVal) {
97 | case 'ORDINAL_X':
98 | this._graphics = this._modelToGraphics(this._dependencies.x, datum => datum.groupIndex);
99 | break;
100 |
101 | case 'TIME_X':
102 | default:
103 | this._graphics = this._modelToGraphics(this._dependencies.timeX, (datum, d) => d.data.timestamp);
104 | break;
105 | }
106 | }
107 | },
108 | {
109 | path: 'mode',
110 | executable: (newVal, oldVal) => {
111 | if (newVal === oldVal) {
112 | return;
113 | }
114 |
115 | switch(newVal) {
116 | case 'COMMUNITY_VIEW':
117 | this._group.select('hf-axisline-g')
118 | .style('opacity', 1.0);
119 |
120 | this._group.select('hf-atomic-snapshot-g')
121 | .style('opacity', 1.0);
122 |
123 | this._graphics[1]
124 | .attr('width', 0.5)
125 | .style('fill', d => d.hunk.meta.color);
126 | break;
127 |
128 | case 'LATEST_COMMIT_VIEW':
129 | default:
130 | this._group.select('hf-axisline-g')
131 | .style('opacity', 0.5);
132 |
133 | this._graphics[1]
134 | .attr('width', d => {
135 | if (d.hunk.recent) {
136 | return 2;
137 | } else {
138 | return null;
139 | }
140 | })
141 | .style('fill', d => {
142 | if (d.hunk.recent) {
143 | return d.hunk.meta.color;
144 | } else {
145 | return color(d.hunk.meta.color).fade(0.9);
146 | }
147 | });
148 | break;
149 | }
150 | }
151 | },
152 | {
153 | path: 'focus',
154 | executable: (newVal, oldVal) => {
155 | let focusMocker,
156 | nestedMocker,
157 | y,
158 | tx,
159 | data= [];
160 |
161 | if (newVal === oldVal) {
162 | return;
163 | }
164 |
165 | focusMocker = this._dependencies.focusMocker;
166 | y = this._dependencies.y;
167 | if (newVal === null || !isFinite(newVal)) {
168 | focusMocker.attr('transform', 'translate(' + -9999 + ',0)');
169 | return;
170 | }
171 |
172 | this._graphics[1]
173 | .each(function (d) {
174 | if (d.groupIndex === newVal) {
175 | tx = d._plotXStartPos;
176 | data.push(d);
177 | }
178 | });
179 |
180 | nestedMocker = focusMocker
181 | .selectAll('rect')
182 | .data(data);
183 | nestedMocker
184 | .exit()
185 | .remove();
186 | nestedMocker
187 | .enter()
188 | .append('rect')
189 | .attr('x', 0)
190 | .attr('width', 5)
191 | .merge(nestedMocker)
192 | .attr('y', d => d._plotYStartPos = y(d.hunk.range[0] - 1))
193 | .attr('height', d => y(d.hunk.range[1]) - d._plotYStartPos)
194 | .style('fill', d => {
195 | if (this._dependencies.store.getState().mode === 'COMMUNITY_VIEW') {
196 | return d.hunk.meta.color;
197 | } else {
198 | if (d.hunk.recent) {
199 | return d.hunk.meta.color;
200 | } else {
201 | return color(d.hunk.meta.color).fade(0.9);
202 | }
203 | }
204 | });
205 | focusMocker.attr('transform', 'translate(' + tx + ',0)');
206 | data.length = 0;
207 | }
208 | }
209 | ];
210 | }
211 | };
212 |
213 | export { SnapshotPresentation as default };
214 |
--------------------------------------------------------------------------------
/src/snapshot/index.spec.js:
--------------------------------------------------------------------------------
1 | /* global describe it before */
2 |
3 | import { expect } from 'chai';
4 | import { Snapshot } from '../snapshot';
5 |
6 |
7 | describe('Snapshot', function() {
8 | describe('#transact', function() {
9 | let firstSnapshot,
10 | secondSnapshot,
11 | thirdSnapshot,
12 | fourthSnapshot,
13 | fifthSnapshot,
14 | sixthSnapshot;
15 |
16 | before(function () {
17 | firstSnapshot = Snapshot
18 | .with(Snapshot.root(), { timestamp: '2011-08-18 10:10:16 -0700', user: { email: 'any@any.com' }})
19 | .transact([[0, 0, 1, 7]]);
20 | });
21 |
22 | describe('first snapshot i.e. first time commit', function () {
23 | it('creates only one hunk', function() {
24 | expect(firstSnapshot.hunks.length).to.equal(1);
25 | });
26 |
27 | it('creates a single hunk which represents all the lines', function() {
28 | expect(firstSnapshot.hunks[0].range).to.deep.equal([1, 7]);
29 | });
30 |
31 | it('creates a single tracker for hunks which points to a single hunk for all the lines', function() {
32 | expect(firstSnapshot.tracker).to.deep.equal([null].concat(Array(7).fill(0)));
33 | });
34 | });
35 |
36 | before(function () {
37 | secondSnapshot = Snapshot
38 | .with(firstSnapshot, { timestamp: '2012-08-18 10:10:16 -0700', user: { email: 'any2@any.com'}})
39 | .transact([[0, 0, 8, 3]]);
40 | });
41 |
42 | describe('addition of new content at the end of the file', function () {
43 | it('creates two hunks', function() {
44 | expect(secondSnapshot.hunks.length).to.equal(2);
45 | });
46 |
47 | it('retains range of the first hunk', function() {
48 | expect(secondSnapshot.hunks[0].range).to.deep.equal([1, 7]);
49 | });
50 |
51 | it('creates the second hunk with range indicating the incremental changes', function() {
52 | expect(secondSnapshot.hunks[1].range).to.deep.equal([8, 10]);
53 | });
54 |
55 | it('creates a single tracker for hunks which points to hunks for all the lines', function() {
56 | expect(secondSnapshot.tracker).to.deep.equal([null].concat(Array(7).fill(0)).concat(Array(3).fill(1)));
57 | });
58 | });
59 |
60 | before(function () {
61 | thirdSnapshot = Snapshot
62 | .with(secondSnapshot, { timestamp: '2013-08-18 10:10:16 -0700', user: { email: 'any3@any.com'}})
63 | .transact([[0, 0, 8, 2]]);
64 | });
65 |
66 | describe('addition of new content at the middle of the file where just one hunk starts', function () {
67 | it('creates three hunks', function() {
68 | expect(thirdSnapshot.hunks.length).to.equal(3);
69 | });
70 |
71 | it('retains range of the first hunk', function() {
72 | expect(thirdSnapshot.hunks[0].range).to.deep.equal([1, 7]);
73 | });
74 |
75 | it('creates the second hunk with range indicating the incremental changes', function() {
76 | expect(thirdSnapshot.hunks[1].range).to.deep.equal([8, 9]);
77 | });
78 |
79 | it('creates the third hunk with the range but shifted by the incremental change', function() {
80 | expect(thirdSnapshot.hunks[2].range).to.deep.equal([10, 12]);
81 | });
82 |
83 | it('creates a single tracker for hunks which points to hunks for all the lines', function() {
84 | expect(thirdSnapshot.tracker).to.deep.equal(
85 | [null]
86 | .concat(Array(7).fill(0))
87 | .concat(Array(2).fill(1))
88 | .concat(Array(3).fill(2)));
89 | });
90 | });
91 |
92 | before(function () {
93 | fourthSnapshot = Snapshot
94 | .with(thirdSnapshot, { timestamp: '2014-08-18 10:10:16 -0700', user: { email: 'any4@any.com'} })
95 | .transact([[0, 0, 5, 1]]);
96 | });
97 |
98 | describe('addition of new content in middle of a hunk', function () {
99 | it('creates five hunks', function() {
100 | expect(fourthSnapshot.hunks.length).to.equal(5);
101 | });
102 |
103 | it('cuts the first hunks into two and reduces the first hunks range', function() {
104 | expect(fourthSnapshot.hunks[0].range).to.deep.equal([1, 4]);
105 | });
106 |
107 | it('creates the second hunk with range indicating the incremental changes', function() {
108 | expect(fourthSnapshot.hunks[1].range).to.deep.equal([5, 5]);
109 | });
110 |
111 | it('creates the third hunk with the remaining range of the prev first hunk', function() {
112 | expect(fourthSnapshot.hunks[2].range).to.deep.equal([6, 8]);
113 | });
114 |
115 | it('creates a single tracker for hunks which points to hunks for all the lines', function() {
116 | expect(fourthSnapshot.tracker).to.deep.equal(
117 | [null]
118 | .concat(Array(4).fill(0))
119 | .concat(Array(1).fill(1))
120 | .concat(Array(3).fill(2))
121 | .concat(Array(2).fill(3))
122 | .concat(Array(3).fill(4)));
123 | });
124 | });
125 |
126 | before(function () {
127 | fifthSnapshot = Snapshot
128 | .with(thirdSnapshot, { timestamp: '2015-08-18 10:10:16 -0700', user: { email: 'any5@any.com'} })
129 | .transact([[0, 0, 7, 1]]);
130 | });
131 |
132 | describe('addition of new content where the hunk ends', function () {
133 | it('cuts the first hunks into two and reduces the first hunks range by one', function() {
134 | expect(fifthSnapshot.hunks[0].range).to.deep.equal([1, 6]);
135 | });
136 |
137 | it('creates the second hunk with range indicating the incremental changes', function() {
138 | expect(fifthSnapshot.hunks[1].range).to.deep.equal([7, 7]);
139 | });
140 |
141 | it('creates the third hunk with the remaining range of the prev first hunk', function() {
142 | expect(fifthSnapshot.hunks[2].range).to.deep.equal([8, 8]);
143 | });
144 |
145 | it('creates a single tracker for hunks which points to hunks for all the lines', function() {
146 | expect(fifthSnapshot.tracker).to.deep.equal(
147 | [null]
148 | .concat(Array(6).fill(0))
149 | .concat(Array(1).fill(1))
150 | .concat(Array(1).fill(2))
151 | .concat(Array(2).fill(3))
152 | .concat(Array(3).fill(4)));
153 | });
154 | });
155 |
156 | before(function () {
157 | sixthSnapshot = Snapshot
158 | .with(fifthSnapshot, { timestamp: '2016-08-18 10:10:16 -0700', user: { email: 'any6@any.com'} })
159 | .transact([[3, 2, 1, 0]]);
160 | });
161 |
162 | describe('deletion of content which spans only one hunk', function () {
163 | it('splits the hunk in the middle to create thre hunk, deletes the middle hunk to leave with two hunks',
164 | function () {
165 | expect(sixthSnapshot.hunks.length).to.equal(6);
166 | });
167 |
168 | it('updates the tracker to indicate the split of the hunk which has been affected', function () {
169 | expect(sixthSnapshot.tracker).to.deep.equal(
170 | [null]
171 | .concat(Array(2).fill(0))
172 | .concat(Array(2).fill(1))
173 | .concat(Array(1).fill(2))
174 | .concat(Array(1).fill(3))
175 | .concat(Array(2).fill(4))
176 | .concat(Array(3).fill(5)));
177 | });
178 | });
179 | });
180 | });
181 |
--------------------------------------------------------------------------------
/docs/out/main.js:
--------------------------------------------------------------------------------
1 | (function webpackUniversalModuleDefinition(root, factory) {
2 | if(typeof exports === 'object' && typeof module === 'object')
3 | module.exports = factory();
4 | else if(typeof define === 'function' && define.amd)
5 | define("GitHistoryFlow", [], factory);
6 | else if(typeof exports === 'object')
7 | exports["GitHistoryFlow"] = factory();
8 | else
9 | root["GitHistoryFlow"] = factory();
10 | })(this, function() {
11 | return /******/ (function(modules) { // webpackBootstrap
12 | /******/ // The module cache
13 | /******/ var installedModules = {};
14 | /******/
15 | /******/ // The require function
16 | /******/ function __webpack_require__(moduleId) {
17 | /******/
18 | /******/ // Check if module is in cache
19 | /******/ if(installedModules[moduleId])
20 | /******/ return installedModules[moduleId].exports;
21 | /******/
22 | /******/ // Create a new module (and put it into the cache)
23 | /******/ var module = installedModules[moduleId] = {
24 | /******/ i: moduleId,
25 | /******/ l: false,
26 | /******/ exports: {}
27 | /******/ };
28 | /******/
29 | /******/ // Execute the module function
30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
31 | /******/
32 | /******/ // Flag the module as loaded
33 | /******/ module.l = true;
34 | /******/
35 | /******/ // Return the exports of the module
36 | /******/ return module.exports;
37 | /******/ }
38 | /******/
39 | /******/
40 | /******/ // expose the modules object (__webpack_modules__)
41 | /******/ __webpack_require__.m = modules;
42 | /******/
43 | /******/ // expose the module cache
44 | /******/ __webpack_require__.c = installedModules;
45 | /******/
46 | /******/ // identity function for calling harmony imports with the correct context
47 | /******/ __webpack_require__.i = function(value) { return value; };
48 | /******/
49 | /******/ // define getter function for harmony exports
50 | /******/ __webpack_require__.d = function(exports, name, getter) {
51 | /******/ if(!__webpack_require__.o(exports, name)) {
52 | /******/ Object.defineProperty(exports, name, {
53 | /******/ configurable: false,
54 | /******/ enumerable: true,
55 | /******/ get: getter
56 | /******/ });
57 | /******/ }
58 | /******/ };
59 | /******/
60 | /******/ // getDefaultExport function for compatibility with non-harmony modules
61 | /******/ __webpack_require__.n = function(module) {
62 | /******/ var getter = module && module.__esModule ?
63 | /******/ function getDefault() { return module['default']; } :
64 | /******/ function getModuleExports() { return module; };
65 | /******/ __webpack_require__.d(getter, 'a', getter);
66 | /******/ return getter;
67 | /******/ };
68 | /******/
69 | /******/ // Object.prototype.hasOwnProperty.call
70 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
71 | /******/
72 | /******/ // __webpack_public_path__
73 | /******/ __webpack_require__.p = "";
74 | /******/
75 | /******/ // Load entry module and return exports
76 | /******/ return __webpack_require__(__webpack_require__.s = 535);
77 | /******/ })
78 | /************************************************************************/
79 | /******/ ({
80 |
81 | /***/ 199:
82 | /***/ (function(module, exports, __webpack_require__) {
83 |
84 | "use strict";
85 |
86 |
87 | (function (win) {
88 | var api,
89 | axisType,
90 | mode,
91 | watcher,
92 | populateContributors,
93 | doc = win.document;
94 |
95 | function editCls(elm, clsName, ops) {
96 | var newList,
97 | clsList = elm.getAttribute('class'),
98 | arr = clsList.split(/\s+/),
99 | found = arr.reduce(function (cache, item) {
100 | if (cache) {
101 | return cache;
102 | }
103 | return item === clsName;
104 | }, false);
105 |
106 | switch (ops) {
107 | case 'add':
108 | if (!found) {
109 | arr.push(clsName);
110 | newList = arr.join(' ');
111 | elm.setAttribute('class', newList);
112 | }
113 | break;
114 |
115 | case 'remove':
116 | if (found) {
117 | newList = arr.filter(function (item) {
118 | return item !== clsName;
119 | }).join(' ');
120 | elm.setAttribute('class', newList);
121 | }
122 | break;
123 | }
124 |
125 | return elm;
126 | }
127 |
128 | api = win.GitHistoryFlow.render({
129 | mountPoint: doc.getElementById('viz-container')
130 | }, win.data);
131 |
132 | axisType = doc.getElementById('axis-type');
133 | axisType.addEventListener('click', function (e) {
134 | var target = e.target,
135 | val = target.getAttribute('_value_');
136 |
137 | Array.prototype.forEach.call(this.getElementsByTagName('button'), function (btn) {
138 | if (btn === target) {
139 | editCls(btn, 'active', 'add');
140 | } else {
141 | editCls(btn, 'active', 'remove');
142 | }
143 | });
144 |
145 | if (val === 'ordinal') {
146 | api.store.dispatch(api.actions.changeXType('ORDINAL_X'));
147 | } else if (val === 'time') {
148 | api.store.dispatch(api.actions.changeXType('TIME_X'));
149 | }
150 |
151 | mode.getElementsByTagName('button')[0].click();
152 | });
153 |
154 | mode = doc.getElementById('mode');
155 | mode.addEventListener('click', function (e) {
156 | var target = e.target,
157 | val = target.getAttribute('_value_');
158 |
159 | Array.prototype.forEach.call(this.getElementsByTagName('button'), function (btn) {
160 | if (btn === target) {
161 | editCls(btn, 'active', 'add');
162 | } else {
163 | editCls(btn, 'active', 'remove');
164 | }
165 | });
166 |
167 | if (val === 'community') {
168 | api.store.dispatch(api.actions.changeMode('COMMUNITY_VIEW'));
169 | } else if (val === 'commit') {
170 | api.store.dispatch(api.actions.changeMode('LATEST_COMMIT_VIEW'));
171 | }
172 | });
173 |
174 | populateContributors = function populateContributors(mode) {
175 | var contributorBase,
176 | key,
177 | hbSrc,
178 | hbTemplate,
179 | genHtml,
180 | contributorBaseArr = [];
181 |
182 | contributorBase = api.snapshots.reduce(function (store, item) {
183 | var user = item.data.user;
184 |
185 | if (user.email in store) {
186 | store[user.email].count++;
187 | } else {
188 | store[user.email] = {
189 | user: user,
190 | count: 1,
191 | color: mode === 'COMMUNITY_VIEW' ? item.data.flowColor : item.data.color
192 | };
193 | }
194 |
195 | return store;
196 | }, {});
197 |
198 | for (key in contributorBase) {
199 | contributorBaseArr.push(contributorBase[key]);
200 | }
201 |
202 | contributorBaseArr = contributorBaseArr.sort(function (m, n) {
203 | return n.count - m.count;
204 | });
205 |
206 | hbSrc = doc.getElementById('contributor-template').innerHTML;
207 | hbTemplate = win.Handlebars.compile(hbSrc);
208 | genHtml = hbTemplate({ contributor: contributorBaseArr, count: contributorBaseArr.length,
209 | commits: api.snapshots.length });
210 | doc.getElementById('contributor-mount').innerHTML = genHtml;
211 | };
212 |
213 | populateContributors('COMMUNITY_VIEW');
214 | watcher = api.watch(api.store.getState, 'mode');
215 | api.store.subscribe(watcher(function (newVal, oldVal) {
216 | if (oldVal === newVal) {
217 | return;
218 | }
219 | populateContributors(newVal);
220 | }));
221 |
222 | watcher = api.watch(api.store.getState, 'focus');
223 | api.store.subscribe(watcher(function (newVal, oldVal) {
224 | var snapshot = void 0,
225 | hbSrc = void 0,
226 | hbTemplate = void 0,
227 | genHtml = void 0,
228 | data = void 0,
229 | changes = void 0,
230 | context = {};
231 |
232 | if (oldVal === newVal) {
233 | return;
234 | }
235 |
236 | if (newVal === null) {
237 | doc.getElementById('summary-mount').innerHTML = '';
238 | return;
239 | }
240 |
241 | snapshot = api.snapshots[newVal];
242 | data = snapshot.data;
243 | context.desc = data.desc;
244 | context.commitId = data.commitId;
245 | context.timestamp = data.timestamp;
246 | context.user = data.user;
247 | changes = data.diff.changes.reduce(function (store, status) {
248 | store.add += status[3];
249 | store.del += status[1];
250 |
251 | return store;
252 | }, { add: 0, del: 0 });
253 | context.addition = changes.add + ' ++ ';
254 | context.deletion = changes.del + ' -- ';
255 |
256 | hbSrc = doc.getElementById('summary-template').innerHTML;
257 | hbTemplate = win.Handlebars.compile(hbSrc);
258 | genHtml = hbTemplate(context);
259 | doc.getElementById('summary-mount').innerHTML = genHtml;
260 | }));
261 |
262 | populateContributors('COMMUNITY_VIEW');
263 | watcher = api.watch(api.store.getState, 'mode');
264 | api.store.subscribe(watcher(function (newVal, oldVal) {
265 | if (oldVal === newVal) {
266 | return;
267 | }
268 | populateContributors(newVal);
269 | }));
270 | })(window);
271 |
272 | /***/ }),
273 |
274 | /***/ 535:
275 | /***/ (function(module, exports, __webpack_require__) {
276 |
277 | module.exports = __webpack_require__(199);
278 |
279 |
280 | /***/ })
281 |
282 | /******/ });
283 | });
--------------------------------------------------------------------------------
/docs/ghf-d3/data.d3.js:
--------------------------------------------------------------------------------
1 | var data = [ { "commitId": "8238584", "desc": "Replace-submodule-with-package.json", "timestamp": "2011-08-18 10:10:16 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[0,0,1,14]] } },{ "commitId": "45098ed", "desc": "Auto-update-version-number-in-package.json", "timestamp": "2011-08-23 14:03:24 +0100", "user": { "name": "Jason Davies", "email": "jason@jasondavies.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "a97464f", "desc": "Use-pure-node.js-to-generate-package.json", "timestamp": "2011-08-26 00:36:40 +0100", "user": { "name": "Jason Davies", "email": "jason@jasondavies.com" } , "diff": { "changes": [[5,1,5,8],[7,2,14,8]] } },{ "commitId": "02da702", "desc": "Update-package.json-s-version-number", "timestamp": "2011-09-11 17:36:16 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[5,8,5,1],[14,8,7,2]] } },{ "commitId": "f8ce523", "desc": "Merge-branch-package-of-https-github.com-jasondavies-d3-into-release", "timestamp": "2011-09-17 20:07:56 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[5,1,5,8],[7,2,14,8]] } },{ "commitId": "fb38f19", "desc": "Merge-branch-release", "timestamp": "2011-09-17 20:35:40 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "151d09d", "desc": "Merge-branch-release", "timestamp": "2011-09-27 08:47:14 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "9e16bee", "desc": "Merge-branch-release", "timestamp": "2011-09-27 15:00:27 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "cd3d236", "desc": "Fix-a-NodeList-bug-in-transition.selectAll", "timestamp": "2011-09-29 15:00:51 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "6804a60", "desc": "Fix-a-bug-with-empty-children-arrays", "timestamp": "2011-09-29 21:53:22 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "9edd4bc", "desc": "Merge-branch-release", "timestamp": "2011-10-07 12:33:08 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "0e0ba08", "desc": "Fix-a-centroid-bug-with-CCW-polygons", "timestamp": "2011-10-07 15:37:51 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "672b331", "desc": "Merge-branch-release", "timestamp": "2011-10-10 21:40:10 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "307016e", "desc": "Fix-a-rounding-bug-in-SI-prefix-format", "timestamp": "2011-10-11 16:46:47 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "32907ec", "desc": "Merge-branch-release", "timestamp": "2011-10-11 18:02:06 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "c86108e", "desc": "Fix-a-daylight-savings-bug-in-d3.time.format", "timestamp": "2011-10-12 15:37:32 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "c724d95", "desc": "Update-UglifyJS-JSDOM-and-Vows", "timestamp": "2011-10-13 11:35:59 +0100", "user": { "name": "Jason Davies", "email": "jason@jasondavies.com" } , "diff": { "changes": [[23,3,23,3]] } },{ "commitId": "3f3569c", "desc": "Consistent-timing-for-subtransitions", "timestamp": "2011-10-14 13:45:16 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[23,3,23,3]] } },{ "commitId": "76bcd2a", "desc": "Update-JSDOM-to-0.2.8", "timestamp": "2011-10-17 08:43:27 +0100", "user": { "name": "Jason Davies", "email": "jason@jasondavies.com" } , "diff": { "changes": [[3,1,3,1],[23,3,23,3]] } },{ "commitId": "a62bd52", "desc": "Fix-a-bug-in-enter-selection-s-empty", "timestamp": "2011-10-19 20:57:53 -0400", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[23,3,23,3]] } },{ "commitId": "2f6d2fa", "desc": "Merge-branch-deps-of-https-github.com-jasondavies-d3", "timestamp": "2011-10-19 21:01:39 -0400", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[23,3,23,3]] } },{ "commitId": "c0e5b96", "desc": "Fix-two-sorting-bugs-in-chord-layout", "timestamp": "2011-10-23 22:39:40 -0400", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "338094e", "desc": "Add-main-property-to-package.json", "timestamp": "2011-10-28 17:11:20 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[21,0,22,1]] } },{ "commitId": "8cba1d7", "desc": "Merge-branch-2.5.0", "timestamp": "2011-11-04 19:36:50 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "4d6b5bc", "desc": "Update-JSDOM-and-Vows-versions", "timestamp": "2011-11-09 19:19:15 +0000", "user": { "name": "Jason Davies", "email": "jason@jasondavies.com" } , "diff": { "changes": [[25,2,25,2]] } },{ "commitId": "b04112a", "desc": "Merge-branch-release", "timestamp": "2011-11-16 13:55:11 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "2df8c62", "desc": "Merge-branch-fix-dispatch", "timestamp": "2011-11-22 14:38:43 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "9523f94", "desc": "Merge-branch-release", "timestamp": "2011-11-23 13:00:39 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "fe671a7", "desc": "Merge-branch-release", "timestamp": "2011-11-29 21:57:58 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "05d871b", "desc": "Merge-branch-order", "timestamp": "2011-12-08 18:06:31 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "5166102", "desc": "Update-dependencies-and-use-devDependencies", "timestamp": "2011-12-10 09:42:12 +0000", "user": { "name": "Jason Davies", "email": "jason@jasondavies.com" } , "diff": { "changes": [[23,4,23,7]] } },{ "commitId": "0fc6b90", "desc": "Update-dependency-versions", "timestamp": "2011-12-28 11:41:55 +0000", "user": { "name": "Jason Davies", "email": "jason@jasondavies.com" } , "diff": { "changes": [[24,1,24,1],[26,1,26,1]] } },{ "commitId": "f217c23", "desc": "Reinstate-JSDOM-as-a-primary-npm-dependency", "timestamp": "2011-12-30 18:41:40 +0000", "user": { "name": "Jason Davies", "email": "jason@jasondavies.com" } , "diff": { "changes": [[22,0,23,3],[25,2,28,1]] } },{ "commitId": "a92f8b3", "desc": "Merge-branch-2.7.1", "timestamp": "2011-12-30 12:00:45 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "fbcb028", "desc": "Merge-branch-release", "timestamp": "2012-01-17 13:07:17 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "303478b", "desc": "Merge-branch-2.7.x", "timestamp": "2012-01-26 11:18:42 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "76400ac", "desc": "Merge-branch-release", "timestamp": "2012-02-01 20:07:45 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "3a4f45f", "desc": "Add-index.js-for-easier-usage-within-Node", "timestamp": "2012-02-09 09:09:23 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[22,1,22,1]] } },{ "commitId": "230d8e9", "desc": "Merge-branch-v2.7.5", "timestamp": "2012-02-18 13:10:03 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "0bd675c", "desc": "Merge-branch-v2.8.0", "timestamp": "2012-02-24 19:19:34 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "eb607a5", "desc": "update-to-latest-jsdom", "timestamp": "2012-02-25 00:14:55 -0500", "user": { "name": "Stephen Bannasch", "email": "stephen.bannasch@gmail.com" } , "diff": { "changes": [[24,1,24,1]] } },{ "commitId": "af2af6a", "desc": "Merge-branch-release", "timestamp": "2012-03-01 16:30:25 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "7ac9a5d", "desc": "Add-sizzle-selector-dependency-to-node-module-build", "timestamp": "2012-03-08 15:44:09 -0800", "user": { "name": "David Poncelow", "email": "david@balrog.org" } , "diff": { "changes": [[24,1,24,2]] } },{ "commitId": "18724b0", "desc": "Fixed-version-specification-for-new-sizzle-dependency", "timestamp": "2012-03-08 16:08:41 -0800", "user": { "name": "David Poncelow", "email": "david@balrog.org" } , "diff": { "changes": [[25,1,25,1]] } },{ "commitId": "e7025c7", "desc": "Update-jsdom-to-v0.2.14", "timestamp": "2012-04-15 19:59:56 +0100", "user": { "name": "Jason Davies", "email": "jason@jasondavies.com" } , "diff": { "changes": [[24,1,24,1]] } },{ "commitId": "907c5a5", "desc": "Merge-branch-2.9.0", "timestamp": "2012-04-15 14:42:51 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "d3bad29", "desc": "Fix-background-events-for-d3.svg.brush", "timestamp": "2012-04-18 18:30:22 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "637896b", "desc": "add-browserify-compatibility", "timestamp": "2012-05-01 16:35:58 +0200", "user": { "name": "Lachèze Alexandre", "email": "alexandre.lacheze@gmail.com" } , "diff": { "changes": [[22,0,23,1]] } },{ "commitId": "1affcd2", "desc": "Fix-commit-637896b-to-use-src-package.js", "timestamp": "2012-05-09 10:29:22 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[23,1,23,1]] } },{ "commitId": "dd2a424", "desc": "Merge-branch-2.9.2", "timestamp": "2012-05-16 09:57:18 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "b6153d2", "desc": "Merge-branch-2.9.3", "timestamp": "2012-06-14 09:34:45 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "ad43a2e", "desc": "Merge-branch-2.9.4", "timestamp": "2012-06-19 09:15:34 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "e71de33", "desc": "Merge-branch-2.9.5", "timestamp": "2012-06-24 11:03:14 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "c5d230d", "desc": "Merge-branch-2.9.6", "timestamp": "2012-07-02 18:08:52 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "fb86373", "desc": "Add-generated-file", "timestamp": "2012-07-19 11:02:35 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[30,1,30,2]] } },{ "commitId": "108d65d", "desc": "Merge-branch-2.9.7", "timestamp": "2012-07-31 14:59:49 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "b0686e2", "desc": "Merge-branch-2.10.0", "timestamp": "2012-08-09 20:11:48 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "31d205c", "desc": "Update-to-UglifyJS-v1.3.3", "timestamp": "2012-08-12 23:14:44 +0100", "user": { "name": "Jason Davies", "email": "jason@jasondavies.com" } , "diff": { "changes": [[29,1,29,1],[31,1,31,1]] } },{ "commitId": "39347b4", "desc": "Merge-branch-2.10.1", "timestamp": "2012-09-03 19:19:37 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "342924c", "desc": "Bump-version-number-to-appease-Bower", "timestamp": "2012-09-14 12:48:53 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "3d0c548", "desc": "jam-integration", "timestamp": "2012-09-17 17:29:05 -0400", "user": { "name": "Tim Branyen", "email": "tim@tabdeveloper.com" } , "diff": { "changes": [[23,0,24,6]] } },{ "commitId": "41fece5", "desc": "Merge-branch-2.10.3", "timestamp": "2012-10-04 10:14:16 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "044f615", "desc": "Merge-branch-transition-reselect-into-2.11", "timestamp": "2012-10-05 16:29:56 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[25,1,25,1]] } },{ "commitId": "d3f855a", "desc": "Rename-d3.v3.js-to-d3.js", "timestamp": "2012-10-05 18:46:05 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[25,1,25,1]] } },{ "commitId": "5fd98b6", "desc": "Fix-URLs-in-package.json", "timestamp": "2012-10-05 21:33:03 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[13,1,13,1],[20,1,20,1]] } },{ "commitId": "ecb3339", "desc": "Switch-to-UglifyJS2", "timestamp": "2012-11-02 12:50:20 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[35,1,35,1]] } },{ "commitId": "61ffb8e", "desc": "Remove-canvas-dependency", "timestamp": "2012-12-09 13:44:05 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[36,2,36,1]] } },{ "commitId": "cd1ccd3", "desc": "Update-UglifyJS", "timestamp": "2012-12-13 15:36:25 +0000", "user": { "name": "Jason Davies", "email": "jason@jasondavies.com" } , "diff": { "changes": [[35,1,35,1]] } },{ "commitId": "ca14516", "desc": "Merge-branch-master-into-3.0", "timestamp": "2012-12-20 17:21:49 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "74582d8", "desc": "Version-number", "timestamp": "2012-12-21 09:18:07 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "d2ad9f7", "desc": "Merge-branch-3.0.1", "timestamp": "2012-12-28 09:19:30 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "662226b", "desc": "Merge-branch-3.0.2", "timestamp": "2013-01-01 15:52:50 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "baf7855", "desc": "Merge-branch-3.0.3", "timestamp": "2013-01-10 16:42:56 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "11a19ec", "desc": "Merge-branch-fix-greatarc-target", "timestamp": "2013-01-15 10:01:05 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "183060d", "desc": "Upgrade-dependencies-remove-Sizzle-dependency", "timestamp": "2013-01-24 09:26:22 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[31,2,31,1],[35,2,34,2]] } },{ "commitId": "35ac9dd", "desc": "Merge-branch-3.0.6", "timestamp": "2013-02-06 11:04:42 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "7dbb732", "desc": "Merge-branch-3.0.7", "timestamp": "2013-03-03 09:11:21 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[31,1,31,1],[34,2,34,2]] } },{ "commitId": "ed987d0", "desc": "Fix-ISO-date-parsing-on-Safari-5", "timestamp": "2013-03-03 10:28:58 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "368f80c", "desc": "Change-path-to-vows-for-npm-test", "timestamp": "2013-03-07 09:53:00 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[38,1,38,1]] } },{ "commitId": "8d3c6d4", "desc": "JSDOM-now-correctly-returns-null", "timestamp": "2013-03-11 10:57:57 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[31,1,31,1]] } },{ "commitId": "86e8f88", "desc": "Smash", "timestamp": "2013-03-12 21:01:49 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[33,0,34,1]] } },{ "commitId": "48e449b", "desc": "Smash-dependencies", "timestamp": "2013-03-12 21:31:15 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[34,1,34,1]] } },{ "commitId": "d392331", "desc": "Simplify-Makefile", "timestamp": "2013-03-13 01:59:36 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[34,1,34,1]] } },{ "commitId": "61a1651", "desc": "Merge-branch-clip-circle-of-git-github.com-jasondavies-d3-into-3.1.0", "timestamp": "2013-03-13 10:57:10 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[34,1,34,1]] } },{ "commitId": "840c112", "desc": "Remove-index-from-directory-imports", "timestamp": "2013-03-13 11:46:32 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[34,1,34,1]] } },{ "commitId": "608902c", "desc": "Checkpoint-test-refactoring", "timestamp": "2013-03-13 23:21:23 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[34,1,34,1]] } },{ "commitId": "697db52", "desc": "Refactor-interpolate-tests-for-minimal-loading", "timestamp": "2013-03-14 10:18:11 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[34,1,34,1]] } },{ "commitId": "133011f", "desc": "Use-npm-test-in-the-Makefile", "timestamp": "2013-03-14 14:32:13 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[39,1,39,1]] } },{ "commitId": "232f050", "desc": "Pre-release-version", "timestamp": "2013-03-14 15:30:24 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "ba7bbe6", "desc": "Upgrade-jsdom-uglify-js", "timestamp": "2013-03-15 08:27:24 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[31,1,31,1],[35,1,35,1]] } },{ "commitId": "8b2975d", "desc": "Update-JSDOM-version", "timestamp": "2013-03-20 09:12:09 +0000", "user": { "name": "Jason Davies", "email": "jason@jasondavies.com" } , "diff": { "changes": [[31,1,31,1]] } },{ "commitId": "51228cc", "desc": "Merge-branch-3.1.0", "timestamp": "2013-03-21 15:54:53 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "b9e35eb", "desc": "Merge-branch-clip-extent", "timestamp": "2013-03-21 16:46:26 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "b6bc8c9", "desc": "Fix-1155-for-d3.geom.quadtree", "timestamp": "2013-03-21 16:52:26 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "91d35b4", "desc": "Automatic-clipExtent-determination-for-mercator", "timestamp": "2013-03-22 15:38:56 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "e1bbecd", "desc": "Merge-branch-fix-closing-point", "timestamp": "2013-03-24 16:51:00 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "608ef12", "desc": "Updated-package.json-to-include-d3-s-license", "timestamp": "2013-03-26 23:49:52 -0700", "user": { "name": "Lee Leathers", "email": "leeleathers@gmail.com" } , "diff": { "changes": [[40,1,40,7]] } },{ "commitId": "87dd8a5", "desc": "Add-Jason-to-official-contributors-list", "timestamp": "2013-04-02 21:51:29 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[17,0,18,6],[40,7,46,1]] } },{ "commitId": "8a415b4", "desc": "Merge-branch-master-of-https-github.com-theoreticaLee-d3-into-license", "timestamp": "2013-04-03 10:54:21 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[46,1,46,7]] } },{ "commitId": "43d09af", "desc": "Merge-branch-3.1.5", "timestamp": "2013-04-06 19:29:44 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "32e77ed", "desc": "Exit-with-what-vows-exits", "timestamp": "2013-04-11 23:10:08 +0800", "user": { "name": "Chia-liang Kao", "email": "clkao@clkao.org" } , "diff": { "changes": [[45,1,45,1]] } },{ "commitId": "1d0409e", "desc": "Merge-branch-3.1.6", "timestamp": "2013-04-30 15:56:30 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "d1d71e1", "desc": "Bump-version", "timestamp": "2013-05-15 16:11:34 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "ab3149e", "desc": "Merge-branch-3.1.8", "timestamp": "2013-05-20 10:31:07 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "eb38418", "desc": "Revert-Define-rangeBand-in-context-of-rangePoints", "timestamp": "2013-05-20 13:36:44 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "e15ac86", "desc": "Merge-branch-3.1.10", "timestamp": "2013-05-28 15:59:53 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "201b540", "desc": "Update-UglifyJS-to-2.3.6", "timestamp": "2013-05-29 16:24:16 +0100", "user": { "name": "Jason Davies", "email": "jason@jasondavies.com" } , "diff": { "changes": [[41,1,41,1]] } },{ "commitId": "84f4a62", "desc": "Merge-branch-3.2", "timestamp": "2013-06-13 15:47:51 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "1177d1d", "desc": "Merge-branch-3.2.1", "timestamp": "2013-06-19 10:55:00 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "47b39c4", "desc": "Update-version", "timestamp": "2013-06-25 17:07:20 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "747c523", "desc": "Merge-branch-3.2.3", "timestamp": "2013-07-01 11:10:05 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "d3488d6", "desc": "Bump-version", "timestamp": "2013-07-05 11:20:20 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "7d813c9", "desc": "Merge-branch-3.2.5", "timestamp": "2013-07-11 16:54:20 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "95bc9f4", "desc": "Fix-log.nice", "timestamp": "2013-07-12 09:38:35 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "268af88", "desc": "Merge-branch-3.2.7", "timestamp": "2013-07-18 23:17:22 -0400", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "23ee2c0", "desc": "Merge-branch-3.2.8", "timestamp": "2013-08-01 07:37:51 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "1c18bcd", "desc": "Bump-version", "timestamp": "2013-08-13 14:19:07 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "f59fc64", "desc": "Release-3.3.0", "timestamp": "2013-08-21 21:08:52 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "354f54d", "desc": "Bump-version", "timestamp": "2013-08-23 14:18:53 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "fc5a586", "desc": "Fix-missing-d3.transition-in-IE.-Fixes-1491", "timestamp": "2013-08-26 22:39:58 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "a83ff05", "desc": "Merge-branch-3.3.3", "timestamp": "2013-09-05 11:21:15 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "9ca071e", "desc": "Merge-branch-3.3.4", "timestamp": "2013-09-18 20:52:44 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "7bb322f", "desc": "Merge-branch-fix-implicit-ordinal-domain", "timestamp": "2013-09-21 11:49:09 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "c6f9932", "desc": "Update-dependencies", "timestamp": "2013-09-26 14:38:01 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[37,1,37,1],[40,3,40,3]] } },{ "commitId": "2c93d9c", "desc": "Merge-branch-3.3.6", "timestamp": "2013-09-26 14:38:51 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "097a3a0", "desc": "Bump-version", "timestamp": "2013-10-09 08:10:05 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "af6a4e0", "desc": "Merge-branch-3.3.7", "timestamp": "2013-10-10 16:46:19 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "ed54503", "desc": "Merge-branch-3.3.8", "timestamp": "2013-10-14 08:56:31 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "87acef2", "desc": "Merge-branch-3.3.9", "timestamp": "2013-10-25 15:36:50 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "249c01e", "desc": "Update-smash-fixes-1626", "timestamp": "2013-11-11 13:06:20 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[40,1,40,1]] } },{ "commitId": "f2525e9", "desc": "add-jspm-package.json-config", "timestamp": "2013-11-15 12:43:23 +0200", "user": { "name": "Guy Bedford", "email": "guybedford@gmail.com" } , "diff": { "changes": [[29,0,30,5]] } },{ "commitId": "70a16b5", "desc": "Merge-branch-master-of-git-github.com-guybedford-d3", "timestamp": "2013-11-19 08:37:28 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[33,1,33,3]] } },{ "commitId": "696c6d8", "desc": "Merge-branch-3.3.10", "timestamp": "2013-11-19 09:19:00 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "c43a1c3", "desc": "Use-seed-random-to-seed-Math.random", "timestamp": "2013-11-24 13:02:42 +1100", "user": { "name": "Daniel Goldbach", "email": "dan.goldbach@gmail.com" } , "diff": { "changes": [[49,1,49,2]] } },{ "commitId": "242e351", "desc": "include-d3-shim-config-for-jspm", "timestamp": "2013-11-25 14:26:45 +0200", "user": { "name": "Guy Bedford", "email": "guybedford@gmail.com" } , "diff": { "changes": [[3,1,3,1],[31,0,32,5],[33,2,38,2],[49,2,54,1]] } },{ "commitId": "65ec4c7", "desc": "Fix-for-cross-domain-d3.dsv-in-IE9", "timestamp": "2013-11-29 20:04:42 -0500", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[32,5,31,0],[38,2,33,2]] } },{ "commitId": "4e9cbcb", "desc": "Merge-branch-master-of-git-github.com-guybedford-d3", "timestamp": "2013-12-03 08:23:14 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[32,1,32,8]] } },{ "commitId": "840a2d1", "desc": "Merge-branch-3.3.12", "timestamp": "2013-12-13 16:06:11 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "3234f47", "desc": "Merge-branch-fix-subscale-time-scale-nice", "timestamp": "2013-12-16 09:24:53 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "e44ae3c", "desc": "Merge-branch-random-tests-of-https-github.com-DanGoldbach-d3-into-random-tests", "timestamp": "2014-01-09 15:14:25 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[56,1,56,2]] } },{ "commitId": "aee1214", "desc": "Restore-Math.random-on-teardown", "timestamp": "2014-01-09 15:25:16 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[57,1,57,1]] } },{ "commitId": "e425945", "desc": "Bump-version-number", "timestamp": "2014-01-09 15:36:18 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "ceee009", "desc": "Fix-browserify-reference", "timestamp": "2014-01-10 22:18:17 +0000", "user": { "name": "Jason Davies", "email": "jason@jasondavies.com" } , "diff": { "changes": [[29,1,29,1]] } },{ "commitId": "657effb", "desc": "Fix-a-winding-order-bug-in-viewport-clipping", "timestamp": "2014-01-13 11:42:04 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "441e8a4", "desc": "Abbreviate-per-maxogden", "timestamp": "2014-01-15 14:34:25 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[60,1,60,1]] } },{ "commitId": "04fa5dd", "desc": "Merge-branch-fix-prefix", "timestamp": "2014-02-18 08:39:33 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "a4bd167", "desc": "Merge-branch-safe-sin", "timestamp": "2014-02-27 07:50:18 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "fa55eea", "desc": "Merge-branch-3.4.4", "timestamp": "2014-03-24 20:45:44 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "92c9d9d", "desc": "Merge-branch-3.4.5", "timestamp": "2014-04-07 21:43:32 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "49ba8af", "desc": "Merge-branch-3.4.6", "timestamp": "2014-04-13 20:55:29 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "e159b81", "desc": "removing-.js-extension-from-the-main-property", "timestamp": "2014-05-13 22:21:37 +0100", "user": { "name": "Erin Jane", "email": "mserinjane@gmail.com" } , "diff": { "changes": [[31,1,31,1]] } },{ "commitId": "61c568f", "desc": "Merge-branch-3.4.7", "timestamp": "2014-05-18 21:42:53 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "db8d305", "desc": "Merge-branch-fix-hierarchy-revalue", "timestamp": "2014-05-19 11:25:57 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "30dcc0e", "desc": "Add-spm-support", "timestamp": "2014-05-22 18:31:22 +0800", "user": { "name": "afc163", "email": "afc163@gmail.com" } , "diff": { "changes": [[49,0,50,3]] } },{ "commitId": "974a62f", "desc": "Merge-branch-3.4.9", "timestamp": "2014-06-30 13:16:59 -0400", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "a30a79a", "desc": "Merge-branch-3.4.10", "timestamp": "2014-07-11 23:01:16 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "48ad44f", "desc": "Merge-branch-fix-transverse-mercator-center", "timestamp": "2014-07-17 15:58:17 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "b5ddd2b", "desc": "Update-to-JSDom-1.0.0", "timestamp": "2014-10-02 13:28:44 +0200", "user": { "name": "Yves Le Maout", "email": "yves@sutoiku.com" } , "diff": { "changes": [[54,1,54,1]] } },{ "commitId": "ac7fb15", "desc": "Bump-version", "timestamp": "2014-10-08 08:09:42 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "9364923", "desc": "Bump-version", "timestamp": "2014-10-17 13:01:43 +0100", "user": { "name": "Jason Davies", "email": "jason@jasondavies.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "80e9f3c", "desc": "Pre-release-version-number", "timestamp": "2014-11-14 12:55:22 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "97bc2fe", "desc": "Match-GitHub-description.-Fixes-2129", "timestamp": "2014-12-06 13:31:35 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[4,1,4,1]] } },{ "commitId": "737a991", "desc": "Merge-branch-3.5", "timestamp": "2014-12-06 14:28:55 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "8a0fe7f", "desc": "Merge-branch-3.5.1", "timestamp": "2014-12-08 10:50:38 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "4a4c3ce", "desc": "Merge-branch-3.5.2", "timestamp": "2014-12-09 10:11:40 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "4e4709c", "desc": "Fix-selection.interrupt", "timestamp": "2014-12-30 09:03:54 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "9c3df31", "desc": "Demote-JSDOM-to-development-dependency-fix-2190", "timestamp": "2015-02-06 22:41:51 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[53,3,52,0],[57,4,54,5]] } },{ "commitId": "2b9b451", "desc": "Pin-version-of-UglifyJS", "timestamp": "2015-02-07 08:13:36 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[57,1,57,1]] } },{ "commitId": "aa8605b", "desc": "Merge-branch-3.5.4", "timestamp": "2015-02-07 12:31:21 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "1fad2e0", "desc": "Simpler-no-global-for-CommonJS", "timestamp": "2015-02-07 18:48:24 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[28,1,28,1]] } },{ "commitId": "a40a611", "desc": "Fix-d3.select-document-and-d3.select-object", "timestamp": "2015-02-10 08:33:30 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[28,1,28,1]] } },{ "commitId": "d0750ec", "desc": "update-license-attribute", "timestamp": "2015-05-19 13:23:15 +0300", "user": { "name": "Gilad Peleg", "email": "giladp007@gmail.com" } , "diff": { "changes": [[63,6,63,1]] } },{ "commitId": "b484209", "desc": "Merge-branch-3.5.6", "timestamp": "2015-07-03 20:03:11 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "ca6e944", "desc": "Merge-branch-simpler-global-into-3.5.7", "timestamp": "2015-10-22 13:35:16 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[28,1,28,1]] } },{ "commitId": "0d639ef", "desc": "Merge-branch-3.5.7", "timestamp": "2015-11-09 19:57:48 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "45a2064", "desc": "Merge-branch-fix-firefox", "timestamp": "2015-11-10 08:34:18 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "7e92b73", "desc": "Merge-branch-3.5.9", "timestamp": "2015-11-16 08:02:22 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[57,1,57,1]] } },{ "commitId": "b516d77", "desc": "Merge-branch-3.5.10", "timestamp": "2015-11-29 18:39:55 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "e7652fc", "desc": "Merge-branch-3.5.11", "timestamp": "2015-12-14 11:16:41 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "78ce531", "desc": "Back-port-d3-d3-scale-9-fix", "timestamp": "2015-12-17 08:55:17 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "d030eee", "desc": "Checkpoint-4.0-branch", "timestamp": "2016-01-01 21:41:20 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,2,3,2],[7,1,6,0],[13,1,12,2],[18,6,18,2],[28,24,24,4],[54,8,30,6],[63,1,37,7]] } },{ "commitId": "1a8aa72", "desc": "Flatten-exports", "timestamp": "2016-01-02 09:12:35 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[38,0,39,1],[39,0,41,1]] } },{ "commitId": "df9e8ef", "desc": "Add-d3-dispatch-d3-dsv-d3-request", "timestamp": "2016-01-02 09:16:22 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[39,0,40,2],[43,0,46,1]] } },{ "commitId": "b55d7a3", "desc": "Add-d3-format-d3-time-d3-time-format-d3-timer", "timestamp": "2016-01-02 09:28:32 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[42,0,43,1],[47,1,48,4]] } },{ "commitId": "f69008d", "desc": "Add-d3-scale", "timestamp": "2016-01-04 17:06:04 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[47,0,48,1]] } },{ "commitId": "51fb7e9", "desc": "Add-d3-selection", "timestamp": "2016-01-04 17:10:00 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[48,0,49,1]] } },{ "commitId": "19f7432", "desc": "Update-dependencies", "timestamp": "2016-01-05 09:54:11 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[31,1,31,1],[39,1,39,1],[47,1,47,1]] } },{ "commitId": "2272115", "desc": "Update-d3-ease", "timestamp": "2016-01-05 10:29:18 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[42,1,42,1]] } },{ "commitId": "75370b3", "desc": "Publish-prerelease", "timestamp": "2016-01-05 12:52:22 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "82cc135", "desc": "Update-d3-selection", "timestamp": "2016-01-07 10:50:09 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[49,1,49,1]] } },{ "commitId": "2849160", "desc": "Update-dependencies", "timestamp": "2016-01-07 12:42:41 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[38,1,38,2],[47,2,48,2]] } },{ "commitId": "6ddc10e", "desc": "Update-d3-random", "timestamp": "2016-01-07 12:56:10 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[47,1,47,1]] } },{ "commitId": "51f86e7", "desc": "Update-d3-ease", "timestamp": "2016-01-07 13:10:57 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[43,1,43,1]] } },{ "commitId": "de114b5", "desc": "Update-dependencies", "timestamp": "2016-01-07 15:06:39 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[44,2,44,2],[49,1,49,1],[52,2,52,2]] } },{ "commitId": "e40f18a", "desc": "Update-d3-shape", "timestamp": "2016-01-07 15:28:45 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[51,1,51,1]] } },{ "commitId": "dd6f063", "desc": "Update-d3-request.-Fix-d3-time-format-exports", "timestamp": "2016-01-07 15:37:46 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[48,1,48,1]] } },{ "commitId": "cffa2aa", "desc": "Update-alpha-release", "timestamp": "2016-01-07 15:38:12 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "e43d4be", "desc": "Add-d3-axis", "timestamp": "2016-01-08 14:08:01 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[38,0,39,1],[50,1,51,1]] } },{ "commitId": "945c21e", "desc": "Update-d3-axis", "timestamp": "2016-01-08 15:28:50 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[39,1,39,1]] } },{ "commitId": "5db0abd", "desc": "Bump-version", "timestamp": "2016-01-08 16:03:00 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "696f794", "desc": "Add-d3-polygon", "timestamp": "2016-01-11 11:12:10 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[47,0,48,1]] } },{ "commitId": "a574239", "desc": "Add-d3-quadtree", "timestamp": "2016-01-11 11:16:37 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[48,0,49,1]] } },{ "commitId": "de69bae", "desc": "Add-d3-voronoi", "timestamp": "2016-01-11 11:21:37 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[57,1,57,2]] } },{ "commitId": "b5dc7e2", "desc": "Fix-browserify-key-in-package.json", "timestamp": "2016-01-11 14:27:22 -0800", "user": { "name": "Fernando Lores", "email": "flores@lendingclub.com" } , "diff": { "changes": [[3,2,3,2],[6,0,7,1],[12,2,13,1],[18,2,18,6],[24,4,28,24],[30,6,54,8],[37,23,63,1]] } },{ "commitId": "eb2e0b8", "desc": "Update-d3-axis", "timestamp": "2016-01-12 10:31:37 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,2,3,2],[7,1,6,0],[13,1,12,2],[18,6,18,2],[28,24,24,4],[54,8,30,6],[63,1,37,23]] } },{ "commitId": "f982b60", "desc": "Update-d3-scale", "timestamp": "2016-01-12 10:39:12 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[52,1,52,1]] } },{ "commitId": "12137ae", "desc": "Add-publishConfig", "timestamp": "2016-01-13 17:11:32 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,0,4,3]] } },{ "commitId": "c22314f", "desc": "Alpha-5", "timestamp": "2016-01-14 16:06:20 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[42,1,42,1],[56,1,56,1]] } },{ "commitId": "34de937", "desc": "Update-d3-quadtree-d3-voronoi", "timestamp": "2016-01-14 17:19:58 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[52,1,52,1],[61,1,61,1]] } },{ "commitId": "7836658", "desc": "Update-d3-voronoi", "timestamp": "2016-01-14 17:33:41 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[61,1,61,1]] } },{ "commitId": "7c26322", "desc": "Update-dependencies", "timestamp": "2016-01-18 14:52:42 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[34,1,34,1],[56,1,56,1],[61,1,61,1]] } },{ "commitId": "f749f70", "desc": "Bump-version", "timestamp": "2016-01-20 09:55:52 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,5,3,2],[9,0,7,1],[15,2,13,1],[21,2,18,6],[27,4,28,24],[33,6,54,8],[40,23,63,1]] } },{ "commitId": "ac6c9e9", "desc": "Update-d3-selection", "timestamp": "2016-01-20 10:34:14 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,2,3,5],[7,1,9,0],[13,1,15,2],[18,6,21,2],[28,24,27,4],[54,8,33,6],[63,1,40,23]] } },{ "commitId": "0f91ff9", "desc": "Alpha-10", "timestamp": "2016-01-22 15:56:10 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[54,1,54,1]] } },{ "commitId": "c6fcd61", "desc": "Add-d3-queue-release-alpha-11", "timestamp": "2016-01-22 21:11:53 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[52,0,53,1]] } },{ "commitId": "05fd32d", "desc": "Fix-2722-case-sensitivity-of-selection.append", "timestamp": "2016-01-27 09:30:55 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,5,3,2],[9,0,7,1],[15,2,13,1],[21,2,18,6],[27,4,28,24],[33,6,54,8],[40,24,63,1]] } },{ "commitId": "9cdd2cd", "desc": "Update-dependencies", "timestamp": "2016-01-27 14:51:58 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,2,3,5],[7,1,9,0],[13,1,15,2],[18,6,21,2],[28,24,27,4],[54,8,33,6],[63,1,40,24]] } },{ "commitId": "fc4b7f3", "desc": "Update-d3-shape", "timestamp": "2016-01-28 10:49:59 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[58,1,58,1]] } },{ "commitId": "730250e", "desc": "Alpha-12", "timestamp": "2016-01-28 10:50:07 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "d8c6cb3", "desc": "Update-d3js.org-on-postpublish", "timestamp": "2016-01-28 10:53:43 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[30,1,30,2]] } },{ "commitId": "c02ef14", "desc": "Generate-anonymous-AMD", "timestamp": "2016-01-29 10:50:58 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[42,22,42,22]] } },{ "commitId": "e5c80a7", "desc": "Push-after-publish", "timestamp": "2016-01-29 10:51:26 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[31,1,31,1]] } },{ "commitId": "644a03c", "desc": "Update-dependencies", "timestamp": "2016-02-01 12:30:13 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[48,1,48,1],[50,1,50,1],[57,1,57,1]] } },{ "commitId": "c5182e7", "desc": "Update-d3-timer", "timestamp": "2016-02-02 09:10:12 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[62,1,62,1]] } },{ "commitId": "05e932d", "desc": "Update-dependencies", "timestamp": "2016-02-03 10:03:07 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[43,1,43,1],[45,1,45,1],[50,1,50,1],[54,1,54,1],[58,1,58,1],[62,0,63,1]] } },{ "commitId": "be8ce1f", "desc": "Bump-version", "timestamp": "2016-02-03 10:03:35 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "54e19b0", "desc": "Update-d3-scale", "timestamp": "2016-02-03 14:06:53 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[57,1,57,1]] } },{ "commitId": "dbdfede", "desc": "Update-d3-ease", "timestamp": "2016-02-04 10:04:53 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[48,1,48,1]] } },{ "commitId": "74cd3b2", "desc": "Update-d3-dsv-d3-request", "timestamp": "2016-02-04 13:30:54 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[47,1,47,1],[56,1,56,1]] } },{ "commitId": "fba1026", "desc": "Update-dependencies", "timestamp": "2016-02-04 16:22:17 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[46,1,46,1],[56,1,56,1]] } },{ "commitId": "eb4748d", "desc": "Update-dependencies", "timestamp": "2016-02-05 14:24:13 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[58,1,58,1],[63,1,63,1]] } },{ "commitId": "295901f", "desc": "Rename-of-rollup-plugin-npm", "timestamp": "2016-02-07 19:46:19 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[37,1,37,1]] } },{ "commitId": "23e383f", "desc": "Bump", "timestamp": "2016-02-07 19:47:46 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "8491377", "desc": "Update-dependencies", "timestamp": "2016-02-09 16:01:18 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[46,1,46,1],[56,1,56,1],[62,1,62,1]] } },{ "commitId": "606771a", "desc": "Update-d3-time", "timestamp": "2016-02-10 10:34:24 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[60,1,60,1]] } },{ "commitId": "a4a7b45", "desc": "Oops-I-meant-d3-timer", "timestamp": "2016-02-10 11:17:55 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[60,1,60,1],[62,1,62,1]] } },{ "commitId": "92b8ef3", "desc": "Update-dependencies", "timestamp": "2016-02-10 12:10:50 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[46,1,46,1],[62,2,62,2]] } },{ "commitId": "9d9be7b", "desc": "Update-d3-selection", "timestamp": "2016-02-11 09:41:02 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[58,1,58,1]] } },{ "commitId": "de7e9ef", "desc": "Update-d3-timer", "timestamp": "2016-02-11 10:34:57 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[62,1,62,1]] } },{ "commitId": "18dd2b9", "desc": "Merge-branch-dragstart-svg-use-internet-explorer-of-https-github.com-stefwalter-d3", "timestamp": "2016-02-11 11:43:39 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,5,3,2],[9,0,7,1],[15,2,13,1],[21,2,18,6],[27,5,28,24],[34,6,54,8],[41,25,63,1]] } },{ "commitId": "0ac5903", "desc": "Replace-Makefile-with-package.json", "timestamp": "2016-02-11 12:01:54 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[61,1,61,3]] } },{ "commitId": "05ec942", "desc": "Update-d3-transition", "timestamp": "2016-02-11 15:02:54 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,2,3,5],[7,1,9,0],[13,1,15,2],[18,6,21,2],[28,24,27,5],[54,10,34,6],[65,1,41,25]] } },{ "commitId": "5104f9f", "desc": "Bump-version", "timestamp": "2016-02-11 15:07:56 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "65e0e18", "desc": "Update-d3-transition", "timestamp": "2016-02-12 13:51:48 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[63,1,63,1]] } },{ "commitId": "76fbe7b", "desc": "Update-d3-timer-d3-transition", "timestamp": "2016-02-12 15:11:22 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[62,2,62,2]] } },{ "commitId": "6146802", "desc": "Update-d3-bower-on-publish", "timestamp": "2016-02-13 15:06:11 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,5,3,2],[9,0,7,1],[15,2,13,1],[21,2,18,6],[27,5,28,24],[34,6,54,10],[41,25,65,1]] } },{ "commitId": "b561c02", "desc": "Remove-component.json-continued", "timestamp": "2016-02-17 17:36:45 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[62,1,62,1]] } },{ "commitId": "c36befc", "desc": "Only-use-createElement-for-HTML", "timestamp": "2016-02-17 17:37:32 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "945942e", "desc": "Update-dependencies", "timestamp": "2016-02-18 09:50:39 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,2,3,5],[7,1,9,0],[13,1,15,2],[18,6,21,2],[28,24,27,5],[54,10,34,6],[65,1,41,25]] } },{ "commitId": "734620e", "desc": "Update-dependencies", "timestamp": "2016-02-22 09:05:50 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[44,3,44,3],[51,1,51,1],[58,1,58,1],[62,3,62,3]] } },{ "commitId": "afd8a43", "desc": "Update-d3-scale", "timestamp": "2016-02-22 09:25:18 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[57,1,57,1]] } },{ "commitId": "acc65d0", "desc": "Update-dependencies", "timestamp": "2016-02-22 15:56:27 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[43,1,43,1],[58,1,58,1],[63,1,63,1]] } },{ "commitId": "4e3ace7", "desc": "Bump-version", "timestamp": "2016-02-22 15:56:43 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "31c1568", "desc": "Update-d3-dsv-d3-request", "timestamp": "2016-02-22 20:15:40 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[47,1,47,1],[56,1,56,1]] } },{ "commitId": "e541c4d", "desc": "Update-d3-transition", "timestamp": "2016-02-22 20:53:39 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[63,1,63,1]] } },{ "commitId": "400c3d7", "desc": "Update-d3-time", "timestamp": "2016-02-23 19:43:07 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[60,1,60,1]] } },{ "commitId": "c750efd", "desc": "Update-d3-time-d3-transition", "timestamp": "2016-02-23 21:39:06 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[60,1,60,1],[63,1,63,1]] } },{ "commitId": "294802a", "desc": "Update-dependencies", "timestamp": "2016-02-26 13:46:19 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[50,1,50,1],[57,1,57,1],[63,1,63,1]] } },{ "commitId": "9620eb2", "desc": "Update-d3-transition", "timestamp": "2016-02-26 15:30:24 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[63,1,63,1]] } },{ "commitId": "ed8e161", "desc": "Update-dependencies", "timestamp": "2016-02-28 09:37:03 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[50,1,50,1],[57,1,57,1],[63,1,63,1]] } },{ "commitId": "359e71f", "desc": "Match-npm-version-s-convention", "timestamp": "2016-02-29 11:50:30 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[31,1,31,1]] } },{ "commitId": "58c8722", "desc": "Update-d3-time", "timestamp": "2016-02-29 15:55:55 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[60,1,60,1]] } },{ "commitId": "40951a7", "desc": "Fix-2755", "timestamp": "2016-03-01 13:31:01 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[21,1,21,2],[28,1,29,1],[36,1,36,0]] } },{ "commitId": "6e017f2", "desc": "Fix-n", "timestamp": "2016-03-01 16:07:30 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[29,1,29,1]] } },{ "commitId": "bf8f43f", "desc": "Update-d3-request", "timestamp": "2016-03-02 09:12:40 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[56,1,56,1]] } },{ "commitId": "8a775aa", "desc": "Update-d3-transition", "timestamp": "2016-03-02 15:12:41 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[63,1,63,1]] } },{ "commitId": "5d1d48d", "desc": "Update-dependencies", "timestamp": "2016-03-02 22:09:48 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[46,1,46,1],[63,1,63,1]] } },{ "commitId": "f4106ef", "desc": "Create-zipball-on-postpublish", "timestamp": "2016-03-04 09:54:11 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[31,2,31,2]] } },{ "commitId": "23ca5be", "desc": "Update", "timestamp": "2016-03-06 15:06:59 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[54,1,54,1],[63,1,63,1]] } },{ "commitId": "1f4043c", "desc": "Fix-postpublish", "timestamp": "2016-03-06 15:08:16 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[32,1,32,1]] } },{ "commitId": "7fb1ecc", "desc": "Update-d3-shape", "timestamp": "2016-03-07 20:14:04 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[59,1,59,1]] } },{ "commitId": "3896582", "desc": "Windows-build-compatibility", "timestamp": "2016-03-14 12:25:49 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[29,3,29,3],[35,0,36,1],[47,1,48,1]] } },{ "commitId": "61d8fa8", "desc": "Drop-faucet", "timestamp": "2016-03-14 13:15:17 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[30,1,30,1],[35,1,34,0]] } },{ "commitId": "fcc84c7", "desc": "Use-glob", "timestamp": "2016-03-15 10:20:44 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[30,1,30,1]] } },{ "commitId": "1e7a0cf", "desc": "Add-d3-hierarchy", "timestamp": "2016-04-01 14:39:50 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[49,0,50,1]] } },{ "commitId": "c3a8d87", "desc": "Update", "timestamp": "2016-04-28 15:58:41 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[36,1,36,1],[47,1,47,1],[48,0,49,1],[54,1,55,1]] } },{ "commitId": "2e40081", "desc": "Update-d3-timer", "timestamp": "2016-04-29 09:12:01 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[64,1,64,1]] } },{ "commitId": "5ae4af1", "desc": "Update-d3-force", "timestamp": "2016-04-29 10:51:52 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[49,1,49,1]] } },{ "commitId": "8108bc7", "desc": "Update-d3-timer", "timestamp": "2016-04-29 11:22:34 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[64,1,64,1]] } },{ "commitId": "22c2882", "desc": "Update", "timestamp": "2016-05-02 09:27:46 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[43,1,43,1],[51,1,51,1],[59,1,59,1]] } },{ "commitId": "189c1b3", "desc": "Update-dependencies", "timestamp": "2016-05-03 11:30:04 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[51,1,51,1],[55,1,55,1],[58,1,58,1],[60,1,60,1],[63,1,63,1]] } },{ "commitId": "44b98b0", "desc": "Update-uglify-js", "timestamp": "2016-05-04 17:23:42 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,5,3,2],[9,0,7,1],[15,2,13,1],[21,3,18,6],[28,5,28,24],[35,5,54,10],[41,27,65,1]] } },{ "commitId": "c68cb1c", "desc": "Adopt-npm-version", "timestamp": "2016-05-04 17:28:56 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[63,1,63,1]] } },{ "commitId": "9cc9a87", "desc": "3.5.17", "timestamp": "2016-05-04 17:29:22 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "c77c2b7", "desc": "Update", "timestamp": "2016-05-11 08:44:36 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,2,3,5],[7,1,9,0],[13,1,15,2],[18,6,21,3],[28,24,28,5],[54,10,35,5],[65,1,41,28]] } },{ "commitId": "6e5c47c", "desc": "Oops-forgot-to-export-symbols", "timestamp": "2016-05-11 08:51:29 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "aa47c28", "desc": "Update-d3-drag", "timestamp": "2016-05-11 16:46:45 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[47,1,47,1]] } },{ "commitId": "d6765d8", "desc": "Update-d3-force", "timestamp": "2016-05-12 13:44:17 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[50,1,50,1]] } },{ "commitId": "d50f6c5", "desc": "Merge-branch-4", "timestamp": "2016-05-13 09:16:17 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[15,1,15,1],[19,1,19,1],[26,1,26,1]] } },{ "commitId": "c1254fc", "desc": "Update-d3-force", "timestamp": "2016-05-13 11:16:18 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[50,1,50,1]] } },{ "commitId": "bf95c3e", "desc": "Alpha-41", "timestamp": "2016-05-24 15:55:28 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[44,1,44,1],[47,1,47,1],[50,1,50,1],[52,2,52,2],[56,1,56,1],[59,4,59,4],[65,3,65,4]] } },{ "commitId": "1cb0e5e", "desc": "Update-d3-zoom-d3-axis", "timestamp": "2016-05-25 17:01:19 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[43,1,43,1],[68,1,68,1]] } },{ "commitId": "d09543d", "desc": "Update-d3-zoom", "timestamp": "2016-05-25 22:22:55 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[68,1,68,1]] } },{ "commitId": "dd5252f", "desc": "Update-d3-zoom", "timestamp": "2016-05-26 09:05:28 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[68,1,68,1]] } },{ "commitId": "67d01c0", "desc": "Alpha-45", "timestamp": "2016-06-07 21:43:51 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[15,1,15,1],[31,1,31,1],[36,1,36,2],[42,27,43,28]] } },{ "commitId": "d9e149c", "desc": "Adopt-rollup-plugin-ascii", "timestamp": "2016-06-09 13:07:31 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[37,0,38,1]] } },{ "commitId": "7424322", "desc": "Alpha-46", "timestamp": "2016-06-10 08:52:03 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[44,1,44,1],[66,1,66,1],[68,1,68,1]] } },{ "commitId": "9f27b1f", "desc": "Alpha-47", "timestamp": "2016-06-10 09:11:37 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[45,1,45,1]] } },{ "commitId": "93105bd", "desc": "Alpha-48", "timestamp": "2016-06-10 09:58:01 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[46,1,46,1]] } },{ "commitId": "9b8fda9", "desc": "Alpha-49", "timestamp": "2016-06-10 10:04:32 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[46,1,46,1]] } },{ "commitId": "907a425", "desc": "Add-d3-geo-update-dependencies", "timestamp": "2016-06-19 09:52:51 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[37,1,37,1],[44,1,44,1],[46,8,46,8],[54,0,55,1],[56,16,57,16]] } },{ "commitId": "d02d74e", "desc": "Move-API-reference-out-of-README", "timestamp": "2016-06-23 17:00:49 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[32,1,32,1]] } },{ "commitId": "21b6f2b", "desc": "4.0-release-candidate-1", "timestamp": "2016-06-24 16:34:00 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[37,1,37,1],[45,2,45,2],[50,1,50,1],[53,4,53,4],[64,3,64,3],[68,1,68,1],[70,1,70,1],[72,1,72,1]] } },{ "commitId": "4d777f8", "desc": "Update-d3-brush-zoom", "timestamp": "2016-06-25 08:16:09 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1],[46,1,46,1],[72,1,72,1]] } },{ "commitId": "a7caf93", "desc": "Prepare-for-major-release", "timestamp": "2016-06-28 08:01:38 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,4,3,1],[32,1,29,1]] } },{ "commitId": "435793a", "desc": "4.0.0", "timestamp": "2016-06-28 08:01:50 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "024e8b0", "desc": "Tweak-postpublish", "timestamp": "2016-06-28 08:02:50 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[29,1,29,1]] } },{ "commitId": "193a860", "desc": "Update-bower-on-publish.-Fixes-2874", "timestamp": "2016-06-29 17:16:54 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[29,1,29,1]] } },{ "commitId": "15a892f", "desc": "Fix-2885-missing-d3-chord", "timestamp": "2016-07-01 14:02:36 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[45,0,46,1]] } },{ "commitId": "07f0cd1", "desc": "Update-dependencies", "timestamp": "2016-07-01 14:10:07 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[53,1,53,1],[55,1,55,1],[67,1,67,1]] } },{ "commitId": "17e15a9", "desc": "4.1.0", "timestamp": "2016-07-01 22:27:51 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "8d9c8f6", "desc": "Update-dependencies", "timestamp": "2016-07-10 22:47:46 -0400", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[53,1,53,1],[61,2,61,2],[69,2,69,2]] } },{ "commitId": "bd55ecf", "desc": "4.1.1", "timestamp": "2016-07-10 22:47:51 -0400", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "5ae7fda", "desc": "Update-dependencies", "timestamp": "2016-07-29 16:02:32 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[34,1,34,1],[42,1,42,1],[46,1,46,1],[51,4,51,4],[62,5,62,5]] } },{ "commitId": "e03fcfb", "desc": "4.2.0", "timestamp": "2016-07-29 16:02:36 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "2b0e668", "desc": "Update-rollup-plugin-node-resolve", "timestamp": "2016-08-02 17:42:07 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[28,1,28,1],[36,1,36,1],[41,30,41,30]] } },{ "commitId": "5a7c7fd", "desc": "4.2.1", "timestamp": "2016-08-02 17:42:13 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "a9878e9", "desc": "Add-module-entry-point", "timestamp": "2016-08-11 08:47:25 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[19,0,20,1]] } },{ "commitId": "0314d03", "desc": "Update-d3-geo", "timestamp": "2016-08-15 21:19:18 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[54,1,54,1]] } },{ "commitId": "a92d40d", "desc": "4.2.2", "timestamp": "2016-08-15 21:19:25 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "b5cd563", "desc": "Disable-negate_iife", "timestamp": "2016-08-15 21:21:49 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[29,1,29,1]] } },{ "commitId": "20a69e7", "desc": "Update-dependencies", "timestamp": "2016-09-12 16:35:30 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[35,1,35,1],[50,1,50,1],[54,1,54,1],[57,1,57,1],[60,1,60,1],[65,2,65,2],[68,2,68,2]] } },{ "commitId": "55a8388", "desc": "4.2.3", "timestamp": "2016-09-12 16:35:35 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "10319b4", "desc": "Fix-2973", "timestamp": "2016-09-19 09:11:00 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[27,1,27,1],[34,0,35,1]] } },{ "commitId": "fa405ae", "desc": "Update-dependencies", "timestamp": "2016-09-19 11:55:39 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[36,1,36,1],[45,1,45,1]] } },{ "commitId": "d1e6346", "desc": "4.2.4", "timestamp": "2016-09-19 11:55:44 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "9467367", "desc": "Update-d3-geo", "timestamp": "2016-09-20 13:12:52 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[55,1,55,1]] } },{ "commitId": "9eace22", "desc": "4.2.5", "timestamp": "2016-09-20 13:12:56 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "6d17d41", "desc": "Update-d3-time", "timestamp": "2016-09-22 10:30:06 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[67,1,67,1]] } },{ "commitId": "6a128b8", "desc": "4.2.6", "timestamp": "2016-09-22 10:30:22 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "9c7f524", "desc": "Update-dependencies", "timestamp": "2016-10-11 08:54:51 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[53,1,53,1],[55,1,55,1]] } },{ "commitId": "97255a5", "desc": "4.2.7", "timestamp": "2016-10-11 08:54:56 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "42e072c", "desc": "Update-dependencies", "timestamp": "2016-10-20 10:49:59 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[70,1,70,1]] } },{ "commitId": "46cce19", "desc": "4.2.8", "timestamp": "2016-10-20 10:50:30 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "635865c", "desc": "Update-dependencies", "timestamp": "2016-10-27 11:43:10 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[55,1,55,1],[71,1,71,1]] } },{ "commitId": "33da151", "desc": "4.3.0", "timestamp": "2016-10-27 11:43:19 -0700", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } },{ "commitId": "748cf52", "desc": "Update-dependencies", "timestamp": "2016-11-22 17:30:16 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[43,2,43,2],[46,5,46,5],[52,2,52,2],[55,6,55,6],[62,5,62,5],[68,1,68,1],[72,1,72,1]] } },{ "commitId": "f797dfe", "desc": "4.4.0", "timestamp": "2016-11-22 17:31:45 -0800", "user": { "name": "Mike Bostock", "email": "mbostock@gmail.com" } , "diff": { "changes": [[3,1,3,1]] } } ];
2 |
3 |
--------------------------------------------------------------------------------