Dom-to-image printing only first chart

I have an Ionic page (page 2) that receives an id number, start date and end date from the previous page (page 1), sends a JSON to the API and receives two arrays: one for dates and one for measurements. Then, the arrays are put in a graph and shown.

When the user clicks the button “print to pdf”, the library dom-to-image transforms the canvas in which the graph is placed into a png image and puts it in a generated PDF file which is finally stored in the users device.

If the user presses the button “print to pdf” once, the process works fine.

If the user presses the button “print to pdf” once, receives the pdf, comes back to page 1, inputs other dates to recalculate the graph and presses another time the button “print to pdf”, the the graph shown in the pdf is the previous, not the newly generated. However, the graph shown in the screen is the newly generated…

Here the code for the graph population:

(loadQuarantineStatsDefault()) is loaded in ionViewWillEnter()

  loadQuarantineStatsDefault(){
    return new Promise(resolve => {
  		let body = {
        serial_numb : this.serial_number,
        fecha_inicio : this.fecha_inicio,
        fecha_final : this.fecha_final
      };
                 
      this.postPvdr.postData(body, 'proses_tower.php').subscribe(array_data => {
        this.array_data = array_data['array_data'];
        resolve(true);

        for(var i:number=0; i<this.array_data.length; i++){
          this.array_dates[i] = this.array_data[i]['fecha_hora'];
          this.array_co2[i] = this.array_data[i]['co2'];
          this.array_tvoc[i] = this.array_data[i]['tvoc'];
          this.array_temperature[i] = this.array_data[i]['temperature'];
          this.array_humidity[i] = this.array_data[i]['humidity'];
        }
        
        this.array_dates.reverse();
        this.array_co2.reverse();
        this.array_tvoc.reverse();
        this.array_temperature.reverse();
        this.array_humidity.reverse();

        if(this.data_selector=="co2"){
          this.array_selected_data = this.array_co2;
          this.array_label = 'Medición de CO₂ (ppm)';
          this.backgroundColor1 = 'white';
          this.yAxisSegement1 = 0;
          this.backgroundColor2 = 'white'; 
          this.yAxisSegement2 = 0;
          this.backgroundColor3 = 'white'; 
          this.yAxisSegement3 = 0;
          this.backgroundColor4 = 'white'; 
          this.yAxisSegement4 = 0;
          this.backgroundColor5 = 'rgb(0, 184, 175, 0.4)'; 
          this.yAxisSegement5 = 600;
          this.backgroundColor6 = 'rgb(255, 235, 59, 0.4)'; 
          this.yAxisSegement6 = 1000;
          this.backgroundColor7 = 'rgb(255, 152, 0, 0.4)'; 
          this.yAxisSegement7 = 1500;
          this.backgroundColor8 = 'rgb(255, 87, 34, 0.4)'; 
          this.yAxisSegement8 = 2500;
          this.backgroundColor9 = 'rgb(229, 57, 53, 0.4)'; 
          this.yAxisSegement9 = Infinity;
        }

        if(this.data_selector=="tvoc"){
          this.array_selected_data = this.array_tvoc;
          this.array_label = 'Medición de TVOC (ppb)';
          this.backgroundColor1 = 'white';
          this.yAxisSegement1 = 0;
          this.backgroundColor2 = 'white'; 
          this.yAxisSegement2 = 0;
          this.backgroundColor3 = 'white'; 
          this.yAxisSegement3 = 0;
          this.backgroundColor4 = 'white'; 
          this.yAxisSegement4 = 0;
          this.backgroundColor5 = 'rgb(0, 184, 175, 0.4)'; 
          this.yAxisSegement5 = 333;
          this.backgroundColor6 = 'rgb(255, 235, 59, 0.4)'; 
          this.yAxisSegement6 = 1000;
          this.backgroundColor7 = 'rgb(255, 152, 0, 0.4)'; 
          this.yAxisSegement7 = 3333;
          this.backgroundColor8 = 'rgb(255, 87, 34, 0.4)'; 
          this.yAxisSegement8 = 8332;
          this.backgroundColor9 = 'rgb(229, 57, 53, 0.4)'; 
          this.yAxisSegement9 = Infinity;
        }

        if(this.data_selector=="temperatura"){
          this.array_selected_data = this.array_temperature;
          this.array_label = 'Medición de Temperatura (ºC)';
          this.backgroundColor1 = 'rgb(229, 57, 53, 0.4)';
          this.yAxisSegement1 = 9;
          this.backgroundColor2 = 'rgb(255, 87, 34, 0.4)'; 
          this.yAxisSegement2 = 11;
          this.backgroundColor3 = 'rgb(255, 152, 0, 0.4)'; 
          this.yAxisSegement3 = 17;
          this.backgroundColor4 = 'rgb(255, 152, 0, 0.4)'; 
          this.yAxisSegement4 = 18;
          this.backgroundColor5 = 'rgb(0, 184, 175, 0.4)'; 
          this.yAxisSegement5 = 25;
          this.backgroundColor6 = 'rgb(255, 152, 0, 0.4)'; 
          this.yAxisSegement6 = 26;
          this.backgroundColor7 = 'rgb(255, 152, 0, 0.4)'; 
          this.yAxisSegement7 = 31;
          this.backgroundColor8 = 'rgb(255, 87, 34, 0.4)'; 
          this.yAxisSegement8 = 33;
          this.backgroundColor9 = 'rgb(229, 57, 53, 0.4)'; 
          this.yAxisSegement9 = Infinity;
        }

        if(this.data_selector=="humedad"){
          this.array_selected_data = this.array_humidity;
          this.array_label = 'Medición de Humedad (%)';
          this.backgroundColor1 = 'rgb(229, 57, 53, 0.4)';
          this.yAxisSegement1 = 15;
          this.backgroundColor2 = 'rgb(255, 87, 34, 0.4)'; 
          this.yAxisSegement2 = 20;
          this.backgroundColor3 = 'rgb(255, 152, 0, 0.4)'; 
          this.yAxisSegement3 = 35;
          this.backgroundColor4 = 'rgb(255, 235, 59, 0.4)'; 
          this.yAxisSegement4 = 40;
          this.backgroundColor5 = 'rgb(0, 184, 175, 0.4)'; 
          this.yAxisSegement5 = 50;
          this.backgroundColor6 = 'rgb(255, 235, 59, 0.4)'; 
          this.yAxisSegement6 = 60;
          this.backgroundColor7 = 'rgb(255, 152, 0, 0.4)'; 
          this.yAxisSegement7 = 65;
          this.backgroundColor8 = 'rgb(255, 87, 34, 0.4)'; 
          this.yAxisSegement8 = 80;
          this.backgroundColor9 = 'rgb(229, 57, 53, 0.4)'; 
          this.yAxisSegement9 = Infinity;      
        }

        if(this.serial_numb_confirm==undefined || this.tower_name == undefined){
          this.loadQuarantineStatsDefault(this.username);
        }

        if (this.bars) this.bars.destroy();

        this.bars = new Chart(this.barChart.nativeElement, {
          type: 'line',
          data: {
            labels: this.array_dates,
            datasets: [{
              label: this.array_label,
              data: this.array_selected_data,
              backgroundColor: 'rgb(27, 18, 64, 0.0)', 
              borderColor: '#00b8af',
              borderWidth: 3,
              pointBackgroundColor:'rgba(0, 0, 0, 0.1)',
              pointBorderWidth: 1,
              pointHitRadius: 8,
              pointHoverBorderWidth: 1,
              pointHoverRadius: 4,
              pointRadius: 3,
            }]
          },
          options: {
            title: {
              display: true,
              text: 'TOWER "' + this.tower_name + '" (' + this.serial_numb_confirm + ')'
            },  
            backgroundRules: [{
              backgroundColor: this.backgroundColor1, 
              yAxisSegement: this.yAxisSegement1
          }, {
              backgroundColor: this.backgroundColor2, 
              yAxisSegement: this.yAxisSegement2
          }, {
              backgroundColor: this.backgroundColor3, 
              yAxisSegement: this.yAxisSegement3
          }, {
              backgroundColor: this.backgroundColor4, 
              yAxisSegement: this.yAxisSegement4
          }, {
              backgroundColor: this.backgroundColor5, 
              yAxisSegement: this.yAxisSegement5
          }, {
              backgroundColor: this.backgroundColor6, 
              yAxisSegement: this.yAxisSegement6
          }, {
              backgroundColor: this.backgroundColor7, 
              yAxisSegement: this.yAxisSegement7
          }, {
              backgroundColor: this.backgroundColor8, 
              yAxisSegement: this.yAxisSegement8
          }, {
              backgroundColor: this.backgroundColor9, 
              yAxisSegement: this.yAxisSegement9
          }],
            responsive: true,
            maintainAspectRatio: false,
            scales: {
              yAxes: [{
                ticks: {
                  beginAtZero: true,
                }
              }],
              xAxes: [{
                ticks: {
                  beginAtZero: true,
                  autoSkip: true,
                  maxRotation: 90,
                  minRotation: 90,
                  maxTicksLimit: 24
                }
              }]
            }
          },
          plugins: [{
            beforeDraw: function (chart) {
                var ctx = chart.chart.ctx;
                var ruleIndex = 0;
                var rules = chart.chart.options.backgroundRules;
                var yaxis = chart.chart.scales["y-axis-0"];
                var xaxis = chart.chart.scales["x-axis-0"];
                var partPercentage = 1 / (yaxis.ticksAsNumbers.length - 1);
                for (var i = yaxis.ticksAsNumbers.length - 1; i > 0; i--) {
                    if (yaxis.ticksAsNumbers[i] < rules[ruleIndex].yAxisSegement) {
                        ctx.fillStyle = rules[ruleIndex].backgroundColor;
                        ctx.fillRect(xaxis.left, yaxis.top + ((i - 1) * (yaxis.height * partPercentage)), xaxis.width, yaxis.height * partPercentage);
                    } else {
                        ruleIndex++;
                        i++;
                    }
                }
            }
        }]

        });   

      });
            
    });
    
  }

Here the code for the graph saving into pdf:

public saveGraph() {
    var div = document.getElementById("barChart");
    console.log('div', div);
    var options = { background: "white", quality: 0.99, height: 800, width: 800, cacheBust: true};
    domtoimage.toPng(div, options).then((dataUrl)=> {
      //Initialize JSPDF
      var doc = new jspdf("p","mm","a4");
      //Add image Url to PDF
      var graph = new Image();
      graph.src = dataUrl;
      doc.addImage(graph, 'PNG', 20, 40, 170, 128);
      var img = new Image();
      img.src = 'https://xx.com/xx/logos/color_compressed.png';
      doc.addImage(img, "PNG", 163, 5, 40, 23); 


      let pdfOutput = doc.output();
      // using ArrayBuffer will allow you to put image inside PDF
      let buffer = new ArrayBuffer(pdfOutput.length);
      let array = new Uint8Array(buffer);
      for (var i = 0; i < pdfOutput.length; i++) {
          array[i] = pdfOutput.charCodeAt(i);
      }
  
  
      //This is where the PDF file will stored , you can change it as you like
      // for more information please visit https://ionicframework.com/docs/native/file/
      const directory = this.file.dataDirectory ;
      const fileName = "Exportación_gráfico_mediciones.pdf" + new Date().getTime();
      let options: IWriteOptions = { replace: true };
  
        //Writing File to Device
        this.file.writeFile(directory,fileName,buffer, options)
        .then((success)=> {
          console.log("File created Succesfully" + JSON.stringify(success));
          this.fileOpener.open(this.file.dataDirectory + fileName, 'application/pdf')
        })
        .catch((error)=> {
          console.log("Cannot Create File " +JSON.stringify(error));
        });
        //Writing File to Device
        this.file.writeFile(directory,fileName,buffer)
        .then((success)=> {
          console.log("File created Succesfully" + JSON.stringify(success));
          this.fileOpener.open(this.file.dataDirectory + fileName, 'application/pdf')
            .then(() => console.log('File is opened'))
            .catch(e => console.log('Error opening file', e));
        })
        .catch((error)=> {
          console.log("Cannot Create File " +JSON.stringify(error));
        });
    })
    .catch(function (error) {
      console.error('oops, something went wrong!', error);
    });
  
  }

This function is doing way too much, and I think breaking it down should design away the race condition you have created.

I find the “assembly line” metaphor very useful for tasks like this.

First, a useful type:

type AtmosphericField = "co2" | "tvoc" | "temperatura" | "humedad";

We’re going to move a lot of your huge function into a separately injected service. One thing this service can do is to take a:

interface RangeRequest {
  serialNumber: string;
  fechaInicio: Date;
  fechaFinal: Date;
}

…and turn it into an array of:

interface AtmosphericConditions {
  fecha_hora: Date;
  co2: number;
  tvoc: number;
  temperature: number;
  humidity: number;
}

I don’t know (and in a perfect world, even you should try assume as little about, so that you have more freedom to change it later) what the timing is as far as when you fetch new data from the server and when you simply change what part of cached responses you’re looking at, so I’m going to just assume that every time you switch what you’re calling data_selector, we hit the backend. It should be fairly straightforward to have your service manage a cache if you want to.

I’ve conveniently made all your “data” fields numbers here. Things get slightly more complicated if that’s not true, but if it is, then regardless of data_selector, what we want back from the service ends up looking like an array of this:

interface AtmosphericMeasurement<K extends AtmosphericField> {
  recorded: Date;
  value: number;
}

You can use the generic argument K to change up the type of value if that’s needed:

interface AtmosphericMeasurement<K extends AtmosphericField> {
  recorded: Date;
  value: K extends "tvoc" ? string : number;
}

So our service will have some methods with this shape:

class MeasurementService {
  allMeasurementsByDateRange(range: RangeRequest):
    Observable<AtmosphericConditions[]> {...}
  measurementByDateRange<K extends AtmosphericField>(range: RangeRequest, wantedField: K):
    Observable<AtmosphericMeasurement<K>[]> {...}
}

The second function will call the first one internally, and then use the RxJS map operator to strip out and selectively repackage the desired measurement out of the set that comes back from the server.

This should make the code considerably more readable, and will also subtly make it so that you are following functional programming conventions more strictly. This is a major reason I constantly advocate splitting data wrangling out into services, even when they’re only used by one page. It becomes physically impossible for you to shoot yourself in the foot with side effects in an asynchronous pipeline when the producer and the consumer are split up: there is no this you can assign to the properties of, so you aren’t tempted to do it as you are currently.

Also, I believe taking the time to go through this exercise should magically design away the problematic behavior you’re seeing. If it doesn’t, post the revised code and the conversation can continue.