lunes, 14 de junio de 2010

ActionScript 3: Gráfico circular (radial)

Hoy te traigo un jueguito muy simple pero que tiene algunas cosas interesante para analizar como listeners para teclado y mouse, setInterval() y otras yerbas que irán apareciendo en el script.
Hace un tiempo me pidieron desarrollar un gráfico circular pero no era de torta, sino que era un gráfico destinado a mostrar evoluciones (como si fuera un gráfico lineal) pero distribuído en un círculo, pues comparaba que tan distantes estaban de un punto común de inicio diferentes sucursales.
Este tipo de gráfico es standar en excel y otras planillas de cálculo y no tenía idea de como se aplicaría en actionscript 3.

Después de mucho dar vueltas y revisar algo de trigonometría llegué a una formulita (qué resultó ser bastante sencilla) pude realizar el gráfico que resultó como te lo muestro aquí.

Para que este gráfico se genere sólo bastará con completar 3 arrays y una variable:
1) Array con los valores del eje Y (en este ejemplo referencias).
2) Array con los valores del eje X (en este ejemplo los países.
3) Array con los datos a representar (valores del gráfico)
4) Variable que contendrá el simbolo de la unidad en que se representa el gráfico (%, $, MM, etc).
Además podrás manipular el tamaño del gráfico desde otra variable que luego comentaré en el código que explico a continuación:
Vamos a trabajar


1) Preparando el flash

  • En una nueva película de flash necesitamos generar un MovieClip llamado "bolita_mc" que contenga un circulo pequeño (Por ejemplo 20x20px). Este MC debe quedar en la biblioteca y estar vinculado como "bolita_mc".
  • Otro MovieClip llamado "grafico". Este MC estará vacío pero tendrá el código para generar todo el gráfico. Tenés que controlar al crear este MC de que el punto de registro de alineación sea el centro.
2) El código de nuestro grafico circular:

Poné el MC "gráfico" dentro del escenario y en el primer fotograma escribí el actioinscript:


//Importo la clase GCSafeTween, es externa y funciona mejor que los tweens de actionscript
import fl.transitions.Tween;
import fl.transitions.TweenEvent;
import fl.transitions.easing.*;
//arrays que contienen la información del gráfico
var aRef:Array=new Array(0.00,10.00,20.00,30.00,40.00,50.00,60.00,70.00,80.00); //referencias para dibujar en el gráfico
var aRefSymbol:String = " %"; //unidad en la que están representadas las referencias
var aPais:Array=new Array("Chile Móviles", "Colombia Móviles", "Ecuador Móviles", "El Salvador", "Guatemala", "México", "Nicaragua", "Panamá", "Perú Fija", "Perú Móviles", "Uruguay Móviles", "Venezuela", "Argentina Fija", "Argentina Móviles", "Brasil Fija", "Chile Fija"); //primer eje del gráfico
var aDatos:Array=new Array(50.00,"na",50.00,"na",24.95,29.25,74.00,0,67.09,48.21,"na",49.63,62.67,0,0,75.00) //segundo eje del gráfico
//Datos comúnes a todo el gráfico
var radio:int=200; //para un gráfico de 400 x 400 pixels.
var variaGrados:Number = 360/aPais.length; //360 dividido la cantidad datos que se van a graficar
var lineas:Sprite = new Sprite();
var lUnion:Sprite = new Sprite();
//funcion para dibujar el cuadrante del gráfico según las referencias (aRef) y cantidad de cantidad de datos (aPais)
function fCuadrante():void{
//dibujar las líneas de referencia (junto a los valores de referencia con sus textos
var lineasRef:Sprite = new Sprite();
for(var m:uint=0; m<aRef.length;m++){
var posRY:Number=((radio/(aRef.length-1))*m)*-1;
lineasRef.graphics.lineStyle(1,0xf0f0f0);
lineasRef.graphics.moveTo(0,posRY);
lineasRef.graphics.lineTo(-(radio+(radio/3)),posRY);
//las etiquetas de texto que acompañan a cada Referencia.
var formatRef:TextFormat = new TextFormat();
formatRef.size = 14;
formatRef.color = 0xc0c0c0;
formatRef.font = "Verdana";
formatRef.bold = true;
var campoRef:TextField = new TextField();
campoRef.autoSize = TextFieldAutoSize.CENTER;
campoRef.defaultTextFormat = formatRef;
campoRef.text = aRef[m] + aRefSymbol;
campoRef.x=-(radio+(radio/3))-campoRef.width;
campoRef.y=posRY-campoRef.height/2;
lineasRef.addChild(campoRef);
}
lineas.addChild(lineasRef);
//dibujar radios (la línea que ocupará cada país);
var grados:Number=0; //grados en los que se vá a inclinar el siguiente radio
var lineasCuad:Shape = new Shape(); //los "rayos" que representarán a cada pais
for(var i:Number=0;i<aPais.length;i++){ //tantos radios como países haya en el array "aPais"
lineasCuad.graphics.lineStyle(1,0x77BBBB); // creo el gráfico (ancho de la linea, color)
lineasCuad.graphics.moveTo(0,0); //Defino el inicio de la línea (x,y), como todas las lineas serán concentricas voy a 0,0.
var rotarX:Number = radio*Math.cos(Math.PI/180*grados); //formula que saca la posición X según los grados necesarios)
var rotarY:Number = radio*Math.sin(Math.PI/180*grados); //formula que saca la posición Y según los grados necesarios)
lineasCuad.graphics.lineTo(rotarX,rotarY); //defino el final de la línea
//las etiquetas de texto que acompañan a cada rádio del gráfico.
var formato:TextFormat = new TextFormat();
formato.size = 11;
formato.color = 0x003366;
formato.font = "Verdana";
formato.bold = true;
var campo:TextField = new TextField();
campo.autoSize = TextFieldAutoSize.CENTER;
campo.defaultTextFormat = formato;
campo.text = aPais[i];
var rotarTX:Number = (radio+5+(campo.width/2))*Math.cos(Math.PI/180*grados);
var rotarTY:Number = (radio+5+(campo.height/2))*Math.sin(Math.PI/180*grados);
campo.x=rotarTX-(campo.width/2);
campo.y=rotarTY-(campo.height/2);
lineas.addChild(campo);
grados=grados+variaGrados; //aumento los grados que va a rotar la próxima línea
}
//dibujar los circulos que harán la referencia (tantos circulos como referencias haya en aRef)
for(var l:uint=0; l<aRef.length;l++){
var radioB:Number=(radio/(aRef.length-1))*l;
var posIX:Number;
var posIY:Number;
lineasCuad.graphics.lineStyle(1,0x77BBBB);
for(var k:int=0;k<aPais.length+1;k++){
lineasCuad.graphics.moveTo(posIX,posIY);
var posFX:Number = radioB*Math.cos(Math.PI/180*(variaGrados*k));
var posFY:Number = radioB*Math.sin(Math.PI/180*(variaGrados*k));
lineasCuad.graphics.lineTo(posFX,posFY);
posIX = posFX;
posIY = posFY;
}
lineas.addChild(lineasCuad);
}
this.addChildAt(lineas,0);
var myTween:Tween = new Tween(lineas, "alpha", Strong.easeOut,0,1,2,true);
}
//FUNCION PARA GENERAR LAS BOLITAS EN EL ESCENARIO
var timer:Timer = new Timer(25,aPais.length);
timer.addEventListener(TimerEvent.TIMER,fDibujaBolita);
timer.addEventListener(TimerEvent.TIMER_COMPLETE,fUnirLineas);
function fBolita():void{
timer.start()
}
var bGrados:Number = 0;
var aDatosPos:int=0;
function fDibujaBolita(event:TimerEvent):void{
if(aDatos[aDatosPos]!="na"){ //siempre y cuando tenga un valor de referencia asociado distinto de "n/a"
var bolita:MovieClip = new bolita_mc();
var regMayor:Number =aRef[aRef.length-1];
bolita.x = ((aDatos[aDatosPos]*radio)/regMayor)*Math.cos(Math.PI/180*bGrados);
bolita.y = ((aDatos[aDatosPos]*radio)/regMayor)*Math.sin(Math.PI/180*bGrados);
}else{ //cuando no tenga un valor (cuando el valor sea "n/a"
bolita = new bolita_mc(); //creo de todos modos la bolita
regMayor =aRef[aRef.length-1]; //pero la mando al borde extremo del ráfico
bolita.x = radio*Math.cos(Math.PI/180*bGrados);
bolita.y = radio*Math.sin(Math.PI/180*bGrados);
bolita.alpha=0; //y la pongo invisible, porque es distinta de 0
}
addChild(bolita);
bGrados=bGrados+variaGrados;
aDatosPos++;
}
//FUNCION PARA LAS LÍNEAS QUE UNEN LOS PUNTOS
function fUnirLineas(event:TimerEvent):void{
for (var i:int=1;i<this.numChildren;i++){
if(i<this.numChildren-1){
if(this.getChildAt(i).alpha>0 && this.getChildAt(i+1).alpha>0){ //siempre y cuando el valor no sea alpha (quiere decir que tiene un valor asociado).
lUnion.graphics.lineStyle(2,0xFF9900); // creo el gráfico (ancho de la linea, color)
lUnion.graphics.moveTo(this.getChildAt(i).x,this.getChildAt(i).y); //Defino el inicio de la línea (x,y), como todas las lineas serán concentricas voy a 0,0.
lUnion.graphics.lineTo(this.getChildAt(i+1).x,this.getChildAt(i+1).y);
}
}else if(this.getChildAt(i).alpha>0 && this.getChildAt(1).alpha>0){
lUnion.graphics.lineStyle(2,0xFF9900); // creo el gráfico (ancho de la linea, color)
lUnion.graphics.moveTo(this.getChildAt(i).x,this.getChildAt(i).y); //Defino el inicio de la línea (x,y), como todas las lineas serán concentricas voy a 0,0.
lUnion.graphics.lineTo(this.getChildAt(1).x,this.getChildAt(1).y);
}
}
this.addChildAt(lUnion,1);
var myTween:Tween = new Tween(lUnion, "alpha", Strong.easeOut,0,1,5,true);
}
fCuadrante();
fBolita();
Por las dudas te dejo un link de descarga del archivo .fla
DESCARGAR EL .FLA

NOTA: Si la animación no funciona bien es porque el tween de flash trae muchos problemas cuando se ejecuta dentro de un bucle for.

Para solucionar este problama se crearon clases esternas como las GCSafeTween. En este otro post te cuento como utilizar esta clase externa que funciona increiblemente bien.

1 comentario:

Anónimo dijo...

enhorabuena

El portal que comparte sus ingresos