Built with blockbuilder.org
forked from anonymous's block: fresh block
forked from anonymous's block: fresh block
forked from anonymous's block: fresh block
Built with blockbuilder.org
forked from anonymous's block: fresh block
forked from anonymous's block: fresh block
forked from anonymous's block: fresh block
// Taken from https://groups.google.com/forum/#!msg/d3-js/WC_7Xi6VV50/j1HK0vIWI-EJ | |
// Calls a function only after the total transition ends | |
export default function endall(transition, callback) { | |
let n = 0; | |
transition | |
.each(function() { ++n; }) | |
.each('end', function() { if (!--n) callback.apply(this, arguments); }); | |
}// endall |
<!DOCTYPE html> | |
<script src="https://jspm.io/[email protected]"></script> | |
<script> | |
System.config({ | |
transpiler: 'babel', | |
babelOptions: {} | |
}); | |
System.import('./main'); | |
</script> |
import React from 'react'; | |
import ReactDOM from 'react-dom'; | |
import d3 from 'd3'; | |
import endall from './endall'; | |
// Number of clocks on the page | |
let spinnerCounter = 0; | |
class GooeySpinner extends React.Component { | |
static defaultProps = { | |
width: 60, | |
height: 60, | |
margin: {top: 0, right: 0, bottom: 0, left: 0}, | |
steps: 12, | |
color: [ | |
'#a50026', '#d73027', '#f46d43', | |
'#fdae61', '#fee08b', | |
'#d9ef8b', '#a6d96a', '#66bd63', | |
'#1a9850', '#006768', '#00425d', | |
], | |
}; | |
constructor(props) { | |
super(props); | |
this.spinnerID = spinnerCounter++; | |
this._repeat = this._repeat.bind(this); | |
this._getFilterID = this._getFilterID.bind(this); | |
} | |
componentDidMount() { | |
const {width, steps, color} = this.props; | |
// Create scale | |
this.radialScale = d3.scale.linear() | |
.domain([-1.5, 1.5]) | |
.range([-width/2, width/2]); | |
// color from http://colrd.com/palette/24070/ | |
this.colorScale = d3.scale.ordinal().range(color); | |
this._flyCirclesContainer = d3.select(ReactDOM.findDOMNode(this.refs.flyCircles)); | |
this._flyCircles = this._flyCirclesContainer.selectAll('.flyCircle') | |
.data(d3.range(steps).map((num) => (num / steps ) * (2 * Math.PI))); | |
this._flyCircles.enter() | |
.append('circle') | |
.attr({ | |
class: 'flyCircle', | |
cx: 0, | |
cy: 0, | |
r: 4, | |
}) | |
.style('fill', (d, i) => this.colorScale(i)) | |
.call(this._repeat); | |
} | |
_repeat() { | |
const {steps} = this.props; | |
const dur = 1000; | |
const del = 100; | |
this._flyCircles | |
.transition('outward') | |
.duration(dur) | |
.delay((_,i) => i * del) | |
.attr({ | |
cx: (d) => this.radialScale(Math.cos(d)), | |
cy: (d) => this.radialScale(Math.sin(d)), | |
}) | |
.transition('inward') | |
.duration(dur) | |
.delay((d,i) => (steps * 1.2) * del + i * del) | |
.attr('cx', 0) | |
.attr('cy', 0) | |
.call(endall, this._repeat); | |
} | |
_getFilterID() { | |
return `gooey-${this.spinnerID}`; | |
} | |
render() { | |
const {width, height, margin} = this.props; | |
return ( | |
<div> | |
<svg | |
width={width + margin.left + margin.right} | |
height={height + margin.top + margin.bottom}> | |
<defs> | |
<filter id={this._getFilterID()}> | |
<feGaussianBlur | |
in="SourceGraphic" | |
stdDeviation={4} | |
result="blur" | |
/> | |
<feColorMatrix | |
in="blur" | |
mode="matrix" | |
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 18 -7" | |
/> | |
</filter> | |
</defs> | |
<g | |
filter={`url(#${this._getFilterID()})`} | |
transform={`translate(${width/2 + margin.left}, ${height/2 + margin.top})`}> | |
<circle | |
className="centerCircle" | |
cx={0} | |
cy={0} | |
r={4} | |
fill="#ffffbf" | |
/> | |
<g ref="flyCircles" /> | |
</g> | |
</svg> | |
</div> | |
); | |
} | |
} | |
class App extends React.Component { | |
render() { | |
return ( | |
<div style={{ | |
display: 'flex', | |
justifyContent: 'center', | |
alignItems: 'flexstart', | |
}}> | |
{[0].map((i) => ( | |
<GooeyClock key={i} steps={12} /> | |
))} | |
</div> | |
); | |
} | |
} | |
ReactDOM.render( | |
<App />, | |
document.body.appendChild(document.createElement("div")) | |
); |