Can't see updated variables on requestAnimationFrame - class

I've been trying to update a class property. And I am trying to change the font of the canvas based on that variable in RequestAnimationFrame but I can't seem to update the variable. When I console.log the IsChecked variable it always evaluates to false even though I set it to true when the condition is met.So I wanted to ask if my implementation was wrong or what the problem is ?
This is the main class;
type TCanvas = {
canvasWidth: number;
canvasHeight: number;
context: CanvasRenderingContext2D | null;
};
export class Canvas {
canvasWidth!: number;
canvasHeight!: number;
context!: CanvasRenderingContext2D | null;
fontSize: number;
columns: number;
symbols: any[];
lastTime: number;
requiredPassedTime: number;
fps: number;
time: number;
animated: number;
isChecked: boolean;
constructor({ canvasWidth, canvasHeight, context }: TCanvas) {
this.canvasWidth = canvasWidth;
this.canvasHeight = canvasHeight;
this.context = context;
this.fontSize = 0.8;
this.columns = canvasWidth / this.fontSize;
this.symbols = [];
this.#init();
this.animate();
this.lastTime = 0;
this.fps = 40;
this.requiredPassedTime = 1000 / this.fps;
this.time = 0;
this.animated = 0;
this.context.font = this.fontSize + "px monospace";
this.isChecked = false;
}
getY() {
this.isChecked = true;
}
#init() {
[...Array(Math.floor(this.columns)).fill(0)].map(
(_, i) =>
(this.symbols[i] = new Symbol({
x: i,
y: 0,
fontSize: this.fontSize,
context: this.context,
getY: this.getY,
}))
);
}
animate = () => {
this.symbols.forEach((symbol) => symbol.drawCircle());
//When it's updated the font should be changed here. But is checked is evaluates to false all the time.
if (this.isChecked) {
this.context.font = "35px monospace";
}
requestAnimationFrame(this.animate);
};
}
And this one is the one that when the condition is met the passed function updates the other class' property
import { text } from "./text";
interface CanvasProps {
context: CanvasRenderingContext2D;
fontSize: number;
y: any;
x: any;
getY: any;
}
export class Symbol {
context: CanvasRenderingContext2D;
fontSize: number;
getY: any;
x: any;
y: any;
texts: any;
canDrawCircle: any;
constructor({ x, y, fontSize, context, getY }: CanvasProps) {
this.context = context;
this.fontSize = fontSize;
this.x = x;
this.y = y;
this.texts = text;
this.getY = getY;
this.canDrawCircle = true;
}
drawCircle() {
const text = this.texts.charAt(
Math.floor(Math.random() * this.texts.length)
);
this.context.fillStyle = "white";
this.context.fillText(
text,
this.x * Math.random() * 10 * this.fontSize,
this.y * this.fontSize
);
if (this.y * this.fontSize > window.innerHeight && Math.random() > 0.2) {
// here the isChecked variable should be updated.
this.getY();
this.y = 0;
} else {
this.y += Math.random() * 10;
}
}
}

Related

Flutter: How to do this emoji firework animation?

I want to do this emoji fireworks animation in a flutter widget:
https://codepen.io/z3vin/pen/QEqqdY
The code is in TypeScript:
class Firework {
constructor(app) {
this.app = app;
this.rnd = app.rnd;
this.bursts = [];
this.reset();
}
reset() {
this.color = `hsl(${this.rnd.int(360)},90%,50%)`;
this.alive = true;
this.bursting = false;
this.pos = new Vec( this.rnd.int(0,this.app.w), this.app.h+40 );
this.vel = new Vec(0,-this.rnd.real(16.0,this.app.h/40));
this.acc = new Vec(0,0);
this.size = this.rnd.real(0.25,5.0);
this.emoji = this.rnd.pick(this.app.emojis);
}
applyForce(f){
this.vel.add(f);
}
update() {
this.applyForce(this.app.forces.gravity);
this.vel.add(this.acc);
this.pos.add(this.vel);
if(this.vel.y > 1) {
this.bursting = true;
const maxBursts = Math.floor(this.app.w / 4);
const numBursts = this.rnd.chance(5) ? this.rnd.int(100,maxBursts) : this.rnd.int(20,80);
for(let i = 1; i < numBursts; i++){
this.bursts.push(new Burst(this.pos, this));
}
}
}
draw() {
const ctx = this.app.ctx;
if(!this.bursting) {
this.update();
ctx.save();
ctx.fillStyle = this.color;
ctx.font = `${this.size}em sans-serif`;
ctx.fillText(this.emoji,this.pos.x,this.pos.y);
//ctx.fillRect(this.pos.x, this.pos.y, this.size, this.size);
ctx.restore();
} else {
this.bursts.forEach(burst=>{
if(!burst.alive){
without(this.bursts,burst);
if(this.bursts.length ===0){
this.alive = false;
}
}
burst.draw();
});
}
}
}
class Burst {
constructor(origin,firework){
this.firework = firework;
this.app = firework.app;
this.pos = origin.clone();
this.rnd = firework.rnd;
this.lifespan = this.rnd.int(5,50);
this.vel = new Vec(this.rnd.real(-8.0,8.0),this.rnd.real(-8.0,8.0));
this.acc = new Vec(0,0);
this.color = this.firework.color;
this.size = this.rnd.real(0.5,15.0);
const sparkle = this.rnd.chance(20) ? 2 : 1;
this.sizeStep = this.size/(this.lifespan/sparkle);
this.alive = true;
this.rotate = this.rnd.real(0,Math.PI*2);
}
applyForce(f){
this.vel.add(f);
}
update() {
this.applyForce(this.app.forces.gravity);
this.vel.add(this.acc);
this.pos.add(this.vel);
//this.size -= this.sizeStep;
//this.rotate += 0.1;
}
draw() {
const ctx = this.app.ctx;
this.update();
ctx.save();
ctx.translate(this.pos.x,this.pos.y)
ctx.rotate(this.rotate);
ctx.font = `${this.firework.size/2}em sans-serif`;
ctx.fillText(this.firework.emoji,0,0);
ctx.restore();
this.lifespan--;
if(this.lifespan<=0){
this.alive = false;
}
}
}
class App {
constructor(){
this.ctx = document.getElementById('cnv').getContext('2d');
this.sizeCanvas();
this.initEvents();
this.rnd = new Random();
this.fireworks = [];
this.forces = {
gravity: new Vec(0,0.25)
};
this.emojis = ['😊','🍕','💩','☘','👀','🐟','💥','⚡️','🍉','🍟','⚽️'];
window.requestAnimationFrame((t)=>{this.draw(t)});
log(this);
}
sizeCanvas(){
this.w = this.ctx.canvas.width = window.innerWidth;
this.h = this.ctx.canvas.height = window.innerHeight;
}
clearIt() {
//this.ctx.clearRect(0,0,this.w,this.h);
this.ctx.save();
this.ctx.fillStyle = 'hsla(220,60%,10%,0.12)';
this.ctx.fillRect(0,0,this.w,this.h)
this.ctx.restore();
}
draw(t){
this.clearIt();
window.requestAnimationFrame((t)=>{this.draw(t)});
if(this.rnd.chance(this.w/80)){
this.fireworks.push(new Firework(this));
}
this.fireworks.forEach(f=>{
if(!f.alive){
without(this.fireworks,f);
}
//log(this.fireworks.length)
f.draw();
});
}
initEvents(){
window.onresize = (e)=>{this.sizeCanvas(e)};
}
}
const foo = 'dsdsa';
const log = console.log.bind(console);
const Vec = TinyVector;
document.addEventListener('DOMContentLoaded', function () {
const app = new App();
});
function without (arr, el) {
arr.splice(arr.indexOf(el),1);
}
Can someone guide me to do this kind of UI making in flutter?
I'm desesperate to find the solution but i don't understand how animation works and i found nothing on stack or other website to do this.
Thanks a lot to people who know how to do that.
I already tried the confetti package but it's not my final goal.

How do I integrate a game in my flutter app?

I want to use this game https://github.com/yum650350/tissuebox in my Flutter project. I tried calling different parts of the main page but it wasn't coming up properly even though the game itself is working.
I want to integrate it in my iOS app and want to call it with a
function which opens a separate page where the user can play the game.
Is there a way I can do it?
So this is what I tried:
I called the method of the game in a separate screen
import 'package:flutter/material.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(new MaterialApp(
debugShowCheckedModeBanner: false,
home: FirstScreen(),
));
}
class FirstScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Screen'),
),
body: Center(
child: RaisedButton(
color: Colors.red,
child: Text('Go to Second Screen'),
onPressed: () {
Navigator.of(context)
.push(MaterialPageRoute<Null>(builder: (BuildContext context) {
**???**
}));
},
),
),
);
}
}
But I don't know what to call in ?? this part since the game isn't wrapped in any class. This is the code of the game I want to call.
import 'package:shared_preferences/shared_preferences.dart';
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/gestures.dart';
import 'package:flame/sprite.dart';
import 'package:flame/flame.dart';
import 'package:flame/util.dart';
import 'package:flame/game.dart';
import 'dart:math';
tissuemain() async {
var util = Util();
await util.fullScreen();
await util.setOrientation(DeviceOrientation.portraitUp);
//loadimages
//tissuebox : 0,1,2,3,4,5,6
//background : b
//crown : c
//tissue : t
await Flame.images.loadAll(['b', '0', '1', '2', '3', '4', '5', '6', 't', 'c']);
audioLoad(c) async => (await Flame.audio.load(c)).path;
setAudio(a, s, v) async {
await a.setUrl(await audioLoad(s), isLocal: true);
a.setVolume(v);
}
//audios
//single drag : s.mp3
//double drag : s.mp3
//triple drag : s.mp3
//tick tock : tk.mp3
//game over : a.mp3
GameTable.setAudioList(GameTable.audioList1, await audioLoad('s.mp3'));
GameTable.setAudioList(GameTable.audioList2, await audioLoad('d.mp3'));
GameTable.setAudioList(GameTable.audioList3, await audioLoad('t.mp3'));
await setAudio(GameTable.tickTock, 'tk.mp3', 1.0);
await setAudio(GameTable.gameOver, 'a.mp3', .5);
var game = GameTable((await SharedPreferences.getInstance()).getInt('hs') ?? 0);
var hDrag = HorizontalDragGestureRecognizer();
var vDrag = VerticalDragGestureRecognizer();
hDrag.onUpdate = game.onDragUpdate;
hDrag.onStart = game.onDragStart;
hDrag.onEnd = game.onDragEnd;
vDrag.onUpdate = game.onDragUpdate;
vDrag.onStart = game.onDragStart;
vDrag.onEnd = game.onDragEnd;
runApp(game.widget);
util.addGestureRecognizer(hDrag);
util.addGestureRecognizer(vDrag);
}
enum Drag { tissue, box, none }
class GameTable extends Game {
//Audio
//These are workarounds for the ios memory leak
static var
tickTock = AudioPlayer(),
gameOver = AudioPlayer(),
audioList1 = [AudioPlayer(), AudioPlayer(), AudioPlayer()],
audioList2 = [AudioPlayer(), AudioPlayer()],
audioList3 = [AudioPlayer(), AudioPlayer()],
audioIndex1 = 0,
audioIndex2 = 0,
audioIndex3 = 0;
static getPlayIndex(int audioPlayer) {
if (audioPlayer == 1)
audioIndex1 = audioIndex1 < audioList1.length - 1 ? audioIndex1 + 1 : 0;
else if (audioPlayer == 2)
audioIndex2 = audioIndex2 < audioList2.length - 1 ? audioIndex2 + 1 : 0;
else if (audioPlayer == 3) audioIndex3 = audioIndex3 < audioList3.length - 1 ? audioIndex3 + 1 : 0;
return audioPlayer == 1 ? audioIndex1 : audioPlayer == 2 ? audioIndex2 : audioIndex3;
}
static get tissue1 => audioList1[getPlayIndex(1)];
static get tissue2 => audioList2[getPlayIndex(2)];
static get tissue3 => audioList3[getPlayIndex(3)];
static setAudioList(List<AudioPlayer> al,String audioName) => al.forEach((x) {
x.setUrl(audioName, isLocal: true);
x.setVolume(.2);
});
//
var background = Sprite('b'),
crown = Sprite('c'),
initialPoint = Offset.zero,
destPoint = Offset.zero,
dragState = Drag.none,
gameing = false,
gameover = false,
timePass = .0,
heighScore = 0,
score = 0,
timePassTemp = 0;
double tileSize, point1;
double get k => screenSize.width / 5 / tileSize;
Size screenSize;
Rect rect;
TissueBox tissueBox;
saveHighScore() async => await (await SharedPreferences.getInstance()).setInt('hs', heighScore);
GameTable(this.heighScore) {
init();
}
init() async {
resize(await Flame.util.initialDimensions());
rect = Rect.fromLTWH(.0, screenSize.height - tileSize * 23, tileSize * 9, tileSize * 23);
tissueBox = TissueBox(this);
}
#override
render(Canvas c) {
paintText(txt, offset, center, fontSize) {
var painter = TextPainter(
text: TextSpan(
style: TextStyle(
color: Colors.white,
fontSize: fontSize,
fontFamily: 'NS'),
text: txt),
textScaleFactor: k,
textDirection: TextDirection.ltr);
painter.layout();
painter.paint(c, center ? Offset(offset.dx - painter.width / 2, offset.dy) : offset);
}
background.renderRect(c, rect);
tissueBox.render(c);
var horCenter = tissueBox.initialLeft + tissueBox.boxRect.width / 2;
if (gameing)
paintText(timePass.toStringAsFixed(timePass < 1 ? 1 : 0) + 's', Offset(horCenter + 8, k * 23), true, k * 10);
var heighScoreTxt = heighScore.toString();
paintText(heighScoreTxt, Offset(heighScoreTxt.length==1?44.0:heighScoreTxt.length>2?22.0:33.0, k * 30), false, k * 12);
crown.renderRect(c, Rect.fromLTWH(28.0, k * 10, 49.2, 39.0));
paintText(score.toString(), Offset(horCenter, k * 50), true, k * 25);
heighScore = score > heighScore ? score : heighScore;
}
#override
update(double t) {
tissueBox.update(t);
timePass -= gameing || gameover ? t : 0;
if (timePass < 0 && gameing) {
tissueBox.isAway = true;
gameing = false;
timePass = 2;
gameover = true;
saveHighScore();
tissueBox.newGame();
} else if (gameing && !gameover) {
var floor = timePass.floor();
if (floor < timePassTemp && floor < 6 && floor != 0)
TissueBox.delay(Duration(milliseconds: 300), () => GameTable.tickTock.resume());
timePassTemp = floor;
}
gameover = timePass <= 0 && gameover ? false : gameover;
}
resize(s) {
screenSize = s;
tileSize = screenSize.width / 9;
}
onDragStart(DragStartDetails detail) {
var point = detail.globalPosition;
dragState = tissueBox.tissue.rect.contains(point) ? Drag.tissue : tissueBox.boxRect.contains(point) ? Drag.box : Drag.none;
initialPoint = Offset(point.dx == 0 ? initialPoint.dx : point.dx, point.dy == 0 ? initialPoint.dy : point.dy);
point1 = (tissueBox.tissue.rect.left - point.dx).abs();
}
onDragUpdate(DragUpdateDetails detail) {
if (gameover || dragState == Drag.none) return;
var point = detail.globalPosition;
destPoint = Offset(point.dx == 0 ? destPoint.dx : point.dx, point.dy == 0 ? destPoint.dy : point.dy);
if (dragState == Drag.tissue) {
if (initialPoint.dy - destPoint.dy > 100) {
if (gameing != true && gameover != true) {
gameing = true;
timePass = 10;
score = 0;
}
var sub = (point1 - (tissueBox.tissue.rect.left - point.dx).abs()).abs();
var addPoint = sub < 3 ? 3 : sub < 6 ? 2 : 1;
dragState = Drag.none;
tissueBox.nextTissue(addPoint);
playTissueAudio(addPoint);
score += addPoint;
}
} else if (dragState == Drag.box) {
tissueBox.boxRect = Rect.fromLTWH(tissueBox.initialLeft + destPoint.dx - initialPoint.dx, tissueBox.boxRect.top, TissueBox.boxSize.dx, TissueBox.boxSize.dy);
tissueBox.ismoving = true;
}
}
playTissueAudio(i) => (i == 1 ? GameTable.tissue1 : i == 2 ? GameTable.tissue2 : GameTable.tissue3).resume();
onDragEnd(DragEndDetails detail) {
initialPoint = Offset.zero;
dragState = Drag.none;
tissueBox.tissue.isMoving = false;
tissueBox.ismoving = false;
destPoint = initialPoint;
}
}
class TissueBox {
Rect get initialRect => Rect.fromLTWH(boxRect.center.dx - Tissue.width / 2, boxRect.top - boxRect.height + 20.3, Tissue.width, Tissue.width);
Sprite get getBoxSprite =>Sprite( rnd.nextInt(7).toString());
var tissueAwayList = List<TissueAway>(), rnd = Random(), ismoving = false, isAway = false;
Offset get getTissueUpPosition => Offset(initialRect.left, initialRect.top - 150);
final GameTable game;
Sprite boxSprite;
Rect boxRect;
int tissueCount;
Tissue tissue;
double get initialLeft => game.screenSize.width / 2 - TissueBox.boxSize.dx / 2;
double get initialTop => game.screenSize.height - game.tileSize * 5.5;
static var boxSize = Offset(150.0, 100.0);
TissueBox(this.game) {
boxRect = Rect.fromLTWH(initialLeft, initialTop, boxSize.dx, boxSize.dy);
tissueCount = 10 - rnd.nextInt(5);
tissue = Tissue(game, this);
boxSprite = getBoxSprite;
}
render(Canvas c) {
boxSprite.renderRect(c, boxRect);
tissue.render(c);
tissueAwayList.forEach((x) => x.render(c));
}
update(double t) {
tissue.update(t);
tissueAwayList.removeWhere((x) => x.isAway);
tissueAwayList.forEach((x) => x.update(t));
var distense = boxRect.left - initialLeft;
if (ismoving && !game.gameover) {
if (distense.abs() > 50 && tissueCount == 0){
isAway = true;
}
} else if (isAway && !game.gameover) {
boxRect = boxRect.shift(Offset(distense > 0 ? boxRect.left + game.k * 11 : boxRect.left - game.k * 11, boxRect.top));
if (boxRect.right < -50 || boxRect.left > game.screenSize.width + 50) {
newBox();
}
} else if (isAway && game.gameover) {
var target = Offset(boxRect.left, game.screenSize.height + Tissue.width) - Offset(boxRect.left, boxRect.top);
boxRect = boxRect.shift(
game.k * 11 < target.distance ?
Offset.fromDirection(target.direction, game.k * 11)
: target);
} else {
var target = Offset(initialLeft, initialTop) - Offset(boxRect.left, boxRect.top);
boxRect = boxRect.shift(
game.k * 11 < target.distance ?
Offset.fromDirection(target.direction, game.k * 11)
: target);
}
}
nextTissue(int pointsAdd) {
var duration = Duration(milliseconds: 100);
tissueAwayList.add(TissueAway(game, this));
if (pointsAdd > 1)
delay(duration, () {
tissueAwayList.add(TissueAway(game, this));
if (pointsAdd > 2)
delay(duration, () {
tissueAwayList.add(TissueAway(game, this));
});
});
tissue = Tissue(game, this, --tissueCount == 0);
}
newBox() {
boxSprite = getBoxSprite;
boxRect = Rect.fromLTWH(boxRect.right < -0 ? game.screenSize.width + 50 - boxSize.dx : -50.0, initialTop, boxSize.dx, boxSize.dy);
tissueCount = 10 - rnd.nextInt(5);
tissue = Tissue(game, this);
isAway = false;
ismoving = false;
}
newGame() async {
isAway = true;
GameTable.gameOver.resume();
await delay(Duration(seconds: 2), () {});
newBox();
}
static delay(duration, func()) async => await Future.delayed(duration, func);
}
class Tissue {
var tissueSprite = Sprite('t'), isMoving = false;
static var width = 100.0;
final TissueBox tissueBox;
final GameTable game;
bool isAway;
Rect rect;
Tissue(this.game, this.tissueBox, [this.isAway = false]) {
rect = tissueBox.initialRect;
}
render(Canvas c) => tissueSprite.renderRect(c, rect);
update(double t) => rect = isAway ? rect.shift(Offset.infinite) : tissueBox.initialRect;
}
class TissueAway extends Tissue {
TissueAway(GameTable game, TissueBox tissueBox) : super(game, tissueBox);
render(Canvas c) => tissueSprite.renderRect(c, rect);
update(double t) {
var speed = 500 * t;
Offset target = tissueBox.getTissueUpPosition - Offset(rect.left, rect.top);
if (speed < target.distance)
rect = rect.shift(Offset.fromDirection(target.direction, speed));
else
isAway = true;
}
}
The code in the main method will have a line that says runApp(...). The value of the ... is a widget that will be treated as the root widget of the app. In theory, you could just take that widget and pass it to your Navigator.push method and it should treat that widget as any other widget.
That being said, real life probably won't be as clean as this. There might be some initialization code in that app's main method or root widget that won't work properly if the app has long since already been initialized. Since virtually every Flutter app's root widget creates a WidgetsApp (or one of its derived classes MaterialApp or CupertinoApp), there might be some conflict that arrives from having one of those widgets being inserted as a descendent of another one of those widgets.
The game in question may work simply, or it might take some tweaking to work properly. That's something that will depend entirely on what app you are trying to embed into your own, so the only solution is to try it out and see for yourself.

Numbers Fractionate

can you ask me a question? I have several double that receive a value of a calculation but would like to know how I can fractionate these values received? In the case would be: 13.232 to 13.23 ..
Code:
Future calculate() {
double precototal1 =
double.parse(_precoCtrl.text.replaceAll(new RegExp(r'[,.]'), '')) * 12;
double precototal2 =
double.parse(_precoCtrl1.text.replaceAll(new RegExp(r'[,.]'), '')) * 12;
double unidademl1 =
double.parse(_unitCtrl.text.replaceAll(new RegExp(r'[,.]'), ''));
double unidademl2 =
double.parse(_unitCtrl1.text.replaceAll(new RegExp(r'[,.]'), ''));
double difvalor = 0;
double latas = 0;
double litros = 0;
double totlitros = 0;
double diflitros = 0;
setState(() {
_completed = false;
_busy = true;
});
return new Future.delayed(const Duration(seconds: 1), () {
setState(
() {
print(formatter.format(precototal2));
if (precototal1 > precototal2) {
difvalor = precototal1 - precototal2;
latas = (difvalor / precototal2);
litros = (latas * unidademl2);
totlitros = litros + unidademl2;
diflitros = (totlitros - unidademl1);
} else {
difvalor = (precototal2 - precototal1);
latas = (difvalor + precototal1);
totlitros = litros + unidademl1;
litros = (latas / unidademl1);
diflitros = (unidademl2 - totlitros);
}
Use the toStringAsFixed method.
Example:
double value = 15.2235;
String fmtValue = value.toStringAsFixed(2);

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);

React Leaflet.Arc - Animate

I am trying create ReactLeafletArc Plugin with animation this is the code
if (!L) {
throw "Leaflet.js not included";
} else if (!arc || !arc.GreatCircle) {
throw "arc.js not included";
} else {
L.Polyline.Arc = function (from, to, options) {
from = L.latLng(from);
to = L.latLng(to);
var vertices = 10;
var arcOptions = {};
if (options) {
if (options.offset) {
arcOptions.offset = options.offset;
delete options.offset;
}
if (options.vertices) {
vertices = options.vertices;
delete options.vertices;
}
}
var generator = new arc.GreatCircle({ x: from.lng, y: from.lat }, { x: to.lng, y: to.lat });
var line = generator.Arc(vertices, arcOptions);
var latLngs = [];
var newLine = L.polyline(line.geometries[0].coords.map(function (c) {
return c.reverse();
}), options);
var totalLength = newLine._path.getTotalLength() * 4;
newLine._path.classList.add('path-start');
newLine._path.style.strokeDashoffset = totalLength;
newLine._path.style.strokeDasharray = totalLength;
setTimeout((function (path) {
return function () {
path.style.strokeDashoffset = 0;
};
})(newLine._path), 200);
return newLine;
};
}
I get error
Uncaught TypeError: Cannot read property 'getTotalLength' of undefined
with console.log(newLine) i can see that there is not _path.
NewClass
_bounds:L.LatLngBounds
_initHooksCalled:true
_latlngs:Array[100]
options:Object
__proto__:NewClass
But if i comment the part
var totalLength = newLine._path.getTotalLength() * 4;
newLine._path.classList.add('path-start');
newLine._path.style.strokeDashoffset = totalLength;
newLine._path.style.strokeDasharray = totalLength;
setTimeout((function (path) {
return function () {
path.style.strokeDashoffset = 0;
};
})(newLine._path), 200);
The line is created without animation and with console.log(newLine) i get this
NewClass
_bounds:L.LatLngBounds
_initHooksCalled:true
_latlngs:Array[100]
_leaflet_id:122
_map:NewClass
_mapToAdd:NewClass
_parts:Array[1]
_path:path
_pxBounds:L.Bounds
_renderer:NewClass
_rings:Array[1]
_zoomAnimated:true
options:Object
__proto__:NewClass
Any suggestion?