All eazyBI for Jira eazyBI for Confluence Private eazyBI

Custom chart for Private eazyBI example - Google Maps
Private eazyBI

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

Private eazyBI by default includes similar OpenStreetMap view 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/javascripts directory in your Private eazyBI directory eazybi_private.

Then download https://raw.githubusercontent.com/googlemaps/js-marker-clusterer/gh-pages/src/markerclusterer.js and save it as public/javascripts/markerclusterer.js. To use the Google Maps API you need a key, please read here how to get the key. When you got the key put it into the GOOGLE_API_KEY variable at the top of the code example.

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

var GOOGLE_API_KEY = "";
 
Eazy.resultsViews.push({
  name: "Google Maps",
  key: "custom_google_maps",
  view: "CustomGoogleMapsView",
  icon: "fa fa-map-marker"
});
CustomGoogleMapsView = Eazy.ChartView.extend({
  infoWindowTemplate: _.template('<div class="content">' +
    '<div><strong><%- title %></strong></div>' +
    '</div>'),
  infoWindowDataTemplate: _.template('<div><%- name %>: <%- formattedValue %></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?key=" + GOOGLE_API_KEY + "\" 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.infoWindowContent(title, 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);
  },
  infoWindowContent: function(title, data) {
    self = this;
    dataHtml = _.map(data, function(item){
      return self.infoWindowDataTemplate({name: item.name, formattedValue: item.formattedValue})
    }).join("");

    return self.infoWindowTemplate({title: title}) + dataHtml
  },
  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/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.