Adding a NVD3 chart into a Leaflet popup

My AngularJs web application incorporates a map built with Leaflet.js and Leaflet markercluster. For rendering charts, I utilize nvd3.js along with the nvd3-angular-directive.

The data I have represents various countries worldwide, each displayed on the map as a CircleMarker based on the data structure below:

{
  US : {country:"US", count:10, rate: {high: 3, medium: 4, low: 3}},
  IT : {country:"IT", count:4, rate: {high: 1, medium: 1, low: 2}}
}

This indicates that a CircleMarker will be shown at the center of the USA and Italy in the map.

The main challenge arises when I attempt to bind a popup containing an nvd3 pie (specifically a donut) chart for each respective nation's rate distribution upon clicking on the CircleMarker. Despite the markers displaying correctly on the map, the popups appear empty when clicked.


Here is my approach to building the CircleMarkers and clusters:

var markers = L.markerClusterGroup({
  maxClusterRadius: 120,
  iconCreateFunction: function (cluster) {
    return L.divIcon({html: '<b>' + cluster.getChildCount() + '</b>', className: 'mycluster', iconSize: L.point(40, 40)});
  }
})

var geolocData = {
  US : {country:"US", count:10, rate: {high: 3, medium: 4, low: 3}},
  IT : {country:"IT", count:4, rate: {high: 1, medium: 1, low: 2}}
}

for (key in geolocData){

  var latlng = retrieveLatLongFromCountryCode(key)
  var marker = new L.circleMarker(latlng, {stroke: false, className:'the-marker-class', fillOpacity: 1, color:'#00FF00', weight: 1})

  var popupChartOptions = {
    chart: {
      type: 'pieChart',
      height: 140,
      donut: true,
      donutRatio: 0.40,
      x: function(d) { return d.label },
      y: function(d) { return d.value },
      color: [
        '#2ecc71',
        '#f1c40f',
        '#e74c3c',
      ],
      valueFormat: d3.format(',.0d')
    }
  }
  var popupChartData = [{
    label: 'low',
    value: geolocData[key].rate.low
  }, {
    label: 'medium',
    value: geolocData[key].rate.medium
  }, {
    label: 'high',
    value: geolocData[key].rate.high
  }]

  marker.bindPopup('<nvd3 options="chartOptions" data="chartData"></nvd3>')
  markers.addLayers(marker)
}

map.addLayer(markers)

UPDATE

I've made adjustments by extending the CircleMarker class:

var customCircleMarker = L.CircleMarker.extend({
  options: { 
    countrycode: '',
    rates: {},
    count: 0
  }
})

Subsequently, I implement this CustomCircleMarker within my map:

var marker = new customCircleMarker(latlng, {stroke: false, className: 'the-class-name', fillOpacity: 1, weight: 1})

var popupChartData = [{
    label: 'low',
    value: [geolocLoad[key].rate.low]
}, {
    label: 'medium',
    value: [geolocLoad[key].rate.medium]
}, {
    label: 'high',
    value: [geolocLoad[key].rate.high]
}]

Then, I bind the popup, populate the marker with custom data, and establish an on click callback which emits relevant information:

marker.bindPopup('<div id="chart-' + key + '"></div>')
marker.countrycode = key
marker.rates = popupChartData
marker.count = geolocLoad[key].count

marker.on('click', function(e){
  $scope.$emit('marker:click', this.countrycode, this.count, this.rates)
})

The receiver processes the data and renders the chart accordingly:

$scope.$on('marker:click', function(caller, countrycode, count, rates){
  console.log('received', countrycode, count, rates)

  var width = 500,
      height = 500

  nv.addGraph(function(){
    var chart = nv.models.pieChart()
      .x(function(d) { return d.label })
      .y(function(d) { return d.value })
      .width(width)
      .height(height);

    d3.select("#chart-"+countrycode)
      .datum(rates)
      .attr('width', width)
      .attr('height', height)
      .call(chart);

    return chart;
  })
})

Unfortunately, this revised approach is still not yielding desired results. https://i.sstatic.net/0oHDK.png

NOTE: I opted against using the angular-leaflet-directive due to personal preference. Do you believe it would be beneficial for me to reconsider utilizing it?

Answer №1

I managed to troubleshoot the problem on my own and decided to share my solution as it could benefit other users in the future.

The key issue I overlooked was not including an <svg> element within the popup, which prevented the chart from rendering properly due to the absence of a 'canvas' for the library to render on.

Here is how the marker was generated:

var marker = new customCircleMarker(countries.names[c].latlng, {stroke: false, className: 'the-class-name', fillOpacity: 1, weight: 1})

marker.bindPopup('<div id="chart-' + key + '" class="country-popup-chart-container"><svg class="country-popup-chart"></svg></div>', {closeButton: false})
marker.countrycode = key
marker.rates = geolocLoad[key].rate
marker.count = geolocLoad[key].count

marker.on('click', function(e){
  $scope.$emit('marker:click', this.countrycode, this.count, this.rates)
})

The chart generation is triggered by an $emit event on click:

$scope.$on('marker:click', function(caller, countrycode, count, rates) {

  var popupChartData = [{
    label: 'low',
    value: rates.low,
    color: '#2ecc71'
  }, {
    label: 'medium',
    value: rates.medium,
    color: '#f1c40f'
  }, {
    label: 'high',
    value: rates.high,
    color: '#e74c3c'
  }]

  nv.addGraph(function() {
    var chart = nv.models.pieChart()
      .x(function(d) { return d.label })
      .y(function(d) { return d.value })
      .showLabels(false)
      .donut(true)
      .donutRatio(0.5)

    d3.select('#chart-' + countrycode + ' svg')
      .datum(popupChartData)
      .call(chart)

    return chart
  })
}) 

The size is adjusted with the following CSS:

div.country-popup-chart-container{
  height: 80px;
  width: 200px;
}

Here is the final outcome:

https://i.sstatic.net/FEyLM.png

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Jenkins integration with a MEAN stack application: A step-by-step guide

I'm working on a Mean stack application and looking to integrate Jenkins CI into my workflow. I am uncertain about the steps needed to accomplish this task. Currently, I use bower for installing frontend packages and npm for other functionalities. ...

When the mouse is released, modify the button

Looking for assistance with changing the background color of a button when clicked, without any post back. I have multiple buttons and want only the clicked one to change color while the others stay the same. Any suggestions on how to achieve this? ...

What is the most effective method to include JSON data containing various IDs at the end within an $http.get() request in AngularJS?

I'm facing a challenge with displaying JSON items that have an array of different ids at the end of the URL (/api/messages/:messageId). For instance, accessing /api/messages/12345 would return {"subject":"subject12345","body":"body12345","id":"12345"} ...

Tips for maintaining a database connection pool in hooks.server.js while utilizing hot reload in SvelteKit using Vite.js and mariadb

I'm currently utilizing SvelteKit, Vite.js, and the mariadb package with Node.js in my application. In my db.js file, I have the code snippet below: import mariadb from 'mariadb'; const databaseConnectionPoolConfig = { ... }; let databas ...

AngularJs fails to apply style to dynamic content in IE8 when using HTML5

Currently, I am in the process of developing a website with AngularJs and utilizing numerous JS scripts to address compatibility issues with Internet Explorer. I have organized the data in JSON format, and each page is designed to fetch and display differ ...

Error: self has not been declared

We have come across an obstacle. "Reference Error: self is not defined' When attempting to utilize React-data-grid, the problem arises on the server side when building the nodejs application with webpack. The issue is noticeable in the generated b ...

Enhance the appearance of the jQuery document.ready function

I'm attempting to customize the jQuery document.ready function <html> <head> <script src="http://code.jquery.com/jquery-1.11.0.min.js"></script> <script type="text/javascript> $(function() { c ...

storing HTML elements in Chrome's cache

I have developed a content uploading module that utilizes the file API. The HTML code is <input type="file" id="filepicker-input" multiple="true" style="display:none;"/> <div class="addfile_btndiv" id="addfile_btndiv"> ...

Arranging and structuring Handlebars templates

In this particular example... http://example.com/1 ...I am experimenting with organizing a Handlebars template and its corresponding content in separate files, as opposed to having them all combined in the HTML file or JavaScript script, like in this con ...

I can see the JSON data printing to the console in Ionic 3, but it doesn't display on

I seem to be facing a challenge with passing the 'item' to my search function in my Ionic 3 app. Although I was able to successfully connect to a json data file and print objects to the console, I am encountering an error message on the page that ...

How to Choose Two Checkboxes from a Set of Multiple Checkboxes in AngularJS

I am looking to choose just two checkboxes from a selection of multiple checkboxes that are not nested in a list. <input type="checkbox" class="two-checkbox" id="curr-EUR" ng-model="reqObject.EUR" ng-click="checkChanged()">EUR</input> <inpu ...

Looking to show an image when a checkbox is ticked and hide it when it is unticked in HTML?

How do I make a specific image appear when a user clicks on a checkbox? I'm unsure if I should use jQuery, Ajax, or another method. What is the best approach for this task? Your assistance would be greatly appreciated. I have successfully created the ...

Node.js socket.io emit function not functioning properly

In my current configuration, I have the following setup: app.set('port', process.env.PORT || 3000); var httpserver = http.createServer(app).listen(app.get('port'), function(){ console.log('Express server listening on port ' ...

Guide to resolving domain names in express.js

I've been working on an expressJS script that includes a mongoDB fetch. My objective is to create an API that displays my JSON-based test list on the /api/testlist route. When I try to access the index page, everything seems to be working fine. Howev ...

Issue with React component not receiving dataThe values are not being

Currently, I am a beginner in using React and as part of my training, I have embarked on a project to create a simple book ranking system. In this project, the user enters the title of the book they would like to vote for. If the input field is left empty, ...

The pop-up window created programmatically refuses to close

I am struggling with an issue where I am opening a pop-up from the code behind as a waiting image while processing some background activities. However, the pop-up does not close after the activity is done. Can someone please help me figure out what I am ...

Data does not have a permanent home in MongoDB

Recently, I delved into the world of nodejs, mongoose, and express with the intention of creating a basic twitter clone. However, I encountered an issue where nothing happens when I attempt to submit a new tweet. Below is the code snippet I'm working ...

Problem: Repeated attempts to open the popup are unsuccessful

Developed a leaflet map featuring markers that open popups when clicked. Also integrated a search bar for marker searchability. However, encountering an issue where each popup can only be opened once and on selecting a marker name, the zoom functionality w ...

react-widgets: deciding on the return value for the onSearch function in Multiselect

I'm currently experimenting with react-widgets and utilizing the onSearch function in conjunction with the Multiselect component. Even though I can see that onSearch is being called with the searchTerm, I am unable to incorporate the response into the ...

Update the appearance of an element in real-time using VUEJS

I am trying to use VueJS to dynamically change the position of a div. In the data function, I have a variable called x that I want to assign to the top property. However, the code I wrote doesn't seem to be working. Here is what it looks like: <tem ...