├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── docs
└── react-commits-graph.png
├── examples
├── commits.json
├── demo.js
├── index.html
└── run.js
├── index.js
├── package.json
└── src
├── commits-graph-mixin.coffee
├── commits-graph.coffee
├── generate-graph-data.coffee
└── svg-path-data.coffee
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore docs files
2 | _gh_pages
3 | _site
4 | .ruby-version
5 |
6 | # Numerous always-ignore extensions
7 | *.diff
8 | *.err
9 | *.orig
10 | *.log
11 | *.rej
12 | *.swo
13 | *.swp
14 | *.zip
15 | *.vi
16 | *~
17 |
18 | # OS or Editor folders
19 | .DS_Store
20 | ._*
21 | Thumbs.db
22 | .cache
23 | .project
24 | .settings
25 | .tmproj
26 | *.esproj
27 | nbproject
28 | *.sublime-project
29 | *.sublime-workspace
30 | .idea
31 |
32 | # Komodo
33 | *.komodoproject
34 | .komodotools
35 |
36 | # Folders to ignore
37 | node_modules
38 | bower_components
39 |
40 | lib/
41 | examples/bundle.js
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | src/
2 | test/
3 | examples/
4 | docs/
5 | Cakefile
6 | *.sh
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 James Friend
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-commits-graph
2 |
3 | a react component to render an svg graph of git commits
4 |
5 | adapted from [tclh123/commits-graph](https://github.com/tclh123/commits-graph)
6 |
7 | 
8 |
9 | ## example
10 |
11 | code to generate the graph above
12 |
13 | ```js
14 | var React = require('react')
15 | var CommitsGraph = require('react-commits-graph')
16 | var commits = require('./commits.json')
17 |
18 | var selected = null
19 |
20 | function handleClick(sha) {
21 | selected = sha
22 | render()
23 | }
24 |
25 | function render() {
26 | React.render(
27 |
32 | ), document.body)
33 | }
34 |
35 | render()
36 | ```
37 |
38 | expected structure of `commits` prop:
39 | ```js
40 | [
41 | {
42 | "parents": [
43 | "82aa2102c8291f56f8dfefce1dce40d8a0dd686b",
44 | "175dfbbdbf8734069efaafced5a531dbf77c3a57"
45 | ],
46 | "sha": "5a7e04df76e21f9ba4a48098b6b26f19b51b99b1"
47 | },
48 | {
49 | "parents": [
50 | "90113cac59463df2e182e48444b8395658ebf840"
51 | ],
52 | "sha": "175dfbbdbf8734069efaafced5a531dbf77c3a57"
53 | },
54 | ...
55 | ]
56 | ```
57 |
--------------------------------------------------------------------------------
/docs/react-commits-graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsdf/react-commits-graph/170ab272020e1dc8b960ca6110f23c91524013f3/docs/react-commits-graph.png
--------------------------------------------------------------------------------
/examples/commits.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "parents": [
4 | "82aa2102c8291f56f8dfefce1dce40d8a0dd686b",
5 | "175dfbbdbf8734069efaafced5a531dbf77c3a57"
6 | ],
7 | "sha": "5a7e04df76e21f9ba4a48098b6b26f19b51b99b1"
8 | },
9 | {
10 | "parents": [
11 | "90113cac59463df2e182e48444b8395658ebf840"
12 | ],
13 | "sha": "175dfbbdbf8734069efaafced5a531dbf77c3a57"
14 | },
15 | {
16 | "parents": [
17 | "82aa2102c8291f56f8dfefce1dce40d8a0dd686b"
18 | ],
19 | "sha": "90113cac59463df2e182e48444b8395658ebf840"
20 | },
21 | {
22 | "parents": [
23 | "395037182d9c34de2c26afc65b045ee875c93d25",
24 | "3f3d28b62f46f40959e31d54dbe21941801e91e3"
25 | ],
26 | "sha": "82aa2102c8291f56f8dfefce1dce40d8a0dd686b"
27 | },
28 | {
29 | "parents": [
30 | "6c7fa94319d3ab727b5606ad2bd0a732e65e7012"
31 | ],
32 | "sha": "3f3d28b62f46f40959e31d54dbe21941801e91e3"
33 | },
34 | {
35 | "parents": [
36 | "639f3e8d021af55953b2d9ee55bf83a0193c7797"
37 | ],
38 | "sha": "6c7fa94319d3ab727b5606ad2bd0a732e65e7012"
39 | },
40 | {
41 | "parents": [
42 | "f22fae5a4b3bcdcd8583f9cd25da8fbacad8fcf3"
43 | ],
44 | "sha": "639f3e8d021af55953b2d9ee55bf83a0193c7797"
45 | },
46 | {
47 | "parents": [
48 | "3b4cb078964d91ed9e9d422bd61087c552a928a4"
49 | ],
50 | "sha": "f22fae5a4b3bcdcd8583f9cd25da8fbacad8fcf3"
51 | },
52 | {
53 | "parents": [
54 | "f6b061794ccd2f9a46daefe082cae360dea85a41"
55 | ],
56 | "sha": "3b4cb078964d91ed9e9d422bd61087c552a928a4"
57 | },
58 | {
59 | "parents": [
60 | "2e00fd89a9c551e51d3f7a44044b4d29ed33bbd2"
61 | ],
62 | "sha": "f6b061794ccd2f9a46daefe082cae360dea85a41"
63 | },
64 | {
65 | "parents": [
66 | "395037182d9c34de2c26afc65b045ee875c93d25"
67 | ],
68 | "sha": "2e00fd89a9c551e51d3f7a44044b4d29ed33bbd2"
69 | },
70 | {
71 | "parents": [
72 | "4cde659b0b50e3ffe4ef73def1a077d47c241b44"
73 | ],
74 | "sha": "395037182d9c34de2c26afc65b045ee875c93d25"
75 | },
76 | {
77 | "parents": [
78 | "8e0b15259fbb7dd339b9aca646501b4203973874"
79 | ],
80 | "sha": "4cde659b0b50e3ffe4ef73def1a077d47c241b44"
81 | },
82 | {
83 | "parents": [
84 | "acaf8ad845d28fb9b4119bcfc1500c9ce1a903b5"
85 | ],
86 | "sha": "8e0b15259fbb7dd339b9aca646501b4203973874"
87 | },
88 | {
89 | "parents": [
90 | "4335d3ab62d8b5531508a27e622ad186d86bcdc7"
91 | ],
92 | "sha": "acaf8ad845d28fb9b4119bcfc1500c9ce1a903b5"
93 | },
94 | {
95 | "parents": [
96 | "cd10df3588ca6858af0201c28b58a8259289e3f1"
97 | ],
98 | "sha": "4335d3ab62d8b5531508a27e622ad186d86bcdc7"
99 | },
100 | {
101 | "parents": [
102 | "496f8a4e3aaa53630c573076a9d988e020cbf1ca"
103 | ],
104 | "sha": "cd10df3588ca6858af0201c28b58a8259289e3f1"
105 | },
106 | {
107 | "parents": [
108 | "ee9f4af79ff6fab50f7ebdf823f06dfdbc000b24"
109 | ],
110 | "sha": "496f8a4e3aaa53630c573076a9d988e020cbf1ca"
111 | },
112 | {
113 | "parents": [
114 | "4b247b9f19b0b345755aaae1914a280e66d09a0c"
115 | ],
116 | "sha": "ee9f4af79ff6fab50f7ebdf823f06dfdbc000b24"
117 | },
118 | {
119 | "parents": [
120 | "2cb54f702fa5f0ffd2435dbae7b05d52f8124c1e"
121 | ],
122 | "sha": "4b247b9f19b0b345755aaae1914a280e66d09a0c"
123 | },
124 | {
125 | "parents": [
126 | "69d9c1b99fa70108614a453a91c57c2945261dfa"
127 | ],
128 | "sha": "2cb54f702fa5f0ffd2435dbae7b05d52f8124c1e"
129 | },
130 | {
131 | "parents": [
132 | "503867bfcf9b5b615909225af2410e8225c8a6a4"
133 | ],
134 | "sha": "69d9c1b99fa70108614a453a91c57c2945261dfa"
135 | },
136 | {
137 | "parents": [
138 | "d62b312eb28f396ff67afecdd68ca9bcf4adf722"
139 | ],
140 | "sha": "503867bfcf9b5b615909225af2410e8225c8a6a4"
141 | },
142 | {
143 | "parents": [
144 | "071e7bde5b16832be2a2e48f1f5c91733644adf5"
145 | ],
146 | "sha": "d62b312eb28f396ff67afecdd68ca9bcf4adf722"
147 | },
148 | {
149 | "parents": [
150 | "e72dd3608da184c8760d011bd4320b902f5fdc0b"
151 | ],
152 | "sha": "071e7bde5b16832be2a2e48f1f5c91733644adf5"
153 | },
154 | {
155 | "parents": [
156 | "0e2a0bfb87ae21c79518a6c6dc6aa9aee0bf6dfb"
157 | ],
158 | "sha": "e72dd3608da184c8760d011bd4320b902f5fdc0b"
159 | },
160 | {
161 | "parents": [
162 | "e41d31976f002cee56a917dde2c1c75615e39d63"
163 | ],
164 | "sha": "0e2a0bfb87ae21c79518a6c6dc6aa9aee0bf6dfb"
165 | },
166 | {
167 | "parents": [
168 | "96cd2f670d5829716a9a98d543d063f4fe304026"
169 | ],
170 | "sha": "e41d31976f002cee56a917dde2c1c75615e39d63"
171 | },
172 | {
173 | "parents": [
174 | "7d9b3577930547e3549f630cf21160fdbcaea87e"
175 | ],
176 | "sha": "96cd2f670d5829716a9a98d543d063f4fe304026"
177 | },
178 | {
179 | "parents": [
180 | "876ba955da6788d60180261ec884b34fc73e574c"
181 | ],
182 | "sha": "7d9b3577930547e3549f630cf21160fdbcaea87e"
183 | },
184 | {
185 | "parents": [
186 | "cc74c787c3e6f174700fac0f146acf67fe4233f4"
187 | ],
188 | "sha": "876ba955da6788d60180261ec884b34fc73e574c"
189 | },
190 | {
191 | "parents": [
192 | "50a5dae8710e0d7cd5aaa9bd240ff2ac2135f463"
193 | ],
194 | "sha": "cc74c787c3e6f174700fac0f146acf67fe4233f4"
195 | },
196 | {
197 | "parents": [
198 | "ac7dcd4513d30a970d3576be4492f82815a5964b"
199 | ],
200 | "sha": "50a5dae8710e0d7cd5aaa9bd240ff2ac2135f463"
201 | },
202 | {
203 | "parents": [],
204 | "sha": "ac7dcd4513d30a970d3576be4492f82815a5964b"
205 | }
206 | ]
--------------------------------------------------------------------------------
/examples/demo.js:
--------------------------------------------------------------------------------
1 |
2 | React = require('react')
3 | CommitsGraph = require('../')
4 | commits = require('./commits.json')
5 |
6 | var graphEl = document.querySelector('#graph')
7 |
8 | var selected = null
9 |
10 | function handleClick(sha) {
11 | selected = sha
12 | render()
13 | }
14 |
15 | function render() {
16 | React.renderComponent(CommitsGraph({
17 | commits: commits,
18 | onClick: handleClick,
19 | selected: selected,
20 | height: window.innerHeight,
21 | width: window.innerWidth,
22 | }), graphEl)
23 | }
24 |
25 | window.addEventListener('resize', render)
26 |
27 | render()
28 |
--------------------------------------------------------------------------------
/examples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/examples/run.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | exec = require('child_process').exec
4 | spawn = require('child_process').spawn
5 |
6 | exec('git rev-parse --show-toplevel', function(err, stdout, stderr) {
7 | REPO_ROOT = stdout.split('\n')[0]
8 | EXAMPLES_DIR = REPO_ROOT+'/examples'
9 |
10 | BROWSERIFY_ARGS = [
11 | '--extension=".coffee"',
12 | '--outfile', EXAMPLES_DIR+'/bundle.js',
13 | '--entry', EXAMPLES_DIR+'/demo.js',
14 | ]
15 |
16 | exec('npm run prepublish', function(err, stdout, stderr) {
17 | if (process.argv[2] == 'watch') {
18 | console.log('watch and build')
19 | spawn('npm', ['run', 'watch'], { stdio: 'inherit' })
20 | spawn('watchify', ['-v'].concat(BROWSERIFY_ARGS), { stdio: 'inherit' })
21 | } else {
22 | console.log('build once')
23 | spawn('browserify', BROWSERIFY_ARGS, { stdio: 'inherit' })
24 | }
25 | spawn('http-server', [EXAMPLES_DIR], { stdio: 'inherit' })
26 | exec('open http://localhost:8080')
27 | })
28 | })
29 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = require('./lib/commits-graph');
3 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-commits-graph",
3 | "version": "0.2.0",
4 | "description": "a react component to render an svg graph of git commits",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "prepublish": "mkdir -p lib/ && rm lib/*.js && npm run build",
9 | "build": "coffee -cbo lib/ src/*.coffee",
10 | "watch": "coffee -cbwo lib/ src/*.coffee",
11 | "demo": "examples/run.js",
12 | "demo-watch": "examples/run.js watch"
13 | },
14 | "keywords": [
15 | "react-component",
16 | "react",
17 | "git",
18 | "graph",
19 | "visualization"
20 | ],
21 | "author": "James Friend",
22 | "licenses": [
23 | {
24 | "type": "MIT",
25 | "url": "https://raw.github.com/jsdf/react-commits-graph/master/LICENSE"
26 | }
27 | ],
28 | "homepage": "https://github.com/jsdf/react-commits-graph",
29 | "bugs": "https://github.com/jsdf/react-commits-graph/issues",
30 | "repository": {
31 | "type": "git",
32 | "url": "git://github.com/jsdf/react-commits-graph.git"
33 | },
34 | "devDependencies": {
35 | "coffee-script": "^1.9.0",
36 | "react": "^0.12.2"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/commits-graph-mixin.coffee:
--------------------------------------------------------------------------------
1 | React = require 'react'
2 |
3 | generateGraphData = require './generate-graph-data'
4 |
5 | SVGPathData = require './svg-path-data'
6 |
7 | COLOURS = [
8 | "#e11d21",
9 | "#fbca04",
10 | "#009800",
11 | "#006b75",
12 | "#207de5",
13 | "#0052cc",
14 | "#5319e7",
15 | "#f7c6c7",
16 | "#fad8c7",
17 | "#fef2c0",
18 | "#bfe5bf",
19 | "#c7def8",
20 | "#bfdadc",
21 | "#bfd4f2",
22 | "#d4c5f9",
23 | "#cccccc",
24 | "#84b6eb",
25 | "#e6e6e6",
26 | "#ffffff",
27 | "#cc317c",
28 | ]
29 |
30 | classSet = (classes...) -> classes.filter(Boolean).join(' ')
31 |
32 | getColour = (branch) ->
33 | n = COLOURS.length
34 | COLOURS[branch % n]
35 |
36 | branchCount = (data) ->
37 | maxBranch = -1
38 | i = 0
39 |
40 | while i < data.length
41 | j = 0
42 |
43 | while j < data[i][2].length
44 | if maxBranch < data[i][2][j][0] or maxBranch < data[i][2][j][1]
45 | maxBranch = Math.max.apply(Math, [
46 | data[i][2][j][0]
47 | data[i][2][j][1]
48 | ])
49 | j++
50 | i++
51 | maxBranch + 1
52 |
53 | distance = (point1, point2) ->
54 | xs = 0
55 | ys = 0
56 | xs = point2.x - point1.x
57 | xs = xs * xs
58 | ys = point2.y - point1.y
59 | ys = ys * ys
60 | Math.sqrt xs + ys
61 |
62 | CommitsGraphMixin =
63 | getDefaultProps: ->
64 | y_step: 20
65 | x_step: 20
66 | dotRadius: 3
67 | lineWidth: 2
68 | selected: null
69 | mirror: false
70 | unstyled: false
71 |
72 | componentWillReceiveProps: ->
73 | @graphData = null
74 | @branchCount = null
75 |
76 | cursorPoint: (e) ->
77 | svg = @getDOMNode()
78 | svgPoint = svg.createSVGPoint()
79 | svgPoint.x = e.clientX
80 | svgPoint.y = e.clientY
81 | svgPoint.matrixTransform svg.getScreenCTM().inverse()
82 |
83 | handleClick: (e) ->
84 | cursorLoc = @cursorPoint(e)
85 |
86 | smallestDistance = Infinity
87 | closestCommit = null
88 | for commit in @renderedCommitsPositions
89 | commitDistance = distance(cursorLoc, commit)
90 | if commitDistance < smallestDistance
91 | smallestDistance = commitDistance
92 | closestCommit = commit
93 |
94 | @props.onClick?(closestCommit.sha)
95 |
96 | getGraphData: ->
97 | @graphData ||= generateGraphData(@props.commits)
98 |
99 | getBranchCount: ->
100 | @branchCount ||= branchCount(@getGraphData())
101 |
102 | getWidth: ->
103 | return @props.width if @props.width?
104 | @getContentWidth()
105 |
106 | getContentWidth: ->
107 | (@getBranchCount() + 0.5) * @props.x_step
108 |
109 | getHeight: ->
110 | return @props.height if @props.height?
111 | @getContentHeight()
112 |
113 | getContentHeight: ->
114 | (@getGraphData().length + 2) * @props.y_step
115 |
116 | getInvert: ->
117 | if @props.mirror
118 | 0 - @props.width
119 | else
120 | 0
121 |
122 | getOffset: ->
123 | @getWidth() / 2 - @getContentWidth() / 2
124 |
125 | renderRouteNode: (svgPathDataAttribute, branch) ->
126 | unless @props.unstyled
127 | colour = getColour(branch)
128 | style =
129 | 'stroke': colour
130 | 'stroke-width': @props.lineWidth
131 | 'fill': 'none'
132 |
133 | classes = "commits-graph-branch-#{branch}"
134 |
135 | React.DOM.path
136 | d: svgPathDataAttribute
137 | style: style
138 | className: classes
139 |
140 | renderRoute: (commit_idx, [from, to, branch]) ->
141 | {x_step, y_step} = @props
142 | offset = @getOffset()
143 | invert = @getInvert()
144 |
145 | svgPath = new SVGPathData
146 |
147 | from_x = offset + invert + (from + 1) * x_step
148 | from_y = (commit_idx + 0.5) * y_step
149 | to_x = offset + invert + (to + 1) * x_step
150 | to_y = (commit_idx + 0.5 + 1) * y_step
151 |
152 | svgPath.moveTo(from_x, from_y)
153 | if from_x is to_x
154 | svgPath.lineTo(to_x, to_y)
155 | else
156 | svgPath.bezierCurveTo(
157 | from_x - x_step / 4, from_y + y_step / 3 * 2,
158 | to_x + x_step / 4, to_y - y_step / 3 * 2,
159 | to_x, to_y
160 | )
161 |
162 | @renderRouteNode(svgPath.toString(), branch)
163 |
164 | renderCommitNode: (x, y, sha, dot_branch) ->
165 | radius = @props.dotRadius
166 |
167 | unless @props.unstyled
168 | colour = getColour(dot_branch)
169 | if sha is @props.selected
170 | strokeColour = '#000'
171 | strokeWidth = 2
172 | else
173 | strokeColour = colour
174 | strokeWidth = 1
175 | style =
176 | 'stroke': strokeColour
177 | 'stroke-width': strokeWidth
178 | 'fill': colour
179 |
180 | selectedClass = 'selected' if @props.selected
181 | classes = classSet("commits-graph-branch-#{dot_branch}", selectedClass)
182 |
183 | React.DOM.circle
184 | cx: x
185 | cy: y
186 | r: radius
187 | style: style
188 | onClick: @handleClick
189 | 'data-sha': sha
190 | className: classes
191 |
192 | renderCommit: (idx, [sha, dot, routes_data]) ->
193 | [dot_offset, dot_branch] = dot
194 |
195 | # draw dot
196 | {x_step, y_step} = @props
197 | offset = @getOffset()
198 | invert = @getInvert()
199 |
200 | x = offset + invert + (dot_offset + 1) * x_step
201 | y = (idx + 0.5) * y_step
202 |
203 | commitNode = @renderCommitNode(x, y, sha, dot_branch)
204 |
205 | routeNodes = for route, index in routes_data
206 | @renderRoute(idx, route)
207 |
208 | @renderedCommitsPositions.push {x, y, sha}
209 |
210 | [commitNode, routeNodes]
211 |
212 | renderGraph: ->
213 | # reset lookup table of commit node locations
214 | @renderedCommitsPositions = []
215 |
216 | allCommitNodes = []
217 | allRouteNodes = []
218 |
219 | for commit, index in @getGraphData()
220 | [commitNode, routeNodes] = @renderCommit(index, commit)
221 | allCommitNodes.push commitNode
222 | allRouteNodes = allRouteNodes.concat routeNodes
223 |
224 | children = [].concat allRouteNodes, allCommitNodes
225 |
226 | height = @getHeight()
227 | width = @getWidth()
228 | unless @props.unstyled
229 | style = {height, width, cursor: 'pointer'}
230 |
231 | svgProps = {height, width, style, children}
232 |
233 | React.DOM.svg
234 | onClick: @handleClick
235 | height: height
236 | width: width
237 | style: style
238 | children: children
239 |
240 | module.exports = CommitsGraphMixin
--------------------------------------------------------------------------------
/src/commits-graph.coffee:
--------------------------------------------------------------------------------
1 | React = require 'react'
2 |
3 | CommitsGraphMixin = require './commits-graph-mixin'
4 |
5 | CommitsGraph = React.createClass
6 | displayName: 'CommitsGraph'
7 |
8 | mixins: [
9 | CommitsGraphMixin,
10 | ]
11 |
12 | render: ->
13 | @renderGraph()
14 |
15 | module.exports = CommitsGraph
16 |
--------------------------------------------------------------------------------
/src/generate-graph-data.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | Generate preformatted data of commits graph.
3 | ###
4 |
5 |
6 | generateGraphData = (commits) ->
7 | ###
8 | Generate graph data.
9 |
10 | :param commits: a list of commit, which should have
11 | `sha`, `parents` properties.
12 | :returns: data nodes, a json list of
13 | [
14 | sha,
15 | [offset, branch], //dot
16 | [
17 | [from, to, branch], // route 1
18 | [from, to, branch], // route 2
19 | [from, to, branch],
20 | ] // routes
21 | ], // node
22 | ###
23 |
24 | nodes = []
25 | branchIndex = [0]
26 | reserve = []
27 | branches = {}
28 |
29 | getBranch = (sha) ->
30 | unless branches[sha]?
31 | branches[sha] = branchIndex[0]
32 | reserve.push(branchIndex[0])
33 | branchIndex[0]++
34 | branches[sha]
35 |
36 | for commit in commits
37 | branch = getBranch(commit.sha)
38 | numParents = commit.parents.length
39 | offset = reserve.indexOf(branch)
40 | routes = []
41 |
42 | if numParents is 1
43 | if branches[commit.parents[0]]?
44 | # create branch
45 | for b, i in reserve[offset + 1..]
46 | routes.push [i + offset + 1, i + offset + 1 - 1, b]
47 | for b, i in reserve[...offset]
48 | routes.push [i, i, b]
49 | remove(reserve, branch)
50 | routes.push([offset, reserve.indexOf(branches[commit.parents[0]]), branch])
51 | else
52 | # straight
53 | for b, i in reserve
54 | routes.push [i, i, b]
55 | branches[commit.parents[0]] = branch
56 | else if numParents is 2
57 | # merge branch
58 | branches[commit.parents[0]] = branch
59 | for b, i in reserve
60 | routes.push [i, i, b]
61 | otherBranch = getBranch(commit.parents[1])
62 | routes.push([offset, reserve.indexOf(otherBranch), otherBranch])
63 |
64 | node = Node(commit.sha, offset, branch, routes)
65 | nodes.push(node)
66 |
67 | nodes
68 |
69 | remove = (list, item) ->
70 | list.splice(list.indexOf(item), 1)
71 | list
72 |
73 | Node = (sha, offset, branch, routes) ->
74 | [sha, [offset, branch], routes]
75 |
76 | module.exports = generateGraphData
77 |
--------------------------------------------------------------------------------
/src/svg-path-data.coffee:
--------------------------------------------------------------------------------
1 | # a canvas like api for building an svg path data attribute
2 | class SVGPathData
3 | constructor: ->
4 | @commands = []
5 | toString: ->
6 | @commands.join(' ')
7 | moveTo: (x,y) ->
8 | @commands.push "M #{x},#{y}"
9 | lineTo: (x,y) ->
10 | @commands.push "L #{x},#{y}"
11 | closePath: (x,y) ->
12 | @commands.push "Z"
13 | bezierCurveTo: (cp1x, cp1y, cp2x, cp2y, x, y) ->
14 | @commands.push "C #{cp1x}, #{cp1y}, #{cp2x}, #{cp2y}, #{x}, #{y}"
15 | quadraticCurveTo: (cp1x, cp1y, x, y) ->
16 | @commands.push "Q #{cp1x}, #{cp1y}, #{x}, #{y}"
17 |
18 | module.exports = SVGPathData
--------------------------------------------------------------------------------