Windows Phone. Uso de gráficas.

En ocasiones en nuestras aplicaciones tendremos la necesidad de mostrar al usuario cierta información de la manera más directa y sencilla posible de modo que de un solo vistazo el usuario sea capaz de obtener la mayor información posible. A nivel de interfaz tenemos muchísimas opciones muy interesantes sin embargo, un excelente recurso muy utilizado para este tipo de situaciones es el uso de gráficas. Por ejemplo, estamos realizando una aplicación de compra de viviendas. Sería fantástico mostrarle al usuario la información de como ha oscilado el precio de la viviendo en el último trimestre por ejemplo. ¿Como lo hacemos?, ¿Que piensas que sería lo más acertado?. Posiblemente una gráfica de barras u otra similar te haya pasado por la mente.

En esta entrada vamos a aprender como utilizar gráficas en nuestras aplicaciones Windows Phone además de analizar las opciones más interesantes que tenemos a nuestro alcance para ello.

Podemos encontrar el Silverlight Toolkit en Codeplex. La dirección sería:

http://silverlight.codeplex.com

El primero de los enlaces es un archivo de instalación (.msi) que nos instalará los binarios del Toolkit.

EL segundo de los enlaces en un archivo comprimido (.zip) que contiene el código fuente. Aunque no pienses modificar los controles del Toolkit y tal como están disponibles en los binarios te son totalmente válidos, te recomiendo si tienes tiempo echarle un vistazo.

Vamos a crear un proyecto nuevo.

La plantilla seleccionada será “Windows Phone Application” para simplificar al máximo el ejemplo.

Agregamos dentro del Grid principal de la página MainPage.xaml:

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
     <StackPanel>
          <Button x:Name="btnBarChart" Content="Gráfica de Barras"/>
          <Button x:Name="btnPieChart" Content="Gráfica Circular"/>
          <Button x:Name="btnLineChart" Content="Gráfica de Líneas"/>
          <Button x:Name="btnStyleBarChart" Content="Gráfica de Barras - Estilos"/>
     </StackPanel>
</Grid>

Tenemos cuatro botones. Vamos a añadir los eventos Click de cada botón:

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
     <StackPanel>
          <Button x:Name="btnBarChart" Content="Gráfica de Barras" Click="btnBarChart_Click"/>
          <Button x:Name="btnPieChart" Content="Gráfica Circular" Click="btnPieChart_Click"/>
          <Button x:Name="btnLineChart" Content="Gráfica de Líneas" Click="btnLineChart_Click"/>
          <Button x:Name="btnStyleBarChart" Content="Gráfica de Barras - Estilos" Click="btnStyleBarChart_Click"/>
     </StackPanel>
</Grid>

Al pulsar sobre cada uno de los botones llevaremos al usuario a una nueva página por lo que necesitamos antes que nada añadir al proyecto las 4 páginas necesarias que utilizaremos. En el ejemplo que podéis descargar al final de la entrada se añadieron 4 páginas llamadas:

  • BarChart
  • PieChart
  • LineChart
  • StyleBarChart

A continuación, vamos a ver que se hace en cada uno de los botones.

private void btnBarChart_Click(object sender, RoutedEventArgs e)
{
     NavigationService.Navigate(new Uri("/BarChart.xaml", UriKind.Relative));
}

private void btnPieChart_Click(object sender, RoutedEventArgs e)
{
     NavigationService.Navigate(new Uri("/PieChart.xaml", UriKind.Relative));
}

private void btnLineChart_Click(object sender, RoutedEventArgs e)
{
     NavigationService.Navigate(new Uri("/LineChart.xaml", UriKind.Relative));
}

private void btnStyleBarChart_Click(object sender, RoutedEventArgs e)
{
     NavigationService.Navigate(new Uri("/StyleBarChart.xaml", UriKind.Relative));
}

Nada fuera de lo común. Utilizamos el servicio de navegación NavigationService para enviar al usuario a la página correspondiente. Puedes ver todo lo relacionado con el servicio de navegación NavigationService en esta entrada.

Entramos en materia. Lo primero que vamos a necesitar es un objeto donde almacenar la información necesaria. Cada elemento dentro de la gráfica tendrá un valor en el eje X y otro en el eje Y:

public class ChartItem
{
     public string EjeX { get; set; }
     public decimal EjeY { get; set; }
}

Para poder añadir gráficas en nuestra interfaz lo primero que debemos hacer es añadir las referencias a las siguientes librerías:

  • System.Windows.Controls 
  • System.Windows.Controls.DataVisualization.Toolkit

Además, debemos añadir el siguiente espacio de nombres en cada página donde utilicemos gráficas (en la parte superior de nuestro XAML):

xmlns:chart="clr-namespace:System.Windows.Controls.DataVisualization.Charting; assembly=System.Windows.Controls.DataVisualization.Toolkit"

Podremos utilizar los siguientes tipos de gráficas:

  • Area
  • Bar
  • Bubble
  • Column
  • Line
  • Pie Scatter
  • Stacked

Nos centramos en la página “BarChart.xaml”. La definición más simple posible (aún sin enlazar con fuente de datos alguna) de una gráfica de barras (BarSeries) es la siguiente:

<chart:Chart>
  <chart:BarSeries />
</chart:Chart>

Ya tenemos el objeto que almacenará la información y la interfaz básica preparada. Vamos a crearnos en el constructor de la clase una colección de objetos ChartItem para poder abastecer de la información necesaria a la interfaz:

public BarChart()
{
InitializeComponent();

     List<ChartItem> data = new List<ChartItem>
     {
          new ChartItem { EjeX = "Prueba 1", EjeY = 50 },
          new ChartItem { EjeX = "Prueba 2", EjeY = 100 },
          new ChartItem { EjeX = "Prueba 3", EjeY = 163.2M }
     };

     ContentPanel.DataContext = data;
}

Veamos como quedaría la interfaz:

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
     <chart:Chart x:Name="barChart" Foreground="LightGray" Title="Ejemplo Título">
     <chart:BarSeries
     ItemsSource="{Binding}"
     Title="Ejemplo Título"
     IndependentValueBinding="{Binding EjeX}"
     DependentValueBinding="{Binding EjeY}"/>
          <chart:Chart.Axes>
               <chart:CategoryAxis Title="Eje Y" Orientation="Y"/>
               <chart:LinearAxis Title="Eje X" Orientation="X" Minimum="0" Maximum="250" Interval="50" ShowGridLines="True"/>
          </chart:Chart.Axes>
     </chart:Chart>
</Grid>

El resultado lo podéis ver en la pantalla superior. Analicemos con más calma lo que acabamos de hacer. Hemos asignado por código el DataContext del Grid que contiene la gráfica a nuestra colección. En la gráfica de barras hacemos binding a la colección de memoria usando su propiedad ItemsSource. Asignamos la propiedad  IndependentValue a EjeX que significa que cada barra correspondiente a la gráfica tendra una entrada de texto con el valor asignado al EjeX junto con la propiedad DependentValuePath a EjeY que mostrará la cantidad almacenada en la propiedad EjeY asigando un ancho determinado a cada barra.

¿Fácil, verdad?

Continuamos con más gráficas diferentes, a continuación, las gráficas circulares (denominados también gráficos de pastel o gráficas de 360 grados). Nos centramos ahora en la página “PieChart.xaml”. Al igual que hicimos con el gráfico de barras comenzaremos añadiendo a la interfaz el código mínimo para crear la gráfica circular:

<chart:Chart>
     <chart:PieSeries />
</chart:Chart>

Definimos la interfaz completa de la gráfica circular (PieSeries):

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
     <chart:Chart x:Name="pieChart" Foreground="LightGray" Title="Ejemplo Título">
          <chart:PieSeries
          ItemsSource="{Binding}"
          Title="Ejemplo Título"
          IndependentValueBinding="{Binding EjeX}"
          DependentValueBinding="{Binding EjeY}"/>
     </chart:Chart>
</Grid>

Al igual que hicimos con el gráfico de barras, en el contructor crearemos una colección en memoria:

public PieChart()
{
     InitializeComponent();

     List<ChartItem> data = new List<ChartItem>
     {
          new ChartItem { EjeX = "Prueba 1", EjeY = 25 },
          new ChartItem { EjeX = "Prueba 2", EjeY = 70 },
          new ChartItem { EjeX = "Prueba 3", EjeY = 203.7M }
     };

     ContentPanel.DataContext = data;
}

Para seguir viendo posibilidades repetiremos la misma acción con una tercera gráfica (página “LineChart.xaml”). En esta ocasión utilizaremos una gráfica de líneas (LineSeries):

<chart:Chart>
     <chart:LineSeries />
</chart:Chart>

Definimos la interfaz:

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
     <chart:Chart x:Name="barChart" Foreground="LightGray" Title="Ejemplo Título">
          <chart:LineSeries
          ItemsSource="{Binding}"
          Title="Ejemplo Título"
          IndependentValueBinding="{Binding EjeX}"
          DependentValueBinding="{Binding EjeY}"/>
          <chart:Chart.Axes>
               <chart:CategoryAxis Title="Eje Y" Orientation="Y"/>
               <chart:LinearAxis Title="Eje X" Orientation="X" Minimum="0" Maximum="250" Interval="50" ShowGridLines="True"/>
          </chart:Chart.Axes>
     </chart:Chart>
</Grid>

De nuevo, en el constructor creamos una colección en memoria que abastecerá a nuestra gráfica:

public LineChart()
{
     InitializeComponent();

     List<ChartItem> data = new List<ChartItem>
     {
          new ChartItem { EjeX = "Prueba 1", EjeY = 35 },
          new ChartItem { EjeX = "Prueba 2", EjeY = 80 },
          new ChartItem { EjeX = "Prueba 3", EjeY = 223.7M }
     };

     ContentPanel.DataContext = data;
}

Lo visto hasta ahora nos demuestra como podemos conseguir de manera rápida y sencilla múltiples gráficas gracias al Toolkit de Silverligth. Sin embargo, la apariencia por defecto de gráficas creadas hasta ahora aunque perfectamente válida podría mejorar. Ese va a ser nuestro objetivo en este último ejemplo. Para ello vamos a crear un estilo para la gráfica que añadiremos en los recursos de la página:

<phone:PhoneApplicationPage.Resources>
</phone:PhoneApplicationPage.Resources>

Creamos un color sólido que utilizaremos para rellenar las barras de nuestra gráfica. Si descargáis el ejemplo, podéis probar distintos colores con suma facilidad modificando el valor de este recurso:

<SolidColorBrush Color="GreenYellow" x:Key="appColor"/>

Comenzamos:

<ControlTemplate x:Key="PhoneChartPortraitTemplate" TargetType="chart:Chart">
     <Grid>
          <Grid.RowDefinitions>
               <RowDefinition Height="Auto"/>
               <RowDefinition Height="*"/>
               <RowDefinition Height="Auto"/>
          </Grid.RowDefinitions>
          <datavis:Title
          Content="{TemplateBinding Title}"
          Style="{TemplateBinding TitleStyle}"/>
          <datavis:Legend x:Name="Legend"
          Grid.Row="2"
          Header="{TemplateBinding LegendTitle}"
          Style="{TemplateBinding LegendStyle}">
               <datavis:Legend.ItemsPanel>
                    <ItemsPanelTemplate>
                         <StackPanel Orientation="Horizontal"/>
                    </ItemsPanelTemplate>
               </datavis:Legend.ItemsPanel>
               <datavis:Legend.Template>
                    <ControlTemplate TargetType="datavis:Legend">
                         <Border
                         Background="{TemplateBinding Background}"
                         BorderBrush="{TemplateBinding BorderBrush}"
                         BorderThickness="{TemplateBinding BorderThickness}"
                         Padding="2">
                         <Grid>
                              <Grid.RowDefinitions>
                                   <RowDefinition Height="Auto" />
                                   <RowDefinition />
                              </Grid.RowDefinitions>
                              <datavis:Title
                              Grid.Row="0"
                              x:Name="HeaderContent"
                              Content="{TemplateBinding Header}"
                              ContentTemplate="{TemplateBinding HeaderTemplate}"
                              Style="{TemplateBinding TitleStyle}"/>
                              <ScrollViewer
                              Grid.Row="1"
                              HorizontalScrollBarVisibility="Auto"
                              VerticalScrollBarVisibility="Disabled"
                              BorderThickness="0"
                              Padding="0"
                              IsTabStop="False">
                                   <ItemsPresenter
                                   x:Name="Items"
                                   Margin="10,0,10,10"/>
                              </ScrollViewer>
                         </Grid>
                         </Border>
                     </ControlTemplate>
                </datavis:Legend.Template>
           </datavis:Legend>
           <chartingprimitives:EdgePanel
           Grid.Column="0"
           Grid.Row="1"
           x:Name="ChartArea"
           Style="{TemplateBinding ChartAreaStyle}">
                <Grid
                Canvas.ZIndex="-1"
                Style="{TemplateBinding PlotAreaStyle}" />
           </chartingprimitives:EdgePanel>
     </Grid>
</ControlTemplate>

Ahora nos centramos en el estilo (en nuestro ejemplo lo hemos llamado PhoneChartStyle):

<Style x:Key="PhoneChartStyle" TargetType="chart:Chart">
     <Setter Property="IsTabStop" Value="False" />
     <Setter Property="BorderThickness" Value="0" />
     <Setter Property="Padding" Value="10" />
     <Setter Property="Palette">
          <Setter.Value>
               <datavis:ResourceDictionaryCollection>
                    <ResourceDictionary>
                         <SolidColorBrush x:Key="Background" Color="Transparent"/>
                         <Style x:Key="DataPointStyle" TargetType="Control">
                              <Setter Property="Background" Value="{StaticResource appColor}" />
                         </Style>
                         <Style x:Key="DataShapeStyle" TargetType="Shape">
                              <Setter Property="Stroke" Value="{StaticResource appColor}" />
                              <Setter Property="StrokeThickness" Value="1" />
                              <Setter Property="StrokeMiterLimit" Value="1" />
                              <Setter Property="Fill" Value="{StaticResource appColor}" />
                         </Style>
                    </ResourceDictionary>
               </datavis:ResourceDictionaryCollection>
          </Setter.Value>
     </Setter>
     <Setter Property="TitleStyle">
          <Setter.Value>
               <Style TargetType="datavis:Title">
                    <Setter Property="HorizontalAlignment" Value="Center"/>
                    <Setter Property="FontSize" Value="{StaticResource PhoneFontSizeExtraExtraLarge}"/>
                    <Setter Property="Margin" Value="5"/>
               </Style>
         </Setter.Value>
     </Setter>
     <Setter Property="LegendStyle">
          <Setter.Value>
               <Style TargetType="datavis:Legend">
                    <Setter Property="HorizontalAlignment" Value="Center"/>
                    <Setter Property="VerticalAlignment" Value="Center"/>
                    <Setter Property="BorderBrush" Value="{StaticResource PhoneForegroundBrush}"/>
                    <Setter Property="Margin" Value="5"/>
               </Style>
          </Setter.Value>
     </Setter>
     <Setter Property="ChartAreaStyle">
          <Setter.Value>
               <Style TargetType="Panel">
                    <Setter Property="MinWidth" Value="100" />
                    <Setter Property="MinHeight" Value="75" />
               </Style>
          </Setter.Value>
     </Setter>
     <Setter Property="PlotAreaStyle">
          <Setter.Value>
               <Style TargetType="Grid">
                    <Setter Property="Background" Value="Transparent"/>
               </Style>
          </Setter.Value>
     </Setter>
     <Setter Property="Template" Value="{StaticResource PhoneChartPortraitTemplate}"/>
</Style>

Asignamos el estilo creado a la gráfica de barras:

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
     <chart:Chart Style="{StaticResource PhoneChartStyle}" Height="225">
          <chart:Chart.LegendStyle>
               <Style TargetType="datavis:Legend">
                    <Setter Property="Width" Value="0"/>
                    <Setter Property="Height" Value="0"/>
               </Style>
          </chart:Chart.LegendStyle>
          <chart:ColumnSeries x:Name="barSeries"
          ItemsSource="{Binding}"
          DependentValuePath="Eje Y"
          IndependentValuePath="Eje X"
          IndependentValueBinding="{Binding EjeX}"
          DependentValueBinding="{Binding EjeY}"
          Title="Ejemplo Estilos"
          IsSelectionEnabled="True"
          TransitionDuration="0"
          AnimationSequence="Simultaneous">
          </chart:ColumnSeries>
     </chart:Chart>
</Grid>

Para no complicar el ejemplo y centrarnos exclusivamente en el estilo visual, hemos repetido la misma forma de abastecer la gráfica, con datos en memoria:

public StyleBarChart()
{
     InitializeComponent();

     List<ChartItem> data = new List<ChartItem>
     {
          new ChartItem { EjeX = "Prueba 1", EjeY = 35 },
          new ChartItem { EjeX = "Prueba 2", EjeY = 80 },
          new ChartItem { EjeX = "Prueba 3", EjeY = 223.7M }
     };

     ContentPanel.DataContext = data;
}

El resultado conseguido tras trabajar sobre los estilos lo podéis ver en la captura superior. ¿Mejor?

Nota: Todo lo relacionado con plantillas, estilos y la utilización de recursos lo veremos con mucha más calma y detalle en una próxima entrada.

Puedes ver en video el resultado de nuestro ejemplo a continuación:

También, puedes descargar el ejemplo realizado:

Nada más en esta entrada. Recordar simplemente que cualquier tipo de duda o sugerencia la podéis dejar en los comentarios. Espero que lo visto os haya sido útil.

Alternativas

Tenemos múltiples alternativas tanto de pago como gratuitas de gran calidad:

  • Telerik. Atesoran una enorme experiencia a la hora de realizar controles para entornos .NET por lo tanto han logrado un conjunto cada vez más completo de controles para Windows Phone de una enorme calidad y a un precio muy competitivo (99$). Centrandonos en la parte relacionada con gráficas, ofrecen variedad de tipos de gráficas, donde lo más destacable sin duda es el gran rendimiento ofrecido junto a la sencillez para trabajar con datos.
  • Amcharts. Controles para crear gráficas gratuitos (como limitación colocan un enlace a su web en la versión gratuita).
  • Visifire. Muy completo conjunto de gráficas de diferente tipo. A destacar la interactividad que permiten por parte del usuario de manera muy sencilla de implementar junto a animaciones de bella factura. Tienen un coste de 99$.
  • Touch Enabled Graph Control WP7. Interesante proyecto disponible en Codeplex que nos ofrece la posibilidad de crear gráficas (de líneas) donde el usuario puede manipular con los dedos el nivel de zoom, zona visible, etc. Muy interesante.

Más información:

Maromas Digitales: 31 días de Windows Phone | Día #31: Graficando datos

3 pensamientos en “Windows Phone. Uso de gráficas.

  1. Tengo un problema al querer generar las gráficas, todo parece bien al instanciar las dll y agregar las etiquetas para indicar las características, inclusive se van mostrando los cambios en el diseñador del lado izquierdo pero al ejecutar la aplicación, en el emulador, y mandar llamar la página que tiene el gráfico se genera un problema indicando XamlParseException en la pagina que contiene el código para crear la misma.

    ¿Se trata de un error con mis versiones de dll o un problema con algún componente?

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s