I'm trying to draw a 'countdown' circle that will produce a visual clock, but don't like the resulting shape as it is not smooth. The code below draws and fills a series of triangles that approximate a circle when the variable 'numberOfSides' is sufficiently large, but this is both inefficient and produces an ugly 'circle.' What I would like to do is draw a series of arcs across the line segments but I don't know how to do it. Can anyone give me a prod in the right direction?
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" minWidth="955" minHeight="600" frameRate="100" creationComplete="init()">
<mx:Script>
<![CDATA[
private var numberOfSides:int = 10;
private var angle:Number = 0;
private var lineLength:int = 100;
private var xStartingPos:int = 300;
private var yStartingPos:int = 300;
private var i:int = 90;//Start the drawing at 0 to 360;
private var speed:int = 100;
private var timer:Timer
private function init():void{
//uic.graphics.lineStyle(1);
uic.graphics.moveTo(xStartingPos,yStartingPos);
uic.graphics.beginFill(0xffff00);
timer = new Timer(speed, numberOfSides + 1);
timer.addEventListener(TimerEvent.TIMER,cont);
timer.start()
}
private function cont(event:TimerEvent):void{
angle = (Math.PI / numberOfSides * 2) * i;
var xAngle:Number = Math.sin(angle);
var yAngle:Number = Math.cos(angle);
var xResult:int = xAngle * lineLength;
var yResult:int = yAngle * lineLength;
uic.graphics.lineTo(xStartingPos + xResult, yStartingPos + (yResult*-1));
i++
}
]]>
</mx:Script>
<mx:Canvas id="uic"/>
</mx:Application>
This could be accomplished by drawing wedges using nl.funkymonkey.drawing.DrawingShapes:
Draw Wedge function:
public static function drawWedge(target:Graphics, x:Number, y:Number, radius:Number, arc:Number, startAngle:Number=0, yRadius:Number=0):void
{
if (yRadius == 0)
yRadius = radius;
target.moveTo(x, y);
var segAngle:Number, theta:Number, angle:Number, angleMid:Number, segs:Number, ax:Number, ay:Number, bx:Number, by:Number, cx:Number, cy:Number;
if (Math.abs(arc) > 360)
arc = 360;
segs = Math.ceil(Math.abs(arc) / 45);
segAngle = arc / segs;
theta = -(segAngle / 180) * Math.PI;
angle = -(startAngle / 180) * Math.PI;
if (segs > 0)
{
ax = x + Math.cos(startAngle / 180 * Math.PI) * radius;
ay = y + Math.sin(-startAngle / 180 * Math.PI) * yRadius;
target.lineTo(ax, ay);
for (var i:int = 0; i < segs; ++i)
{
angle += theta;
angleMid = angle - (theta / 2);
bx = x + Math.cos(angle) * radius;
by = y + Math.sin(angle) * yRadius;
cx = x + Math.cos(angleMid) * (radius / Math.cos(theta / 2));
cy = y + Math.sin(angleMid) * (yRadius / Math.cos(theta / 2));
target.curveTo(cx, cy, bx, by);
}
target.lineTo(x, y);
}
}
Example implementation, animating countdown timer:
var value:Number = 0;
addEventListener(Event.ENTER_FRAME, frameHandler);
function frameHandler(event:Event):void
{
var g:Graphics = graphics;
g.clear();
value += 0.01;
g.lineStyle(1, 0x0000ff, 0.25);
g.beginFill(0x123456, 0.25);
drawWedge(g, 100, 100, 50, (360 * value) % 360);
g.endFill();
}
Any graphics line style or fill may be used; or, the fill could be used as a mask.
An other piece of code I found online:
http://flassari.is/2009/11/pie-mask-in-as3/
Changed this into:
var circleToMask:Sprite = new Sprite();
circleToMask.graphics.beginFill(0x004BA5DC); // color circle 0x00RRGGBB
circleToMask.graphics.drawCircle(0, 0, 50);
circleToMask.graphics.endFill();
addChildAt(circleToMask, 0);
var circleMask:Sprite = new Sprite();
circleToMask.x = (circleMask.x = 50);
circleToMask.y = (circleMask.y = 50);
circleToMask.mask = circleMask;
addChild(circleMask);
// inner circle
var circleTopMask:Sprite = new Sprite();
circleTopMask.graphics.beginFill(0x00FFFFFF); // color inner circle
circleTopMask.graphics.drawCircle(0, 0, 25);
circleTopMask.graphics.endFill();
addChild(circleTopMask);
circleTopMask.x = 50;
circleTopMask.y = 50;
// textfield in the center
var myFormat:TextFormat = new TextFormat();
myFormat.size = 18;
var myText:TextField = new TextField();
myText.defaultTextFormat = myFormat;
myText.text = "0%";
myText.autoSize = TextFieldAutoSize.CENTER;
myText.y = 50 - (myText.height/2);
addChild(myText);
var percentage:Number = 0;
var tper:Number = 0;
addEventListener(Event.ENTER_FRAME, function (_arg1:Event):void{
graphics.clear();
// Percentage should be between 0 and 1
tper = percentage < 0 ? 0 : (percentage > 1 ? 1 : percentage);
// Draw the masked circle
circleMask.graphics.clear();
circleMask.graphics.beginFill(0);
drawPieMask(circleMask.graphics, tper, 50, 0, 0, (-(Math.PI) / 2), 3);
circleMask.graphics.endFill();
// Increase percentage with margins so it appears to stop for a short while
percentage = (percentage + 0.01);
if (percentage > 1){
percentage = 0;
}
myText.text = ((percentage*100).toFixed(0))+"%";
})
function drawPieMask(graphics:Graphics, percentage:Number, radius:Number = 50, x:Number = 0, y:Number = 0, rotation:Number = 0, sides:int = 6):void {
// graphics should have its beginFill function already called by now
graphics.moveTo(x, y);
if (sides < 3) sides = 3; // 3 sides minimum
// Increase the length of the radius to cover the whole target
radius /= Math.cos(1/sides * Math.PI);
// Shortcut function
var lineToRadians:Function = function(rads:Number):void {
graphics.lineTo(Math.cos(rads) * radius + x, Math.sin(rads) * radius + y);
};
// Find how many sides we have to draw
var sidesToDraw:int = Math.floor(percentage * sides);
for (var i:int = 0; i <= sidesToDraw; i++)
lineToRadians((i / sides) * (Math.PI * 2) + rotation);
// Draw the last fractioned side
if (percentage * sides != sidesToDraw)
lineToRadians(percentage * (Math.PI * 2) + rotation);
}
The size of the resuling FLA should be 100 x 100 px.
Resulting in:
Related
i'm trying to make an animated pie chart in AS3, which it only shows a single var, but this var updates every X seconds. What I wanna do, it's to animated this pie chart to update to the new "status" with a tween animation. Something I can't make work. I already tried with tweenmax and such.
This is my pie chart code generator:
var degree: Number = 90;
var degChange: Number = 1;
var circleR: Number = 100;
var circleX: Number = 100;
var circleY: Number = 100;
var angleR: Number = circleR / 4;
var spBoard: Sprite = new Sprite();
spBoard.x = circleX;
spBoard.y = circleY;
C1.Maskmc.addChild(spBoard);
var shFill: Shape = new Shape();
shFill.graphics.lineStyle(1, 0x000000);
shFill.graphics.moveTo(0, 0);
shFill.graphics.lineTo(circleR, 0);
shFill.x = 0;
shFill.y = 0;
var shAngle: Shape = new Shape();
spBoard.addChild(shFill);
function updatePicture(t: Number): void {
var radianAngle: Number //=// t * Math.PI / 180.0;
var i: int;
shFill.graphics.clear();
shFill.graphics.moveTo(0, 0);
shFill.graphics.beginFill(0xFF00FF, 1);
for (i = 0; i <= t; i++) {
shFill.graphics.lineTo(circleR * Math.cos(i * Math.PI / 180), -circleR * Math.sin(i * Math.PI / 180));
trace(i)
}
shFill.graphics.lineTo(0, 0);
shFill.graphics.endFill();
}
var clockTimerTRas: Timer = new Timer(1000, 0);
clockTimerTRas.addEventListener(TimerEvent.TIMER, UpdateP);
clockTimerTRas.start();
function UpdateP(evt: Event): void {
var TurnoEstaHora
var TurnoActual_A: int = (it takes a var from a php here)
degree = TurnoActual_A * (11.305)
updatePicture(degree);
}
Which as i said before, it only makes a circle or incomplete circle depending on the var that loads from the PHP.
So far it works something like this, https://media.giphy.com/media/qiutE2wCo1YXe/giphy.gif
I wanna tween that animation. Any ideas?
Thank you!
Here an example:
import flash.display.Sprite;
import fl.transitions.Tween;
import fl.transitions.easing.*;
var tempSprite:Sprite = new Sprite();
addChild(tempSprite);
tempSprite.x = 100;
tempSprite.y = 100;
function convertAngleToRadians(angle:Number):Number
{
return angle / 180 * Math.PI;
}
function fillRound(
graphics:Graphics,
color:uint = 0x000000,
alpha:Number = 1,
radius:Number = 10,
startAngle:Number = 0,
endAngle:Number = 360,
angleStep:Number = 1):void
{
var tempRadians:Number;
var startRadians:Number = convertAngleToRadians(startAngle);
var endRadians:Number = convertAngleToRadians(endAngle);
var radiansStep:Number = convertAngleToRadians(angleStep);
var tempPoint:Point;
graphics.beginFill(color, alpha);
if (endAngle % 360 == startAngle % 360)
{
graphics.drawCircle(0, 0, radius);
}else
{
var pointsCount:Number = Math.ceil((endAngle - startAngle) / angleStep);
for (var pointIndex:int = 0; pointIndex <= pointsCount; pointIndex++)
{
tempRadians = startRadians + pointIndex * radiansStep;
tempPoint = new Point();
tempPoint.x = Math.cos(tempRadians) * radius;
tempPoint.y = Math.sin(tempRadians) * radius;
graphics.lineTo(tempPoint.x, tempPoint.y);
}
}
graphics.endFill();
}
var _curPieStep:Number = 0;
function get curPieStep():Number
{
return _curPieStep;
}
function set curPieStep(value:Number):void
{
_curPieStep = value;
drawPie();
}
var pieMaxStep:int = 30;
var pieAngleStep:int = 360 / pieMaxStep;
function drawPie():void
{
tempSprite.graphics.clear();
fillRound(tempSprite.graphics, 0xFF0000, 1, 100, 0, pieAngleStep * curPieStep);
}
var myTween:Tween = new Tween(this, "curPieStep", Bounce.easeOut, 0, pieMaxStep, 4, true);
You may just copy'n'paste it into Flash IDE and check the result.
Try this
import flash.utils.Timer;
import flash.events.TimerEvent;
var degree: Number = 90;
var degChange: Number = 1;
var circleR: Number = 100;
var circleX: Number = 100;
var circleY: Number = 100;
var angleR: Number = circleR / 4;
var spBoard: Sprite = new Sprite();
spBoard.x = circleX;
spBoard.y = circleY;
C1.Maskmc.addChild(spBoard);
var shFill: Shape = new Shape();
shFill.graphics.lineStyle(1, 0x000000);
shFill.graphics.moveTo(0, 0);
shFill.graphics.lineTo(circleR, 0);
shFill.x = 0;
shFill.y = 0;
shFill.graphics.clear();
shFill.graphics.moveTo(0, 0);
shFill.graphics.beginFill(0xFF00FF, 1);
var shAngle: Shape = new Shape();
spBoard.addChild(shFill);
function updatePicture(t: Number): void {
var radianAngle: Number //=// t * Math.PI / 180.0;
var i: int;
i = clockTimerTRas.currentCount ;
shFill.graphics.lineTo(circleR * Math.cos(i * Math.PI / 180), -circleR * Math.sin(i * Math.PI / 180));
trace(i)
}
var TurnoActual_A: int = (100) ;
var clockTimerTRas: Timer = new Timer(10, TurnoActual_A);
clockTimerTRas.addEventListener(TimerEvent.TIMER, UpdateP);
clockTimerTRas.start();
function UpdateP(evt: Event): void {
var TurnoEstaHora
degree = TurnoActual_A * (11.305)
updatePicture(degree);
}
I am trying to animate many objects to a canvas using KineticJS. I am using the built-in move method on every frame. It is known that redrawing a layer is an expensive operation which can cause performance issues, so I am calling layer.draw() only after each move operations has already been executed. Despite this, the more objects I animate, the poor the performance becomes and the end result is a sluggish animation.
To compare the KineticJS performance against the native canvas, I prepared two demos that do the same thing - bouncing balls in a canvas of 500x500. The first one is using the native canvas. It just clears the canvas on each frame and draws the balls. The second one uses KineticJS and once the image objects are created, it uses the move method to move them.
It is obvious that while the native demo performs the same with 10, 100 and 1000 balls, the performance of KineticJS demo is strongly affected by the number of balls. With 1000, it is just unusable. There are many optimizations that can be made to both examples, including using requestAnimationFrame for animation loop or using the built-in Animation object for KineticJS, but these will not change the performance of the demos much.
So here are the two demos. First, the native one - http://jsfiddle.net/uxsLN/1/
(function() {
window.addEventListener('load', loaded, false);
function loaded() {
img = new Image();
img.onload = canvasApp;
img.src = 'ball.png';
}
function canvasApp() {
var theCanvas = document.getElementById("canvas");
var context = theCanvas.getContext("2d");
function drawScreen() {
context.clearRect(0, 0, theCanvas.width, theCanvas.height);
context.strokeStyle = '#000000';
context.strokeRect(1, 1, theCanvas.width - 2, theCanvas.height - 2);
context.fillStyle = "#000000";
var ball;
for (var i = 0; i < balls.length; i++) {
ball = balls[i];
ball.x += ball.xunits;
ball.y += ball.yunits;
context.drawImage(img, ball.x, ball.y);
if (ball.x + ball.radius * 2 > theCanvas.width || ball.x < 0) {
ball.angle = 180 - ball.angle;
updateBall(ball);
} else if (ball.y + ball.radius * 2 > theCanvas.height || ball.y < 0) {
ball.angle = 360 - ball.angle;
updateBall(ball);
}
}
}
function updateBall(ball) {
ball.radians = ball.angle * Math.PI / 180;
ball.xunits = Math.cos(ball.radians) * ball.speed;
ball.yunits = Math.sin(ball.radians) * ball.speed;
}
var numBalls = 1000;
var maxSize = 8;
var minSize = 5;
var maxSpeed = maxSize + 5;
var balls = [];
var radius = 24;
for (var i = 0; i < numBalls; i++) {
var speed = maxSpeed - radius;
var angle = Math.floor(Math.random() * 360);
var radians = angle * Math.PI / 180;
var ball = {
x : (theCanvas.width - radius) / 2,
y : (theCanvas.height - radius) / 2,
radius : radius,
speed : speed,
angle : angle,
xunits : Math.cos(radians) * speed,
yunits : Math.sin(radians) * speed
}
balls.push(ball);
}
function gameLoop() {
window.setTimeout(gameLoop, 20);
drawScreen()
}
gameLoop();
}
})();
Next, KineticJS - http://jsfiddle.net/MNpUX/
(function() {
window.addEventListener('load', loaded, false);
function loaded() {
img = new Image();
img.onload = canvasApp;
img.src = 'ball.png';
}
function canvasApp() {
var stage = new Kinetic.Stage({
container : 'container',
width : 500,
height : 500
});
var layer = new Kinetic.Layer();
stage.add(layer);
rect = new Kinetic.Rect({
x : 0,
y : 0,
width : stage.getWidth(),
height : stage.getHeight(),
fill : '#EEEEEE',
stroke : 'black'
});
layer.add(rect);
function drawScreen() {
var ball;
for ( var i = 0; i < balls.length; i++) {
ball = balls[i];
ball.obj.move(ball.xunits, ball.yunits);
if (ball.obj.getX() + ball.radius * 2 > stage.getWidth() || ball.obj.getX() < 0) {
ball.angle = 180 - ball.angle;
updateBall(ball);
} else if (ball.obj.getY() + ball.radius * 2 > stage.getHeight() || ball.obj.getY() < 0) {
ball.angle = 360 - ball.angle;
updateBall(ball);
}
}
layer.draw();
}
function updateBall(ball) {
ball.radians = ball.angle * Math.PI / 180;
ball.xunits = Math.cos(ball.radians) * ball.speed;
ball.yunits = Math.sin(ball.radians) * ball.speed;
}
var numBalls = 1000;
var maxSize = 8;
var minSize = 5;
var maxSpeed = maxSize + 5;
var balls = [];
var radius = 24;
for ( var i = 0; i < numBalls; i++) {
var speed = maxSpeed - radius;
var angle = Math.floor(Math.random() * 360);
var radians = angle * Math.PI / 180;
var obj = new Kinetic.Image({
image : img,
x : (stage.getWidth() - radius) / 2,
y : (stage.getHeight() - radius) / 2
});
layer.add(obj);
var ball = {
radius : radius,
speed : speed,
angle : angle,
xunits : Math.cos(radians) * speed,
yunits : Math.sin(radians) * speed,
obj : obj
};
balls.push(ball);
}
function gameLoop() {
window.setTimeout(gameLoop, 20);
drawScreen()
}
gameLoop();
}
})();
So the question is - do I miss something about KineticJS or it is just not built for such a purpose?
You can gain a little speed by:
Turning listening off on the stage.
Using layer.drawScene instead of layer.draw. (drawScene doesn't also redraw the hit scene).
Reducing the ball count to 500 (the effect looks pretty much the same).
If your design permits, use a custom Kinetic.Shape to get "closer to the metal".
The Kinetic.Shape gives you a wrapped Context on which you can run native Context commands.
Using Shape, you'll get magnitudes better results because there's only 1 object being managed.
Here's code and a Fiddle: http://jsfiddle.net/m1erickson/AVJyr/
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v4.7.2.min.js"></script>
<style>
body{padding:20px;}
#container{
border:solid 1px #ccc;
margin-top: 10px;
width:500px;
height:500px;
}
</style>
<script>
$(function(){
var stage = new Kinetic.Stage({
container: 'container',
width: 500,
height: 500,
listening:false
});
var layer = new Kinetic.Layer();
stage.add(layer);
//
var cw=stage.getWidth();
var ch=stage.getHeight();
var numBalls = 1000;
var maxSize = 8;
var minSize = 5;
var maxSpeed = maxSize + 5;
var balls = [];
var radius = 24;
// this is a custom Kinetic.Shape
var shape;
for (var i = 0; i < numBalls; i++) {
var speed = maxSpeed - radius;
var angle = Math.floor(Math.random() * 360);
var radians = angle * Math.PI / 180;
var ball = {
x : (cw-radius)/2,
y : (ch-radius)/2,
radius : radius,
speed : speed,
angle : angle,
xunits : Math.cos(radians) * speed,
yunits : Math.sin(radians) * speed
}
balls.push(ball);
}
// load the ball image and create the Kinetic.Shape
img = new Image();
img.onload=function(){
shape=new Kinetic.Shape({
x: 0,
y: 0,
width:500,
height:500,
draggable: true,
drawFunc: function(context) {
context.beginPath();
var ball;
for (var i = 0; i < balls.length; i++) {
ball = balls[i];
ball.x += ball.xunits;
ball.y += ball.yunits;
context.drawImage(img, ball.x, ball.y);
if (ball.x+ball.radius*2>cw || ball.x<0) {
ball.angle = 180 - ball.angle;
} else if (ball.y+ball.radius*2>ch || ball.y<0) {
ball.angle = 360 - ball.angle;
}
ball.radians = ball.angle * Math.PI / 180;
ball.xunits = Math.cos(ball.radians) * ball.speed;
ball.yunits = Math.sin(ball.radians) * ball.speed;
}
context.fillStrokeShape(this);
},
});
layer.add(shape);
// GO!
gameLoop();
}
img.src = 'http://users-cs.au.dk/mic/dIntProg/e12/uge/4/Projekter/bouncingballs/assignment/ball.png';
// RAF used to repeatedly redraw the custom shape
function gameLoop(){
window.requestAnimationFrame(gameLoop);
layer.clear();
shape.draw();
}
}); // end $(function(){});
</script>
</head>
<body>
<div id="container"></div>
</body>
</html>
I've created this animation for my project that had to use any form of physics.
I am a total beginner, too :) Anyway, this is my project now :
Bouncing Balls
You can setup gravity and force, then click play, and just drag and drop to shoot the balls. You can change the values and hit update too see an effect.
My question is, how can I create an effect that when I press ratio button (for example) I can see the path that ball makes? Is it complicated? As I was saying I am a beginner, so no complex code for me :)
Also, doyou have any ideas to make the project better? Any additional "physics" effects? Or maybe you know a website that shows tutorials for simile (please) effects made in HTML5/js so I can add additional effects to my project.
One possibility (as you're clearing the canvas each frame) would be to draw ball paths onto a secondary canvas, which would not be cleared each frame. Then, when you come to clear the first frame, render the second frame after clearing, and before rendering the balls.
The second canvas would of course have to be the same dimensions as the first, so that all of the ball points line up correctly. The second canvas should also have a z-index lower than the first, so that it is only shown when you specifically render it to the first canvas (i.e. when the radio button is checked).
To decrease any lag while the radio is not checked, you could skip drawing the ball paths to the second canvas, although I don't think you would see any great increase in performance.
On each frame update, you would mark the position of each ball with a pixel, or line (from the previous position to the current) on the second canvas.
Looking at your code, you seem pretty competent, so I've skipped writing an example as I think this would be good experience for you :)
Modified 'script.js' source demonstrating solution
window.onload = function(){
$("#canvas").hide();
var howManyPaths = 0;
var showPath=false;
// SLIDERS
var gravitySlider = document.getElementById('gravitySlider');
var gravityVal = document.getElementById('gravityValue');
gravitySlider.onchange = function(){
gravityVal.value = gravitySlider.value;
}
gravityVal.onkeyup = function(){
gravitySlider.value = gravityVal.value;
}
var forceSlider = document.getElementById('forceSlider');
var forceValue = document.getElementById('forceValue');
forceSlider.onchange = function(){
forceValue.value = forceSlider.value;
}
forceValue.onkeyup = function(){
forceSlider.value = forceValue.value;
}
// GLOBAL VARIABLES
var test = false;
var gravityCount = $("#gravity").val();
var forceCount = $("#rectangles").val();
// CSS :
var playCSS = document.getElementById("play");
var restartCSS = document.getElementById("restart");
var clickableCSS = document.getElementById("setup");
var clickableBG = document.getElementById("img");
//restartCSS.style.visibility="hidden";
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var canvas2 = document.getElementById("canvas2");
var ctx2 = canvas2.getContext("2d");
//var ctx;
var gravity = 9.86;
var forceFactor = 0.5;
var mouseDown = false;
var balls = new Array();
var mousePos = new Array();
// EVENT HANDLER
function onMouseDown(evt){
mouseDown = true;
mousePos['downX'] = evt.pageX;
mousePos['downY'] = evt.pageY;
}
function onMouseUp(evt){
mouseDown = false;
setup.style.visibility="visible";
if(test == true && !( mousePos['downX'] < 200 && mousePos['downY'] < 150) ){
restartCSS.style.visibility="visible";
forceFactor = forceCount;
balls.push(new ball(mousePos["downX"],
mousePos["downY"],
(evt.pageX - mousePos["downX"]) * forceFactor,
(evt.pageY - mousePos["downY"]) * forceFactor,
10 + (Math.random() * 10),
0.8,
randomColor()
));
}
ctx2.clearRect(0, 0, canvas2.width, canvas2.height);
}
function onMouseMove(evt){
mousePos['currentX'] = evt.pageX;
mousePos['currentY'] = evt.pageY;
}
function resizeWindow(evt){
//canvas.height = 960;
//canvas.width = 720;
canvas.height = $(window).height()-6;
canvas.width = $(window).width();
canvas2.height = $(window).height()-6;
canvas2.width = $(window).width();
}
$(document).mousedown(onMouseDown);
$(document).mouseup(onMouseUp);
$(document).mousemove(onMouseMove);
$(window).bind("resize", resizeWindow);
// GRAPHICS CODE
function circle(x, y, r, col){
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI*2, true);
ctx.closePath;
// fill
ctx.fillStyle = col;
ctx.fill();
// stroke
ctx.lineWidth = r * 0.1;
ctx.strokeStyle = "#000000";
ctx.stroke();
}
function circlePath(x, y)
{
ctx2.clearRect(0, 0, canvas2.width, canvas2.height);
ctx2.fillStyle = '#3f4043';
ctx2.fillRect(x, y, 5, 5);
ctx2.strokeStyle = "black";
ctx2.strokeRect(x, y, 5, 5);
}
function randomColor(){
var letter = "0123456789ABCDEF".split("");
var color = "#";
for(var i=0; i<6; i++){
color += letter[Math.round(Math.random()*15)];
}
return color;
}
function arrow(fromX, fromY, toX, toY, color){
// path
ctx.beginPath();
var headLen = 10;
var angle = Math.atan2(toY - fromY, toX - fromX);
ctx.moveTo(fromX, fromY);
ctx.lineTo(toX, toY);
ctx.lineTo(toX - headLen * Math.cos(angle - Math.PI/6), toY - headLen * Math.sin(angle - Math.PI/6));
ctx.moveTo(toX, toY);
ctx.lineTo(toX - headLen * Math.cos(angle + Math.PI/6), toY - headLen * Math.sin(angle + Math.PI/6));
// style
ctx.lineWith = 1;
ctx.strokeStyle = color;
ctx.lineCap = "butt";
ctx.stroke();
}
function drawBall(){
// Gravity
gravity = gravityCount;
this.speedY += gravity * 0.5; // v = a * t
this.x += this.speedX * 0.05; // s = v * t
this.y += this.speedY * 0.05;
// prawa ściana
if(this.x + this.r > canvas.width){
this.x = canvas.width - this.r;
this.speedX *= -1 * this.bounce;
}
// lewa ściana
if(this.x - this.r < 0){
this.x = this.r;
this.speedX *= -1 * this.bounce;
}
// dolna ściana
if(this.y + this.r > canvas.height){
this.y = canvas.height - this.r;
this.speedY *= -1 * this.bounce;
}
// górna ściana
if(this.y - this.r < 0){
this.y = this.r;
this.speedY *= -1 * this.bounce;
}
// zwalnianie na ziemi
if (this.speedX > 0.25){
this.speedX -= 0.25;
if (this.speedY > 0.25)
this.speedY -= 0.25;
}
if (this.speedX < -0.25){
this.speedX += 0.25;
//if (this.speedY < -0.25)
// this.speedY += 0.25;
}
circle(this.x, this.y, this.r, this.col);;
}
// OBJECTS
function ball(positionX, positionY, sX, sY, radius, b, color){
this.x = positionX;
this.y = positionY;
this.speedX = sX;
this.speedY = sY;
this.r = radius;
this.bounce = b;
this.col = color;
this.draw = drawBall;
}
//GAME LOOP
function gameLoop(){
ctx.clearRect(0, 0, canvas.width, canvas.height);
//grab the context from your destination canvas
//if path drawing is enabled, first draw the path canvas to the display canvas
if (showPath) ctx.drawImage(canvas2,0,0);
if(mouseDown == true){
// ctx.clearRect(0, 0, canvas.width, canvas.height); /* !important !!!!!!!!!!!!!!! */
arrow(mousePos['downX'], mousePos['downY'], mousePos['currentX'], mousePos['currentY'], "red");
}
for(var i=0; i<balls.length; i++){
balls[i].draw();
if (i==balls.length-1) {
//draw path
ctx2.fillStyle = '#3f4043';
ctx2.fillRect(balls[i].x, balls[i].y, 5, 5);
ctx2.strokeStyle = "black";
ctx2.strokeRect(balls[i].x, balls[i].y, 5, 5);
}
}
ctx.fillStyle = "#000000";
ctx.font = "15px Arial";
ctx.fillText("Balls: " + balls.length + " " + gravityCount + " " + forceCount + " " + howManyPaths, 10, canvas.height -10);
}
// START THE GAME
function init(){
//$("#setup").hide();
$("#canvas").show();
$("#canvas2").hide();
ctx = $('canvas')[0].getContext("2d");
canvas.height = $(window).height()-6;
canvas.width = $(window).width();
//canvas.width = 960;
//canvas.height = 720;
canvas2.height = $(window).height()-6;
canvas2.width = $(window).width();
return setInterval(gameLoop, 10);
}
$("#play").click(function() {
test = true;
playCSS.style.visibility="hidden";
gravityCount = $("#gravitySlider").val();
forceCount = $("#forceSlider").val();
init();
});
$("#restart").click(function() {
window.location.href="index.html";
});
$("#refresh").click(function() {
gravityCount = $("#gravitySlider").val();
forceCount = $("#forceSlider").val();
});
$("#showPath").click(function() {
showPath=true;
});
$("#hidePath").click(function() {
showPath=false;
});
}
I am using very similar code to create a pie chart using canvas as per this article:
http://wickedlysmart.com/how-to-make-a-pie-chart-with-html5s-canvas/
As you can see from this image, there are cases where the labels are upside down:
Here is the code that writes the labels to the graph:
var drawSegmentLabel = function(canvas, context, i) {
context.save();
var x = Math.floor(canvas.width / 2);
var y = Math.floor(canvas.height / 2);
var degrees = sumTo(data, i);
var angle = degreesToRadians(degrees);
context.translate(x, y);
context.rotate(angle);
context.textAlign = 'right';
var fontSize = Math.floor(canvas.height / 32);
context.font = fontSize + 'pt Helvetica';
var dx = Math.floor(canvas.width * 0.3) - 20;
var dy = Math.floor(canvas.height * 0.05);
context.fillText(labels[i], dx, dy);
context.restore();
};
I am trying to rectify this so the text is always readable and not upside down but cant work out how to do it!
Here's my solution! (A little kludgey but seems to work on the basic example, I haven't tested in on edge cases...)
var drawSegmentLabel = function(canvas, context, i) {
context.save();
var x = Math.floor(canvas.width / 2);
var y = Math.floor(canvas.height / 2);
var angle;
var angleD = sumTo(data, i);
var flip = (angleD < 90 || angleD > 270) ? false : true;
context.translate(x, y);
if (flip) {
angleD = angleD-180;
context.textAlign = "left";
angle = degreesToRadians(angleD);
context.rotate(angle);
context.translate(-(x + (canvas.width * 0.5))+15, -(canvas.height * 0.05)-10);
}
else {
context.textAlign = "right";
angle = degreesToRadians(angleD);
context.rotate(angle);
}
var fontSize = Math.floor(canvas.height / 25);
context.font = fontSize + "pt Helvetica";
var dx = Math.floor(canvas.width * 0.5) - 10;
var dy = Math.floor(canvas.height * 0.05);
context.fillText(labels[i], dx, dy);
context.restore();
};
To display the text in the correct way you have to check if the rotation angle is between 90 and 270 degree. If it is then you know the text will be display upside down.
To switch it correctly you then have to rotate you canvas of planed rotation - 180 degree and then to align it in left not right :
var drawSegmentLabel = function(canvas, context, i) {
context.save();
var x = Math.floor(canvas.width / 2);
var y = Math.floor(canvas.height / 2);
var degrees = sumTo(data, i);
var angle = 0;
if (degree > 90 && degree < 270)
angle = degreesToRadians(degrees - 180);
else
angle = degreesToRadians(degrees);
context.translate(x, y);
context.rotate(angle);
context.textAlign = 'right';
var fontSize = Math.floor(canvas.height / 32);
context.font = fontSize + 'pt Helvetica';
var dx = Math.floor(canvas.width * 0.3) - 20;
if (degree > 90 && degree < 270)
dx = 20;
var dy = Math.floor(canvas.height * 0.05);
context.fillText(labels[i], dx, dy);
context.restore();
};
I am currently playing around with a blob code and have a small problem.
The problem is that sometimes the blob gets inverted so the white color gets inside the blob itself and makes a white hole in it which I don't really want.
Any suggestions on how to fix this, so the blob stays all the time as one little nice piece?
This is the one im playing around with:
http://wonderfl.net/c/rYzh
class Blob extends Sprite
{
private var speed :Number = .01;
private var grav :Number = .25;
private var dist :Number = 27;
private var k :Number = .55;
private var damp :Number = .99;
private var cx :Number = 370;
private var cy :Number = 0;
private var points :Array = [];
private var mids :Array = [];
private var numPoints:Number = 30;
private var oneSlice :Number = Math.PI * 2 / numPoints;
private var radius :Number = 100;
public function Blob()
{
for (var i:Number = 0; i < numPoints; i++)
{
var angle:Number = oneSlice * i;
var obj:Object = {x:Math.cos(angle) * radius + cx, y:Math.sin(angle) * radius + cy, a:angle - Math.PI / 2, wave:i*.08, vx:0, vy:0};
points[i] = obj;
}
this.addEventListener(Event.ENTER_FRAME, update);
}
private function update(event:Event):void
{
this.graphics.clear();
this.graphics.lineStyle(1, 0x666666, 50);
this.graphics.beginFill(0x000000, 100);
for (var i:Number = 0; i < numPoints-1; i++)
{
mids[i] = {x:(points[i].x + points[i + 1].x) / 2, y:(points[i].y + points[i + 1].y) / 2};
}
mids[i] = {x:(points[i].x + points[0].x) / 2, y:(points[i].y + points[0].y) / 2};
this.graphics.moveTo(mids[0].x, mids[0].y);
for (var j:Number = 0; j < numPoints - 1; j++)
{
this.graphics.curveTo(points[j+1].x, points[j+1].y, mids[j+1].x, mids[j+1].y);
}
this.graphics.curveTo(points[0].x, points[0].y, mids[0].x, mids[0].y);
this.graphics.endFill();
var point:Object;
for (var k:Number = 0; k < numPoints - 1; k++)
{
point = points[k];
spring(point, points[k + 1]);
mouseSpring(point);
}
spring(points[k], points[0]);
mouseSpring(points[k]);
for (var l:Number = 0; l < numPoints; l++)
{
point = points[l];
point.vx *= damp;
point.vy *= damp;
point.vy += grav;
point.x += point.vx;
point.y += point.vy;
if (point.y > stage.stageHeight)
{
point.y = stage.stageHeight;
point.vy = 0;
}
if (point.x < 20)
{
point.x = 20;
point.vx = 0;
}
else if (point.x > stage.stageWidth)
{
point.x = stage.stageWidth;
point.vx = 0;
}
}
}
private function spring(p0:Object, p1:Object):void
{
var dx:Number = p0.x - p1.x;
var dy:Number = p0.y - p1.y;
var angle:Number = p0.a+Math.sin(p0.wave += speed)*2;
var tx:Number = p1.x + dist * Math.cos(angle);
var ty:Number = p1.y + dist * Math.sin(angle);
var ax:Number = (tx - p0.x) * k;
var ay:Number = (ty - p0.y) * k;
p0.vx += ax * .5;
p0.vy += ay * .5;
p1.vx -= ax * .5;
p1.vy -= ay * .5;
}
private function mouseSpring(p:Object):void
{
var dx:Number = p.x - stage.mouseX;
var dy:Number = p.y - stage.mouseY;
var dist:Number = Math.sqrt(dx * dx + dy * dy);
if (dist < 40)
{
var angle:Number = Math.atan2(dy, dx);
var tx:Number = stage.mouseX + Math.cos(angle) * 40;
var ty:Number = stage.mouseY + Math.sin(angle) * 40;
p.vx += (tx - p.x) * k;
p.vy += (ty - p.y) * k;
}
}
}
By default, the Graphics APIs use an evenOdd winding, which means if a filled path overlaps itself, it negates the fill.
You need to use the Graphics.drawPath function with a winding value of "nonZero". This will cause it not to negate when the path overlaps itself. Check out this little demo, make a shape that overlaps itself, and switch the winding from evenOdd to nonZero to see how it works.
As for translating your code, instead of using graphics.moveTo() and .curveTo() calls in your update() routine, you'll need to build up a description of your path (aka, the inputs to drawPath) and pass them into graphics.drawPath() last. Adobe shows an example here.