Spectacular dashboard with Flexmonster Pivot and amCharts

I have always liked something bright and unusual, no matter whether it is clothing, a painting, or an interesting visualization. And when I come across something like that, I immediately want to try to do it myself.

It happened this time too. When I saw all the examples amCharts have, I immediately knew how I would spend in the near future. That's how this mini-tutorial appeared on how to combine charts and pivots and create a cool interactive and at the same time meaningful dashboard.

So let's get to work!

Step 1. Data

Let's start with the data. This time I decided to take information about cheese production from France, Greece and Italy. They are in my JSON array, which I will insert directly into the code and through the function getData() I will get the data for the pivot.

function getData() {
  return [
    {
      Date: "2020-01-05",
      id: "GR",
      CountryCode: "gr",
      Country: "Greece",
      Feta: 62,
      Mozzarella: 8,
      "Parmigiano-Reggiano": 8
    },
    {
      Date: "2020-01-12",
      id: "GR",
      CountryCode: "gr",
      Country: "Greece",
      Feta: 62,
      Mozzarella: 11,
      "Parmigiano-Reggiano": 8
    },
] }

Step 2. Pivot

Now let's create the pivot itself and add a reportcompleteevent handler to create charts for the dashboard at the end.

let pivot = new Flexmonster({
  container: "pivot-container",
  licenseFilePath: "https://cdn.flexmonster.com/codepen.key",
  componentFolder: "https://cdn.flexmonster.com/",
  height: 440,
  customizeCell: customizeCell,
  report: {
    dataSource: {
      data: getData(),
      mapping: {
        Date: {
          type: "date"
        },
        Country: {
          type: "string"
        },
        id: {
          type: "string"
        },
        CountryCode: {
          type: "property",
          hierarchy: "Country"
        },
        Feta: {
          type: "number"
        },
        Mozzarella: {
          type: "number"
        },
        "Parmigiano-Reggiano": {
          type: "number"
        }
      }
    },
    slice: {
      rows: [
        {
          uniqueName: "Country"
        },
        {
          uniqueName: "[Measures]"
        }
      ],
      columns: [
        {
          uniqueName: "Date.Month",
          filter: {
            members: [
              "date.month.[january]",
              "date.month.[february]",
              "date.month.[march]",
              "date.month.[april]",
              "date.month.[may]",
              "date.month.[june]"
            ]
          }
        }
      ],
      measures: [
        {
          uniqueName: "Feta",
          aggregation: "sum",
          grandTotalCaption: "Feta"
        },
        {
          uniqueName: "Mozzarella",
          aggregation: "sum",
          grandTotalCaption: "Mozzarella"
        },
        {
          uniqueName: "Parmigiano-Reggiano",
          aggregation: "sum",
          grandTotalCaption: "Parmigiano-Reggiano"
        }
      ]
    },
    options: {
      grid: {
        showHeaders: false,
        showGrandTotals: "rows"
      },
      showAggregationLabels: false
    }
  },
  reportcomplete: function () {
    pivot.off("reportcomplete");
    createPieChart();
    createStackedChart();
    createMapChart();
    createPictorialChart();
  }
});

Step 3. Customization
In order to make the pivot itself more interesting, I suggest adding the flags to the countries. We will do this through the customizeCell function, and we will set the styles through CSS. Note that we are styling both expanded and collapsed variants.

function customizeCell(cell, data) {
  if (data.hierarchy && data.type == "header") {
    console.log(data);
    if (
      data.hierarchy.caption == "Country" &&
      data.member &&
      data.member.properties
    ) {
      let name = data.member.properties.CountryCode;
      let flag = `<i class="fm-icon ${
        data.expanded ? "fm-expanded-icon" : "fm-collapsed-icon"
      }" 
        title="${data.expanded ? "Click to collapse" : "Click to expand"}"></i>
        <img class="flag-icon" src="https://cdn.flexmonster.com/i/flags/${name.toLowerCase()}.svg">`;
      cell.text = `${flag}<span style="margin-left: 2px; line-height: 16px">${data.member.caption}</span>`;
      cell.addClass("fm-custom-cell");
    }
  }
}

CSS

.fm-custom-cell {
  display: flex !important;
  align-items: center !important;
  font-size: 12px !important;
}

.fm-custom-cell .flag-icon {
  width: 21px !important;
  height: 16px !important;
  margin-left: 0 !important;
  margin-right: 2px !important;
}

#fm-pivot-view
  .fm-grid-layout
  .fm-custom-cell.fm-expanded
  .fm-expanded-icon::before,
#fm-pivot-view
  .fm-grid-layout
  .fm-custom-cell.fm-collapsed
  .fm-collapsed-icon::before {
  top: -2px;
  height: 16px;
}

Step 4. Charts

Now we can get to the charts.

First, we will declare our charts and choose the colors that we will use in them.

let mapChart, pieChart, stackedChart, pictorialChart;
// Apply amCharts theme
am4core.useTheme(am4themes_animated);

const chartColors = [
  am4core.color("#4CBF8B"),
  am4core.color("#FFCD4C"),
  am4core.color("#E8734C"),
  am4core.color("#9875E3"),
  am4core.color("#4C9EFF"),
  am4core.color("#8ACFC3"),
  am4core.color("#CD97E6"),
  am4core.color("#F1D34C"),
  am4core.color("#65D2E7")
];

const cheeseColors = [
  am4core.color("#FFE268"),
  am4core.color("#FFCD4C"),
  am4core.color("#FFB037")
];

The data in charts will be transmitted from the pivot through a special getData API call. Like here:

function createPieChart() {
  pivot.amcharts.getData(
    {
      slice: {
        rows: [
          {
            uniqueName: "Date.Month",
            filter: {
              members: [
                "date.month.[january]",
                "date.month.[february]",
                "date.month.[march]",
                "date.month.[april]",
                "date.month.[may]",
                "date.month.[june]"
              ]
            }
          }
        ],
        measures: [
          {
            uniqueName: "Feta",
            aggregation: "sum"
          }
        ]
      }
    },
    drawPieChart,
    updatePieChart
  );
}

On the amCharts demo page you can find a wide variety of charts and graphics and apply them into your project. For this dashboard, I chose a StackedChart, PieChart, MapChart and PictorialChart.

In this article, I will show using the PictorialChart how to transfer data from the pivot, in the rest of the charts everything works the same way. You can study them in more detail in the source code of the demo.

So first we initialize the chart and then add data to it:

pictorialChart = am4core.create(
    "amcharts-pictorial-container",
    am4charts.SlicedChart
  );
 pictorialChart.data = chartData.data;

This is how it should look at the end:

function drawPictorialChart(chartData, rawData) {
  var iconPath =
    "M10.512,156.385l231.164,35.647c-1.779-3.578-2.869-7.554-2.869-11.817c0-14.766,11.962-26.728,26.733-26.728 c14.768,0,26.733,11.962,26.733,26.728c0,7.173-2.861,13.657-7.466,18.466l75.392,11.622c8.752,1.351,12.632-3.979,8.66-11.906 l-35.401-70.833c-4.172,2.687-9.125,4.29-14.459,4.29c-14.768,0-26.726-11.968-26.726-26.733c0-11.491,7.29-21.213,17.473-24.98l-17.429-34.825c-3.955-7.92-13.781-11.547-21.94-8.093L232.122,53.41c5.122,7.019,8.175,15.631,8.175,24.98c0,23.463-19.015,42.477-42.49,42.477c-20.977,0-38.365-15.222-41.804-35.223L9.43,147.683C1.28,151.141,1.759,155.032,10.512,156.385z M125.661,123.215c4.883,0,8.838,3.95,8.838,8.833c0,4.883-3.955,8.833-8.838,8.833c-4.873,0-8.833-3.949-8.833-8.833C116.829,127.165,120.788,123.215,125.661,123.215zM30.294,218.466c17.012,0,30.799,13.793,30.799,30.805c0,16.996-13.788,30.797-30.799,30.797c-6.444,0-12.419-1.987-17.364-5.374l3.048,26.361c1.02,8.812,9.011,16.362,17.859,16.875l189.024,11.157c-0.068-0.754-0.229-1.476-0.229-2.24c0-14.644,11.857-26.497,26.493-26.497c14.643,0,26.497,11.854,26.497,26.497c0,1.815-0.192,3.595-0.529,5.313l59.086,3.486c8.844,0.514,17.34-6.115,18.979-14.827l18.431-98.29L0,162.851l7.598,65.657C13.229,222.353,21.297,218.466,30.294,218.466z M135.451,219.127c10.283,0,18.612,8.331,18.612,18.622c0,10.287-8.337,18.619-18.612,18.619c-10.293,0-18.623-8.332-18.623-18.619C116.829,227.458,125.158,219.127,135.451,219.127zM98.206,271.243c4.885,0,8.844,3.947,8.844,8.829c0,4.885-3.959,8.832-8.844,8.832c-4.871,0-8.831-3.947-8.831-8.832C89.375,275.19,93.335,271.243,98.206,271.243z";

  pictorialChart = am4core.create(
    "amcharts-pictorial-container",
    am4charts.SlicedChart
  );
  pictorialChart.hiddenState.properties.opacity = 0; // this makes initial fade in effect

  pictorialChart.data = chartData.data;

  var series = pictorialChart.series.push(
    new am4charts.PictorialStackedSeries()
  );
  series.colors.list = cheeseColors;

  series.dataFields.value = pivot.amcharts.getMeasureNameByIndex(rawData, 0);
  series.dataFields.category = pivot.amcharts.getCategoryName(rawData);
  series.alignLabels = true;
  series.labels = "disabled";
  series.ticks = "disabled";

  series.maskSprite.path = iconPath;

  pictorialChart.legend = new am4charts.Legend();
  pictorialChart.legend.position = "right";
  let marker = pictorialChart.legend.markers.template.children.getIndex(0);
  pictorialChart.legend.markers.template.width = 20;
  pictorialChart.legend.markers.template.height = 20;
  marker.cornerRadius(20, 20, 20, 20);

  pictorialChart.responsive.enabled = true;
  pictorialChart.responsive.rules.push({
    relevant: function (target) {
      if (target.pixelWidth <= 600) {
        return true;
      }
      return false;
    },
    state: function (target, stateId) {
      if (target instanceof am4charts.PictorialStackedSeries) {
        var state = target.states.create(stateId);

        var labelState = target.labels.template.states.create(stateId);
        labelState.properties.disabled = true;

        var tickState = target.ticks.template.states.create(stateId);
        tickState.properties.disabled = true;
        return state;
      }

      return null;
    }
  });
}

Also, we do not forget about updating the chart so that it remains interactive for us and transforms depending on changes in the pivot data.

function updatePictorialChart(chartData, rawData) {
  pictorialChart.dispose();
  drawPictorialChart(chartData, rawData);
}

Step 5. Dashboard

Now we can finally start adding all this to the page. First add the libraries themselves.

<script src="https://cdn.flexmonster.com/flexmonster.js"></script>
<script src="https://cdn.flexmonster.com/lib/flexmonster.amcharts.js"></script>
<script src="https://www.amcharts.com/lib/4/core.js"></script>
<script src="https://www.amcharts.com/lib/4/charts.js"></script>
<script src="https://www.amcharts.com/lib/4/maps.js"></script>
<script src="https://www.amcharts.com/lib/4/themes/animated.js"></script>
<script src="https://cdn.amcharts.com/lib/4/geodata/worldHigh.js"></script>

In the CSS code, we will pre-specify styles for labels and container for charts.

body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
    Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}

.chart-container {
  height: 450px;
}

.demo-box {
  background-color: #fafafa;
  position: relative;
  padding: 40px 30px 30px 30px;
  border: 1px solid #e9e9e9;
  margin-bottom: 20px;
  margin-top: 40px;
}

.demo-title {
  font-size: 18px;
  margin-bottom: 30px;
  white-space: nowrap;
  text-overflow: ellipsis;
  overflow: hidden;
  line-height: 24px;
}

Next, we will simply add all the components in a row to the page, signing titles for our charts.

<div id="pivot-container"></div>

<div class="demo-box">
  <div class="demo-title"><strong>Overall Cheese Interest by Month</strong></div>
  <div id="amcharts-stacked-container" class="chart-container"></div>
</div>

<div class="demo-box">
  <div class="demo-title"><strong>Total Cheese Interest by Country</strong></div>
  <div id="amcharts-pictorial-container" class="chart-container"></div>
  <div>Icons made by <a href="https://www.freepik.com" title="Freepik">Freepik</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a></div>
</div>

<div class="demo-box">
  <div class="demo-title"><strong>Feta Interest by Month</strong></div>
  <div id="amcharts-pie-container" class="chart-container"></div>
</div>

<div class="demo-box no-text-before no-text-after">
  <div class="demo-title"><strong>Feta Interest by Country</strong></div>
  <div id="amcharts-map-container" class="chart-container"></div>
</div>

And voila! - our super interactive and modern cheese making dashboard is ready!

26