├── 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 |
21 |
22 |

Contributors

23 |
24 |
25 | 26 |
27 |
28 |
29 |

X Axis

30 |
31 | 32 | 33 |
34 |
35 |
36 |

Mode

37 |
38 | 39 | 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 |
21 |
22 |

Contributors

23 |
24 |
25 | 26 |
27 |
28 |
29 |

X Axis

30 |
31 | 32 | 33 |
34 |
35 |
36 |

Mode

37 |
38 | 39 | 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 |
21 |
22 |

Contributors

23 |
24 |
25 | 26 |
27 |
28 |
29 |

X Axis

30 |
31 | 32 | 33 |
34 |
35 |
36 |

Mode

37 |
38 | 39 | 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 |
21 |
22 |

Contributors

23 |
24 |
25 | 26 |
27 |
28 |
29 |

X Axis

30 |
31 | 32 | 33 |
34 |
35 |
36 |

Mode

37 |
38 | 39 | 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 |
21 |
22 |

Contributors

23 |
24 |
25 | 26 |
27 |
28 |
29 |

X Axis

30 |
31 | 32 | 33 |
34 |
35 |
36 |

Mode

37 |
38 | 39 | 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 |
21 |
22 |

Contributors

23 |
24 |
25 | 26 |
27 |
28 |
29 |

X Axis

30 |
31 | 32 | 33 |
34 |
35 |
36 |

Mode

37 |
38 | 39 | 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 |
21 |
22 |

Contributors

23 |
24 |
25 | 26 |
27 |
28 |
29 |

X Axis

30 |
31 | 32 | 33 |
34 |
35 |
36 |

Mode

37 |
38 | 39 | 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 | --------------------------------------------------------------------------------