Skip to content

Instantly share code, notes, and snippets.

@mkfreeman
Last active June 29, 2016 11:52
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mkfreeman/6cddbf8d11953346df004fc58b1dd37d to your computer and use it in GitHub Desktop.
Save mkfreeman/6cddbf8d11953346df004fc58b1dd37d to your computer and use it in GitHub Desktop.
Gooey effect - Hexagon (with React, not D3.js)

Attempted to recreate the amazing work of Nadieh Bremmer to see how to start using React with D3.js. Ended up not using D3.js at all! The pointInPolygon function doesn't work perfectly (obviously). Comments/feedback are welcome via Gist or twitter.

<!DOCTYPE>
<html>
<head>
<meta charset="utf-8">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.34/browser.min.js"></script>
<style>
.hexagon {
fill:none;
stroke-width:4px;
stroke:#F2F2F2;
}
</style>
</head>
<body>
<div id="container" >
</div>
</body>
</html>
<script type="text/babel">
// Attempting to refactor Nadieh Bremmer's block into react
// Original src: http://bl.ocks.org/nbremer/3da658e9a21cd3c71d0819f9698f3bfa
const SQRT3 = Math.sqrt(3);
// App
var App = React.createClass({
// Calculate hexagon shape
calculateBorder(){
this.hexRadius = Math.min(this.props.width, this.props.height)/2;
this.hexWidth = SQRT3 * this.hexRadius;
this.hexHeight = 2 * this.hexRadius;
this.hexagonPoly = [[0,-1],[SQRT3/2,0.5],[0,1],[-SQRT3/2,0.5],[-SQRT3/2,-0.5],[0,-1],[SQRT3/2,-0.5]];
this.hexagonPath = "m" + this.hexagonPoly.map(function(p){
return [p[0]*this.hexRadius, p[1]*this.hexRadius].join(',');
}, this).join('l') + "z";
},
// Test if point is in polygon, mostly works, from: https://github.com/substack/point-in-polygon
isPointInPoly(point, vs, multiplier){
var x = point[0], y = point[1];
var inside = false;
for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) {
var xi = vs[i][0]*multiplier, yi = vs[i][1]*multiplier;
var xj = vs[j][0]*multiplier, yj = vs[j][1]*multiplier;
var intersect = ((yi > y) != (yj > y))
&& (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
if (intersect) inside = !inside;
}
return inside;
},
// Get Initial state
getInitialState(){
this.calculateBorder();
var circleData = [];
for(var i = 0; i < 30; i++) {
circleData.push({
rHex: Math.random() * this.hexWidth/5,
theta: Math.random() * 2 * Math.PI,
r: 15 + Math.random() * 25,
addHex:1,
addR:.01
});
}//for i
return {circleData:circleData}
},
// Update function
tick: function() {
var circleData = this.state.circleData.map(function(d, i){
// Point to test if in Polygon
var pt = [(d.rHex + 2 * d.r)* Math.cos(d.theta),(d.rHex + 2 * d.r) * Math.sin(d.theta)]
// If not in polygon, reverse the direction of movement
if(!this.isPointInPoly(pt, this.hexagonPoly, this.hexRadius)){
d.addHex = d.addHex * -1;
}
// Change radius
if(d.r > 40) {
d.addR = -.1;
}
else if (d.r < 15) {
d.addR = .1;
};
d.rHex += d.addHex;
d.r += d.addR;
return d;
}, this);
// Set the new state
this.setState({circleData: circleData});
},
componentDidMount: function() {
this.interval = setInterval(this.tick, 20);
},
render(){
return (
<svg id="my-svg" width={1000} height={1000}>
<Clip width={this.props.width/2} height={this.props.height/2} path={this.hexagonPath}>
<Filter />
</Clip>
<g clipPath='url(#clip)' transform="translate(0,10)">
<Hexagon path={this.hexagonPath} height={this.props.height} width={this.props.width} />
<CircleWrapper width={this.props.width/2} height={this.props.height/2}>
{this.state.circleData.map(function(d,i){
return <circle id={'circle_' + i} style={{fill:"url(#gradientRainbow)"}} key={i} r={d.r} cx={d.rHex * Math.cos(d.theta)} cy={d.rHex * Math.sin(d.theta)} />
})}
</CircleWrapper>
</g>
</svg>
)
}
})
// Linear gradient
var LinearGradient = React.createClass({
getInitialState(){
return {colors:["#490A3D","#BD1550","#E97F02","#F8CA00","#8A9B0F"]}
},
render(){
return (
<linearGradient id="gradientRainbow" gradientUnits="userSpaceOnUse" y1='0' y2='0' x1={this.props.x1} x2={this.props.x2}>
{this.state.colors.map(function(color,i){
return(<stop key={'stop' + i} offset={i/(this.state.colors.length-1)*100 + "%"} stopColor={color}></stop>)
}, this)}
</linearGradient>
)
}
})
// Circle wrapper
var CircleWrapper = React.createClass({
render(){
return(
<g transform={'translate(' + (this.props.width) + "," + (this.props.height) + ")"} style={{filter:'url(#gooeyCodeFilter)'}}>
{this.props.children}
</g>
)
}
});
// Filter
var Filter = React.createClass({
render(){
return(
<filter id="gooeyCodeFilter">
<feGaussianBlur in="SourceGraphic" stdDeviation="10" color-interpolation-filters="sRGB" result="blur" />
<feColorMatrix in="blur" mode="matrix" result="gooey" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 19 -9">
</feColorMatrix>
</filter>
)
}
})
// Clip path
var Clip = React.createClass({
render(){
return(
<defs>
<clipPath id="clip" >
<path d={"M" + (this.props.width) + "," + (this.props.height) + this.props.path}></path>
</clipPath>
<LinearGradient x1={-this.props.width/2*0.85} x2={this.props.width/2*0.85} />
{this.props.children}
</defs>
)
}
})
// Hexagon
var Hexagon = React.createClass({
// Render
render(){
return(
<path id="tmp" className="hexagon" d={"M" + (this.props.width/2) + "," + (this.props.height/2) + this.props.path} />
)
}
});
// Render App on DOM
ReactDOM.render(
<App width='475' height='475'/>
, document.getElementById('container')
);
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment