Skip to content

Instantly share code, notes, and snippets.

@theadam
Last active November 30, 2016 08:15
Show Gist options
  • Save theadam/e172275dc9895b8103e989e263a5605e to your computer and use it in GitHub Desktop.
Save theadam/e172275dc9895b8103e989e263a5605e to your computer and use it in GitHub Desktop.
esnextbin sketch
<!DOCTYPE HTML>
<html>
<head>
<style>
body {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="app"></div>
</body>
</html>
import React, { Component } from 'react';
import { render } from 'react-dom';
import { sortBy } from 'ramda';
let key = 1;
function newKey() {
return key++;
}
const anim = 600;
const Tree = {
Empty: {
type: 'Empty',
args: [],
match(obj) {
return Tree.match(obj, this);
},
weight: 0,
height: 0,
},
Node(value, left, right, key = newKey()) {
return {
type: 'Node',
args: [value, left, right, key],
match(obj) {
return Tree.match(obj, this);
},
value,
left,
right,
key,
weight: 1 + left.weight + right.weight,
};
},
match(obj, node) {
const fn = obj.hasOwnProperty(node.type) ? obj[node.type] : obj['_'];
if (typeof fn !== 'function') return fn;
return fn(...node.args);
}
};
const { Empty, Node } = Tree;
function insert(v, tree = this) {
return tree.match({
Empty: () => Node(v, Empty, Empty),
Node(value, left, right, key) {
if (value >= v) {
return Node(value, left::insert(v), right, key);
} else {
return Node(value, left, right::insert(v), key);
}
}
});
}
function isEmpty(tree = this) {
return tree.match({
Empty: true,
Node: false,
});
}
function weight(tree = this) {
return tree.weight;
}
function isBalanced(tree = this) {
return tree.match({
Empty: true,
Node(v, l, r) {
const lw = l::weight();
const rw = r::weight();
if (Math.abs(lw - rw) > 1) return false;
return l::isBalanced() && r::isBalanced();
},
});
}
function balance(tree = this) {
let cur = tree;
while(!cur::isBalanced()) {
cur = cur::balanceStep();
}
return cur;
}
function rightRotate(tree = this) {
return tree.match({
Empty: Empty,
Node(v, l, r, k) {
if (l.type === 'Empty') return tree;
const rot = l::leftRotate();
if (l !== rot) return Node(v, rot, r, k)::rightRotate()
return Node(l.value, l.left, Node(
v, l.right, r, k
), l.key
);
},
})
}
function leftRotate(tree = this) {
return tree.match({
Empty: Empty,
Node(v, l, r, k) {
if (r.type === 'Empty') return tree;
const rot = r::rightRotate();
if (r !== rot) return Node(v, l, rot, k)::leftRotate()
return Node(r.value, Node(
v, l, r.left, k
), r.right, r.key
);
},
})
}
function balanceStep(tree = this) {
return tree.match({
Empty: Empty,
Node(v, l, r, k) {
if (tree::isBalanced()) return tree;
if (!l::isBalanced()) return Node(v, l::balance(), r, k);
if (!r::isBalanced()) return Node(v, l, r::balance(), k);
const lw = l.weight;
const rw = r.weight;
if (lw > rw) {
return tree::rightRotate();
} else {
return tree::leftRotate();
}
}
});
}
function getConnections(key, x, y, l, r) {
const connections = [];
const endY = y + 0.9;
if (l.weight !== 0) {
connections.push({ start: [x, y], end: [x - l.right.weight/2 - 0.5, endY], key: `${key}-left` })
} else {
connections.push({ start: [x, y], end: [x, y + 0.0001], key: `${key}-left` })
}
if (r.weight !== 0) {
connections.push({ start: [x, y], end: [x + r.left.weight/2 + 0.5, endY], key: `${key}-right` })
} else {
connections.push({ start: [x, y], end: [x, y + 0.0001], key: `${key}-right` })
}
return connections;
}
function positions(xOffset = 0, yOffset = 0, tree = this) {
return tree.match({
Empty: [],
Node(v, l, r, k) {
const leftW = l::weight();
const x = leftW/2 + xOffset;
const y = yOffset;
const lp = l::positions(xOffset, y + 0.9);
const rp = r::positions(x + 0.5, y + 0.9);
return lp.concat(rp).concat({
value: v,
xOffset: x,
yOffset: y,
key: k,
connections: getConnections(k, x, y, l, r),
});
}
})
}
const size = 40;
const vpad = 25;
const hpad = 25;
class TreeView extends Component {
renderPosition = ({ value, xOffset, yOffset, key }) => {
return (
<div
key={key}
style={{
fontFamily: 'sans-serif',
position: 'absolute',
top: 0,
left: 0,
width: size,
height: size,
backgroundColor: '#FF6961',
textAlign: 'center',
lineHeight: `${size}px`,
border: '2px #696969 solid',
borderRadius: size,
transform: `translate(${xOffset * (hpad + size)}px, ${yOffset * (vpad + size)}px)`,
transition: `transform ${anim/1000}s`,
boxSizing: 'border-box',
transitionTimingFunction: 'ease-out',
}}
>
{value}
</div>
);
}
render() {
const pos = sortBy(x => x.key, this.props.tree::positions());
const connections = pos.reduce((acc, v) => acc.concat(v.connections), []);
return (
<div>
<Connections connections={connections} />
<div>
{pos.map(this.renderPosition)}
</div>
</div>
);
}
}
class Connections extends Component {
render() {
return (
<div>
{this.props.connections.map(c => <Connection connection={c} key={c.key} />)}
</div>
);
}
}
class Connection extends Component {
scale(v) {
return [
v[0] * (size + hpad),
v[1] * (size + vpad),
];
}
scaledConnection() {
const { connection } = this.props;
return {
start: this.scale(connection.start),
end: this.scale(connection.end),
};
}
distance(start, end) {
const dx = end[0] - start[0];
const dy = end[1] - start[1];
return Math.sqrt(dx * dx + dy * dy);
}
angle(start, end) {
const dx = end[0] - start[0];
const dy = end[1] - start[1];
return Math.atan2(dy, dx);
}
render() {
const { start, end } = this.scaledConnection();
const dist = this.distance(start, end);
const theta = this.angle(start, end);
return (
<div>
<div
style={{
position: 'absolute',
top: 0,
left: 0,
borderTop: '1px solid black',
height: 0,
width: dist,
transformOrigin: '0 0 0',
transform: `translate(${start[0] + size/2}px, ${start[1] + size/2}px) rotate(${theta}rad)`,
transition: `transform ${anim/1000}s, width ${anim/1000}s`,
transitionTimingFunction: 'ease-out',
}}
/>
</div>
);
}
}
const vals = [2, 1, 3, 4, 5, 6, 7];
const start = vals.reduce((acc, v) => acc::insert(v.toString()), Empty);
class App extends Component {
state = {
tree: start,
value: '',
showSteps: true,
autoBalance: false,
}
insert = (e) => {
e.preventDefault();
if(!this.state.value) return;
this.setState(
{ tree: this.state.tree::insert(this.state.value), value: '' },
this.state.autoBalance ? this.balance : undefined,
);
}
balance = (e) => {
clearTimeout(this._id);
e && e.preventDefault();
if (!this.state.showSteps) {
return this.setState({ tree: this.state.tree::balance() });
}
if (this.state.tree::isBalanced()) return;
this.setState({ tree: this.state.tree::balanceStep()}, () => { this._id = setTimeout(this.balance, anim * 4/5); });
}
reset = () => {
clearTimeout(this._id);
this.setState({ tree: start, autoBalance: false })
}
render() {
const { showSteps, value, tree, autoBalance } = this.state;
return (
<div style={{ margin: 20 }}>
<form onSubmit={this.insert}>
<input value={value} onChange={e => this.setState({ value: e.target.value })} />
</form>
<button onClick={this.insert}>Insert</button>
<button onClick={this.balance}>Balance</button>
<button onClick={this.reset}>Reset</button>
<label htmlFor="steps" style={{ display: 'block' }}>
<input
id="steps"
type="checkbox"
checked={showSteps}
onChange={() => this.setState({ showSteps: !showSteps })}
/>
Show Steps
</label>
<label htmlFor="balance" style={{ display: 'block' }}>
<input
id="balance"
type="checkbox"
checked={autoBalance}
onChange={() => this.setState({ autoBalance: !autoBalance }, this.balance)}
/>
Auto Balance
</label>
<div style={{ position: 'relative', marginTop: 10 }}>
<TreeView tree={tree} />
</div>
</div>
);
}
}
render(<App />, document.getElementById('app'));
{
"name": "esnextbin-sketch",
"version": "0.0.0",
"dependencies": {
"react": "15.4.1",
"react-dom": "15.4.1",
"ramda": "0.22.1",
"babel-runtime": "6.18.0"
}
}
'use strict';
var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _createClass2 = require('babel-runtime/helpers/createClass');
var _createClass3 = _interopRequireDefault(_createClass2);
var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
var _inherits2 = require('babel-runtime/helpers/inherits');
var _inherits3 = _interopRequireDefault(_inherits2);
var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray');
var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2);
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _reactDom = require('react-dom');
var _ramda = require('ramda');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var key = 1;
function newKey() {
return key++;
}
var anim = 600;
var Tree = {
Empty: {
type: 'Empty',
args: [],
match: function match(obj) {
return Tree.match(obj, this);
},
weight: 0,
height: 0
},
Node: function Node(value, left, right) {
var key = arguments.length <= 3 || arguments[3] === undefined ? newKey() : arguments[3];
return {
type: 'Node',
args: [value, left, right, key],
match: function match(obj) {
return Tree.match(obj, this);
},
value: value,
left: left,
right: right,
key: key,
weight: 1 + left.weight + right.weight
};
},
match: function match(obj, node) {
var fn = obj.hasOwnProperty(node.type) ? obj[node.type] : obj['_'];
if (typeof fn !== 'function') return fn;
return fn.apply(undefined, (0, _toConsumableArray3.default)(node.args));
}
};
var _Empty = Tree.Empty;
var _Node = Tree.Node;
function insert(v) {
var tree = arguments.length <= 1 || arguments[1] === undefined ? this : arguments[1];
return tree.match({
Empty: function Empty() {
return _Node(v, _Empty, _Empty);
},
Node: function Node(value, left, right, key) {
if (value >= v) {
return _Node(value, insert.call(left, v), right, key);
} else {
return _Node(value, left, insert.call(right, v), key);
}
}
});
}
function isEmpty() {
var tree = arguments.length <= 0 || arguments[0] === undefined ? this : arguments[0];
return tree.match({
Empty: true,
Node: false
});
}
function weight() {
var tree = arguments.length <= 0 || arguments[0] === undefined ? this : arguments[0];
return tree.weight;
}
function isBalanced() {
var tree = arguments.length <= 0 || arguments[0] === undefined ? this : arguments[0];
return tree.match({
Empty: true,
Node: function Node(v, l, r) {
var lw = weight.call(l);
var rw = weight.call(r);
if (Math.abs(lw - rw) > 1) return false;
return isBalanced.call(l) && isBalanced.call(r);
}
});
}
function balance() {
var tree = arguments.length <= 0 || arguments[0] === undefined ? this : arguments[0];
var cur = tree;
while (!(_context = cur, isBalanced).call(_context)) {
var _context, _context2;
cur = (_context2 = cur, balanceStep).call(_context2);
}
return cur;
}
function rightRotate() {
var tree = arguments.length <= 0 || arguments[0] === undefined ? this : arguments[0];
return tree.match({
Empty: _Empty,
Node: function Node(v, l, r, k) {
var _context3;
if (l.type === 'Empty') return tree;
var rot = leftRotate.call(l);
if (l !== rot) return (_context3 = _Node(v, rot, r, k), rightRotate).call(_context3);
return _Node(l.value, l.left, _Node(v, l.right, r, k), l.key);
}
});
}
function leftRotate() {
var tree = arguments.length <= 0 || arguments[0] === undefined ? this : arguments[0];
return tree.match({
Empty: _Empty,
Node: function Node(v, l, r, k) {
var _context4;
if (r.type === 'Empty') return tree;
var rot = rightRotate.call(r);
if (r !== rot) return (_context4 = _Node(v, l, rot, k), leftRotate).call(_context4);
return _Node(r.value, _Node(v, l, r.left, k), r.right, r.key);
}
});
}
function balanceStep() {
var tree = arguments.length <= 0 || arguments[0] === undefined ? this : arguments[0];
return tree.match({
Empty: _Empty,
Node: function Node(v, l, r, k) {
if (isBalanced.call(tree)) return tree;
if (!isBalanced.call(l)) return _Node(v, balance.call(l), r, k);
if (!isBalanced.call(r)) return _Node(v, l, balance.call(r), k);
var lw = l.weight;
var rw = r.weight;
if (lw > rw) {
return rightRotate.call(tree);
} else {
return leftRotate.call(tree);
}
}
});
}
function getConnections(key, x, y, l, r) {
var connections = [];
var endY = y + 0.9;
if (l.weight !== 0) {
connections.push({ start: [x, y], end: [x - l.right.weight / 2 - 0.5, endY], key: key + '-left' });
} else {
connections.push({ start: [x, y], end: [x, y + 0.0001], key: key + '-left' });
}
if (r.weight !== 0) {
connections.push({ start: [x, y], end: [x + r.left.weight / 2 + 0.5, endY], key: key + '-right' });
} else {
connections.push({ start: [x, y], end: [x, y + 0.0001], key: key + '-right' });
}
return connections;
}
function positions() {
var xOffset = arguments.length <= 0 || arguments[0] === undefined ? 0 : arguments[0];
var yOffset = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1];
var tree = arguments.length <= 2 || arguments[2] === undefined ? this : arguments[2];
return tree.match({
Empty: [],
Node: function Node(v, l, r, k) {
var leftW = weight.call(l);
var x = leftW / 2 + xOffset;
var y = yOffset;
var lp = positions.call(l, xOffset, y + 0.9);
var rp = positions.call(r, x + 0.5, y + 0.9);
return lp.concat(rp).concat({
value: v,
xOffset: x,
yOffset: y,
key: k,
connections: getConnections(k, x, y, l, r)
});
}
});
}
var size = 40;
var vpad = 25;
var hpad = 25;
var TreeView = function (_Component) {
(0, _inherits3.default)(TreeView, _Component);
function TreeView() {
var _Object$getPrototypeO;
var _temp, _this, _ret;
(0, _classCallCheck3.default)(this, TreeView);
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return _ret = (_temp = (_this = (0, _possibleConstructorReturn3.default)(this, (_Object$getPrototypeO = (0, _getPrototypeOf2.default)(TreeView)).call.apply(_Object$getPrototypeO, [this].concat(args))), _this), _this.renderPosition = function (_ref) {
var value = _ref.value;
var xOffset = _ref.xOffset;
var yOffset = _ref.yOffset;
var key = _ref.key;
return _react2.default.createElement(
'div',
{
key: key,
style: {
fontFamily: 'sans-serif',
position: 'absolute',
top: 0,
left: 0,
width: size,
height: size,
backgroundColor: '#FF6961',
textAlign: 'center',
lineHeight: size + 'px',
border: '2px #696969 solid',
borderRadius: size,
transform: 'translate(' + xOffset * (hpad + size) + 'px, ' + yOffset * (vpad + size) + 'px)',
transition: 'transform ' + anim / 1000 + 's',
boxSizing: 'border-box',
transitionTimingFunction: 'ease-out'
}
},
value
);
}, _temp), (0, _possibleConstructorReturn3.default)(_this, _ret);
}
(0, _createClass3.default)(TreeView, [{
key: 'render',
value: function render() {
var _context5;
var pos = (0, _ramda.sortBy)(function (x) {
return x.key;
}, (_context5 = this.props.tree, positions).call(_context5));
var connections = pos.reduce(function (acc, v) {
return acc.concat(v.connections);
}, []);
return _react2.default.createElement(
'div',
null,
_react2.default.createElement(Connections, { connections: connections }),
_react2.default.createElement(
'div',
null,
pos.map(this.renderPosition)
)
);
}
}]);
return TreeView;
}(_react.Component);
var Connections = function (_Component2) {
(0, _inherits3.default)(Connections, _Component2);
function Connections() {
(0, _classCallCheck3.default)(this, Connections);
return (0, _possibleConstructorReturn3.default)(this, (0, _getPrototypeOf2.default)(Connections).apply(this, arguments));
}
(0, _createClass3.default)(Connections, [{
key: 'render',
value: function render() {
return _react2.default.createElement(
'div',
null,
this.props.connections.map(function (c) {
return _react2.default.createElement(Connection, { connection: c, key: c.key });
})
);
}
}]);
return Connections;
}(_react.Component);
var Connection = function (_Component3) {
(0, _inherits3.default)(Connection, _Component3);
function Connection() {
(0, _classCallCheck3.default)(this, Connection);
return (0, _possibleConstructorReturn3.default)(this, (0, _getPrototypeOf2.default)(Connection).apply(this, arguments));
}
(0, _createClass3.default)(Connection, [{
key: 'scale',
value: function scale(v) {
return [v[0] * (size + hpad), v[1] * (size + vpad)];
}
}, {
key: 'scaledConnection',
value: function scaledConnection() {
var connection = this.props.connection;
return {
start: this.scale(connection.start),
end: this.scale(connection.end)
};
}
}, {
key: 'distance',
value: function distance(start, end) {
var dx = end[0] - start[0];
var dy = end[1] - start[1];
return Math.sqrt(dx * dx + dy * dy);
}
}, {
key: 'angle',
value: function angle(start, end) {
var dx = end[0] - start[0];
var dy = end[1] - start[1];
return Math.atan2(dy, dx);
}
}, {
key: 'render',
value: function render() {
var _scaledConnection = this.scaledConnection();
var start = _scaledConnection.start;
var end = _scaledConnection.end;
var dist = this.distance(start, end);
var theta = this.angle(start, end);
return _react2.default.createElement(
'div',
null,
_react2.default.createElement('div', {
style: {
position: 'absolute',
top: 0,
left: 0,
borderTop: '1px solid black',
height: 0,
width: dist,
transformOrigin: '0 0 0',
transform: 'translate(' + (start[0] + size / 2) + 'px, ' + (start[1] + size / 2) + 'px) rotate(' + theta + 'rad)',
transition: 'transform ' + anim / 1000 + 's, width ' + anim / 1000 + 's',
transitionTimingFunction: 'ease-out'
}
})
);
}
}]);
return Connection;
}(_react.Component);
var vals = [2, 1, 3, 4, 5, 6, 7];
var start = vals.reduce(function (acc, v) {
return insert.call(acc, v.toString());
}, _Empty);
var App = function (_Component4) {
(0, _inherits3.default)(App, _Component4);
function App() {
var _Object$getPrototypeO2;
var _temp2, _this4, _ret2;
(0, _classCallCheck3.default)(this, App);
for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
return _ret2 = (_temp2 = (_this4 = (0, _possibleConstructorReturn3.default)(this, (_Object$getPrototypeO2 = (0, _getPrototypeOf2.default)(App)).call.apply(_Object$getPrototypeO2, [this].concat(args))), _this4), _this4.state = {
tree: start,
value: '',
showSteps: true,
autoBalance: false
}, _this4.insert = function (e) {
var _context6;
e.preventDefault();
if (!_this4.state.value) return;
_this4.setState({ tree: (_context6 = _this4.state.tree, insert).call(_context6, _this4.state.value), value: '' }, _this4.state.autoBalance ? _this4.balance : undefined);
}, _this4.balance = function (e) {
var _context8;
clearTimeout(_this4._id);
e && e.preventDefault();
if (!_this4.state.showSteps) {
var _context7;
return _this4.setState({ tree: (_context7 = _this4.state.tree, balance).call(_context7) });
}
if ((_context8 = _this4.state.tree, isBalanced).call(_context8)) return;
_this4.setState({ tree: (_context8 = _this4.state.tree, balanceStep).call(_context8) }, function () {
_this4._id = setTimeout(_this4.balance, anim * 4 / 5);
});
}, _this4.reset = function () {
clearTimeout(_this4._id);
_this4.setState({ tree: start, autoBalance: false });
}, _temp2), (0, _possibleConstructorReturn3.default)(_this4, _ret2);
}
(0, _createClass3.default)(App, [{
key: 'render',
value: function render() {
var _this5 = this;
var _state = this.state;
var showSteps = _state.showSteps;
var value = _state.value;
var tree = _state.tree;
var autoBalance = _state.autoBalance;
return _react2.default.createElement(
'div',
{ style: { margin: 20 } },
_react2.default.createElement(
'form',
{ onSubmit: this.insert },
_react2.default.createElement('input', { value: value, onChange: function onChange(e) {
return _this5.setState({ value: e.target.value });
} })
),
_react2.default.createElement(
'button',
{ onClick: this.insert },
'Insert'
),
_react2.default.createElement(
'button',
{ onClick: this.balance },
'Balance'
),
_react2.default.createElement(
'button',
{ onClick: this.reset },
'Reset'
),
_react2.default.createElement(
'label',
{ htmlFor: 'steps', style: { display: 'block' } },
_react2.default.createElement('input', {
id: 'steps',
type: 'checkbox',
checked: showSteps,
onChange: function onChange() {
return _this5.setState({ showSteps: !showSteps });
}
}),
'Show Steps'
),
_react2.default.createElement(
'label',
{ htmlFor: 'balance', style: { display: 'block' } },
_react2.default.createElement('input', {
id: 'balance',
type: 'checkbox',
checked: autoBalance,
onChange: function onChange() {
return _this5.setState({ autoBalance: !autoBalance }, _this5.balance);
}
}),
'Auto Balance'
),
_react2.default.createElement(
'div',
{ style: { position: 'relative', marginTop: 10 } },
_react2.default.createElement(TreeView, { tree: tree })
)
);
}
}]);
return App;
}(_react.Component);
(0, _reactDom.render)(_react2.default.createElement(App, null), document.getElementById('app'));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment