Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <html lang="fr">

<head>
  <meta charset="utf-8">
  <title>Exemple de graphique et tableau</title>
  <meta name="description" content="Exemple de graphique et tableau">
  <link href="style.css" rel="stylesheet">
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
</head>

<body>
  <div class="container">
    <div class="row title">
      <header class="masthead mb-auto">
        <div class="inner">
          <h1 class="masthead-brand" id="title"></h1>
        </div>
      </header>
    </div>
    <div class="row graph-and-table">
      <div class="reps0 col-sm-12">
        <div id="reps0">
        </div>
      </div>
      <div class="table-probs col-sm-offset-0 col-sm-12">
        <table id="table-probs" class="probs" media="print">
          <caption id="table-title">Prévisions sur une période de 6 heures</caption>
          <caption id="table-footer">*Veuillez noter que ces données proviennent de modèles météorologiques non expertisées.</caption>
          <thead>
            <tr>
              <th>Débutant le</th>
              <th>≥0.2 mm</th>
              <th>≥1.0 mm</th>
              <th>≥2.5 mm</th>
              <th>≥5.0 mm</th>
              <th>Temp.</th>
              <th>Vents</th>
            </tr>
          </thead>
          <tbody>
          </tbody>
        </table>
      </div>
    </div>
    <div class="col-sm-offset-0 col-sm-3">
      <div class="class-sm-offset-1 col-sm-12">
        <p></p>
      </div>
      <div class="class-sm-offset-1 col-sm-12">
        <p></p>
      </div>
    </div>
  </div>
  <script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.5.0/build/ol.js"></script>
  <script src="https://cdn.plot.ly/plotly-2.0.0.min.js"></script>
  <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
  <script src="main.js"></script>
</body>

</html>
              
            
!

CSS

              
                html,
 body {
   height: 100%;
   background-color: rgb(255, 255, 255);
 }
 h1 {
   text-align: center;
 }
 body {
   display: -ms-flexbox;
   display: -webkit-box;
   display: flex;
   -ms-flex-pack: center;
   -webkit-box-pack: center;
   justify-content: center;
   color: #fff;
   text-shadow: 0 .05rem .1rem rgba(0, 0, 0, .5);
   -webkit-print-color-adjust: exact;
 }
 #reps0 {
   display: flex;
 }
 #table-title {
   padding-top: .75rem;
   padding-bottom: .75rem;
   color: #6c757d;
   text-align: center;
   caption-side: top;
 }
 .probs {
   border: 1px solid #C0C0C0;
   border-collapse: collapse;
   padding: 5px;
   margin: auto;
 }
 .probs th {
   border: 1px solid #C0C0C0;
   padding: 5px;
   background: #F0F0F0;
   background: #797979;
   color: white;
   text-align: center;
 }
 .probs td {
   border: 1px solid #C0C0C0;
   text-align: center;
   padding: 5px;
 }
  #nan {
    background-color: #797979;
  }
  h1#title.masthead-brand {
    text-align: center;
  }
  div.row.title {
    margin: auto;
    display: inline;
  }

.low1 {
   background-color: #d8f3dc;
   color: black;
 }
 
 .low2 {
   background-color: #B7E4C7;
   color: black;
 }
 
 .low3 {
   background-color: #95D5B2;
   color: black;
 }
 
 .medium1 {
   background-color: #74C69D;
   color: black;
 }
 
 .medium2 {
   background-color: #52B788;
   color: black;
 }
 
 .medium3 {
   background-color: #40916C;
   color: white;
 }
 
.high1 {
   background-color: #2D6A4F;
   color: white;
 }
 
.high2 {
   background-color: #245741;
   color: white;
 }
 
 .high3 {
   background-color: #1B4332;
   color: white;
 }
.high4 {
  background-color: #081C15;
  color: white;
}

              
            
!

JS

              
                const parser = new DOMParser();
const [coordX, coordY] = [45.472, -73.750]; //Aéroport de Montréal Pierre-Elliott Trudeau International (CYUL)
const layer02 = 'REPS.DIAG.6_PRMM.ERGE0.2'; //Probabilités de précipitations >= 0.2mm
const layer1 = 'REPS.DIAG.6_PRMM.ERGE1'; //Probabilités de précipitations >= 1mm
const layer25 = 'REPS.DIAG.6_PRMM.ERGE2.5'; //Probabilités de précipitations >= 2.5mm
const layer5 = 'REPS.DIAG.6_PRMM.ERGE5'; //Probabilités de précipitations >= 5mm
const layerTemp = 'RDPS.ETA_TT'; //Température
const layerWindDir = 'RDPS.ETA_WD'; //Direction du vent
const layerWindSp = 'RDPS.ETA_WSPD'; //Module du vent
const server = 'https://geo.weather.gc.ca/geomet?service=WMS&version='; //Serveur de GeoMet du SMC
const getCapab = server + '1.3.0&request=GetCapabilities&layers='; //Lien vers le GetCapabilities
let [version, request, info_format] = ['1.3.0', 'GetFeatureInfo', 'application/json']; //Version, request and format for the information
let [minx, miny, maxx, maxy] = [coordX - 0.25, coordY - 0.25, coordX + 0.25, coordY + 0.25]; //bbox around CYVP airport
const colorArray = ["low0","low1","low2","low3","medium1","medium2","medium3","high1","high2","high3","high4"];
getCapabilities().then(function(response) {
  let startTime = response[0]; //Initialise la date de début
  let endTime = response[1]; //Initialise la date de fin
    let interval = response[2].match(/\d+/)[0]; //RegEx pour avoir le nombre d'heures d'intervalles: Par exemple: PT6H -> 6
    startDate = new Date(startTime); //Objet Date pour la date de début
    endDate = new Date(endTime); //Objet Date pour la date de fin
    var dif = (Math.abs(endDate - startDate) / 36e5) / interval; //Différence entre la date de début et celle de fin selon l'intervalle
    let headers = [];
    headers.push(interval, startDate, dif); //Pousse les informations nécéssaires pour la préparation des requêtes
    headers = prepareRequests(headers, server); //Prépare les requêtes. Headers[0] contient les dates et le reste du tableau, les URLs
    let begin = startTime.split('T')[0]; //Titre de la page
    document.getElementById("title").innerHTML = "Pr&eacute;visions CYUL du " + begin; //Titre du DOM
    let adjustedUTC = adjustUTC(headers[0]); //Ajuste les heures en fonctions du fuseau horaire de l'utilisateur
    let promises = []; //Tableau pour les Promises
    /*
     * La boucle for envoie de manière asynchrone toutes les requêtes contenues dans "headers"
     * Comme nous avons besoin du résultat de toutes les requêtes avant de tracer le graphique et remplir le tableau,
     * l'utilisation du Promise.all est élémentaire
     */
    for (let i = 1; i < headers.length; ++i) {
      promises.push(sendRequests(headers, i)); //Pousse dans la tableau des Promise, la promesse du résultat de la requête
    }
    Promise.all(promises)
      .then((results) => {
        traceGraph(adjustedUTC, results); //Trace the graph
        createTable(adjustedUTC, results); //Populate table
      }).catch(err => { //Catch failed fetch
        console.log(err);
      });
  }).catch(err => {
    console.log(err);
  });

async function getCapabilities() {
  let response = await fetch(getCapab + layer02);
  let data = await response.text().then((data) =>
    parser
      .parseFromString(data, "text/xml")
      .getElementsByTagName("Dimension")[0]
      .innerHTML.split("/")
  );
  return [data[0], data[1], data[2]];
}
function prepareRequests(headers, server) {
  const interval = headers[0];
  let startDate = headers[1];
  let dif = headers[2];
  let timesteps = [];
  timesteps.push(startDate.toISOString().replace('.000', '')); //Remove trailing zeroes for compatibility with time= parameter
  let increment = startDate;
  for (let i = 0; i < dif; ++i) {
    increment.setHours(increment.getHours() + Number(interval)); //Set the hour according to the intervals supported
    timesteps.push(increment.toISOString().replace('.000', '')); //Add to timesteps array
  }
  let url02 = server +
    version + '&request=' + request + '&layers=' + layer02 + '&crs=EPSG:4326&bbox=' +
    minx + ',' + miny + ',' + maxx + ',' + maxy + '&exceptions=xml&width=10&height=10&INFO_FORMAT=' +
    info_format + '&query_layers=' + layer02 + '&x=1&y=1';
  let url1 = server +
    version + '&request=' + request + '&layers=' + layer1 + '&crs=EPSG:4326&bbox=' +
    minx + ',' + miny + ',' + maxx + ',' + maxy + '&exceptions=xml&width=10&height=10&INFO_FORMAT=' +
    info_format + '&query_layers=' + layer1 + '&x=1&y=1';
  let url25 = server +
    version + '&request=' + request + '&layers=' + layer25 + '&crs=EPSG:4326&bbox=' +
    minx + ',' + miny + ',' + maxx + ',' + maxy + '&exceptions=xml&width=10&height=10&INFO_FORMAT=' +
    info_format + '&query_layers=' + layer25 + '&x=1&y=1';
  let url5 = server +
    version + '&request=' + request + '&layers=' + layer5 + '&crs=EPSG:4326&bbox=' +
    minx + ',' + miny + ',' + maxx + ',' + maxy + '&exceptions=xml&width=10&height=10&INFO_FORMAT=' +
    info_format + '&query_layers=' + layer5 + '&x=1&y=1';
  let urlTemp = server +
    version + '&request=' + request + '&layers=' + layerTemp + '&crs=EPSG:4326&bbox=' +
    minx + ',' + miny + ',' + maxx + ',' + maxy + '&exceptions=xml&width=10&height=10&INFO_FORMAT=' +
    info_format + '&query_layers=' + layerTemp + '&x=1&y=1';
  let urlWd = server +
    version + '&request=' + request + '&layers=' + layerWindDir + '&crs=EPSG:4326&bbox=' +
    minx + ',' + miny + ',' + maxx + ',' + maxy + '&exceptions=xml&width=10&height=10&INFO_FORMAT=' +
    info_format + '&query_layers=' + layerWindDir + '&x=1&y=1';
  let urlWspd = server +
    version + '&request=' + request + '&layers=' + layerWindSp + '&crs=EPSG:4326&bbox=' +
    minx + ',' + miny + ',' + maxx + ',' + maxy + '&exceptions=xml&width=10&height=10&INFO_FORMAT=' +
    info_format + '&query_layers=' + layerWindSp + '&x=1&y=1';
  let info = [];
  info.push(timesteps, url02, url1, url25, url5, urlTemp, urlWd, urlWspd);
  return info;
}
function adjustUTC(time) {
  let adjustedDate = [];
  let dateTime;
  for (let i = 0; i < time.length; ++i) {
    dateTime = new Date(time[i]);
    adjustedDate.push(dateTime);
  }
  return adjustedDate;
}
async function sendRequests(headers, nb) {
  let responses = [];
  let datesMod = [];
  let url = headers[nb].concat("&time=" + headers[0][0]);
  for (let k = 0; k < headers[0].length; ++k) {
    datesMod.push(headers[0][k]);
  }
  for (let i = 0; i < datesMod.length; ++i) {
    try {
      if (i != 0) {
        url = url.replace(/[^=]*$/.exec(url), datesMod[i]);
      }
      let response = await fetch(url);
      let data = await response.json();
      responses.push(data.features[0].properties.value);
    } catch (e) {
      console.log(e);
    }
  }
  return responses;
}
  function traceGraph(xaxis, yaxis) {
  let date = [];
  let tempRounded = [];
  let windKm = [];
  let lastDate = new Date(xaxis[xaxis.length-1]);
  lastDate.setHours(lastDate.getHours() + 6);
  lastDate = lastDate.toLocaleString('fr-CA');
  lastDate = lastDate.substring(0, lastDate.indexOf('m'));
  lastDate = lastDate.replace(' h ', 'h');

  for (let i = 0; i < xaxis.length; ++i) {
    date[i] = xaxis[i].toLocaleString('fr-CA');
    date[i] = date[i].substring(0, date[i].indexOf('m'));
    date[i] = date[i].replace(' h ', 'h');
  }
  date.push(lastDate);
  for (let j = 0; j < yaxis[4].length; ++j) {
    tempRounded[j] = Number(Math.round(yaxis[4][j]));
  }
  for (let k = 0; k < yaxis[6].length; ++k) {
    if (!isNaN(Math.round(yaxis[6][k]))) {
      windKm[k] = Number(Math.round(yaxis[6][k] * 3.6));
    }
  }
  for (let l = 0; l < yaxis.length; ++l) {
    yaxis[l].push(0);
  }
  var trace1 = {
    x: date,
    y: yaxis[0],
    mode: 'lines+markers',
    name: '≥ 0.5mm',
    line: {
      color: 'rgb(144, 224, 239)',
      width: 3,
      shape: 'hv'
    },
    fill: 'tozeroy',
  };
  var trace2 = {
    x: date,
    y: yaxis[1],
    mode: 'lines+markers',
    name: '≥ 1.0mm',
    line: {
      color: 'rgb(72, 202, 228)',
      width: 3,
      shape: 'hv'
    },
    fill: 'tozeroy',
  }
  var trace3 = {
    x: date,
    y: yaxis[2],
    mode: 'lines+markers',
    name: '≥ 2.5mm',
    line: {
      color: 'rgb(0, 150, 199)',
      width: 3,
      shape: 'hv'
    },
    fill: 'tozeroy',
  }
  var trace4 = {
    x: date,
    y: yaxis[3],
    mode: 'lines+markers',
    name: '≥ 5.0mm',
    line: {
      color: 'rgb(2, 62, 138)',
      width: 3,
      shape: 'hv'
    },
    fill: 'tozeroy',
  }
  var layout = {
    title: 'Probabilités de précipitations',
    automargin: true,
    showlegend: true,
    showlegend: true,
    legend: {
      "orientation": "h"
    },
    xaxis: {
      showticklabels: true,
      tickangle: 0,
      nticks: 3,
      tickfont: {
        family: 'Old Standard TT, serif',
        size: 12,
        color: 'black'
      },
    },
    yaxis: {
      title: 'Probabilités (%)',
      range: [0, 100]
    }
  };
  
var data = [trace1, trace2, trace3, trace4 /*trace5, trace6*/];
  Plotly.newPlot('reps0', data, layout);
}
function createTable(date, data) {
  for (let z = 0; z < data.length; ++z) {
    data[z].pop();
  }
  let table = document.querySelector("#table-probs");
  let row;
  for (let k = 0; k < date.length; ++k) {
    date[k] = date[k].toLocaleString('fr-CA');
    date[k] = date[k].substring(0, date[k].indexOf('m'));
    date[k] = date[k].replace(' h ', 'h');
  }
  for (let i = 0; i < date.length; ++i) {
    row = table.insertRow(-1);
    let cell1 = row.insertCell(0);
    let cell2 = row.insertCell(1);
    let cell3 = row.insertCell(-1);
    let cell4 = row.insertCell(-1);
    let cell5 = row.insertCell(-1);
    let cell6 = row.insertCell(-1);
    let cell7 = row.insertCell(-1);
    if (!isNaN(Math.round(data[4][i]))) {
      cell6.innerHTML = Math.round(data[4][i]) + " °C";
    } else {
      cell6.setAttribute("id", "nan");
    }
    if (!isNaN(Math.round(data[5][i])) && !isNaN(Math.round(data[6][i]))) {
      cell7.innerHTML = Math.round(data[6][i] * 3.6) + " km/h " + degToCompass(data[5][i]);
    } else {
      cell7.setAttribute("id", "nan");
    }
    cell1.innerHTML = date[i];
    cell2.innerHTML = data[0][i] + " %";
    cell3.innerHTML = data[1][i] + " %";
    cell4.innerHTML = data[2][i] + " %";
    cell5.innerHTML = data[3][i] + " %";
    for (let j = 0; j < 4; ++j) {
      switch(j) {
        case 0:
          let val = data[0][i] / 10;
          cell2.classList.add(colorArray[Math.round(val % 11)]);
          break;
        case 1:
          let val1 = data[1][i] / 10;
          cell3.classList.add(colorArray[Math.round(val1 % 11)]);
          break;
        case 2:
          let val2 = data[2][i] / 10;
          cell4.classList.add(colorArray[Math.round(val2 % 11)]);
          break;
        case 3:
          let val3 = data[3][i] / 10;
          cell5.classList.add(colorArray[Math.round(val3 % 11)]);
          break;
      }
    }
  }
}

function degToCompass(num) {
  let val = Number((num / 45));
  let arr = ["N", "NE", "E", "SE", "S", "SO", "O", "NO"];
  if (Math.round((val % 8)) == 8) {
    return "Variables";
  }
  return arr[Math.round((val % 8))];
}
              
            
!
999px

Console