25 de agosto de 2006

Gráficas con objetos 100% VFP

En estos últimos días he estado atento a los artículos de Cesar Ch. (San Pablo, Brasil), referidos a los gráficos de barras y de torta generados con código VFP puro. Estos artículos publicados en su Blog y traducidos por Ana María Bisbé York para PortalFox son los siguientes:
y
La técnica utilizada para crear estas gráficas, es mediante el uso de objetos Labels y Lines respectivamente.

El objetivo de este artículo es tener otra opción (siempre VFP nos da esta posibilidad) de generar gráficas con solo el uso del objeto Shape, sin depender del uso de ninguna otra herramienta de terceras partes.

La idea

Con el uso del objeto Shape es fácil crear barras rectangulares configurando las propiedades Width (ancho) y Height (alto) con las dimensiones correspondientes, según la cantidad de barras del gráfico y el valor a graficar. El tema aquí es como graficar las distintas porciones de una torta (sección circular) o las secciones de un anillo con este objeto.

A partir de Visual FoxPro 9.0, disponemos de la nueva propiedad PolyPoints en el objeto Shape (también en el objeto Line), que nos permiten graficar formas poligonales (ver el artículo Dibujando polígonos con VFP 9.0). Con esta técnica, graficaremos cada porción como un polígono con sus respectivas dimensiones, e iremos añadiendo las restantes porciones hasta completar la gráfica como se observa en las figuras siguientes.



Nota: Las opciones del tipo de gráfica torta, anillo y cono, solo se podrá ejecutar en la versión 9.0 de Visual FoxPro, por el uso de la propiedad PolyPoints de los objetos Shapes.

La clase

Como mencioné al principio de estas líneas, mi objetivo es generar gráficas con código VFP puro, para ello he creado una clase a partir de un objeto Container, al que se irán agregando en tiempo de ejecución los objetos Shape para formar la gráfica y también se agregaran las correspondientes leyendas. Su uso es muy fácil, y solo de deben configurar 5 propiedades y ejecutar el método GenerarGrafica() como se expone en las siguientes líneas de código:
WITH THISFORM.lmGraph
  .TipoGrafica = 1 && Anillo
  .TipoLeyenda = 3 && Rótulos
  .TipoColor = 0 && Aleatorios
  .TituloGrafica = "Consumos por mes"
  .Alias = "MiTabla"
  .GenerarGrafica()
ENDWITH
Las propiedades de la clase y sus valores son los siguientes:
  • TipoGrafica: 0=Torta, 1=Anillo, 2=Barras verticales, 3=Barras horizontales, 4=Conos verticales, y 5=Conos horizontales
  • TipoLeyenda: 0=Sin leyendas, 1=Valores, 2=Porcentajes, 3=Rótulos, 4=Rótulos y valores, y 5=Porcentajes y rótulos
  • TipoColor: 0=Aleatorios y 1=Colores básicos (28 colores definidos)
  • TituloGrafica: Cadena con el título superior de la gráfica
  • Alias: Alias de la tabla o cursor que contiene los rótulos y valores a graficar
Sobre la tabla o cursor que contiene los datos a graficar, ésta debe estar abierta al momento de ejecutar el método GenerarGrafica(), y debe tener al menos dos campos. El contenido y la estructura de los dos primeros campos debe ser:
  • 1° Campo: Este campo contiene los valores y debe ser Numérico.
  • 2° Campo: Este campo contiene los rótulos y puede ser de tipo Caracter, Date, DateTime o Numérico.

Los ejemplos

Ejecutando el formulario de ejemplo con la clase incluida, podemos lograr gráficas simples y agradables como lo muestran las figuras siguientes:

Gráfica de torta, con leyenda de porcentajes y rótulos, y colores aleatorios


Gráfica de anillo, con leyenda de rótulos y valores, y colores básicos


Gráfica de barras verticales, con leyenda de rótulos, y colores aleatorios


Gráfica de barras horizontales, sin leyenda, y colores básicos


Gráfica de conos verticales, con leyenda de porcentajes, y colores aleatorios


Gráfica de conos verticales, con leyenda de valores, y colores aleatorios


El formulario del ejemplo tiene una casilla de verificación (DEMO) que marcada, generará aleatoria y automáticamente cada 2 segundos, las distintas combinaciones posibles para cada una de las propiedades de la clase.

El código

El siguiente es el código del formulario de ejemplo y la clase para generar las gráficas:
PUBLIC goForm
goForm = CREATEOBJECT("frmEjemplo")
goForm.SHOW
RETURN
*--
*-- Definición del formulario de ejemplo
*--
DEFINE CLASS frmejemplo AS FORM
  HEIGHT = 431
  WIDTH = 496
  SHOWWINDOW = 2
  AUTOCENTER = .T.
  CAPTION = "Gráficas con VFP9"
  ICON = (HOME(4) + "icons\office\graph11.ico")
  NAME = "frmEjemplo"
  ADD OBJECT tmr AS TIMER WITH ;
    ENABLED = .F., INTERVAL = 2000, NAME = "tmr"
  ADD OBJECT opgColores AS OPTIONGROUP WITH ;
    BUTTONCOUNT = 2, ANCHOR = 6, VALUE = 1, ;
    HEIGHT = 48, LEFT = 344, TOP = 280, WIDTH = 136, ;
    TABINDEX = 4, NAME = "opgColores", ;
    Option1.BACKSTYLE = 0, Option1.CAPTION = "Colores aleatorios", ;
    Option1.VALUE = 1, Option1.HEIGHT = 17, Option1.LEFT = 8, Option1.TOP = 8, ;
    Option1.WIDTH = 120, Option1.AUTOSIZE = .T., Option1.NAME = "Option1", ;
    Option2.BACKSTYLE = 0, Option2.CAPTION = "Colores básicos", ;
    Option2.VALUE = 0, Option2.HEIGHT = 17, Option2.LEFT = 8, Option2.TOP = 24, ;
    Option2.WIDTH = 109, Option2.AUTOSIZE = .T., Option2.NAME = "Option2"
  ADD OBJECT cmdGenerar AS COMMANDBUTTON WITH ;
    TOP = 392, LEFT = 376, HEIGHT = 32, WIDTH = 104, ;
    ANCHOR = 6, WORDWRAP = .T., CAPTION = "Generar gráfica", ;
    TABINDEX = 9, NAME = "cmdGenerar"
  ADD OBJECT opgGraficas AS OPTIONGROUP WITH ;
    BUTTONCOUNT = 6, ANCHOR = 6, VALUE = 1, ;
    HEIGHT = 112, LEFT = 16, TOP = 280, WIDTH = 144, ;
    TABINDEX = 2, NAME = "opgGraficas", ;
    Option1.BACKSTYLE = 0, Option1.CAPTION = "Torta", ;
    Option1.VALUE = 1, Option1.HEIGHT = 17, Option1.LEFT = 8, Option1.TOP = 8, ;
    Option1.WIDTH = 46, Option1.AUTOSIZE = .T., Option1.NAME = "Option1", ;
    Option2.BACKSTYLE = 0, Option2.CAPTION = "Anillo", ;
    Option2.VALUE = 0, Option2.HEIGHT = 17, Option2.LEFT = 8, Option2.TOP = 24, ;
    Option2.WIDTH = 48, Option2.AUTOSIZE = .T., Option2.NAME = "Option2", ;
    Option3.BACKSTYLE = 0, Option3.CAPTION = "Barras verticales", ;
    Option3.HEIGHT = 17, Option3.LEFT = 8, Option3.TOP = 40, Option3.WIDTH = 110, ;
    Option3.AUTOSIZE = .T., Option3.NAME = "Option3", ;
    Option4.BACKSTYLE = 0, Option4.CAPTION = "Barras horizontales", ;
    Option4.HEIGHT = 17, Option4.LEFT = 8, Option4.TOP = 56, Option4.WIDTH = 125, ;
    Option4.AUTOSIZE = .T., Option4.NAME = "Option4", ;
    Option5.BACKSTYLE = 0, Option5.CAPTION = "Conos verticales", ;
    Option5.HEIGHT = 17, Option5.LEFT = 8, Option5.TOP = 72, Option5.WIDTH = 110, ;
    Option5.AUTOSIZE = .T., Option5.NAME = "Option5", ;
    Option6.BACKSTYLE = 0, Option6.CAPTION = "Conos horizontales", ;
    Option6.HEIGHT = 17, Option6.LEFT = 8, Option6.TOP = 88, Option6.WIDTH = 125, ;
    Option6.AUTOSIZE = .T., Option6.NAME = "Option6"
  ADD OBJECT opgLeyendas AS OPTIONGROUP WITH ;
    BUTTONCOUNT = 6, ANCHOR = 6, VALUE = 6, ;
    HEIGHT = 112, LEFT = 176, TOP = 280, WIDTH = 152, ;
    TABINDEX = 3, NAME = "opgLeyendas", ;
    Option1.BACKSTYLE = 0, Option1.CAPTION = "Sin leyendas", ;
    Option1.HEIGHT = 17, Option1.LEFT = 8, Option1.TOP = 8, Option1.WIDTH = 89, ;
    Option1.AUTOSIZE = .T., Option1.NAME = "Option1", ;
    Option2.BACKSTYLE = 0, Option2.CAPTION = "Valores", ;
    Option2.HEIGHT = 17, Option2.LEFT = 8, Option2.TOP = 24, Option2.WIDTH = 60, ;
    Option2.AUTOSIZE = .T., Option2.NAME = "Option2", ;
    Option3.BACKSTYLE = 0, Option3.CAPTION = "Porcentajes", ;
    Option3.HEIGHT = 17, Option3.LEFT = 8, Option3.TOP = 40, Option3.WIDTH = 84, ;
    Option3.AUTOSIZE = .T., Option3.NAME = "Option3", ;
    Option4.BACKSTYLE = 0, Option4.CAPTION = "Rótulos", ;
    Option4.HEIGHT = 17, Option4.LEFT = 8, Option4.TOP = 56, Option4.WIDTH = 61, ;
    Option4.AUTOSIZE = .T., Option4.NAME = "Option4", ;
    Option5.BACKSTYLE = 0, Option5.CAPTION = "Rótulos y valores", ;
    Option5.VALUE = 0, Option5.HEIGHT = 17, Option5.LEFT = 8, Option5.TOP = 72, ;
    Option5.WIDTH = 112, Option5.AUTOSIZE = .T., Option5.NAME = "Option5", ;
    Option6.BACKSTYLE = 0, Option6.CAPTION = "Porcentajes y rótulos", ;
    Option6.VALUE = 1, Option6.HEIGHT = 17, Option6.LEFT = 8, Option6.TOP = 88, ;
    Option6.WIDTH = 133, Option6.AUTOSIZE = .T., Option6.NAME = "Option6"
  ADD OBJECT lmGraph AS lmgraph WITH ;
    ANCHOR = 15, TOP = 8, LEFT = 8, WIDTH = 480, HEIGHT = 264, ;
    TABINDEX = 1, NAME = "lmGraph", lbl.NAME = "lbl"
  ADD OBJECT chk AS CHECKBOX WITH ;
    TOP = 404, LEFT = 304, HEIGHT = 17, WIDTH = 53, ANCHOR = 6, ;
    WORDWRAP = .T., AUTOSIZE = .T., ALIGNMENT = 0, BACKSTYLE = 0, ;
    CAPTION = "DEMO", VALUE = .F., TABINDEX = 8, NAME = "chk"
  ADD OBJECT opgDatos AS OPTIONGROUP WITH ;
    BUTTONCOUNT = 2, ANCHOR = 6, VALUE = 1, HEIGHT = 48, ;
    LEFT = 344, TOP = 336, WIDTH = 136, TABINDEX = 5, NAME = "opgDatos", ;
    Option1.BACKSTYLE = 0, Option1.CAPTION = "Ejemplo semanal", ;
    Option1.VALUE = 1, Option1.HEIGHT = 17, Option1.LEFT = 8, Option1.TOP = 8, ;
    Option1.WIDTH = 116, Option1.AUTOSIZE = .T., Option1.NAME = "Option1", ;
    Option2.BACKSTYLE = 0, Option2.CAPTION = "Ejemplo anual", ;
    Option2.VALUE = 0, Option2.HEIGHT = 17, Option2.LEFT = 8, Option2.TOP = 24, ;
    Option2.WIDTH = 98, Option2.AUTOSIZE = .T., Option2.NAME = "Option2"
  ADD OBJECT lblTitulo AS LABEL WITH ;
    AUTOSIZE = .T., ANCHOR = 6, BACKSTYLE = 0, CAPTION = "Título", ;
    HEIGHT = 17, LEFT = 16, TOP = 404, WIDTH = 32, TABINDEX = 6, NAME = "lblTitulo"
  ADD OBJECT txtTitulo AS TEXTBOX WITH ;
    ANCHOR = 6, VALUE = "TOTAL DE VENTAS", HEIGHT = 23, LEFT = 56, ;
    TABINDEX = 7, TOP = 400, WIDTH = 232, NAME = "txtTitulo"
  PROCEDURE GenerarCursor
    LPARAMETERS tnDatos
    CREATE CURSOR MiCursor (Valor N(10,2), Rotulo C(20))
    IF tnDatos = 1
      INSERT INTO MiCursor VALUES (RAND() * 1000 + 250, "Lunes")
      INSERT INTO MiCursor VALUES (RAND() * 1000 + 250, "Martes")
      INSERT INTO MiCursor VALUES (RAND() * 1000 + 250, "Miércoles")
      INSERT INTO MiCursor VALUES (RAND() * 1000 + 250, "Jueves")
      INSERT INTO MiCursor VALUES (RAND() * 1000 + 250, "Viernes")
      INSERT INTO MiCursor VALUES (RAND() * 1000 + 250, "Sábado")
      INSERT INTO MiCursor VALUES (RAND() * 1000 + 250, "Domingo")
    ELSE
      INSERT INTO MiCursor VALUES (RAND() * 1000 + 250, "Enero")
      INSERT INTO MiCursor VALUES (RAND() * 1000 + 250, "Febrero")
      INSERT INTO MiCursor VALUES (RAND() * 1000 + 250, "Marzo")
      INSERT INTO MiCursor VALUES (RAND() * 1000 + 250, "Abril")
      INSERT INTO MiCursor VALUES (RAND() * 1000 + 250, "Mayo")
      INSERT INTO MiCursor VALUES (RAND() * 1000 + 250, "Junio")
      INSERT INTO MiCursor VALUES (RAND() * 1000 + 250, "Julio")
      INSERT INTO MiCursor VALUES (RAND() * 1000 + 250, "Agosto")
      INSERT INTO MiCursor VALUES (RAND() * 1000 + 250, "Setiembre")
      INSERT INTO MiCursor VALUES (RAND() * 1000 + 250, "Octubre")
      INSERT INTO MiCursor VALUES (RAND() * 1000 + 250, "Noviembre")
      INSERT INTO MiCursor VALUES (RAND() * 1000 + 250, "Diciembre")
    ENDIF
  ENDPROC
  PROCEDURE tmr.TIMER
    THISFORM.opgGraficas.VALUE = CEILING(RAND()*6)
    THISFORM.opgLeyendas.VALUE = CEILING(RAND()*6)
    THISFORM.opgColores.VALUE = CEILING(RAND()*2)
    THISFORM.opgDatos.VALUE = CEILING(RAND()*2)
    THISFORM.txtTitulo.VALUE = IIF(THISFORM.opgDatos.VALUE = 1, ;
      "Total de ventas por día", "Total de ventas por mes")
    THISFORM.cmdGenerar.CLICK
  ENDPROC
  PROCEDURE cmdGenerar.CLICK
    WITH THISFORM.lmGraph
      .TipoGrafica = THISFORM.opgGraficas.VALUE - 1
      .TipoLeyenda = THISFORM.opgLeyendas.VALUE - 1
      .TipoColor = THISFORM.opgColores.VALUE - 1
      .ALIAS = "MiCursor"
      .TituloGrafica = ALLTRIM(THISFORM.txtTitulo.VALUE)
      THISFORM.GenerarCursor(THISFORM.opgDatos.VALUE)
      .GenerarGrafica()
    ENDWITH
  ENDPROC
  PROCEDURE chk.VALID
    IF THIS.VALUE
      THISFORM.tmr.TIMER
    ENDIF
    THISFORM.SETALL("Enabled", NOT THIS.VALUE, "CommandButton")
    THISFORM.SETALL("Enabled", NOT THIS.VALUE, "OptionGroup")
    THISFORM.SETALL("Enabled", NOT THIS.VALUE, "TextBox")
    THISFORM.tmr.ENABLED = THIS.VALUE
  ENDPROC
ENDDEFINE
*--
*-- Definición de la clase lmGraph
*--
DEFINE CLASS lmgraph AS CONTAINER
  WIDTH = 200
  HEIGHT = 100
  SPECIALEFFECT = 1
  BACKCOLOR = RGB(255,255,255)
  ALIAS = ""
  TipoLeyenda = 5
  TipoColor = 0
  TipoGrafica = 0
  TituloGrafica = "Título"
  NAME = "lmgraph"
  ADD OBJECT lbl AS LABEL WITH ;
    AUTOSIZE = .T., FONTBOLD = .T., BACKSTYLE = 0, ;
    CAPTION = "lmGraph v.1.0", HEIGHT = 17, LEFT = 8, ;
    TOP = 8, VISIBLE = .F., WIDTH = 79, NAME = "lbl"
  *-- Genera la Gráfica
  PROCEDURE GenerarGrafica
    LOCAL lcCampoRotulo, lcCampo, lnSaltoH, lnSaltoV, lnReg, ;
      lnTotal, lnCantReg, lnMaximo, lnMaxWidth, lcRotulo, lnValor, lnPorc, ;
      lcObjPor, lcObjShp, lcObjLey, lnDim, lnHasta, ;
      lnI, lnJ, lnAng, lnCos, lnSen, lcObj1, lcObj2
    *--
    *-- Limpio los objetos del gráfico
    *--
    THIS.LimpiarGrafica()
    *---
    *--- Verifico la versión de VFP y tipo de gráfica
    *---
    IF VERSION(5) < 900 AND INLIST(THIS.TipoGrafica, 0, 1, 4, 5)
      MESSAGEBOX("El tipo de gráfica seleccionada no esta disponible para" + ;
        CHR(13) + VERSION(), 48, "lmGraph")
      RETURN
    ENDIF
    *--
    *-- Tabla de datos
    *--
    IF EMPTY(THIS.ALIAS)
      MESSAGEBOX("No especificó la propiedad Alias.", 48, "lmGraph")
      RETURN
    ENDIF
    IF NOT USED(THIS.ALIAS)
      MESSAGEBOX("La tabla " + PROPER(THIS.ALIAS) + ;
        " no está en uso.", 48, "lmGraph")
      RETURN
    ENDIF
    IF AFIELDS(la,THIS.ALIAS) < 2
      MESSAGEBOX("La tabla " + PROPER(THIS.ALIAS) + ;
        " tiene menos de dos campos.", 48, "lmGraph")
      RETURN
    ENDIF
    IF NOT INLIST(la(1,2), "N", "I")
      MESSAGEBOX("El segundo campo de la tabla " + PROPER(THIS.ALIAS) + ;
        " no es numérico.", 48, "lmGraph")
      RETURN
    ENDIF
    SELECT (THIS.ALIAS)
    lcCampoValor = la(1,1)
    lcCampoRotulo = la(2,1)
    CALCULATE COUNT() TO lnCantReg
    IF lnCantReg = 0
      MESSAGEBOX("La tabla " + PROPER(THIS.ALIAS) + ;
        " no contiene datos.", 48, "lmGraph")
      RETURN
    ENDIF
    CALCULATE SUM(EVALUATE(lcCampoValor)) TO lnTotal
    CALCULATE MAX(EVALUATE(lcCampoValor)) TO lnMaximo
    *--
    *-- Variables y área del gráfico
    *--
    #DEFINE AnguloPrimerSector 270
    #DEFINE AngulosParaGraficar 360
    lnAnguloSector = AnguloPrimerSector
    lnLeft = 10
    lnTop = IIF(EMPTY(THIS.TituloGrafica),10,30)
    lnWidth = THIS.WIDTH - lnLeft * 2
    lnHeight = THIS.HEIGHT - lnTop - lnLeft
    lnSaltoH = FLOOR(lnHeight / lnCantReg)
    *--
    *-- Titulo del gráfico
    *--
    IF NOT EMPTY(THIS.TituloGrafica) && Con título
      THIS.ADDOBJECT("lblTitulo","Label")
      WITH THIS.lblTitulo
        .BACKSTYLE = 0
        .ALIGNMENT = 2
        .FONTSIZE = 12
        .FONTBOLD = .T.
        .CAPTION = THIS.TituloGrafica
        .TOP = 5
        .LEFT = lnLeft
        .WIDTH = lnWidth
        .HEIGHT = 30
      ENDWITH
    ENDIF
    *--
    *-- Armo leyenda y tomo el ancho
    *--
    IF THIS.TipoLeyenda # 0 && Con leyenda
      lnMaxWidth = 0
      lnReg = 1
      SCAN ALL
        lcRotulo = ALLTRIM(TRANSFORM(EVALUATE(lcCampoRotulo)))
        lnValor = EVALUATE(lcCampoValor)
        lnPorc = lnValor / lnTotal * 100
        lcObjLey = "oLey" + TRANSFORM(lnReg)
        THIS.ADDOBJECT(lcObjLey,"Label")
        WITH THIS.&lcObjLey
          .TOP = lnSaltoH * lnReg - lnSaltoH + lnTop
          DO CASE
            CASE THIS.TipoLeyenda = 1
              .CAPTION = TRANSFORM(lnValor)
            CASE THIS.TipoLeyenda = 2
              .CAPTION = TRANSFORM(ROUND(lnPorc,2)) + "%"
            CASE THIS.TipoLeyenda = 3
              .CAPTION = lcRotulo
            CASE THIS.TipoLeyenda = 4
              .CAPTION = lcRotulo + " - " + TRANSFORM(lnValor)
            OTHERWISE
              .CAPTION = TRANSFORM(ROUND(lnPorc,2)) + "% - " + lcRotulo
          ENDCASE
          .FONTSIZE = 8
          .BACKSTYLE = 0
          .LEFT = lnWidth + 100
          .AUTOSIZE = .T.
          .VISIBLE = .T.
          lnMaxWidth = MAX(lnMaxWidth,.WIDTH)
        ENDWITH
        lnReg = lnReg + 1
      ENDSCAN
      lnLeftLeyenda = MAX(lnWidth * .60, lnWidth - lnMaxWidth - 40)
    ENDIF
    *--
    *-- Armo el resto del gráfico
    *--
    lnReg = 1
    SCAN ALL
      lnValor = EVALUATE(lcCampoValor)
      lnPorc = lnValor / lnTotal * 100
      *--
      *-- Armo cada porcion
      *--
      lcObjPor = "oPor" + TRANSFORM(lnReg)
      THIS.ADDOBJECT(lcObjPor,"Shape")
      WITH THIS.&lcObjPor
        DO CASE
          CASE THIS.TipoGrafica = 0 OR THIS.TipoGrafica = 1 && Torta/Anillo
            IF THIS.TipoLeyenda = 0  && Sin leyenda
              STORE MIN(lnWidth ,lnHeight) TO .WIDTH, .HEIGHT
              .TOP = FLOOR((lnHeight - .HEIGHT) / 2 + lnTop)
              .LEFT = FLOOR((lnWidth - .WIDTH) / 2 + lnLeft)
            ELSE
              STORE MIN(lnLeftLeyenda, lnHeight) TO .WIDTH, .HEIGHT
              .TOP = FLOOR((lnHeight - .HEIGHT) / 2 + lnTop)
              .LEFT = FLOOR((lnLeftLeyenda - .WIDTH) / 2 + lnLeft)
            ENDIF
            .POLYPOINTS = "This.aPoly"
            lnDim = AngulosParaGraficar * lnPorc / 100
            lnHasta = CEILING(lnDim) + 1
            .ADDPROPERTY("aPoly[" + TRANSFORM(lnHasta) + ",2]")
            STORE 50 TO .aPoly[1,1], .aPoly[1,2]
            FOR lnI = 2 TO lnHasta
              lnAng = (360 / AngulosParaGraficar) * (lnI - 2)
              lnCos = COS(DTOR(lnAng + lnAnguloSector))
              lnSen = SIN(DTOR(lnAng + lnAnguloSector))
              .aPoly(lnI,1) = 50 * lnCos + 50
              .aPoly(lnI,2) = 50 * lnSen + 50
            ENDFOR
            lnAnguloSector = lnAnguloSector + lnDim * 360 / AngulosParaGraficar
          CASE THIS.TipoGrafica = 2 OR THIS.TipoGrafica = 4 && Barras/Conos Verticales
            IF THIS.TipoLeyenda = 0 && Sin leyenda
              lnSaltoV = FLOOR(lnWidth / lnCantReg)
            ELSE
              lnSaltoV = FLOOR(lnLeftLeyenda / lnCantReg)
            ENDIF
            .WIDTH = lnSaltoV + 1
            .LEFT = lnSaltoV * lnReg - lnSaltoV + lnLeft
            .HEIGHT = lnValor / lnMaximo * lnHeight
            .TOP = lnHeight - .HEIGHT + lnTop
            IF THIS.TipoGrafica = 4 && Conos
              .POLYPOINTS = "This.aPoly"
              .ADDPROPERTY("aPoly[" + TRANSFORM(4) + ",2]")
              STORE 0 TO .aPoly[1,1], .aPoly[2,2], .aPoly[3,2]
              STORE 100 TO .aPoly[1,2], .aPoly[4,1], .aPoly[4,2]
              .aPoly[2,1] = 30
              .aPoly[3,1] = 70
            ENDIF
          CASE THIS.TipoGrafica = 3 OR THIS.TipoGrafica = 5 && Barras/Conos Horizontales
            IF THIS.TipoLeyenda = 0 && Sin leyenda
              .WIDTH = lnValor / lnMaximo * lnWidth
            ELSE
              .WIDTH = lnValor / lnMaximo * lnLeftLeyenda
            ENDIF
            .LEFT = lnLeft
            .HEIGHT = lnSaltoH + 1
            .TOP = lnSaltoH * lnReg - lnSaltoH + lnTop
            IF THIS.TipoGrafica = 5 && Conos
              .POLYPOINTS = "This.aPoly"
              .ADDPROPERTY("aPoly[" + TRANSFORM(4) + ",2]")
              STORE 0 TO .aPoly[1,1], .aPoly[2,1], .aPoly[2,2]
              STORE 100 TO .aPoly[1,2], .aPoly[3,1], .aPoly[4,1]
              .aPoly[3,2] = 25
              .aPoly[4,2] = 75
            ENDIF
          OTHERWISE
            MESSAGEBOX("Tipo de gráfica no definida.", 48, "lmGraph")
            RETURN
        ENDCASE
        *--
        *-- Color de la porción
        *--
        IF THIS.TipoColor = 0
          .BACKCOLOR = FLOOR(RAND() * 16777216) && Aleatorio
        ELSE
          .BACKCOLOR = THIS.ColoresBasicos(lnReg)
        ENDIF
      ENDWITH
      *--
      *-- Armo leyendas
      *--
      IF THIS.TipoLeyenda # 0 && Con leyenda
        lcObjShp = "oShp" + TRANSFORM(lnReg)
        THIS.ADDOBJECT(lcObjShp,"Shape")
        WITH THIS.&lcObjShp
          .HEIGHT = 12
          .WIDTH = 12
          .BACKCOLOR = EVALUATE("THIS.oPor" + TRANSFORM(lnReg) + ".BACKCOLOR")
          .TOP = lnSaltoH * lnReg - lnSaltoH + lnTop
          .LEFT = lnLeftLeyenda + lnLeft  + 10
        ENDWITH
        lcObjLey = "oLey" + TRANSFORM(lnReg)
        WITH THIS.&lcObjLey
          .LEFT = lnLeftLeyenda + lnLeft + 30
        ENDWITH
      ENDIF
      lnReg = lnReg + 1
    ENDSCAN
    *--
    *-- Anillo
    *--
    IF THIS.TipoGrafica = 1 && Anillo
      THIS.ADDOBJECT("oShpMed","Shape")
      WITH THIS.oShpMed
        IF THIS.TipoLeyenda = 0  && Sin leyenda
          STORE MIN(lnWidth ,lnHeight) * .45 TO .WIDTH, .HEIGHT
          .TOP = FLOOR((lnHeight - .HEIGHT) / 2 + lnTop)
          .LEFT = FLOOR((lnWidth - .WIDTH) / 2 + lnLeft)
        ELSE
          STORE MIN(lnLeftLeyenda, lnHeight) * .45 TO .WIDTH, .HEIGHT
          .TOP = FLOOR((lnHeight - .HEIGHT) / 2 + lnTop)
          .LEFT = FLOOR((lnLeftLeyenda - .WIDTH) / 2 + lnLeft)
        ENDIF
        .BACKCOLOR = THIS.BACKCOLOR
        .CURVATURE = 99
      ENDWITH
    ENDIF
    *--
    *-- Uno porciones en Torta/Anillo
    *--
    IF THIS.TipoGrafica = 0 OR THIS.TipoGrafica = 1 && Torta/Anillo
      FOR lnI = 1 TO lnCantReg - 1
        lcObj1 = "This.oPor" + TRANSFORM(lnI)
        lcObj2 = "This.oPor" + TRANSFORM(lnI+1)
        lnJ = ALEN(&lcObj1..aPoly,1)
        &lcObj1..aPoly(lnJ,1) = &lcObj2..aPoly(2,1)
        &lcObj1..aPoly(lnJ,2) = &lcObj2..aPoly(2,2)
      ENDFOR
      lcObj1 = "This.oPor" + TRANSFORM(1)
      lnJ = ALEN(&lcObj2..aPoly,1)
      &lcObj2..aPoly(lnJ,1) = &lcObj1..aPoly(2,1)
      &lcObj2..aPoly(lnJ,2) = &lcObj1..aPoly(2,2)
    ENDIF
    *--
    *-- Hago visible los objetos creados
    *--
    THIS.SETALL("Visible",.T., "Shape")
    THIS.SETALL("Visible",.T., "Label")
  ENDPROC
  PROCEDURE ColoresBasicos
    LPARAMETERS tn
    LOCAL la(28)
    tn = MOD(tn-1,28)+1
    la(1) = RGB(255,0,0) && Rojo
    la(2) = RGB(255,255,  0) && Amarillo
    la(3) = RGB(0,0,255) && Azul
    la(4) = RGB(0,128,0) && Verde Oscuro
    la(5) = RGB(255,128,0) && Anaranjado
    la(6) = RGB(128,64,0) && Marrón
    la(7) = RGB(255,0,255) && Magenta
    la(8) = RGB(128,0,255) && Violeta
    la(9) = RGB(0,255,255) && Cyan
    la(10) = RGB(192,192,0) && Amarillo Oscuro
    la(11) = RGB(192,0,0) && Rojo Oscuro
    la(12) = RGB(0,255,0) && Verde
    la(13) = RGB(0,0,128) && Azul Oscuro
    la(14) = RGB(255,192,0) && Anaranjado Claro
    la(15) = RGB(0,192,255) && Azul claro
    la(16) = RGB(128,128,0) && Marrón Claro
    la(17) = RGB(255,192,255) && Magenta Claro
    la(18) = RGB( 64,128,128) && Verde Azulado
    la(19) = RGB(255,0,128) && Fucsia
    la(20) = RGB(255,255,192) && Amarillo Claro
    la(21) = RGB(192,0,255) && Violeta Claro
    la(22) = RGB(192,255,192) && Verde Claro
    la(23) = RGB(128,0,128) && Violeta Oscuro
    la(24) = RGB(192,255,255) && Cyan Claro
    la(25) = RGB(128,128,128) && Gris Oscuro
    la(26) = RGB(255,255,255) && Blanco
    la(27) = RGB(192,192,192) && Gris
    la(28) = RGB(0,0,0) && Negro
    RETURN la(tn)
  ENDPROC
  PROCEDURE LimpiarGrafica
    LOCAL lnI
    FOR lnI = THIS.CONTROLCOUNT TO 1 STEP -1
      THIS.REMOVEOBJECT(THIS.CONTROLS(lnI).NAME)
    ENDFOR
  ENDPROC
  PROCEDURE INIT
    SET TALK OFF
    RAND(-1)
  ENDPROC
ENDDEFINE

La descarga

Pueden descargar el proyecto con el formulario de ejemplo y la clase lmGraph desde el enlace siguiente:

El final

Con la clase presentada en este artículo, ustedes podrán generar gráficas simples y muy fáciles de incorporar en formularios de VFP. Si necesitan gráficas mas avanzadas utilizando solo Visual FoxPro, los invito a que lean el excelente artículo de Cesar Ch. publicado en la revista UTMag y se sorprenderán de todo lo que se puede realizar con GDI+

Los comentarios

Cualquier comentario que quieran realizar sobre este artículo y esta clase, será bienvenido. Solo envien sus comentario haciendo clic en el botón "Enviar comentario" al final de este artículo.

Hasta la próxima.

Luis María Guayán

6 comentarios :

  1. ¿Cómo se podría guardar, por programación, una imagen (bmp, gif, jpg) del gráfico generado?

    ResponderEliminar
    Respuestas
    1. Estimado, ¿encontraste como copiar a una imagen el gráfico generado?
      Lo quiero quiero llevar a mi reporte vfp.
      Gracias

      Eliminar
  2. Lo puedes hacer con la clase GDIPlusX de Cesar Ch.

    http://vfpx.codeplex.com/wikipage?title=GDIPlusX&ProjectName=vfpx

    En tu formulario con la clase lmGraph, en el método Click de un botón Imprimir por ej:



    Do System.App
    local loBmp as xfcBitmap
    With _Screen.System.Drawing
    loBmp = .Bitmap.FromScreen(Thisform.lmGraph1) && parametro objecto lmGraph
    *-- Para guardar como imagen
    *loBmp.Save("c:\lmGraph.Png", .Imaging.ImageFormat.Png)
    *-- Para imprimir
    loBmp.ToPrinter()
    EndWith

    ResponderEliminar
  3. Buen día muy buen ayuda gracias

    Como podría imprimir la gráfica que se genera en la clase?

    ResponderEliminar
  4. En la respuesta de arriba está la solución:

    Lo puedes hacer con la clase GDIPlusX de Cesar Ch.

    http://vfpx.codeplex.com/wikipage?title=GDIPlusX&ProjectName=vfpx

    En tu formulario con la clase lmGraph, en el método Click de un botón Imprimir por ej:


    Do System.App
    local loBmp as xfcBitmap
    With _Screen.System.Drawing
    loBmp = .Bitmap.FromScreen(Thisform.lmGraph1) && parametro objecto lmGraph
    *-- Para guardar como imagen
    *loBmp.Save("c:\lmGraph.Png", .Imaging.ImageFormat.Png)
    *-- Para imprimir
    loBmp.ToPrinter()
    EndWith

    ResponderEliminar