Gráfico de acumulados con Power Apps

Hay varias razones por las que necesitaremos utilizar los gráficos OOTB de Power Apps; las más comunes serían resolver visualizaciones muy sencillas o prescindir directamente de los gráficos de Power BI lo máximo posible (podría ser que los destinatarios de nuestra aplicación no utilicen o no tengan licencias de Power BI).

Las opciones OOTB de Power Apps son muy limitadas, para qué engañarnos, y aunque podríamos idear una solución con un componente desarrollado con PCF mis observaciones cuando me lo planteé fueron dos:
- Queríamos que los usuarios se quedaran dentro de un licenciamiento O365, no está muy claro cómo afectará el licenciamiento a los componentes desarrollados.
- Quería poner, una vez más, Power Apps y su visión #LessCode al límite (y divertirme un rato obviamente).



Con esta breve introducción, os pongo en contexto del requerimiento a resolver: Tenemos una Colección de fallos en un sistema técnico de los últimos tres años. Nuestro objetivo es construir un gráfico de fallos acumulados en barras por cada mes. ¿Qué significa un valor acumulado? Pues si en enero hubo 2 fallos y en febrero 8, en el gráfico dibujaremos un valor de "2" para enero y un valor de "10" (8+2) para febrero... y así para todos los meses.

Tras evaluar cómo construir la colección base para enlazar el gráfico de barras, me encontré con varios problemas/consideraciones que influyó la solución que implementé finalmente:
- Hay que agrupar por mes (o por año, es independiente como empezamos la construcción de la matriz), dividir por año, realizar el conteo y finalmente la fórmula de acumulado.
- En Power Apps tenemos fórmulas para Agrupar, contar, sumar, pero no para acumular.
- En Power Apps si queremos realizar un proceso iterado, solo existe ForAll y nos limita aplicar la misma lógica a todos los registros. Si queremos un proceso iterado con más lógica, hay que implementarlo a mano con la ayuda, por ejemplo, del componente Timer.

Primer paso, tenemos que detectar los componentes necesarios:
- Nuestra colección principal con todos los fallos y sus fechas
- Una variable iterador para controlar el total de ciclos (12, tantos como meses).
- Un componente Timer (lo utilizaremos para repetir los cálculos tantas veces como marque el iterador)
- Una variable booleana para controlar el componente del Timer (componente invisible, no queremos que lo vea el usuario final).
- Variables para ir guardando el valor acumulado
- Una colección auxiliar para agrupar los valores por mes y año
- La colección final para guardar los valores acumulados y enlazar el gráfico de barras

Vamos a realizar los cálculos antes de navegar a la pantalla o mostrar el gráfico en popup. Importante: no visualizaremos el gráfico hasta el último ciclo del timer.

El esquema del cálculo será:
- Preparar la colección final con valores a 0 en sus registros, inicializar el iterador a 1
- Agrupar todos los valores por mes y año en la colección auxiliar.
- Cargar los primeros datos de enero en el primer registro de la colección final, poner el iterador a 2 e iniciar el contador.
- Al inicial el temporizador, calcular siguiente valor acumulado consultando el siguiente valor con el iterador y sumándolo al anterior.
Al finalizar el tiempo del temporizador, incrementar el iterador y comprobar si ya hemos terminado (iterador = 12) o volvemos a encenderlo.

Preparamos la colección final e inicializamos variables:

UpdateContext({iterator:1,timer: false });ClearCollect(FinalCollection,{Month:"January",value2017:0,value2018:0,value2019:0},{Month:"February",value2017:0,value2018:0,value2019:0},{Month:"March",value2017:0,value2018:0,value2019:0},{Month:"April",value2017:0,value2018:0,value2019:0},{Month:"May",value2017:0,value2018:0,value2019:0},{Month:"June",value2017:0,value2018:0,value2019:0},{Month:"July",value2017:0,value2018:0,value2019:0},{Month:"August",value2017:0,value2018:0,value2019:0},{Month:"September",value2017:0,value2018:0,value2019:0},{Month:"October",value2017:0,value2018:0,value2019:0},{Month:"November",value2017:0,value2018:0,value2019:0},{Month:"December",value2017:0,value2018:0,value2019:0});

Seguidamente hacemos el cálculo en la Colección de fallos, vamos a necesitar anidar las acciones para que se apliquen de adentro a fuera. Necesitaremos hacer un GroupBy sobre el valor en texto del mes:
Text(Fecha, "[$-en-US]mmmm" )
Seguidamente hacer un CountIf sobre esa agrupación para contar cuantos registros tenemos por cada año:
CountIf(ColumnaConValoresAgrupados,Year(Fecha) = 2019)

Como he comentado anteriormente, podríamos haber hecho la agrupación por año y luego el conteo por mes, la matriz es la misma.
Aquí un ejemplo de como quedaría:

Obviamente hay pasos opcionales, podemos obviar crear la columna con el nombre del mes, hacer los cálculos en la colección final... pero así me ayudó a ir visualizando los datos que se iban calculando.

A continuación actualizaremos las variables de valores. El uso de variables es importante para tener una implementación más clara (evitamos concatenación de largas expresiones) y en caso de querer testear la ejecución, poder controlar el cálculo del acumulado:

UpdateContext({
     value2017: LookUp(CollectionAux,Month = "January").'2017',
     value2018: LookUp(CollectionAux,Month = "January").'2018',
     value2019: LookUp(CollectionAux,Month = "January").'2019',
     valuemonth: "January"
})

Definimos los primeros valores de nuestra colección final gracias a las variables anteriores, en enero nunca tendremos valores acumulados
Patch(
    FinalCollection,
    LookUp(FinalCollection,Month = valuemonth),{
        value2017: value2017,
        value2018: value2018,
        value2019: value2019
    })
Por último definimos el siguiente valor del iterador y le damos al Go a nuestro temporizador:
UpdateContext({
    iterator: 2,
    timer: true
})
Como ahora la magia la hace nuestro reloj, es imprescindible tener en cuenta la configuración de su inicialización y repetición:
Para la duración del temporizador, la pondremos tan rápido como queramos el ciclo. En mi caso tan rápido como sea posible: duración 1 milisegundo.

¿Qué definiremos en el OnTimerStart del timer? Primero de todo necesitamos saber cuál es nuestro siguiente mes a calcular:
UpdateContext({
        valuemonth: Last(
            FirstN(
                FinalCollection,
                iterator
            )
        ).Month
})

Ahora calculamos el valor acumulado utilizando las variables:
value2017: value2017 + ( LookUp(CollectionAux, Month = valuemonth).'2017')

Y por último, con la competición de las funciones Patch y Lookup, actualizaremos la colección final con los valores calculados. ¿Qué hay que hacer en el OnTimerEnd del timer? Recordarnos de actualizar el iterador con iterador+1

Es todo lo que necesitamos para elaborar una colección de valores acumulados ¡Enhorabuena! (No olvides de hacer un Clear de las Colecciones que no vayas a utilizar más).

Lo último que nos queda es añadir un gráfico de barras. El origen de datos es nuestra colección FinalCollection y vamos a especificar que se visualicen 3 series (3 conjuntos de barras, uno por cada año). Las definiciones de las series y etiquetas quedarían de la siguiente manera:


Y voilà, nuestro gráfico se visualizaría de este modo:


La verdad es que me divertí muchísimo construyendo toda la solución, las colecciones como he dicho podrían ser más óptimas pero eso ya dependerá de cada escenario.

¡Pasad buen fin de semana!