Last active
December 15, 2015 21:52
-
-
Save JnBrymn-EB/5c3709ef137e6b009ac5 to your computer and use it in GitHub Desktop.
This is a Pythagorean fractal -- Until you click and drag the control points. Then it's whatever fractal you want it to be! (Built with custom drag-n-drop library called DragonDroppings.)
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
function DragonDroppings(vis) { | |
this.registeredNodes = {}; | |
this.selector = null; | |
this.xStart = -1; | |
this.yStart = -1; | |
this.xTrans = 0; | |
this.yTrans = 0; | |
this.vis = vis; | |
vis[0][0].addEventListener('mousemove', DragonDroppings._onmousemoveFactory(this)) | |
} | |
/** | |
* Register a draggable item. | |
* @param selector - css style selector | |
* @param cx - initial x value | |
* @param cy - initial x value | |
* @param onGrab f(x, y) called when the object is grabbed | |
* @param onDrag f(x, y) called when the object is dragged | |
* @param onRelease f(x, y) called when the object is released | |
*/ | |
DragonDroppings.prototype.register = function(selector, cx, cy, onGrab, onDrag, onRelease ) { | |
this.registeredNodes[selector] = { | |
'onGrab': onGrab, | |
'onDrag': onDrag, | |
'onRelease': onRelease | |
}; | |
var thing = this.vis.select(selector) | |
thing.attr('transform','translate('+ cx +','+ cy +')'); | |
thing[0][0].addEventListener('mousedown',DragonDroppings._onmousedownFactory(this, selector)) | |
thing[0][0].addEventListener('mouseup',DragonDroppings._onmouseupFactory(this, selector)) | |
}; | |
DragonDroppings._onmousedownFactory = function(dd, selector) { | |
var handler = function(evt) { | |
dd.selector = selector; | |
dd.xStart = evt.offsetX; | |
dd.yStart = evt.offsetY; | |
var trans = dd.vis.select(selector).attr('transform').match(/translate\((\d+),(\d+)\)/); | |
dd.xTrans = parseFloat(trans[1]); | |
dd.yTrans = parseFloat(trans[2]); | |
var onGrab = dd.registeredNodes[selector]['onGrab']; | |
if(onGrab) { | |
onGrab(dd.xTrans, dd.yTrans); | |
} | |
}; | |
return handler | |
}; | |
DragonDroppings._onmousemoveFactory = function(dd) { | |
var handler = function(evt) { | |
if(dd.selector) { | |
var x = evt.offsetX - dd.xStart + dd.xTrans; | |
var y = evt.offsetY - dd.yStart + dd.yTrans; | |
dd.vis.select(dd.selector).attr('transform','translate('+ x +','+ y +')') | |
var onDrag = dd.registeredNodes[dd.selector]['onDrag']; | |
if(onDrag) { | |
onDrag(x,y); | |
} | |
} | |
}; | |
return handler | |
}; | |
DragonDroppings._onmouseupFactory = function(dd) { | |
var handler = function(evt) { | |
if(dd.selector) { | |
var trans = dd.vis.select(dd.selector).attr('transform').match(/translate\((\d+),(\d+)\)/); | |
var x = parseFloat(trans[1]); | |
var y = parseFloat(trans[2]); | |
var onRelease = dd.registeredNodes[dd.selector]['onRelease']; | |
if(onRelease) { | |
onRelease(x, y); | |
} | |
dd.selector = null; | |
} | |
}; | |
return handler | |
}; | |
//////////////////////////////////////////////////////////////////////////////////////// | |
/** | |
* four points of a quad and the depth the line from point 0 to point 3 is considered the base, the | |
* line from 1 to 2 is considered the top. (currently this only works with squares. TODO fix) | |
*/ | |
QuadShape = function(x0, y0, x1, y1, x2, y2, x3, y3, x4, y4, depth) { | |
this.x0 = x0; | |
this.y0 = y0; | |
this.x1 = x1; | |
this.y1 = y1; | |
this.x2 = x2; | |
this.y2 = y2; | |
this.x3 = x3; | |
this.y3 = y3; | |
// apex point | |
this.x4 = x4; | |
this.y4 = y4; | |
if(depth==undefined) { | |
this.depth = 0; | |
} else { | |
this.depth = depth; | |
} | |
this.left_scale = 0; | |
this.left_angle = 0; | |
this.right_scale = 0; | |
this.right_angle = 0; | |
this._calculate_angles_and_scaling() | |
}; | |
/* used to check args in the change_shape function */ | |
QuadShape._xy_set = { | |
x0: true, | |
y0: true, | |
x1: true, | |
y1: true, | |
x2: true, | |
y2: true, | |
x3: true, | |
y3: true, | |
x4: true, | |
y4: true, | |
}; | |
/** | |
* Change the shape of the QuadShape - just specify the dimensions that must change. | |
* @param xy_obj | |
* @returns {string} | |
*/ | |
QuadShape.prototype.change_shape = function(xy_obj) { | |
for (var k in xy_obj) { | |
if (xy_obj.hasOwnProperty(k)) { | |
if (!QuadShape._xy_set[k]) { | |
throw "xy_obj must have keys that are one of x0, y0, x1, y1, x2, y2, x3, y3." | |
} | |
var num = xy_obj[k]; | |
if (isNaN(parseFloat(num))) { | |
throw "xy_obj must have numerical values." | |
} | |
this[k] = num; | |
} | |
} | |
this._calculate_angles_and_scaling() | |
}; | |
QuadShape.prototype._calculate_angles_and_scaling = function() { | |
del_x03 = this.x3-this.x0; | |
del_y03 = this.y3-this.y0; | |
r_03 = Math.sqrt(del_x03*del_x03 + del_y03*del_y03); | |
del_x14 = this.x4-this.x1; | |
del_y14 = this.y4-this.y1; | |
r_14 = Math.sqrt(del_x14*del_x14 + del_y14*del_y14); | |
del_x24 = this.x2-this.x4; | |
del_y24 = this.y4-this.y2; | |
r_24 = Math.sqrt(del_x24*del_x24 + del_y24*del_y24); | |
this.left_scale = r_14/r_03; | |
this.right_scale = r_24/r_03; | |
this.left_angle = Math.atan2(del_y14,del_x14); | |
this.right_angle = - Math.atan2(del_y24,del_x24); | |
}; | |
QuadShape.prototype.points_string = function() { | |
return this.x0 +','+ this.y0 +' '+ this.x1 +','+ this.y1 +' '+ this.x2 +','+ this.y2 +' '+ this.x3 +','+ this.y3; | |
}; | |
QuadShape.prototype.make_fractal = function(depth) { | |
var quad_shapes = [this]; | |
if(this.depth == depth) { | |
return quad_shapes | |
} | |
var sub_quads = this.make_sub_quad_shapes(this.left_angle, this.left_scale, this.right_angle, this.right_scale); | |
quad_shapes = quad_shapes.concat(sub_quads[0].make_fractal(depth)); | |
quad_shapes = quad_shapes.concat(sub_quads[1].make_fractal(depth)); | |
return quad_shapes | |
}; | |
QuadShape.prototype.make_sub_quad_shapes = function(left_angle, left_scale, right_angle, right_scale) { | |
var del_x01 = this.x1-this.x0; | |
var del_y01 = this.y1-this.y0; | |
var del_x12 = this.x2-this.x1; | |
var del_y12 = this.y2-this.y1; | |
var del_x23 = this.x3-this.x2; | |
var del_y23 = this.y3-this.y2; | |
var left_quad = new QuadShape(); | |
left_quad.depth = this.depth + 1; | |
left_quad.x0 = this.x1; | |
left_quad.y0 = this.y1; | |
left_quad.x1 = left_quad.x0+left_scale*(del_x01*Math.cos(left_angle)-del_y01*Math.sin(left_angle)); | |
left_quad.y1 = left_quad.y0+left_scale*(del_x01*Math.sin(left_angle)+del_y01*Math.cos(left_angle)); | |
left_quad.x2 = left_quad.x1+left_scale*(del_x12*Math.cos(left_angle)-del_y12*Math.sin(left_angle)); | |
left_quad.y2 = left_quad.y1+left_scale*(del_x12*Math.sin(left_angle)+del_y12*Math.cos(left_angle)); | |
left_quad.x3 = left_quad.x2+left_scale*(del_x23*Math.cos(left_angle)-del_y23*Math.sin(left_angle)); | |
left_quad.y3 = left_quad.y2+left_scale*(del_x23*Math.sin(left_angle)+del_y23*Math.cos(left_angle)); | |
left_quad.left_angle = left_angle; | |
left_quad.left_scale = left_scale; | |
left_quad.right_angle = right_angle; | |
left_quad.right_scale = right_scale; | |
var right_quad = new QuadShape(); | |
right_quad.depth = this.depth + 1; | |
right_quad.x3 = this.x2; | |
right_quad.y3 = this.y2; | |
right_quad.x2 = right_quad.x3-right_scale*(del_x23*Math.cos(right_angle)-del_y23*Math.sin(right_angle)); | |
right_quad.y2 = right_quad.y3-right_scale*(del_x23*Math.sin(right_angle)+del_y23*Math.cos(right_angle)); | |
right_quad.x1 = right_quad.x2-right_scale*(del_x12*Math.cos(right_angle)-del_y12*Math.sin(right_angle)); | |
right_quad.y1 = right_quad.y2-right_scale*(del_x12*Math.sin(right_angle)+del_y12*Math.cos(right_angle)); | |
right_quad.x0 = right_quad.x1-right_scale*(del_x01*Math.cos(right_angle)-del_y01*Math.sin(right_angle)); | |
right_quad.y0 = right_quad.y1-right_scale*(del_x01*Math.sin(right_angle)+del_y01*Math.cos(right_angle)); | |
right_quad.left_angle = left_angle; | |
right_quad.left_scale = left_scale; | |
right_quad.right_angle = right_angle; | |
right_quad.right_scale = right_scale; | |
return [left_quad, right_quad] | |
}; | |
var depth = 9; | |
function draw(vis, quad_shape) { | |
quad_sel = vis.selectAll('polygon.quad-shape') | |
.data(quad_shape.make_fractal(depth)); | |
quad_sel.enter() | |
.append('polygon') | |
.attr('class', 'quad-shape'); | |
quad_sel.attr('points', function(d) {return d.points_string()}); | |
} | |
function main() { | |
var width = 1000; | |
var height = 600; | |
var vis = d3.select('div') | |
.append('svg:svg') | |
.attr('width', width) | |
.attr('height', height) | |
; | |
var quad_middle = 355; | |
var quad_width = 90; | |
var quad_base = 10; | |
var quad_shape = new QuadShape( | |
quad_middle - quad_width/2,quad_base, | |
quad_middle - quad_width/2,quad_base + quad_width, | |
quad_middle + quad_width/2,quad_base + quad_width, | |
quad_middle + quad_width/2,quad_base, | |
quad_middle, quad_base + quad_width*1.5 | |
); | |
draw(vis,quad_shape) | |
vis.append('circle') | |
.attr('class','a') | |
.attr('fill','red') | |
.attr('cx',0) | |
.attr('cy',0) | |
.attr('r', 7) | |
vis.append('circle') | |
.attr('class','b') | |
.attr('fill','green') | |
.attr('cx',0) | |
.attr('cy',0) | |
.attr('r', 7) | |
vis.append('circle') | |
.attr('class','c') | |
.attr('fill','blue') | |
.attr('cx',0) | |
.attr('cy',0) | |
.attr('r', 7) | |
vis.append('circle') | |
.attr('class','e') | |
.attr('fill','magenta') | |
.attr('cx',0) | |
.attr('cy',0) | |
.attr('r', 7) | |
function move_a(x,y) { | |
quad_shape.change_shape({ | |
x0: x, | |
x3: 2*quad_middle-x | |
}); | |
draw(vis,quad_shape) | |
} | |
function move_b(x,y) { | |
quad_shape.change_shape({ | |
x1: x, | |
y1: y | |
}); | |
draw(vis,quad_shape) | |
} | |
function move_c(x,y) { | |
quad_shape.change_shape({ | |
x2: x, | |
y2: y | |
}); | |
draw(vis,quad_shape) | |
} | |
function move_e(x,y) { | |
quad_shape.change_shape({ | |
x4: x, | |
y4: y | |
}); | |
draw(vis,quad_shape) | |
} | |
var dd = new DragonDroppings(vis); | |
dd.register('circle.a', quad_middle - quad_width/2, quad_base, move_a, move_a, move_a); | |
dd.register('circle.b', quad_middle - quad_width/2, quad_base + quad_width, move_b, move_b, move_b); | |
dd.register('circle.c', quad_middle + quad_width/2, quad_base + quad_width, move_c, move_c, move_c); | |
dd.register('circle.e', quad_middle, quad_base + quad_width*1.5, move_e, move_e, move_e); | |
} |
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
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Pythag</title> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js" charset="utf-8"></script> | |
<script src="fractal.js" charset="utf-8"></script> | |
</head> | |
<body> | |
<!-- This file lives in public/404.html --> | |
<div class="dialog"></div> | |
<script type="text/javascript"> | |
main(); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment