-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
#45 include file-tree-evolution sunburst chart
not configurable, uses fake data generator
- Loading branch information
Showing
13 changed files
with
342 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
127 changes: 127 additions & 0 deletions
127
ui/src/visualizations/file-tree-evolution/chart/Sunburst.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
'use strict'; | ||
|
||
import _ from 'lodash'; | ||
import React from 'react'; | ||
import * as d3 from "d3"; | ||
import { generateData } from "./data-generator"; | ||
|
||
export default class Sunburst extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
this.state = {}; | ||
} | ||
|
||
componentDidMount() { | ||
this.createChart(); | ||
} | ||
|
||
componentDidUpdate() { | ||
this.createChart(); | ||
} | ||
|
||
createChart() { | ||
let radius = 400; | ||
const margin = 1; | ||
const padding = 1; | ||
const contributorColors = { | ||
1: 'red', | ||
2: 'green', | ||
3: 'blue', | ||
4: 'yellow', | ||
5: 'purple' | ||
} | ||
const contributionVisibilityDuration = 4; | ||
const msBetweenIterations = 500; | ||
const variant = 'sunburst'; // 'sunburst' | 'sunrise' | 'sundown' | ||
|
||
const fullAngle = variant === 'sunburst' ? 2 * Math.PI : Math.PI; | ||
const width = 2 * radius// * (variant === 'sunburst' ? 1 : 2); | ||
const height = 2 * radius; | ||
|
||
const rotation = { | ||
'sunburst': 0, | ||
'sundown': 90, | ||
'sunrise': 270 | ||
} | ||
|
||
const arc = d3.arc() | ||
.startAngle(d => d.x0) | ||
.endAngle(d => d.x1) | ||
.padAngle(d => Math.min((d.x1 - d.x0) / 2, 2 * padding / radius)) | ||
.padRadius(radius / 2) | ||
.innerRadius(d => d.y0) | ||
.outerRadius(d => d.y1 - padding); | ||
|
||
const svg = d3.select(this.chartRef) | ||
.attr("transform", "rotate(" + rotation[variant] + ") ") | ||
.attr("viewBox", [-margin - radius, -margin - radius, width, height]) | ||
.attr("width", width) | ||
.attr("height", height) | ||
.attr("style", "max-width: 100%; height: auto; height: intrinsic;") | ||
|
||
const color = d3.scaleSequential(d3.interpolate('#bbb', '#ccc')) | ||
|
||
function getColor(d, iteration) { | ||
const baseColor = color((d.x1 + d.x0) / 2 / fullAngle) | ||
if (!d.data.contributor) { | ||
return baseColor; | ||
} | ||
const contributorBaseColor = contributorColors[d.data.contributor]; | ||
return d3.interpolate(contributorBaseColor, baseColor)(Math.min(1, (iteration - d.data.changeIteration) / contributionVisibilityDuration)) | ||
} | ||
|
||
function arcTween(d) { | ||
if (!this._current) { | ||
this._current = d; | ||
} | ||
|
||
var interpolateStartAngle = d3.interpolate(this._current.x0, d.x0); | ||
var interpolateEndAngle = d3.interpolate(this._current.x1, d.x1); | ||
|
||
this._current = d; | ||
|
||
return function(t) { | ||
d.x0 = interpolateStartAngle(t); | ||
d.x1 = interpolateEndAngle(t); | ||
return arc(d); | ||
}; | ||
}; | ||
|
||
function update(data, iteration) { | ||
const root = d3.hierarchy(data, d => !!d ? d.children : undefined); | ||
root.sum(d => Math.max(0, d.size)) | ||
// root.sort((a, b) => d3.descending(a.value, b.value)) | ||
|
||
d3.partition().size([fullAngle, radius])(root); | ||
|
||
root.children.forEach((child, i) => child.index = i); | ||
|
||
let cell = svg | ||
.selectAll("path") | ||
.data(root.descendants()) | ||
|
||
cell = cell.enter() | ||
.append("path") | ||
.merge(cell) | ||
.transition() | ||
.ease(t => t) | ||
.duration(iteration === 0 ? 0 : msBetweenIterations) | ||
.attrTween("d", arcTween) | ||
.attr("fill", d => getColor(d, iteration)) | ||
.attr("fill-opacity", d => d.depth === 0 ? 0 : 1); | ||
} | ||
|
||
const data = generateData(); | ||
|
||
let index = 0; | ||
|
||
update(data[0], 0); | ||
setInterval(() => !!data[++index] ? update(data[index], index) : undefined, msBetweenIterations); | ||
} | ||
|
||
render() { | ||
return ( | ||
<svg ref={svg => this.chartRef = svg}></svg> | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
'use strict'; | ||
|
||
import React from 'react'; | ||
|
||
import _ from 'lodash'; | ||
|
||
import Sunburst from './Sunburst'; | ||
|
||
export default class FileTreeEvolution extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
this.state = {}; | ||
} | ||
|
||
render() { | ||
return ( | ||
<Sunburst /> | ||
); | ||
} | ||
} |
105 changes: 105 additions & 0 deletions
105
ui/src/visualizations/file-tree-evolution/chart/data-generator.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
|
||
export function generateStartData(maxDepth = 5, minBreadth = 2, maxBreadth = 10, emptyChance = 0.5, firstAllowedEmptyDepth = 1, hugeFileChance = 0.1, emptyFileChance = 0.5) { | ||
const startData = { }; | ||
const empty = firstAllowedEmptyDepth <= 0 && Math.random() < emptyChance; | ||
if (maxDepth > 0 && !empty) { | ||
const desiredBreadth = minBreadth + Math.round(Math.random() * (maxBreadth - minBreadth)); | ||
for (let breadth = 0; breadth < desiredBreadth; breadth++) { | ||
if (!startData.children) { | ||
startData.children = []; | ||
} | ||
startData.children.push(generateStartData(maxDepth - 1, minBreadth, maxBreadth, emptyChance, firstAllowedEmptyDepth - 1, hugeFileChance, emptyFileChance)) | ||
} | ||
} | ||
if (!startData.children || startData.children.length === 0) { | ||
startData.size = Math.random(); | ||
if (Math.random() < hugeFileChance) { | ||
startData.size *= 10; | ||
} | ||
if (Math.random() < emptyFileChance) { | ||
startData.size = 0; | ||
} | ||
} | ||
return startData; | ||
} | ||
|
||
export function pickFile(data) { | ||
if (!data.children) { | ||
return data; | ||
} | ||
const index = Math.floor(Math.random() * data.children.length); | ||
return pickFile(data.children[index]); | ||
} | ||
|
||
export function smallAddition(file) { | ||
file.size += 0.1; | ||
} | ||
|
||
export function bigAddition(file) { | ||
file.size += 1; | ||
} | ||
|
||
export function smallDeletion(file) { | ||
file.size -= 0.1; | ||
} | ||
|
||
export function bigDeletion(file) { | ||
file.size -= 1; | ||
} | ||
|
||
export function deletion(file) { | ||
file.size = 0; | ||
} | ||
|
||
export const fileOperations = [ | ||
smallAddition, | ||
bigAddition, | ||
smallDeletion, | ||
// bigDeletion, | ||
// deletion | ||
] | ||
|
||
|
||
export function generateChange(data, iteration, contributors = 4, changes = 10) { | ||
data = structuredClone(data) | ||
|
||
const contributor = Math.floor(Math.random() * contributors) + 1 | ||
|
||
for (let i = 0; i < changes; i++) { | ||
const file = pickFile(data); | ||
if (file.contributor && file.changeIteration === iteration) { | ||
continue; | ||
} | ||
file.contributor = contributor; | ||
file.changeIteration = iteration; | ||
const operation = Math.floor(Math.random() * fileOperations.length); | ||
fileOperations[operation](file); | ||
} | ||
|
||
return data; | ||
|
||
} | ||
|
||
export function generateData(iterations = 100, contributors = 5) { | ||
const data = [generateStartData()]; | ||
sortData(data[0]); | ||
|
||
for (let i = 0; i < iterations; i++) { | ||
data.push(generateChange(data[i], i+1, contributors)) | ||
} | ||
return data; | ||
} | ||
|
||
export function countLeafs(data) { | ||
return !data.children ? 1 : data.children.reduce((sum, child) => sum + countLeafs(child), 0); | ||
} | ||
|
||
export function sortData(data) { | ||
if (!data.children) { | ||
data.__size = data.size; | ||
} else { | ||
data.children.forEach(sortData); | ||
data.__size = data.children.reduce((sum, child) => sum + child.__size, 0) | ||
data.children.sort((a, b) => b.__size - a.__size); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
'use strict'; | ||
|
||
import { connect } from 'react-redux'; | ||
|
||
import Chart from './chart.js'; | ||
|
||
const mapStateToProps = (state) => { | ||
return {}; | ||
}; | ||
|
||
export default connect(mapStateToProps)(Chart); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
'use strict'; | ||
|
||
import { connect } from 'react-redux'; | ||
|
||
|
||
const mapStateToProps = (state /*, ownProps*/) => { | ||
return {}; | ||
}; | ||
|
||
const mapDispatchToProps = (dispatch /*, ownProps*/) => { | ||
return {}; | ||
}; | ||
|
||
const FileTreeEvolutionConfigComponent = props => { | ||
return (<div></div>); | ||
}; | ||
|
||
const FileTreeEvolutionConfig = connect(mapStateToProps, mapDispatchToProps)(FileTreeEvolutionConfigComponent); | ||
|
||
export default FileTreeEvolutionConfig; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
'use strict'; | ||
|
||
export default () => | ||
<div> | ||
</div>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
'use strict'; | ||
|
||
import ChartComponent from './chart'; | ||
import ConfigComponent from './config.js'; | ||
import HelpComponent from './help.js'; | ||
import saga from './sagas'; | ||
import reducer from './reducers'; | ||
|
||
export default { | ||
id: 'fileTreeEvolution', | ||
label: 'File Tree Evolution', | ||
saga, | ||
reducer, | ||
ChartComponent, | ||
ConfigComponent, | ||
HelpComponent | ||
}; |
11 changes: 11 additions & 0 deletions
11
ui/src/visualizations/file-tree-evolution/reducers/config.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
'use strict'; | ||
|
||
import { handleActions } from 'redux-actions'; | ||
import _ from 'lodash'; | ||
|
||
export default handleActions( | ||
{ | ||
}, | ||
{ | ||
} | ||
); |
11 changes: 11 additions & 0 deletions
11
ui/src/visualizations/file-tree-evolution/reducers/data.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
'use strict'; | ||
|
||
import { handleActions } from 'redux-actions'; | ||
import _ from 'lodash'; | ||
|
||
export default handleActions( | ||
{ | ||
}, | ||
{ | ||
} | ||
); |
10 changes: 10 additions & 0 deletions
10
ui/src/visualizations/file-tree-evolution/reducers/index.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
'use strict'; | ||
|
||
import config from './config.js'; | ||
import data from './data.js'; | ||
import { combineReducers } from 'redux'; | ||
|
||
export default combineReducers({ | ||
data, | ||
config | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
'use strict'; | ||
|
||
export default function*() {} |
Empty file.