How to using flutter implement this html5 canvas animation - flutter

I'm learning flutter recently, but I still don't understand the use of flutter canvas,
I hope to get your help, here.
How to using flutter implement this html5 canvas animation?
jsfiddle animation demo link
HTML code
margin: 0;
padding: 0;
background: #000000;
}
canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<canvas id="mycanvas"></canvas>
<script type="text/javascript">
const mycanvas = document.getElementById('mycanvas')
const context = mycanvas.getContext('2d')
const canvasWidth = window.innerWidth
const canvasHeight = window.innerHeight
mycanvas.width = canvasWidth
mycanvas.height = canvasHeight
console.log(canvasWidth, canvasHeight);
// 创建渐变
function createGradient(context, p0, p1) {
const gradient = context.createLinearGradient(p0.x, p0.y, p1.x, p1.y)
gradient.addColorStop(0, 'rgba(255, 255, 0, 0)')
gradient.addColorStop(1, 'rgba(255, 255, 0, 1)')
return gradient
}
// 绘制曲线
function createCurveLine(points) {
const gradient = createGradient(context, points[0], points[points.length - 1])
context.beginPath()
context.moveTo(points[0].x, points[0].y)
// 参数 points 是曲线上一部分连续点的集合,我们用 lineTo 把这些点连结起来,就近似的得到了曲线
for (let i = 0; i < points.length; i++) {
const p = points[i]
context.lineTo(p.x, p.y)
}
context.moveTo(points[0].x, points[0].y)
context.strokeStyle = gradient
context.lineCap = 'round'
context.lineWidth = 5
context.shadowColor = 'rgba(255, 0, 255, 1)'
context.shadowBlur = 10
context.stroke()
}
const P0 = {
x: 100,
y: canvasHeight / 2
}
const P1 = {
x: canvasWidth / 2,
y: canvasHeight / 2 - 200
}
const P2 = {
x: canvasWidth - 100,
y: canvasHeight / 2
}
let t0 = 0
let t1 = 0
let points = [] // 存储曲线上点的集合
const lineLength = 0.3;
function draw() {
context.clearRect(0, 0, canvasWidth, canvasHeight);
if (t1 < lineLength) {
t0 = 0;
}
if (t0 > 1 - lineLength) {
t1 = 1;
}
const currentPoint = {
x: computeCurvePoint(P0.x, P1.x, P2.x, t1),
y: computeCurvePoint(P0.y, P1.y, P2.y, t1)
}
// 每当 t1 变化时,就将对应的点添加到 points 集合中
points.push(currentPoint)
const len = points.length
context.save()
if (len > 1) {
createCurveLine(points.slice(Math.floor(len * t0), Math.max(Math.ceil(len * t1), 2)))
}
context.restore()
t0 += 0.005
t1 += 0.005
if (t0 > 1 && t1 > 1) {
t0 = 0
t1 = 0
points = []
}
requestAnimationFrame(draw)
}
draw()
/*!
* 计算二次贝塞尔曲线上的点
* #param {Number} p0 起始点
* #param {Number} p1 控制点
* #param {Number} p2 结束点
* #param {Number} t 0-1的集合
* #return {Number} 返回计算后的点
*/
function computeCurvePoint(p0, p1, p2, t) {
return (1 - t) * (1 - t) * p0 + 2 * t * (1 - t) * p1 + t * t * p2
}
function arc(...points) {
points.forEach(p => {
context.beginPath()
context.arc(p.x, p.y, 3, 0, Math.PI * 2)
context.stroke();
});
}
</script>
</body>
</html>

Related

Flutter Tensorflowlite Object Detection

I am working on an object recognition application with Flutter.
When the object is detected, it shows on the screen by covering it with container.
I want to convey the title of the detected object to the user audibly. It takes the title from the list named Results and displays it as a map.
I got it to speak the specified texts with TextToSpeech, but I don't know how to do it in real time.
List<Widget> _renderBoxes() {
return widget.results.map((re) {
var _x = re["rect"]["x"];
var _w = re["rect"]["w"];
var _y = re["rect"]["y"];
var _h = re["rect"]["h"];
var scaleW, scaleH, x, y, w, h;
if (widget.screenH / widget.screenW >
widget.previewH / widget.previewW) {
scaleW = widget.screenH / widget.previewH * widget.previewW;
scaleH = widget.screenH;
var difW = (scaleW - widget.screenW) / scaleW;
x = (_x - difW / 2) * scaleW;
w = _w * scaleW;
if (_x < difW / 2) w -= (difW / 2 - _x) * scaleW;
y = _y * scaleH;
h = _h * scaleH;
} else {
scaleH = widget.screenW / widget.previewW * widget.previewH;
scaleW = widget.screenW;
var difH = (scaleH - widget.screenH) / scaleH;
x = _x * scaleW;
w = _w * scaleW;
y = (_y - difH / 2) * scaleH;
h = _h * scaleH;
if (_y < difH / 2) h -= (difH / 2 - _y) * scaleH;
}
return Positioned(
left: math.max(0, x),
top: math.max(0, y),
width: w,
height: h,
child: Container(
padding: const EdgeInsets.only(top: 5.0, left: 5.0),
decoration: BoxDecoration(
border: Border.all(
color: Colors.amber,
width: 1.0,
),
),
child: Text(
"${re["detectedClass"]} ${(re["confidenceInClass"] * 100).toStringAsFixed(0)}%",
style: const TextStyle(
color: Colors.amber,
fontSize: 14.0,
fontWeight: FontWeight.bold,
),
),
),
);
}).toList();
}
The method I tried is
into the build
_res = Map.fromIterable(widget.results, value: (e) => e.detectedClass)
as String;
add _res value
#override
void initState() {
super.initState();
_textToSpeech.speak(_res);
}
I called it inside the method but it gave an error: "type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'String' in type cast" and "Each child must be laid out exactly once.
"

Extending circleMarker "icon" formatting

I found some genius code. But I do not understand how they designed the marker pin
Is the MarkerPin made from svg in some way or did they just come up with the design out of maths?
I have this svg icon I would like to use. So how would i go about translating that to the code below? I may be stupid and they are completely unrelated to svg.
L.MarkerPin = L.CircleMarker.extend({
_updatePath: function () {
this._renderer._updateMarkerPin(this);
},
_containsPoint: function (p) {
var r = this._radius;
var insideCircle =
p.add([0, r * 2]).distanceTo(this._point) <= r + this._clickTolerance();
var a = this._point,
b = a.subtract([0.58 * r, r]),
c = a.subtract([-0.58 * r, r]);
var insideTriangle = true;
var ap_x = p.x - a.x;
var ap_y = p.y - a.y;
var p_ab = (b.x - a.x) * ap_y - (b.y - a.y) * ap_x > 0;
var p_ac = (c.x - a.x) * ap_y - (c.y - a.y) * ap_x > 0;
var p_bc = (c.x - b.x) * (p.y - b.y) - (c.y - b.y) * (p.x - b.x) > 0;
if (p_ac === p_ab) insideTriangle = false;
if (p_bc !== p_ab) insideTriangle = false;
return insideTriangle || insideCircle;
}
});
Jsfiddle with complete example:
https://jsfiddle.net/n2jf5c4q/
The code you posted is only to check if a point (f.e. a click position) is in the area of the Pin. I think this is made because the creator don't wanted that the click event is fired when it is clicked on the area of a circle.
What you mean is this code:
ctx.beginPath();
ctx.moveTo(p.x, p.y);
ctx.lineTo(p.x - 0.58 * r, p.y - r);
ctx.arc(p.x, p.y - 2 * r, r, -Math.PI * 1.161, Math.PI * 0.161);
ctx.closePath();
This is normal canvas drawing and has nothing to do with svg
Update
I tried a little bit around and i found a way, but this is not supported for all browsers.
You can use Path2D render the svg as canvas path (maybe you have to resize the svg)
window.p = new Path2D("M0-0.1h20.8v20.9H0V-0.1z M0.5,0.4v19.9h19.8V0.4H0.5z M19,14h-3.2l1.6,3.4h-1.7L14,14H6.7l-1.6,3.4H3.3L4.9,14H1.8v-1.6 h3.9l0.8-1.6H4.1V9.1h12.5v1.6h-2.4l0.8,1.6h4V14z M7.4,12.4h5.8l-0.7-1.6H8.2L8.1,11l-0.3,0.6L7.4,12.4z");
L.Canvas.include({
_updateCircle: function (layer) {
if (!this._drawing || layer._empty()) { return; }
var p = layer._point,
ctx = this._ctx,
r = Math.max(Math.round(layer._radius), 1),
s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
if (s !== 1) {
ctx.save();
ctx.scale(1, s);
}
ctx.beginPath();
ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
if (s !== 1) {
ctx.restore();
}
var x = p.x-10; // -10 because of ~20 is the img size
var y = p.y-10;
ctx.translate(x,y);
ctx.fill(window.p);
//this._fillStroke(ctx, layer);
},
});
var canvasRenderer = L.canvas();
new L.CircleMarker([51.505, -0.09], {
renderer: canvasRenderer
}).bindPopup('hello').addTo(mymap);

Location lat-lng getting wrong when using Yandex Map with flutter_map package

I am using flutter_map with yandex map like below. When I mark a location, Getting location lat and lon are wrong. So If I draw a polygon on the map, It drawn on wrong location. How to fix this?
FlutterMap(
options: MapOptions(
center: LatLng(41.334554,36.269617),
zoom: 11.0,
maxZoom: 18,
minZoom: 5,
plugins: [
DragMarkerPlugin(),
],
onTap: (value){
print(value.toString());
markLocation(value);
},
),
layers: [
TileLayerOptions(
urlTemplate: 'https://core-sat.maps.yandex.net/tiles?l=sat&v=3.569.0&x={x}&y={y}&z={z}&lang=tr_TR'
),
TileLayerOptions(
urlTemplate: 'http://vec{s}.maps.yandex.net/tiles?l=skl&v=20.06.03&z={z}&x={x}&y={y}&scale=1&lang=tr_TR',
subdomains: ['01', '02', '03', '04'],
backgroundColor: Colors.transparent
),
CircleLayerOptions(
circles: _circles
),
PolygonLayerOptions(
polygons: getPolygons()
),
DragMarkerPluginOptions(
markers: _markers
)
],
mapController: _mapController,
)
Actual position of polygon:
And How to looks on device?
I used custom csr for solve this problem. Thanks to dmitrienkop for his answer.
https://github.com/fleaflet/flutter_map/issues/642
import 'dart:math' as math;
import 'package:flutter_map/plugin_api.dart';
import 'package:latlong/latlong.dart';
class Epsg3395 extends Earth {
#override
final String code = 'EPSG:3395';
#override
final Projection projection = const Mercator();
#override
final Transformation transformation = const Transformation(_scale, 0.5, -_scale, 0.5);
static const num _scale = 0.5 / (math.pi * Mercator.r);
const Epsg3395() : super();
}
class Mercator extends Projection {
static const int r = 6378137;
static const double rMinor = 6356752.314245179;
static final Bounds<double> _bounds = Bounds<double>(
CustomPoint<double>(-20037508.34279, -15496570.73972),
CustomPoint<double>(20037508.34279, 18764656.23138),
);
const Mercator() : super();
#override
Bounds<double> get bounds => _bounds;
#override
CustomPoint project(LatLng latlng) {
var d = math.pi / 180;
var y = latlng.latitude * d;
var tmp = rMinor / r;
var e = math.sqrt(1 - tmp * tmp);
var con = e * math.sin(y);
var ts = math.tan(math.pi / 4 - y / 2) / math.pow((1 - con) / (1 + con), e / 2);
y = -r * math.log(math.max(ts, 1E-10));
return CustomPoint(latlng.longitude * d * r, y);
}
#override
LatLng unproject(CustomPoint point) {
var d = 180 / math.pi;
var tmp = rMinor / r;
var e = math.sqrt(1 - tmp * tmp);
var ts = math.exp(-point.y / r);
var phi = math.pi / 2 - 2 * math.atan(ts);
for (var i = 0, dphi = 0.1, con; i < 15 && dphi.abs() > 1e-7; i++) {
con = e * math.sin(phi);
con = math.pow((1 - con) / (1 + con), e / 2);
dphi = math.pi / 2 - 2 * math.atan(ts * con) - phi;
phi += dphi;
}
return LatLng(phi * d, point.x * d / r);
}
}

How can one make an ellipse in react-leaflet?

I am trying to draw an ellipse on a map made using react-leaflet, which has built-in support for circles and rectangles.
To achieve this, I am using code to produce an ellipse in (non-react) leaflet from here, that I have adapted and pasted below:
import * as L from 'leaflet';
L.SVG.include ({
_updateEllipse: function (layer) {
var // c = layer._point,
rx = layer._radiusX,
ry = layer._radiusY,
phi = layer._tiltDeg,
endPoint = layer._endPointParams;
var d = 'M' + endPoint.x0 + ',' + endPoint.y0 +
'A' + rx + ',' + ry + ',' + phi + ',' +
endPoint.largeArc + ',' + endPoint.sweep + ',' +
endPoint.x1 + ',' + endPoint.y1 + ' z';
this._setPath(layer, d);
}
});
L.Canvas.include ({
_updateEllipse: function (layer) {
if (layer._empty()) { return; }
var p = layer._point,
ctx = this._ctx,
r = layer._radiusX,
s = (layer._radiusY || r) / r;
this._drawnLayers[layer._leaflet_id] = layer;
ctx.save();
ctx.translate(p.x, p.y);
if (layer._tilt !== 0) {
ctx.rotate( layer._tilt );
}
if (s !== 1) {
ctx.scale(1, s);
}
ctx.beginPath();
ctx.arc(0, 0, r, 0, Math.PI * 2);
ctx.restore();
this._fillStroke(ctx, layer);
},
});
L.Ellipse = L.Path.extend({
options: {
fill: true,
startAngle: 0,
endAngle: 359.9
},
initialize: function (latlng, radii, tilt, options) {
L.setOptions(this, options);
this._latlng = L.latLng(latlng);
if (tilt) {
this._tiltDeg = tilt;
} else {
this._tiltDeg = 0;
}
if (radii) {
this._mRadiusX = radii[0];
this._mRadiusY = radii[1];
}
},
setRadius: function (radii) {
this._mRadiusX = radii[0];
this._mRadiusY = radii[1];
return this.redraw();
},
getRadius: function () {
return new L.point(this._mRadiusX, this._mRadiusY);
},
setTilt: function (tilt) {
this._tiltDeg = tilt;
return this.redraw();
},
getBounds: function () {
// TODO respect tilt (bounds are too big)
var lngRadius = this._getLngRadius(),
latRadius = this._getLatRadius(),
latlng = this._latlng;
return new L.LatLngBounds(
[latlng.lat - latRadius, latlng.lng - lngRadius],
[latlng.lat + latRadius, latlng.lng + lngRadius]);
},
// #method setLatLng(latLng: LatLng): this
// Sets the position of a circle marker to a new location.
setLatLng: function (latlng) {
this._latlng = L.latLng(latlng);
this.redraw();
return this.fire('move', {latlng: this._latlng});
},
// #method getLatLng(): LatLng
// Returns the current geographical position of the circle marker
getLatLng: function () {
return this._latlng;
},
setStyle: L.Path.prototype.setStyle,
_project: function () {
var lngRadius = this._getLngRadius(),
latRadius = this._getLatRadius(),
latlng = this._latlng,
pointLeft = this._map.latLngToLayerPoint([latlng.lat, latlng.lng - lngRadius]),
pointBelow = this._map.latLngToLayerPoint([latlng.lat - latRadius, latlng.lng]);
this._point = this._map.latLngToLayerPoint(latlng);
this._radiusX = Math.max(this._point.x - pointLeft.x, 1);
this._radiusY = Math.max(pointBelow.y - this._point.y, 1);
this._tilt = Math.PI * this._tiltDeg / 180;
this._endPointParams = this._centerPointToEndPoint();
this._updateBounds();
},
_updateBounds: function () {
// http://math.stackexchange.com/questions/91132/how-to-get-the-limits-of-rotated-ellipse
var sin = Math.sin(this._tilt);
var cos = Math.cos(this._tilt);
var sinSquare = sin * sin;
var cosSquare = cos * cos;
var aSquare = this._radiusX * this._radiusX;
var bSquare = this._radiusY * this._radiusY;
var halfWidth = Math.sqrt(aSquare*cosSquare+bSquare*sinSquare);
var halfHeight = Math.sqrt(aSquare*sinSquare+bSquare*cosSquare);
var w = this._clickTolerance();
var p = [halfWidth + w, halfHeight + w];
this._pxBounds = new L.Bounds(this._point.subtract(p), this._point.add(p));
},
_update: function () {
if (this._map) {
this._updatePath();
}
},
_updatePath: function () {
this._renderer._updateEllipse(this);
},
_getLatRadius: function () {
return (this._mRadiusY / 40075017) * 360;
},
_getLngRadius: function () {
return ((this._mRadiusX / 40075017) * 360) / Math.cos((Math.PI / 180) * this._latlng.lat);
},
_centerPointToEndPoint: function () {
// Convert between center point parameterization of an ellipse
// too SVG's end-point and sweep parameters. This is an
// adaptation of the perl code found here:
// http://commons.oreilly.com/wiki/index.php/SVG_Essentials/Paths
var c = this._point,
rx = this._radiusX,
ry = this._radiusY,
theta2 = (this.options.startAngle + this.options.endAngle) * (Math.PI / 180),
theta1 = this.options.startAngle * (Math.PI / 180),
delta = this.options.endAngle,
phi = this._tiltDeg * (Math.PI / 180);
// Determine start and end-point coordinates
var x0 = c.x + Math.cos(phi) * rx * Math.cos(theta1) +
Math.sin(-phi) * ry * Math.sin(theta1);
var y0 = c.y + Math.sin(phi) * rx * Math.cos(theta1) +
Math.cos(phi) * ry * Math.sin(theta1);
var x1 = c.x + Math.cos(phi) * rx * Math.cos(theta2) +
Math.sin(-phi) * ry * Math.sin(theta2);
var y1 = c.y + Math.sin(phi) * rx * Math.cos(theta2) +
Math.cos(phi) * ry * Math.sin(theta2);
var largeArc = (delta > 180) ? 1 : 0;
var sweep = (delta > 0) ? 1 : 0;
return {'x0': x0, 'y0': y0, 'tilt': phi, 'largeArc': largeArc,
'sweep': sweep, 'x1': x1, 'y1': y1};
},
_empty: function () {
return this._radiusX && this._radiusY && !this._renderer._bounds.intersects(this._pxBounds);
},
_containsPoint : function (p) {
// http://stackoverflow.com/questions/7946187/point-and-ellipse-rotated-position-test-algorithm
var sin = Math.sin(this._tilt);
var cos = Math.cos(this._tilt);
var dx = p.x - this._point.x;
var dy = p.y - this._point.y;
var sumA = cos * dx + sin * dy;
var sumB = sin * dx - cos * dy;
return sumA * sumA / (this._radiusX * this._radiusX) + sumB * sumB / (this._radiusY * this._radiusY) <= 1;
}
});
export const lellipse = function (latlng, radii, tilt, options) {
return new L.Ellipse(latlng, radii, tilt, options);
};
To create an ellipse to use with react-leaflet, I followed the example of Circle in react-leaflet to produce the following Ellipse component:
import PropTypes from 'prop-types'
import { lellipse as LeafletEllipse } from '../l.ellipse';
import Path from './Path'
import children from './propTypes/children'
import latlng from './propTypes/latlng'
import type { LatLng, MapLayerProps, PathOptions } from './types'
type LeafletElement = LeafletEllipse
type Props = {
center: LatLng,
mSemiMajorAxis: number,
mSemiMinorAxis: number,
degreeTiltFromWest: number,
} & MapLayerProps &
PathOptions &
Object
export default class Ellipse extends Path<LeafletElement, Props> {
static propTypes = {
center: latlng.isRequired,
mSemiMajorAxis: PropTypes.number.isRequired,
mSemiMinorAxis: PropTypes.number.isRequired,
degreeTiltFromWest: PropTypes.number.isRequired,
children: children,
}
createLeafletElement(props: Props): LeafletElement {
const { center, mSemiMajorAxis, mSemiMinorAxis, degreeTiltFromWest, ...options } = props
return new LeafletEllipse(center, [mSemiMajorAxis, mSemiMinorAxis], this.getOptions(options))
}
updateLeafletElement(fromProps: Props, toProps: Props) {
if (toProps.center !== fromProps.center) {
this.leafletElement.setLatLng(toProps.center);
}
if (toProps.degreeTiltFromWest !== fromProps.degreeTiltFromWest) {
this.leafletElement.setTilt(toProps.degreeTiltFromWest);
}
if (toProps.mSemiMinorAxis !== fromProps.mSemiMinorAxis || toProps.mSemiMajorAxis !== fromProps.mSemiMajorAxis) {
this.leafletElement.setRadius([toProps.mSemiMajorAxis, toProps.mSemiMinorAxis]);
}
}
}
The problem with the code is that it does not render an ellipse and it does not throw any errors. Could someone suggest how to render an ellipse with react-leaflet? Thanks.
Your createLeafletElement function is missing the tilt parameter. It should be:
createLeafletElement(props) {
const { center, mSemiMajorAxis, mSemiMinorAxis, degreeTiltFromWest, ...options } = props
return new LeafletEllipse(center, [mSemiMajorAxis, mSemiMinorAxis], degreeTiltFromWest, this.getOptions(options))
}
See below for the complete file (in ES6 rather than in typescript, as I find it clearer).
import React, { PropTypes } from 'react';
import { lellipse as LeafletEllipse } from './l.ellipse';
import { Path, withLeaflet } from 'react-leaflet';
class Ellipse extends Path {
static propTypes = {
center: PropTypes.arrayOf(PropTypes.number).isRequired,
mSemiMajorAxis: PropTypes.number.isRequired,
mSemiMinorAxis: PropTypes.number.isRequired,
degreeTiltFromWest: PropTypes.number.isRequired
}
createLeafletElement(props) {
const { center, mSemiMajorAxis, mSemiMinorAxis, degreeTiltFromWest, ...options } = props
return new LeafletEllipse(center, [mSemiMajorAxis, mSemiMinorAxis], degreeTiltFromWest, this.getOptions(options))
}
updateLeafletElement(fromProps, toProps) {
if (toProps.center !== fromProps.center) {
this.leafletElement.setLatLng(toProps.center);
}
if (toProps.degreeTiltFromWest !== fromProps.degreeTiltFromWest) {
this.leafletElement.setTilt(toProps.degreeTiltFromWest);
}
if (toProps.mSemiMinorAxis !== fromProps.mSemiMinorAxis || toProps.mSemiMajorAxis !== fromProps.mSemiMajorAxis) {
this.leafletElement.setRadius([toProps.mSemiMajorAxis, toProps.mSemiMinorAxis]);
}
}
}
export default class withLeaflet(Ellipse);

Visualization of recursive Fibonacci calculation

Is there any program/website which can visualize a graph of a recursive Fibonacci calculation.
I want to show how many recursion steps will be needed.
I found this to be a neat little challenge, let me share my implementation:
var canvas = document.getElementById("canvas");
var width = canvas.width;
var height = canvas.height;
var ctx = canvas.getContext("2d");
var FN = 8;
var FONT_SIZE = 11; // in points
var SHOW_BOXES = false;
var SHOW_DISCS = true;
var SHOW_FIB_N = true;
function Tree(fn) {
var pdata = {};
pdata.lhs = null;
pdata.rhs = null;
pdata.fn = fn;
this.getLeft = function() { return pdata.lhs; };
this.setLeft = function(node) { pdata.lhs = node; };
this.getRight = function() { return pdata.rhs; };
this.setRight = function(node) { pdata.rhs = node; };
this.getFn = function() { return pdata.fn; };
}
function fib(n) {
if(n == 0)
return new Tree(0);
if(n == 1)
return new Tree(1);
else {
var lhs = fib(n-1);
var rhs = fib(n-2);
var root = new Tree(lhs.getFn() + rhs.getFn());
root.setLeft(lhs);
root.setRight(rhs);
return root;
}
}
var root = fib(FN);
function Box(x0, y0, x1, y1) {
if(arguments.length < 4) {
x0 = 1;
y0 = 1;
x1 = -1;
y1 = -1;
}
this.x0 = x0;
this.y0 = y0;
this.x1 = x1;
this.y1 = y1;
this.width = function() { return this.x1 - this.x0; };
this.height = function() { return this.y1 - this.y0; };
this.offset = function(x, y) {
this.x0 += x;
this.y0 += y;
this.x1 += x;
this.y1 += y;
};
this.extend = function(x, y) {
if(this.x1 < this.x0 || this.y1 < this.y0) {
this.x0 = x;
this.x1 = x;
this.y0 = y;
this.y1 = y;
} else {
this.x0 = this.x0 < x ? this.x0 : x;
this.y0 = this.y0 < y ? this.y0 : y;
this.x1 = this.x1 > x ? this.x1 : x;
this.y1 = this.y1 > y ? this.y1 : y;
}
}
};
(function () {
// assume spheres of radius 0.5
function setBounds(node, offX, offY) {
var bbox = new Box(offX, offY, offX + 1, offY + 1);
if(node.getLeft() != null || node.getRight() != null) {
var lhs = node.getLeft(), rhs = node.getRight();
if(lhs != null) {
setBounds(lhs, offX + 0, offY + 1.1);
bbox.extend(lhs.bbox.x0, lhs.bbox.y0);
bbox.extend(lhs.bbox.x1, lhs.bbox.y1);
}
if(rhs != null) {
setBounds(rhs, offX + (lhs != null ? lhs.bbox.width() : 0), offY + 1.1);
bbox.extend(rhs.bbox.x0, rhs.bbox.y0);
bbox.extend(rhs.bbox.x1, rhs.bbox.y1);
}
}
node.bbox = bbox;
}
setBounds(root, 0, 0);
})();
var transf = (function() {
var b = 2;
var sx = (width - 2 * b) / root.bbox.width();
var sy = (height - 2 * b) / root.bbox.height();
return {
ox: b / sx - root.bbox.x0,
oy: b / sx - root.bbox.y0,
sx: sx,
sy: sy,
};
})();
transf.smin = Math.min(transf.sx, transf.sy);
ctx.clearRect(0, 0, width, height);
(function(g) {
g.font = FONT_SIZE + "pt Arial";
g.textAlign = "center";
g.strokeStyle = "#000000";
function draw(node, pX, pY) {
if(node == null) return;
var cX = (node.bbox.x0 + node.bbox.x1) / 2;
var cY = (node.bbox.y0 + 0.5);
var radius = 0.475;
cX = transf.sx * (cX + transf.ox);
cY = transf.sy * (cY + transf.oy);
radius *= transf.smin;
draw(node.getLeft(), cX, cY);
draw(node.getRight(), cX, cY);
if(SHOW_BOXES) {
g.fillStyle = "#ff0000";
g.beginPath();
g.moveTo(transf.sx * (node.bbox.x0 + transf.ox), transf.sy * (node.bbox.y0 + transf.oy));
g.lineTo(transf.sx * (node.bbox.x1 + transf.ox), transf.sy * (node.bbox.y0 + transf.oy));
g.lineTo(transf.sx * (node.bbox.x1 + transf.ox), transf.sy * (node.bbox.y1 + transf.oy));
g.lineTo(transf.sx * (node.bbox.x0 + transf.ox), transf.sy * (node.bbox.y1 + transf.oy));
g.closePath();
g.stroke();
}
if(SHOW_DISCS) {
if(arguments.length >= 3) {
g.beginPath();
g.moveTo(pX, pY);
g.lineTo(cX, cY);
g.stroke();
}
g.fillStyle = "#ff0000";
g.beginPath();
g.arc(cX, cY, radius, 0, 2 * Math.PI);
g.fill();
g.stroke();
}
if(SHOW_FIB_N) {
g.fillStyle = "#0000ff";
g.fillText(node.getFn(), cX, cY + FONT_SIZE / 2);
}
}
draw(root);
})(ctx);
<canvas id="canvas" width="800" height="480" />