2

I'm using MDB charts, in particular, the Radar charts: https://mdbootstrap.com/docs/jquery/javascript/charts/

HTML

<!-- MDB core JavaScript -->
<script type="text/javascript" src="../static/js/mdb.min.js"></script>

<div id='canvas_div'></div>

JavaScript

const ids_list = [0,1,2,3,4,5];
var canvas_div = document.getElementById('canvas_div');
for (var i=0; i<ids_list.length; i++){
    canvas_div.innerHTML += "<canvas id='"+ids_list[i]+"'></canvas>";
    generate_chart(ids_list[i], foo_list1, foo_list2);
}

function generate_chart(chartID,foo_list1,foo_list2){
    var ctxR = document.getElementById(chartID).getContext('2d');
        var myRadarChart = new Chart(ctxR, {
          type: 'radar',
          data: {
            labels: Object.keys(foo_list1),
            datasets: [
              {
                label: "Your FOO LIST1",
                data: Object.values(foo_list1),
                backgroundColor: ['rgba(105, 0, 132, .2)',],
                borderColor: ['rgba(200, 99, 132, .7)',],
                borderWidth: 2
              },{
                label: "Your FOO LIST2",
                data: Object.values(foo_list2),
                backgroundColor: ['rgba(0, 250, 220, .2)',],
                borderColor: ['rgba(0, 213, 132, .7)',],
                borderWidth: 1
              }
            ]
          },
          options: {responsive: false}
        });
}

Now, I'm expecting to get a chart for each value of my ids_list. Instead I'm getting only the last chart. Is it possible that I can't display more than 1 chart per page with MDB charts? Or am I missing something?

My code is working fine if I want to display only one chart.

Reproducible Example:

link to GitHub Repo

Download My example from Google drive

BOUNTY UPDATE: This is actually what I'm trying to do:

enter image description here

As You can see in the inspector, the second generated canvas has the chart attached while the first one no. the following is the generate_chart function as in my application:

//radar
      function generate_chart(chartID,last_scores,best_scores){
        var ctxR = document.getElementById(chartID).getContext('2d');
        console.log(ctxR);
        var myRadarChart = new Chart(ctxR, {
          type: 'radar',
          data: {
            labels: Object.keys(last_scores),
            datasets: [
              {
                label: "Your Last Score",
                data: Object.values(last_scores),
                backgroundColor: ['rgba(105, 0, 132, .2)',],
                borderColor: ['rgba(200, 99, 132, .7)',],
                borderWidth: 2
              },{
                label: "Your Best Score",
                data: Object.values(best_scores),
                backgroundColor: ['rgba(0, 250, 220, .2)',],
                borderColor: ['rgba(0, 213, 132, .7)',],
                borderWidth: 1
              }
            ]
          },
          options: {responsive: false}
        });
        console.log(myRadarChart);
      }

which console log returns the following:

--------------------------------------
CanvasRenderingContext2D

canvas: <canvas id="viRE6FIudKYBbuREGRU14GXLepI3" style="display: block;" height="150">
fillStyle: "rgba(0, 0, 0, 0.1)"
​filter: "none"
​font: "10px \"Helvetica Neue\", \"Helvetica\", \"Arial\", sans-serif"
​globalAlpha: 1
​globalCompositeOperation: "source-over"
​imageSmoothingEnabled: true
​lineCap: "butt"
​lineDashOffset: 0
​lineJoin: "miter"
​lineWidth: 1
​miterLimit: 10
​mozCurrentTransform: Array(6) [ 1, 0, 0, … ]
​mozCurrentTransformInverse: Array(6) [ 1, -0, -0, … ]
​mozImageSmoothingEnabled: true
​mozTextStyle: "10px \"Helvetica Neue\", \"Helvetica\", \"Arial\", sans-serif"
​shadowBlur: 0
​shadowColor: "rgba(0, 0, 0, 0)"
​shadowOffsetX: 0
​shadowOffsetY: 0
​strokeStyle: "rgba(0, 0, 0, 0.1)"
​textAlign: "center"
​textBaseline: "top"
​<prototype>: CanvasRenderingContext2DPrototype { drawImage: drawImage(), beginPath: beginPath(), fill: fill(), … }
read_article.html:625:17
{…}

--------------------------------------
​
"$plugins": Object { descriptors: (3) […], id: 4 }
​
_bufferedRender: false
​_listeners: Object { mousemove: n(), mouseout: n(), click: n()
, … }
​animating: false
​aspectRatio: 2
​boxes: Array(3) [ {…}, {…}, {…} ]
​canvas: <canvas id="viRE6FIudKYBbuREGRU14GXLepI3" style="display: block;" height="150">
​chart: Object { id: 0, width: 300, height: 150, … }
​chartArea: Object { left: 0, top: 32, right: 300, … }
​config: Object { type: "radar", data: {…}, options: {…} }
​controller: Object { id: 0, width: 300, height: 150, … }
​ctx: CanvasRenderingContext2D { mozTextStyle: "10px \"Helvetica Neue\", \"Helvetica\", \"Arial\", sans-serif", mozImageSmoothingEnabled: true, globalAlpha: 1, … }
​currentDevicePixelRatio: 1
​data: 
​height: 150
​id: 0
​lastActive: Array []
​legend: Object { doughnutMode: false, fullWidth: true, position: "top", … }
​options: Object { responsive: false, responsiveAnimationDuration: 0, maintainAspectRatio: true, … }
​scale: Object { id: "scale", type: "radialLinear", hidden: false, … }
​scales: Object { scale: {…} }
​titleBlock: Object { fullWidth: true, position: "top", weight: 2000, … }
​tooltip: Object { _chart: {…}, _chartInstance: {…}, _data: {…}, … }
​width: 300
​<get data()>: function get()​
<set data()>: function set(t)​
<prototype>: Object { construct: construct(e, n), initialize: initialize(), clear: clear()
, … }
read_article.html:648:17

--------------------------------------

CanvasRenderingContext2D
​canvas: <canvas id="Eb31hrWcNgZh1FdGiBxVaz3FOnt1" style="display: block;" height="150">
​fillStyle: "rgba(0, 0, 0, 0.1)"
​filter: "none"
​font: "10px \"Helvetica Neue\", \"Helvetica\", \"Arial\", sans-serif"
​globalAlpha: 1
​globalCompositeOperation: "source-over"
​imageSmoothingEnabled: true
​lineCap: "butt"
​lineDashOffset: 0
​lineJoin: "miter"
​lineWidth: 1
​miterLimit: 10
​mozCurrentTransform: Array(6) [ 1, 0, 0, … ]
​mozCurrentTransformInverse: Array(6) [ 1, -0, -0, … ]
​mozImageSmoothingEnabled: true
​mozTextStyle: "10px \"Helvetica Neue\", \"Helvetica\", \"Arial\", sans-serif"
​shadowBlur: 0
​shadowColor: "rgba(0, 0, 0, 0)"
​shadowOffsetX: 0
​shadowOffsetY: 0
​strokeStyle: "rgba(0, 0, 0, 0.1)"
​textAlign: "center"
​textBaseline: "top"
​<prototype>: CanvasRenderingContext2DPrototype { drawImage: drawImage(), beginPath: beginPath(), fill: fill(), … }
read_article.html:625:17
{…}

--------------------------------------
​
"$plugins": Object { descriptors: (3) […], id: 4 }
​
_bufferedRender: false
​_listeners: Object { mousemove: n(), mouseout: n(), click: n()
, … }
​animating: false
​aspectRatio: 2
​boxes: Array(3) [ {…}, {…}, {…} ]
​canvas: <canvas id="Eb31hrWcNgZh1FdGiBxVaz3FOnt1" style="display: block;" height="150">​
chart: Object { id: 1, width: 300, height: 150, … }
​chartArea: Object { left: 0, top: 32, right: 300, … }
​config: Object { type: "radar", data: {…}, options: {…} }
​controller: Object { id: 1, width: 300, height: 150, … }
​ctx: CanvasRenderingContext2D { mozTextStyle: "10px \"Helvetica Neue\", \"Helvetica\", \"Arial\", sans-serif", mozImageSmoothingEnabled: true, globalAlpha: 1, … }
​currentDevicePixelRatio: 1
​data: 
​height: 150
​id: 1
​lastActive: Array []
​legend: Object { doughnutMode: false, fullWidth: true, position: "top", … }
​options: Object { responsive: false, responsiveAnimationDuration: 0, maintainAspectRatio: true, … }
​scale: Object { id: "scale", type: "radialLinear", hidden: false, … }
​scales: Object { scale: {…} }
​titleBlock: Object { fullWidth: true, position: "top", weight: 2000, … }
​tooltip: Object { _chart: {…}, _chartInstance: {…}, _data: {…}, … }
​width: 300
​<get data()>: function get()​
<set data()>: function set(t)​
<prototype>: Object { construct: construct(e, n), initialize: initialize(), clear: clear(), … }

I also add here the for loop in which I call the generate_chart function:

function populate_leaderboard(authorID,submissions,profiles){
        var leaderboard_div = document.getElementById('leaderboard_feed');
        leaderboard_div.innerHTML = '';
        scores_dict = {};
        last_submissions = {};
        best_submissions = {};
        for (var i=0; i<submissions.length; i++){
          sub_user = submissions[i];
          for (var k=0; k<Object.keys(sub_user).length; k++){
            sub = sub_user[Object.keys(sub_user)[k]];
            if (scores_dict[sub['author']]){
              if (scores_dict[sub['author']] < sub['weighted_score']){
                scores_dict[sub['author']] = sub['weighted_score'];
                best_submissions[sub['author']] = sub['scores'];
              }
            } else {
              scores_dict[sub['author']] = sub['weighted_score'];
              best_submissions[sub['author']] = sub['scores'];
            }
            last_submissions[sub['author']] = sub['scores'];
          }
        }

        const lead_array = Object.entries(scores_dict).sort(([,a], [,b]) => b-a).map(([p]) => p);

        let medals = {
          1 : 'Gold Medal',
          2 : 'Silver Medal',
          3 : 'Bronze Medal'
        }

        for (var i=0; i<lead_array.length; i++){
          var medal = '';
          var u = lead_array[i];
          var position = i+1;
          if (i>0 && scores_dict[u] == scores_dict[lead_array[i-1]]) {
            position = position-1;
          }
          medal = medals[position]
          var temp_feed = '';
          temp_feed += "<div class='horiz_line'></div><div class='row centered'>"
          temp_feed += "<div class='col'><img class='profile_picture_leaderboard nomargin' src='"+profiles[u]['profile']['img']+"' alt='profile_picture'>";
          temp_feed += "<p class='nomargin'>"+profiles[u]['profile']['name']+" "+profiles[u]['profile']['surname']+"</p></div><div class='col' align='center'>";
          temp_feed += "<canvas id='"+u+"'></canvas></div><div class='col'>";
          temp_feed += "<p>"+position+" of "+lead_array.length+" on the <strong>Public</strong> Leaderboard</p>";
          temp_feed += "<h3>"+medal+"</h3><div class='progress'>";

          var delta = lead_array.length - (position-1);
          if (delta == 0){
            delta += 0.1;
          }
          var progress = Math.round((delta / lead_array.length) * 100);
          temp_feed += "<div class='progress-bar' role='progressbar' style='width:"+progress+"%' aria-valuenow='"+progress+"' aria-valuemin='0' aria-valuemax='100'></div></div>";
          if (UID == authorID || UID == u){
            temp_feed += "<button style='font-size:70%; margin-top:10px;'>Download Submission <i class='fas fa-cloud-download-alt'></i></button>";
          }
          temp_feed += "</div></div>";
          leaderboard_div.innerHTML += temp_feed;
          generate_chart(u,last_submissions[u],best_submissions[u]);
        }

      }

Fabio Magarelli
  • 1,031
  • 4
  • 14
  • 47

1 Answers1

1

here, with each loop, you are overwriting the content of canvas_div
such that at the end of the loop, only one canvas exists.

canvas_div.innerHTML = "<canvas id='"+ids_list[i]+"'></canvas>";

you should appending a new canvas with each loop.

canvas_div.innerHTML += "<canvas id='"+ids_list[i]+"'></canvas>";

EDIT

rather than adding the html as a string, here...

canvas_div.innerHTML += "<canvas id='"+ids_list[i]+"'></canvas>";

create the canvas element using --> document.createElement

var canvas = canvas_div.appendChild(document.createElement('canvas'));
canvas.id = chartID;

see following working snippet...

<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
    <link href="https://fonts.googleapis.com/css?family=Lato:300,300i,400,400i,700,700i,900&display=swap" rel="stylesheet">
    <link href="https://fonts.googleapis.com/css?family=Noto+Serif:400,400i,700,700i&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">


    <script src="https://kit.fontawesome.com/c346c28ff4.js" crossorigin="anonymous"></script>

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <!-- AJAX -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
    <!-- MDB core JavaScript -->
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mdbootstrap/4.14.1/js/mdb.min.js"></script>

  </head>

  <body>


    <div id='canvas_div'></div>

    <script>

      const foo_list1 = [1,2,3,4,5];
      const foo_list2 = [5,4,3,2,1];

      const ids_list = [0,1,2,3,4,5];
      var canvas_div = document.getElementById('canvas_div');
      for (var i=0; i<ids_list.length; i++){
          generate_chart(ids_list[i], foo_list1, foo_list2);
      }

      function generate_chart(chartID,foo_list1,foo_list2){
          var canvas = canvas_div.appendChild(document.createElement('canvas'));
          canvas.id = chartID;
          var ctxR = canvas.getContext('2d');
          var myRadarChart = new Chart(ctxR, {
            type: 'radar',
            data: {
              labels: Object.keys(foo_list1),
              datasets: [
                {
                  label: "Your FOO LIST1",
                  data: Object.values(foo_list1),
                  backgroundColor: ['rgba(105, 0, 132, .2)',],
                  borderColor: ['rgba(200, 99, 132, .7)',],
                  borderWidth: 2
                },{
                  label: "Your FOO LIST2",
                  data: Object.values(foo_list2),
                  backgroundColor: ['rgba(0, 250, 220, .2)',],
                  borderColor: ['rgba(0, 213, 132, .7)',],
                  borderWidth: 1
                }
              ]
            },
            options: {responsive: false}
          });
      }

    </script>

  </body>
</html>

EDIT 2

following the approach from the first solution, try the following...

in the populate_leaderboard function,
remove the canvas element from the temp_feed and add the id to parent <div>.
see following snippet..

    for (var i=0; i<lead_array.length; i++){
      var medal = '';
      var u = lead_array[i];
      var position = i+1;
      if (i>0 && scores_dict[u] == scores_dict[lead_array[i-1]]) {
        position = position-1;
      }
      medal = medals[position]
      var temp_feed = '';
      temp_feed += "<div class='horiz_line'></div><div class='row centered'>"
      temp_feed += "<div class='col'><img class='profile_picture_leaderboard nomargin' src='"+profiles[u]['profile']['img']+"' alt='profile_picture'>";

      // remove canvas, add id
      temp_feed += "<p class='nomargin'>"+profiles[u]['profile']['name']+" "+profiles[u]['profile']['surname']+"</p></div><div class='col' align='center' id='"+u+"'>";
      temp_feed += "</div><div class='col'>";
      temp_feed += "<p>"+position+" of "+lead_array.length+" on the <strong>Public</strong> Leaderboard</p>";
      temp_feed += "<h3>"+medal+"</h3><div class='progress'>";

      var delta = lead_array.length - (position-1);
      if (delta == 0){
        delta += 0.1;
      }
      var progress = Math.round((delta / lead_array.length) * 100);
      temp_feed += "<div class='progress-bar' role='progressbar' style='width:"+progress+"%' aria-valuenow='"+progress+"' aria-valuemin='0' aria-valuemax='100'></div></div>";
      if (UID == authorID || UID == u){
        temp_feed += "<button style='font-size:70%; margin-top:10px;'>Download Submission <i class='fas fa-cloud-download-alt'></i></button>";
      }
      temp_feed += "</div></div>";
      leaderboard_div.innerHTML += temp_feed;
      generate_chart(u,last_submissions[u],best_submissions[u]);
    }

  }

then in the generate_chart function,
use a similar approach as previous solution.
see following snippet...

  function generate_chart(chartID,last_scores,best_scores){
    var canvas_div = document.getElementById(chartID);
    var canvas = canvas_div.appendChild(document.createElement('canvas'));
    var ctxR = canvas.getContext('2d');
    var myRadarChart = new Chart(ctxR, {
      type: 'radar',
      data: {
        labels: Object.keys(last_scores),
        datasets: [
          {
            label: "Your Last Score",
            data: Object.values(last_scores),
            backgroundColor: ['rgba(105, 0, 132, .2)',],
            borderColor: ['rgba(200, 99, 132, .7)',],
            borderWidth: 2
          },{
            label: "Your Best Score",
            data: Object.values(best_scores),
            backgroundColor: ['rgba(0, 250, 220, .2)',],
            borderColor: ['rgba(0, 213, 132, .7)',],
            borderWidth: 1
          }
        ]
      },
      options: {responsive: false}
    });
  }

EDIT 3

another approach would be to load all the content first,
then afterwards, go back and add the charts.

see the following working snippet,
all of the content is added before trying to draw the charts.
a class name is used to find the canvas elements...

<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
    <link href="https://fonts.googleapis.com/css?family=Lato:300,300i,400,400i,700,700i,900&display=swap" rel="stylesheet">
    <link href="https://fonts.googleapis.com/css?family=Noto+Serif:400,400i,700,700i&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">


    <script src="https://kit.fontawesome.com/c346c28ff4.js" crossorigin="anonymous"></script>

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <!-- AJAX -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
    <!-- MDB core JavaScript -->
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mdbootstrap/4.14.1/js/mdb.min.js"></script>

  </head>

  <body>


    <div id='canvas_div'></div>

    <script>

      const foo_list1 = [1,2,3,4,5];
      const foo_list2 = [5,4,3,2,1];
      const ids_list = [0,1,2,3,4,5];
      var canvas_div = document.getElementById('canvas_div');

      for (var i=0; i<ids_list.length; i++){
        canvas_div.innerHTML += "<canvas class='chart' id='"+ids_list[i]+"'></canvas>";
      }

      var charts = document.getElementsByClassName('chart');
      Array.prototype.forEach.call(charts, function(canvas) {
        var ctxR = canvas.getContext('2d');
        var myRadarChart = new Chart(ctxR, {
          type: 'radar',
          data: {
            labels: Object.keys(foo_list1),
            datasets: [
              {
                label: "Your FOO LIST1",
                data: Object.values(foo_list1),
                backgroundColor: ['rgba(105, 0, 132, .2)',],
                borderColor: ['rgba(200, 99, 132, .7)',],
                borderWidth: 2
              },{
                label: "Your FOO LIST2",
                data: Object.values(foo_list2),
                backgroundColor: ['rgba(0, 250, 220, .2)',],
                borderColor: ['rgba(0, 213, 132, .7)',],
                borderWidth: 1
              }
            ]
          },
          options: {responsive: false}
        });
      });

    </script>

  </body>
</html>
WhiteHat
  • 59,912
  • 7
  • 51
  • 133
  • Hi, yes that was a rookie mistake but unfortunately only a typo here on stackoverflow, in my code, I'm actually using a += already. I attach a reproducible example to my original post, can you check it out? thanks – Fabio Magarelli Mar 24 '20 at 08:26
  • I'll have a look tomorrow and I'll let you know, thank you! :) – Fabio Magarelli Mar 24 '20 at 16:45
  • WhiteHat this is working fine in the example but still not working in my project for some reason... to help me figure it out, can you explain me why the first approach was't working in the example? Why did you have to create the canvas in the generate_chart function instead of getting it by id once already created in the for loop? – Fabio Magarelli Mar 25 '20 at 10:32
  • that part I couldn't figure out. I tried many ways using id at first, none worked. but as soon as i tried this approach, no problem... – WhiteHat Mar 25 '20 at 11:12
  • Thanks man, that's disappointing when you can't figure out why things doesn't work. I'll try to adopt your solution to my code anyway. – Fabio Magarelli Mar 25 '20 at 12:28
  • my only guess is it has something to do with the canvas element, or mdb itself. I have similar code for google charts that uses id and works, with div elements... – WhiteHat Mar 25 '20 at 12:34
  • Sorry to bother you again, what if I want to plot the canvas in 2 different canvas_div? – Fabio Magarelli Mar 26 '20 at 09:32
  • yes, no matter the approach, if want to create a chart for each player (see bounty edit), I'm able to create the canvas but the chart doesn't get drawn – Fabio Magarelli Mar 26 '20 at 12:53
  • I tried exactly like that to use your first approach in my app but is giving the same results: It works if I put all the charts in the same div at the end of the page but not if I want to make a chart for each player (in different divs). The canvas is created, however the chart is not drawn. – Fabio Magarelli Mar 26 '20 at 13:54
  • Well done! EDIT3 Solved my problem (even if I'm not sure why) XD. Please if you have an explaination, feel free to add it :) – Fabio Magarelli Mar 26 '20 at 17:07