I want to create a user defined grid of rectangles using SVG Raphaeljs. The method I am using at the moment is close to what I want it to do but its clearly not right.
My code as of now :
Creating the rectangles and trying to get them placed in an even grid of equal distance from each other
function startup() {
var paper = Raphael(50, 50, 1500, 875);
for (var i = 0; i <= 7; i++) {
for (var j = 0; j <= 4; j++) {
var offset = i; // Stores the number to remove from the next variable to keep an even distance between shapes
var moveRight = (i + 20 - offset) * i; // new variable stores the amount to move the next rectangle along by adding 20 (distance in pixels
// to move to the right) to the loop counter i and then subtracting the offset which is the variable i
// before the + 20 was added and then multiplying it all by i again.
var moveDown = (j + 35 - offset) * j;
var c = paper.rect(moveRight, moveDown, 15, 20);
c.attr("fill", "#f00");
c.attr("stroke", "#fff");
}
}
}
The above currently produces this wonky grid as a result of my poor coding.
I need this to work in such a way that the user can enter the actual amount of rows and columns just by editing the values I put into the for loops and then using that number to change the distance each shape is placed,
As you can see I tried to do this by making a rough formula but I am now stuck, so any help is appreciated.
Ok, silly on my part. I've noticed a mistake and I've amended my code to the following:
function startup() {
var paper = Raphael(50, 50, 1500, 875);
for (var i = 0; i <= 7; i++) {
for (var j = 0; j <= 4; j++) {
var offseti = i; // Stores the number to remove from the next variable to keep an even distance between shapes
var moveRight = (i + 20 - offseti) * i; // new variable stores the amount to move the next rectangle along by adding 20 (distance in pixels
// to move to the right) to the loop counter i and then subtracting the offset which is the variable i
var offsetj = j; // before the + 20 was added and then multiplying it all by i again.
var moveDown = (j + 25 - offsetj) * j;
var c = paper.rect(moveRight, moveDown, 15, 20);
c.attr("fill", "#f00");
c.attr("stroke", "#fff");
}
}
}
I have a sprite in a movie symbol that I would like to hover back and forth within a 360 radius. I was hoping to make it smooth and random. Never really venturing from its original xy cordinates.
I've tried to create some stipulations with if statements and a starting momentum. Like this:
var num = 2;
stage.addEventListener(Event.ENTER_FRAME, hover);
function hover(evt:Event):void{
//start it moving
cloudWhite.y += num;
cloudWhite.x += num;
//declare these variables
var cX = cloudWhite.x;
var cY = cloudWhite.y;
// object travels 10 pixels
var cXP = cX + 10;
var cXN = cX - 10;
var cYP = cY + 10;
var cYN = cY - 10;
// if object goes 10 pixels reverse direction of momentum (maybe)
if (cX >= cXP) {
num = -2;
}
if (cX <= cXN){
num = 2;
}
if (cY >= cYP) {
num = 2;
}
if (cY <= cYN){
num = 2;
}
Clearly this is super wrong because when it runs the object just either goes to 0,0 or to some place that only the math gods know of.
I am clearly a noob at this kind of math so i apologize but I am very excited to learn the trig behind this.
Thank you for your help and thank you for reading.
You are setting all your variables inside the ENTER_FRAME loop, so none of your conditions ever evaluates to true. On every single frame you are doing this:
cloudWhite.x += 2;
cX = cloudWhite.x;
cXP = cX + 10; // Must == cloudWhite's previous x + 10 + 2;
cXN = cX - 10; // Must == cloudWite's previous x -10 + 2;
if(cX > cXP)... // Can never be true.
if(cX < cXN)... // Can never be true.
What you need to do is:
1) Store the original position of cloudWhite somewhere outside the loop, and store it before the loop begins.
2) Define your bounds relative to the original position of cloudWhite, again before your loop begins. Also define the amount you are going to change the position with each iteration.
3) Start your loop.
4) Increment the current position of cloudWhite on each iteration. Add a little random in here if you want the shape to move in a random manner.
5) Check if the new position of cW is outside your bounds and adjust the direction if it is.
The sample below is crude and jerky but I don't know exactly what effect you're looking for. If you want smoother, longer movements in each direction, consider using the Tween class or a Tween library such as the popular Greensock one, instead of incrementing / decrementing the position manually. There's a useful discussion of this here: http://www.actionscript.org/forums/archive/index.php3/t-163836.html
import flash.display.MovieClip;
import flash.events.Event;
// Set up your variables
var original_x:Number = 100; // Original x
var original_y:Number = 100; // Original y
var x_inc:Number = 5; // X Movement
var y_inc:Number = 5; // Y Movenent
var bounds:Number = 50; // Distance from origin allowed
// Doesn't take into account width of object so is distance to nearest point.
// Create an MC to show the bounds:
var display:MovieClip = addChild(new MovieClip()) as MovieClip;
display.graphics.lineStyle(1, 0x0000FF);
display.graphics.beginFill(0x0000FF, 0.5);
display.graphics.drawRect(0-bounds, 0-bounds, bounds * 2, bounds *2);
display.x = original_x;
display.y = original_y;
addChild(display);
// Create our moving mc:
var mc:MovieClip = addChild(new MovieClip()) as MovieClip;
mc.graphics.beginFill(0xFF0000, 1);
mc.graphics.drawCircle(-10, -10, 20);
// Position it:
mc.x = original_x;
mc.y = original_y;
addChild(mc);
// Loop:
function iterate($e:Event = null):void
{
// Move the mc by a random amount related to x/y inc
mc.x += (Math.random() * (2 * x_inc))/2;
mc.y += (Math.random() * (2 * y_inc))/2;
// If the distance from the origin is greater than bounds:
if((Math.abs(mc.x - original_x)) > bounds)
{
// Reverse the direction of travel:
x_inc == 5 ? x_inc = -5 : x_inc = 5;
}
// Ditto on the y axis:
if((Math.abs(mc.y - original_y)) > bounds)
{
y_inc == 5 ? y_inc = -5 : y_inc = 5;
}
}
// Start the loop:
addEventListener(Event.ENTER_FRAME, iterate);
This should get you started. I'm sure there are any number of other ways to do this with formal trig, but this has the benefit of being very simple, and just an extension of your existing method.
When I'm use:
var shape:Shape = new new Shape();
shape.graphics.lineStyle(2,0);
shape.graphics.lineTo(10,10);
addChild(shape);
I get the black line I want, but I also get grey pixels floating around next to them. Is there a way to turn off whatever smoothing/anti-aliasing is adding the fuzzy pixels?
Yes, it is possible to draw pixel-perfect shapes, even with anti-aliasing on. Pixel-hinting is a must. The other half of the equation is to actually issue the drawing commands with whole-pixel coordinates.
For example, you can draw a pixel-perfectly-symmetrical rounded-rectangle with 4px radius curves with the following code. Pay careful attention to what the code is doing, particularly how the offsets relate to the border thickness.
First, keep in mind that when you're drawing filled shapes, the rasterization occurs up to, but no including the right/lower edges of the outline. So to draw a 4x4 pixel filled square, you can just call drawRect(0,0,4,4). That covers pixels 0,1,2,3,4 (5 pixels), but since it doesn't rasterize the right and lower edges, it ends up being 4 pixels. On the other hand, if you're drawing just the outline (without filling it), then you need to call drawRect(0,0,3,3), which will cover pixels 0,1,2,3, which is 4 pixels. So you actually need slightly different dimensions for the fill vs the outline to get pixel-perfect sizes.
Suppose you wanted to draw a button that's 50px wide, 20px tall, with a 4px radius on its rounded edges, which are 2px thick. In order to ensure that exactly 50x20 pixels are covered, and the outside edge of the 2px thick line buts up against the edge pixels without overflowing, you have to issue the drawing command exactly like this. You must use pixel hinting, and you must offset the rectangle by 1px inward on all sides (not half a pixel, but exactly 1). That places the center of the line exactly between pixels 0 and 1, such that it ends up drawing the 2px wide line through pixels 0 and 1.
Here is an example method that you can use:
public class GraphicsUtils
{
public static function drawFilledRoundRect( g:Graphics, x:Number, y:Number, width:Number, height:Number, ellipseWidth:Number = 0, ellipseHeight:Number = 0, fillcolor:Number = 0xFFFFFF, fillalpha:Number = 1, thickness:Number = 0, color:Number = 0, alpha:Number = 1, pixelHinting:Boolean = false, scaleMode:String = "normal", caps:String = null, joints:String = null, miterLimit:Number = 3 )
{
if (!isNaN( fillcolor))
{
g.beginFill( fillcolor, fillalpha );
g.drawRoundRect( x, y, width, height, ellipseWidth, ellipseHeight );
g.endFill();
}
if (!isNaN(color))
{
g.lineStyle( thickness, color, alpha, pixelHinting, scaleMode, caps, joints, miterLimit );
g.drawRoundRect( x, y, width, height, ellipseWidth, ellipseHeight );
}
}
}
Which you'd want to call like this:
var x:Number = 0;
var y:Number = 0;
var width:Number = 50;
var height:Number = 20;
var pixelHinting:Boolean = true;
var cornerRadius:Number = 4;
var fillColor:Number = 0xffffff; //white
var fillAlpha:Number = 1;
var borderColor:Number = 0x000000; //black
var borderAlpha:Number = 1;
var borderThickness:Number = 2;
GraphicsUtils.drawFilledRoundRect( graphics, x + (borderThickness / 2), y + (borderThickness / 2), width - borderThickness, height - borderThickness, cornerRadius * 2, cornerRadius * 2, fillColor, fillAlpha, borderThickness, borderColor, borderAlpha, pixelHinting );
That will produce a pixel-perfectly-symmetrical 2px thick filled rounded rectangle that covers exactly a 50x20 pixel region.
Its very important to notice that using a borderThickness of zero is somewhat non-sensical, and will result in an rectangle oversized by 1 pixel, because it's still drawing a one-pixel wide line, but it's failing to subtract the width (since its zero), hence you'll get an oversized rectangle.
In summary, use the algorithm above, where you add half the border thickness to the x and y coordinates, and subtract the whole border thickness from the width and height, and always use a minimum thickness of 1. That will always result in a rectangle with a border that occupies and does not overflow a pixel region equivalent to the given width and height.
If you want to see it in action, just copy and paste the following code block into a new AS3 Flash Project on the main timeline and run it, as is, since it includes everything necessary to run:
import flash.display.StageScaleMode;
import flash.display.StageAlign;
import flash.events.Event;
import flash.utils.getTimer;
import flash.display.Sprite;
import flash.display.Graphics;
stage.scaleMode = flash.display.StageScaleMode.NO_SCALE;
stage.align = flash.display.StageAlign.TOP_LEFT;
stage.frameRate = 60;
draw();
function draw():void
{
var x:Number = 10;
var y:Number = 10;
var width:Number = 50;
var height:Number = 20;
var pixelHinting:Boolean = true;
var cornerRadius:Number = 4;
var fillColor:Number = 0xffffff; //white
var fillAlpha:Number = 1;
var borderColor:Number = 0x000000; //black
var borderAlpha:Number = 1;
var borderThickness:Number = 2;
var base:Number = 1.6;
var squares:int = 10;
var rows = 4;
var thicknessSteps:Number = 16;
var thicknessFactor:Number = 4;
var offset:Number;
var maxBlockSize:Number = int(Math.pow( base, squares ));
var globalOffset:Number = maxBlockSize; //leave room on left for animation
var totalSize:Number = powerFactorial( base, squares );
var colors:Array = new Array();
for (i = 1; i <= squares; i++)
colors.push( Math.random() * Math.pow( 2, 24 ) );
for (var j:int = 0; j < thicknessSteps; j++)
{
var cycle:Number = int(j / rows);
var subCycle:Number = j % rows;
offset = cycle * totalSize;
y = subCycle * maxBlockSize;
borderThickness = (j + 1) * thicknessFactor;
for (var i:int = 0; i < squares; i++)
{
cornerRadius = Math.max( 8, borderThickness );
borderColor = colors[i];
x = globalOffset + offset + powerFactorial( base, i ); //int(Math.pow( base, i - 1 ));
width = int(Math.pow( base, i + 1 ));
height = width;
if (borderThickness * 2 > width) //don't draw if border is larger than area
continue;
drawFilledRoundRect( graphics, x + (borderThickness / 2), y + (borderThickness / 2), width - borderThickness, height - borderThickness, cornerRadius * 2, cornerRadius * 2, fillColor, fillAlpha, borderThickness, borderColor, borderAlpha, pixelHinting );
}
}
var start:uint = flash.utils.getTimer();
var duration:uint = 5000;
var sprite:Sprite = new Sprite();
addChild( sprite );
var gs:Graphics = sprite.graphics;
addEventListener( flash.events.Event.ENTER_FRAME,
function ( e:Event ):void
{
var t:uint = (getTimer() - start) % duration;
if (t > (duration / 2))
borderThickness = ((duration-t) / (duration/2)) * thicknessSteps * thicknessFactor;
else
borderThickness = (t / (duration/2)) * thicknessSteps * thicknessFactor;
//borderThickness = int(borderThickness);
cornerRadius = Math.max( 8, borderThickness );
borderColor = colors[squares - 1];
x = 0;
y = 0;
width = int(Math.pow( base, squares ));
height = width;
if (borderThickness * 2 > width) //don't draw if border is larger than area
return;
gs.clear();
drawFilledRoundRect( gs, x + (borderThickness / 2), y + (borderThickness / 2), width - borderThickness, height - borderThickness, cornerRadius * 2, cornerRadius * 2, fillColor, fillAlpha, borderThickness, borderColor, borderAlpha, pixelHinting );
}, false, 0, true );
}
function powerFactorial( base:Number, i:int ):Number
{
var result:Number = 0;
for (var c:int = 0; c < i; c++)
{
result += int(Math.pow( base, c + 1 ));
}
return result;
}
function drawFilledRoundRect( g:Graphics, x:Number, y:Number, width:Number, height:Number, ellipseWidth:Number = 0, ellipseHeight:Number = 0, fillcolor:Number = 0xFFFFFF, fillalpha:Number = 1, thickness:Number = 0, color:Number = 0, alpha:Number = 1, pixelHinting:Boolean = false, scaleMode:String = "normal", caps:String = null, joints:String = null, miterLimit:Number = 3 )
{
if (!isNaN( fillcolor))
{
g.beginFill( fillcolor, fillalpha );
g.drawRoundRect( x, y, width, height, ellipseWidth, ellipseHeight );
g.endFill();
}
if (!isNaN(color))
{
g.lineStyle( thickness, color, alpha, pixelHinting, scaleMode, caps, joints, miterLimit );
g.drawRoundRect( x, y, width, height, ellipseWidth, ellipseHeight );
}
}
Try turning on pixelHinting:
shape.graphics.lineStyle(2, 0, 1, true);
More about pixelHinting here.
You can't turn off antialiasing completely. If you want a sharp, pixelated line then unfortunately you have to draw pixel by pixel, using a Bitmap and setPixel()
I am having a container mc with 5 children mcs.
children names mc0,mc1....mc4.
cont.getChildByName("mc"+Number(cont.numChildren-1)).x =
cont.getChildByName("mc0").x - 20 *1.2;
after this re-position process.. I want to set the last item position as 0 and so on. How can I do this?
My target is to attain a circular movement.
like
[mc0][mc1][mc2]
[mc2][mc0][mc1]
[mc1][mc2][mc0]
[mc0][mc1][mc2]
//Of course, you don't necessarily have to create absolute positions,
//this is a simple example...
var positions:Array = [{x:0,y:0} , {x:20, y:20} etc....];
var children:Array = [mc0 , mc1 ... mcN];
//Provided that positions & children have the same length
private function rotate():void
{
//remove the last element of the Array
var lastChild:MovieClip = children.pop();
//Add it to the beginning of the Array
children.unshift(lastChild );
//Assign new positions
//Here you could tween for smoother effect
for( var i:int ; i < positions.length ; ++i )
{
children[i].x = positions[i].x;
children[i].y = positions[i].y;
}
}
Let's introduce an offset variable simulating the rotation's progression:
var offset:uint = 0;
Now we must define each clip's position depending on this variable. I will introduce a gap constant for the distance between two items.
const GAP:uint = 20;
for (var iMc:int=0; iMc < cont.numChildren; iMc++)
{
mc = cont.getChildByName("mc" + iMc.toString()) as Sprite;
mc.x = GAP * ((iMc + offset) % cont.numChildren);
}
The % operator (modulo) allows you to get a number between 0 and the second operand-1
Last Edit: Resolved!
Well, i was unable to find the ENTIRE answer here but i finally got what i was after. thanks very much for all of your help and patience.
as a side note: i think i may have been having problems with using the int and Number types, upon closer inspection of my solution, i realised that Number was being used and not int. turns out number contains floating points and int doesn't. my numbers were probably rounding whenever i tried to fix this my self. for all i know, TDI's answer might have been spot on and the use of int for the padding might have accumulated rounded numbers.. Oh well, you learn something every day..
the correct code to constrain movie clips to a container movie clip (or sprite or whatever) in the fashion i was looking for is this:
var picContainer:PicContainer = new PicContainer();
picContainer.x = stage.stageWidth / 2 - picContainer.width / 2;
picContainer.y = stage.stageHeight / 2 - picContainer.height / 2;
addChild(picContainer);
var totalPics:int = 17;
var pic:Pic = new Pic(); //make an instance just to get its width
var xRange:Number = picContainer.width - pic.width;
var spacing:Number = xRange / (totalPics - 1);
//A little optimization: only need to calculate this number ONCE:
var yLoc:Number = picContainer.height / 2 - pic.height / 2;
for(var i:int = 0; i < totalPics; i++) {
pic = new Pic();
pic.x = i * spacing;
pic.y = yLoc;
picContainer.addChild(pic);
}
the logic is pretty simple, and i don't know why i couldn't get it my self, because i drew diagrams that say exactly this logic. however, i must not have put the numbers in the right places or i wouldn't have had to ask, would i ;P
BONUS CONTENT!
as an added bonus (if anyone finds this thread looking for answers..)
you could also have the 'pic's fan out from the center point (but they'd still be in order of left to right) by using this code:
var picContainer:PicContainer = new PicContainer();
picContainer.x = stage.stageWidth / 2 - picContainer.width / 2;
picContainer.y = stage.stageHeight / 2 - picContainer.height / 2;
addChild(picContainer);
var totalPics:int = 5;
var pic:Pic = new Pic(); //make an instance just to get its width
var padding:Number = (picContainer.width - (pic.width * totalPics)) / (totalPics + 1);
for(var i:int = 0; i < totalPics; i++) {
pic = new Pic();
pic.x = padding + i * (pic.width + padding);
pic.y = picContainer.height / 2 - pic.height / 2;
picContainer.addChild(pic);
}
Try it out, these make for great thumbnail dock engines!
First Edit: Well, there is some progress thanks to TDI but not a complete solution.
you see, the problem remains that the movie clips do not squash completely into the container, the last one or two are left sticking out.
example:
my revised code looks like this:
var newPicContainer:picContainer = new picContainer();
var newPic:pic;
var picwidth:int = 100;
var amountofpics:int = 22;
var i:int;
//add a container
addChild(newPicContainer);
//center our container
newPicContainer.x = (stage.stageWidth/2)- (newPicContainer.width/2);
newPicContainer.y = (stage.stageHeight/2)- (newPicContainer.height/2);
var totalpicwidth:int = picwidth*amountofpics;
var totalpadding:int = newPicContainer.width - totalpicwidth;
var paddingbetween:int = (totalpadding / amountofpics);
for (i = 0; i < amountofpics; ++i)
{
//make a new mc called newPic(and i's value) eg. newPic1
this['newPic' + i] = new pic();
this['newPic' + i].width = picwidth;
//add our pic to the container
newPicContainer.addChild(this['newPic' + i]);
//set the new pics X
if (i != 0)
{
// if i is not 0, set newpic(i)s x to the previous pic plus the previous pics width and add our dynamic padding
this['newPic' + i].x = this['newPic' + (i-1)].x + picwidth + paddingbetween;
}
else
{
this['newPic' + i].x = 0;
}
}
thanks again to anyone in advance!
Original Post:
Hello, First time posting here. I hope I'm not getting anything wrong . my problem is as follows:
I've got a pretty basic for loop that creates a 'thumbnail' and puts it next to the previous one (With a little padding) inside a containing movie clip.
var newPicContainer:picContainer = new picContainer();
var newPic:pic;
var amount:int = 9;
var i:int;
//Add our container
addChild(newPicContainer);
//center our container
newPicContainer.x = (stage.stageWidth/2)- (newPicContainer.width/2);
newPicContainer.y = (stage.stageHeight/2)- (newPicContainer.height/2);
for (i = 0; i < amount; ++i)
{
newPic = new pic();
newPicContainer.addChild(newPic);
//just so i know it's adding them..
trace(newPic.thisOne);
newPic.thisOne = i;
// set this one to its self (+5 for padding..) Times, the amount already placed.
newPic.x = (newPic.width+5) *i;
}
I'm wondering if there is some equation or 'magic math' that I can use to figure out what the length of the container is and have the 'thumbnails' be added relative to that number. basically squashing the thumbnails against each other to make them all fit inside..
something along the lines of:
newPic.x = (newPic.width *i) - stuff here to make it know not to go past the containing width;
I must admit i'm not too great with math and so this part of coding escapes me..
thanks to any takers in advance..
you can get the length of your container by either calling its width property explicitly:
//Container Width
newPicContainer.width;
or the newContainer is also the parent of the added pics:
//Container Width
newPic.parent.width;
then you need to get the total length occupied by you pics:
var arrayOfPics:array = [pic1, pic2, pic3, pic4, pic5];
var picsWidth:Number;
for each (var element:pic in arrayOfPics)
picsWidth =+ element.width;
after than you can subtract the length of the total pics from the container to know your available padding for separation:
var totalPadding:Number = newPicContainer.width - picsWidth;
now you can determine how much padding you can afford between the pics and both sides of the container by dividing the totalPadding by the number of pics, and add an extra padding for the end.
var padding:Number = totalPadding / arrayOfPics.length + 1;
now you can simply add your pics by including the padding
for (var i:int = 0; i < arrayOfPics.length; i++)
{
newPicContainer.addChild(arrayOfPics[i]);
(i == 0) ? arrayOfPics[i].x = padding : arrayOfPics[i].x = arrayOfPics[i - 1].x + arrayOfPics[i - 1].width + padding;
}
Try this...
//maximum available length
var maxLength:int;
// a single thumbnail width
var picWidth:int = 100;
// total number of pics in a container
var maxNumPics:int;
// as long as the maximum available length
// is inferior to the container length
// add a new thumbnail
while( maxLength < newPicContainer.length - 100 )
{
maxLength += 100;
maxNumPics += 1;
}
// calculate the amount of available padding.
var padding:Number = ( newPicContainer.length - maxLength )/ maxNumPics;
//add your thumbs
var picX:int;
for( var i:int ; i < maxNumPics ; ++i )
{
var newPic:Pic = new Pic();
newPic.x = picX;
picX += padding + picWidth;
newPicContainer.addChild( newPic );
}
I'd recommend you look at using the Flex framework (it's a Flash framework), it will make building this sort of view much easier.
You can set a container's layout property, so that items are placed in horizontal, vertical or tiled layouts, and then just add items to the container.
For more info on Flex look here
For info on Flex Layouts