Custom chart example - Google Maps

This example is for the latest Private eazyBI version 4.0.

This example will describe how to add Google Maps based custom chart type to private eazyBI.

Private eazyBI by default includes similar OpenStreetMap and MapQuest interactive maps in Map chart.

This example is just to illustrate how to add custom charts to private eazyBI.

On this page:

Create custom JavaScript files

This example will use Google Maps API and additional MarkerClusterer library. At first, create the public/eazybi/javascripts directory in your Private eazyBI directory eazybi_private.

Then download http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/src/markerclusterer.js and save it as public/eazybi/javascripts/markerclusterer.js.

Then create public/eazybi/javascripts/custom_charts.js file with the following implementation of Google Maps based chart type:

 Eazy.resultsViews.push({
  name: "Google Maps",
  key: "custom_google_maps",
  view: "CustomGoogleMapsView",
  icon: "fa fa-map-marker"
});
CustomGoogleMapsView = Eazy.ChartView.extend({
  infoWindowTemplate: Eazy.template('<div class="content">' +
    '<div><strong>{{title}}</strong></div>' +
    '{{#data}}<div>{{name}}: {{formattedValue}}</div>{{/data}}' +
    '</div>'),
  loadAPI: function() {
    if (window.google && google.maps) {
      return true;
    } else if (!this.constructor.apiLoading) {
      this.constructor.apiLoading = true;
      $("head").append("<script async defer src=\"https://maps.googleapis.com/maps/api/js\" type=\"text/javascript\">");
      return false;
    } else {
      return false;
    }
  },
  render: function() {
    if (!this.loadAPI()) {
      // retry after 1 second as Google Maps API will probably be loaded then
      _.delay(_.bind(this.render, this), 1000);
      return;
    }
    var queryResult = this.model.queryResult;
    this.categories = queryResult.chartCategories("row");
    this.series = queryResult.chartSeries("column");
    if (this.series.length < 2) {
      this.$el.html("Please use latitude and longitude as first two columns.");
      return this;
    }
    var $map = this.$(".google_maps_container");
    if ($map.length && this.map) {
      this.clearMarkers();
    } else {
      this.$el.html('<div class="google_maps_container">');
      $map = this.$(".google_maps_container");
      $map.height(500);
      this.map = new google.maps.Map($map[0], {
        // do not set center and zoom as will be positioned automatically using fitBounds
        mapTypeId: google.maps.MapTypeId.ROADMAP
      });
    }
    this.displayMarkers();
    Eazy.PopupMenuView.removeAll();
    return this;
  },
  displayMarkers: function() {
    var self = this,
        bounds = new google.maps.LatLngBounds();
    this.markers = [];
    _.each(this.categories, function(members, ci) {
      var lat = self.series[0].data[ci].y,
          lng = self.series[1].data[ci].y;
      if (lat && lng) {
        var title = _.invoke(members, "get", "caption").join(", "),
            marker = new google.maps.Marker({
              position: new google.maps.LatLng(lat, lng),
              map: self.map,
              title: title
            });
        self.markers.push(marker);
        var data = [];
        _.each(self.series, function(serie, si) {
          data.push({
            name: _.invoke(serie.name, "get", "caption").join(", "),
            formattedValue: self.series[si].data[ci].name.formattedValue
          });
        });
        var infowindow = new google.maps.InfoWindow({
          content: self.infoWindowTemplate({
            title: title,
            data: data
          })
        });
        google.maps.event.addListener(marker, 'click', function() {
          infowindow.open(self.map, marker);
        });
        bounds.extend(marker.position);
      }
    });
    this.map.fitBounds(bounds);
    this.markerCluster = new MarkerClusterer(this.map, this.markers);
  },
  clearMarkers: function() {
    _.each(this.markers, function(marker) {
      marker.setMap(null);
    });
    this.markerCluster.clearMarkers();
  }
});

This chart assumes that you have selected events or objects on the rows and you would like to show them as markers on Google Maps. The first two columns should contain latitude and longitude of these markers, other column values will be shown in the popup when clicking on the marker. The Google Maps API library will be dynamically loaded from https://maps.googleapis.com/maps/api/js during the first rendering of the chart.

In the beginning of this file, you can see how you can add custom chart types to the list of available report and charts types in Eazy.resultsViews:

  • name will be the display name of the chart type in the Analyze tab.
  • key is the internal key that will be saved in the report definition. It is recommended to use the custom_ prefix to avoid conflicts with the standard chart type keys.
  • view specifies the JavaScript view function that will be used for rendering the results.
  • icon specifies the CSS classes that will be used for the chart type icon. You can use Font Awesome icon names there.

You can also modify the Eazy.resultsViews array if you would like to remove any of the standard chart types that you do not need.

If you will create many custom JavaScript functions then instead of using global function names like CustomGoogleMapsView it is recommended to create just one namespace property with window.YourNamespace = {}; and then use YourNamespace.GoogleMapsView = ... where YourNamespece is, for example, your organization short name which will be unique and not conflict with any other JavaScript library.

Include custom assets

Now create app/views/layouts/_custom_head.html.haml file and include custom JavaScript files that you need for your custom charts:

- if include_custom_report_assets?
  = custom_javascript_include 'markerclusterer', 'custom_charts'

include_custom_report_assets? will return true when it is necessary to load assets for custom charts (when showing reports and dashboards).

If you need to include also custom stylesheet files as well then put stylesheet files in public/eazybi/stylesheets directory and use custom_stylesheet_link method to include them.

Test and debug

If you change the _custom_head.html.haml in the production mode then you will need to restart Private eazyBI to ensure that changed file is used.

Therefore, during the development, it is recommended to set RAILS_ENV to development in your bin/start.sh or bin/start.bat script. In the development mode, if you change _custom_head.html.haml or custom_charts.js, then you can reload the browser page and the new version of the files will be loaded. You can use console.log in the JavaScript code to print debug messages in the console.