class App { constructor(selector, worldData, travelData) { this.nodes = document.querySelectorAll(selector); this.worldData = worldData; this.travelData = travelData; this.populationArray = [ { name: "China", population: "1439323776", rank: 1, }, { name: "India", population: "1380004385", rank: 2, }, { name: "United States", population: "331002651", rank: 3, }, { name: "Indonesia", population: "273523615", rank: 4, }, { name: "Pakistan", population: "220892340", rank: 5, }, { name: "Brazil", population: "212559417", rank: 6, }, { name: "Nigeria", population: "206139589", rank: 7, }, { name: "Bangladesh", population: "164689383", rank: 8, }, { name: "Russia", population: "145934462", rank: 9, }, { name: "Mexico", population: "128932753", rank: 10, }, { name: "Japan", population: "126476461", rank: 11, }, { name: "Ethiopia", population: "114963588", rank: 12, }, { name: "Philippines", population: "109581078", rank: 13, }, { name: "Egypt", population: "102334404", rank: 14, }, { name: "Vietnam", population: "97338579", rank: 15, }, { name: "Democratic Republic of Congo", population: "89561403", rank: 16, }, { name: "Germany", population: "83783942", rank: 19, }, { name: "Turkey", population: "84339067", rank: 17, }, { name: "Iran", population: "83992949", rank: 18, }, { name: "Thailand", population: "69799978", rank: 20, }, { name: "United Kingdom", population: "67886011", rank: 21, }, { name: "France", population: "65273511", rank: 22, }, { name: "Italy", population: "60461826", rank: 23, }, { name: "South Africa", population: "59308690", rank: 25, }, { name: "Tanzania", population: "59734218", rank: 24, }, { name: "Myanmar", population: "54409800", rank: 26, }, { name: "Kenya", population: "53771296", rank: 27, }, { name: "South Korea", population: "51269185", rank: 28, }, { name: "Colombia", population: "50882891", rank: 29, }, { name: "Spain", population: "46754778", rank: 30, }, { name: "Argentina", population: "45195774", rank: 32, }, { name: "Uganda", population: "45741007", rank: 31, }, { name: "Ukraine", population: "43733762", rank: 35, }, { name: "Algeria", population: "43851044", rank: 33, }, { name: "Sudan", population: "43849260", rank: 34, }, { name: "Iraq", population: "40222493", rank: 36, }, { name: "Afghanistan", population: "38928346", rank: 37, }, { name: "Poland", population: "37846611", rank: 38, }, { name: "Canada", population: "37742154", rank: 39, }, { name: "Morocco", population: "36910560", rank: 40, }, { name: "Saudi Arabia", population: "34813871", rank: 41, }, { name: "Uzbekistan", population: "33469203", rank: 42, }, { name: "Peru", population: "32971854", rank: 43, }, { name: "Malaysia", population: "32365999", rank: 45, }, { name: "Angola", population: "32866272", rank: 44, }, { name: "Ghana", population: "31072940", rank: 47, }, { name: "Mozambique", population: "31255435", rank: 46, }, { name: "Yemen", population: "29825964", rank: 48, }, { name: "Nepal", population: "29136808", rank: 49, }, { name: "Venezuela", population: "28435940", rank: 50, }, { name: "Madagascar", population: "27691018", rank: 51, }, { name: "Cameroon", population: "26545863", rank: 52, }, { name: "Cote d'Ivoire", population: "26378274", rank: 53, }, { name: "North Korea", population: "25778816", rank: 54, }, { name: "Australia", population: "25499884", rank: 55, }, { name: "Taiwan", population: "23816775", rank: 57, }, { name: "Niger", population: "24206644", rank: 56, }, { name: "Sri Lanka", population: "21413249", rank: 58, }, { name: "Burkina Faso", population: "20903273", rank: 59, }, { name: "Mali", population: "20250833", rank: 60, }, { name: "Romania", population: "19237691", rank: 61, }, { name: "Chile", population: "19116201", rank: 63, }, { name: "Malawi", population: "19129952", rank: 62, }, { name: "Kazakhstan", population: "18776707", rank: 64, }, { name: "Zambia", population: "18383955", rank: 65, }, { name: "Guatemala", population: "17915568", rank: 66, }, { name: "Ecuador", population: "17643054", rank: 67, }, { name: "Netherlands", population: "17134872", rank: 69, }, { name: "Syria", population: "17500658", rank: 68, }, { name: "Cambodia", population: "16718965", rank: 71, }, { name: "Senegal", population: "16743927", rank: 70, }, { name: "Chad", population: "16425864", rank: 72, }, { name: "Somalia", population: "15893222", rank: 73, }, { name: "Zimbabwe", population: "14862924", rank: 74, }, { name: "Guinea", population: "13132795", rank: 75, }, { name: "Rwanda", population: "12952218", rank: 76, }, { name: "Benin", population: "12123200", rank: 77, }, { name: "Tunisia", population: "11818619", rank: 79, }, { name: "Belgium", population: "11589623", rank: 81, }, { name: "Burundi", population: "11890784", rank: 78, }, { name: "Bolivia", population: "11673021", rank: 80, }, { name: "Cuba", population: "11326616", rank: 83, }, { name: "Haiti", population: "11402528", rank: 82, }, { name: "South Sudan", population: "11193725", rank: 84, }, { name: "Dominican Republic", population: "10847910", rank: 85, }, { name: "Czech Republic", population: "10708981", rank: 86, }, { name: "Greece", population: "10423054", rank: 87, }, { name: "Portugal", population: "10196709", rank: 89, }, { name: "Jordan", population: "10203134", rank: 88, }, { name: "Azerbaijan", population: "10139177", rank: 90, }, { name: "Sweden", population: "10099265", rank: 91, }, { name: "United Arab Emirates", population: "9890402", rank: 93, }, { name: "Honduras", population: "9904607", rank: 92, }, { name: "Hungary", population: "9660351", rank: 94, }, { name: "Belarus", population: "9449323", rank: 96, }, { name: "Tajikistan", population: "9537645", rank: 95, }, { name: "Austria", population: "9006398", rank: 97, }, { name: "Papua New Guinea", population: "8947024", rank: 98, }, { name: "Serbia", population: "8737371", rank: 99, }, { name: "Switzerland", population: "8654622", rank: 101, }, { name: "Israel", population: "8655535", rank: 100, }, { name: "Togo", population: "8278724", rank: 102, }, { name: "Sierra Leone", population: "7976983", rank: 103, }, { name: "Hong Kong", population: "7496981", rank: 104, }, { name: "Laos", population: "7275560", rank: 105, }, { name: "Paraguay", population: "7132538", rank: 106, }, { name: "Bulgaria", population: "6948445", rank: 107, }, { name: "Lebanon", population: "6825445", rank: 109, }, { name: "Libya", population: "6871292", rank: 108, }, { name: "Nicaragua", population: "6624554", rank: 110, }, { name: "El Salvador", population: "6486205", rank: 112, }, { name: "Kyrgyzstan", population: "6524195", rank: 111, }, { name: "Turkmenistan", population: "6031200", rank: 113, }, { name: "Singapore", population: "5850342", rank: 114, }, { name: "Denmark", population: "5792202", rank: 115, }, { name: "Finland", population: "5540720", rank: 116, }, { name: "Slovakia", population: "5459642", rank: 118, }, { name: "Congo", population: "5518087", rank: 117, }, { name: "Norway", population: "5421241", rank: 119, }, { name: "Costa Rica", population: "5094118", rank: 122, }, { name: "Palestine", population: "5101414", rank: 121, }, { name: "Oman", population: "5106626", rank: 120, }, { name: "Liberia", population: "5057681", rank: 123, }, { name: "Ireland", population: "4937786", rank: 124, }, { name: "New Zealand", population: "4822233", rank: 126, }, { name: "Central African Republic", population: "4829767", rank: 125, }, { name: "Mauritania", population: "4649658", rank: 127, }, { name: "Panama", population: "4314767", rank: 128, }, { name: "Kuwait", population: "4270571", rank: 129, }, { name: "Croatia", population: "4105267", rank: 130, }, { name: "Moldova", population: "4033963", rank: 131, }, { name: "Georgia", population: "3989167", rank: 132, }, { name: "Eritrea", population: "3546421", rank: 133, }, { name: "Uruguay", population: "3473730", rank: 134, }, { name: "Bosnia and Herzegovina", population: "3280819", rank: 135, }, { name: "Mongolia", population: "3278290", rank: 136, }, { name: "Armenia", population: "2963243", rank: 137, }, { name: "Jamaica", population: "2961167", rank: 138, }, { name: "Puerto Rico", population: "2860853", rank: 141, }, { name: "Albania", population: "2877797", rank: 140, }, { name: "Qatar", population: "2881053", rank: 139, }, { name: "Lithuania", population: "2722289", rank: 142, }, { name: "Namibia", population: "2540905", rank: 143, }, { name: "Gambia", population: "2416668", rank: 144, }, { name: "Botswana", population: "2351627", rank: 145, }, { name: "Gabon", population: "2225734", rank: 146, }, { name: "Lesotho", population: "2142249", rank: 147, }, { name: "Macedonia", population: "2083374", rank: 148, }, { name: "Slovenia", population: "2078938", rank: 149, }, { name: "Guinea-Bissau", population: "1968001", rank: 150, }, { name: "Latvia", population: "1886198", rank: 151, }, { name: "Bahrain", population: "1701575", rank: 152, }, { name: "Trinidad and Tobago", population: "1399488", rank: 154, }, { name: "Equatorial Guinea", population: "1402985", rank: 153, }, { name: "Estonia", population: "1326535", rank: 155, }, { name: "Timor", population: "1318445", rank: 156, }, { name: "Mauritius", population: "1271768", rank: 157, }, { name: "Cyprus", population: "1207359", rank: 158, }, { name: "Swaziland", population: "1160164", rank: 159, }, { name: "Djibouti", population: "989455", rank: 160, }, { name: "Fiji", population: "896445", rank: 161, }, { name: "Reunion", population: "895312", rank: 162, }, { name: "Comoros", population: "869601", rank: 163, }, { name: "Guyana", population: "786552", rank: 164, }, { name: "Bhutan", population: "771608", rank: 165, }, { name: "Solomon Islands", population: "686884", rank: 166, }, { name: "Macau", population: "649335", rank: 167, }, { name: "Montenegro", population: "628066", rank: 168, }, { name: "Luxembourg", population: "625978", rank: 169, }, { name: "Western Sahara", population: "597339", rank: 170, }, { name: "Suriname", population: "586632", rank: 171, }, { name: "Cape Verde", population: "555987", rank: 172, }, { name: "Maldives", population: "540544", rank: 173, }, { name: "Malta", population: "441543", rank: 174, }, { name: "Brunei", population: "437479", rank: 175, }, { name: "Guadeloupe", population: "400124", rank: 176, }, { name: "Belize", population: "397628", rank: 177, }, { name: "Bahamas", population: "393244", rank: 178, }, { name: "Martinique", population: "375265", rank: 179, }, { name: "Iceland", population: "341243", rank: 180, }, { name: "Vanuatu", population: "307145", rank: 181, }, { name: "French Guiana", population: "298682", rank: 182, }, { name: "Barbados", population: "287375", rank: 183, }, { name: "New Caledonia", population: "285498", rank: 184, }, { name: "French Polynesia", population: "280908", rank: 185, }, { name: "Mayotte", population: "272815", rank: 186, }, { name: "Sao Tome and Principe", population: "219159", rank: 187, }, { name: "Samoa", population: "198414", rank: 188, }, { name: "Saint Lucia", population: "183627", rank: 189, }, { name: "Guam", population: "168775", rank: 190, }, { name: "Curacao", population: "164093", rank: 191, }, { name: "Kiribati", population: "119449", rank: 192, }, { name: "Micronesia", population: "115023", rank: 193, }, { name: "Grenada", population: "112523", rank: 194, }, { name: "Saint Vincent and the Grenadines", population: "110940", rank: 195, }, { name: "Aruba", population: "106766", rank: 196, }, { name: "United States Virgin Islands", population: "104425", rank: 198, }, { name: "Tonga", population: "105695", rank: 197, }, { name: "Seychelles", population: "98347", rank: 199, }, { name: "Antigua and Barbuda", population: "97929", rank: 200, }, { name: "Isle of Man", population: "85033", rank: 201, }, { name: "Andorra", population: "77265", rank: 202, }, { name: "Dominica", population: "71986", rank: 203, }, { name: "Cayman Islands", population: "65722", rank: 204, }, { name: "Bermuda", population: "62278", rank: 205, }, { name: "Marshall Islands", population: "59190", rank: 206, }, { name: "Northern Mariana Islands", population: "57559", rank: 207, }, { name: "Greenland", population: "56770", rank: 208, }, { name: "American Samoa", population: "55191", rank: 209, }, { name: "Saint Kitts and Nevis", population: "53199", rank: 210, }, { name: "Faeroe Islands", population: "48863", rank: 211, }, { name: "Sint Maarten", population: "42876", rank: 212, }, { name: "Monaco", population: "39242", rank: 213, }, { name: "Turks and Caicos Islands", population: "38717", rank: 214, }, { name: "Liechtenstein", population: "38128", rank: 216, }, { name: "Sint Maarten (Dutch part)", population: "38666", rank: 215, }, { name: "San Marino", population: "33931", rank: 217, }, { name: "Gibraltar", population: "33691", rank: 218, }, { name: "British Virgin Islands", population: "30231", rank: 219, }, { name: "Palau", population: "18094", rank: 220, }, { name: "Cook Islands", population: "17564", rank: 221, }, { name: "Anguilla", population: "15003", rank: 222, }, { name: "Tuvalu", population: "11792", rank: 223, }, { name: "Wallis and Futuna", population: "11239", rank: 224, }, { name: "Nauru", population: "10824", rank: 225, }, { name: "Saint Barthélemy", population: "9877", rank: 226, }, { name: "Saint Pierre and Miquelon", population: "5794", rank: 227, }, { name: "Montserrat", population: "4992", rank: 228, }, { name: "Falkland Islands", population: "3480", rank: 229, }, { name: "Niue", population: "1626", rank: 230, }, { name: "Tokelau", population: "1357", rank: 231, }, { name: "Vatican", population: "801", rank: 232, }, { name: "Bonaire Sint Eustatius and Saba", population: "25157", }, { name: "Guernsey", population: "63026", }, { name: "Jersey", population: "106800", }, { name: "Kosovo", population: "1810463", }, { name: "World", population: "7777500000", }, { name: "International", population: "3711", }, ]; this.openingsArray = [ { country: "Aruba", date: "6/15" }, { country: "Mexico", date: "6/1" }, { country: "Saint Lucia", date: "6/4" }, { country: "Dominican Republic", date: "6/1" }, { country: "Barbados", date: "7/12" }, { country: "Turks and Caicos Islands", date: "7/22" }, { country: "Belize", date: "10/1" }, { country: "Curacao", date: "7/1" }, { country: "Dominican Republic", date: "6/1" }, { country: "United States Virgin Islands", date: "9/22" }, { country: "Spain", date: "7/22" }, { country: "Germany", date: "7/2" }, { country: "Japan", date: null }, { country: "Italy", date: "6/3" }, { country: "Thailand", date: "10/1" }, { country: "Portugal", date: null }, { country: "India", date: null }, { country: "Switzerland", date: "6/15" }, { country: "Poland", date: "7/13" }, ]; } async create() { let worldData = this.worldData; let travelData = this.travelData; Promise.all([d3.json(worldData), d3.json(travelData)]).then((files) => { this.worldData = this.formatWorldData(files[0]); this.travelData = this.formatTravelData(files[1]); this.build(); }); } formatTravelData(raw) { return raw; } formatWorldData(raw) { // Group data by country, add population let grouped = []; raw.forEach((line) => { let country = grouped.find((c) => c.name === line.location); let data = { date: line.date, cases: { new: Number(line.new_cases), total: Number(line.total_cases), }, deaths: { new: Number(line.new_deaths), total: Number(line.total_deaths), }, }; if (country) { country.data.push(data); } else { grouped.push({ name: line.location, population: Number( this.populationArray.find((p) => p.name === line.location) .population ), data: [data], perCapita(num, capita = 100000) { return (capita * num) / this.population; }, }); } }); //Add 7-day average grouped.forEach((country) => { country.data.forEach((day, i, arr) => { day.cases.avg = arr .slice(Math.max(i - 6, 0), i + 1) .reduce((acc, val) => acc + val.cases.new, 0) / (i + 1 - Math.max(i - 6, 0)); day.deaths.avg = arr .slice(Math.max(i - 6, 0), i + 1) .reduce((acc, val) => acc + val.deaths.new, 0) / (i + 1 - Math.max(i - 6, 0)); }); }); return grouped; } build() { this.nodes.forEach((node) => { let name = node.dataset.name; let worldData = this.worldData.filter((country) => country.name === name); let travelData = this.travelData.filter( (country) => country.Country === name ); let openDate = this.openingsArray.find((obj) => obj.country === name) .date; new Country(node, worldData[0], travelData[0], openDate).create(); }); } } class Country { constructor(node, worldData, travelData, openDate) { this.node = node; this.name = node.dataset.name; this.worldData = worldData; this.status = travelData.Status; this.text = travelData.Text; this.openDate = openDate; } make(type, parent, CLASS = null, ID = null) { let element = document.createElement(type); CLASS ? element.setAttribute("class", CLASS) : null; ID ? element.setAttribute("id", ID) : null; return parent.appendChild(element); } create() { let [green, red, yellow, grey] = [ "#afd9ab", "#eeaeab", "#e5edb0", "#d9d7d7", ]; let statusText = this.status === "Green" ? "Open to Canadians" : this.status === "Red" ? "Closed to Canadians" : this.status === "Yellow" ? "Open to Canadians, with restrictions" : "Travel status unclear"; let statusColor = this.status === "Green" ? green : this.status === "Red" ? red : this.status === "Yellow" ? yellow : grey; let titleDiv = this.make("div", this.node, "country-title-div"); let pin = this.make("div", titleDiv, "country-pin"); let url; if (this.status === "Red") { url = "/polopoly_fs/1.5106169.1600205568!/httpImage/image.png_gen/derivatives/landscape_150/image.png"; } else if (this.status === "Yellow") { url = "/polopoly_fs/1.5106175.1600205872!/httpImage/image.png_gen/derivatives/landscape_150/image.png"; } else { url = "/polopoly_fs/1.5106177.1600206018!/httpImage/image.png_gen/derivatives/landscape_150/image.png"; } pin.style.backgroundImage = `url(${url})`; let title = this.make("div", titleDiv, "country-title"); title.innerText = this.name; let countryStatus = this.make("div", this.node, "country-status"); countryStatus.style.background = statusColor; countryStatus.innerText = statusText; let content = this.make("div", this.node, "country-content"); let monthArray = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", ]; let dateSplit = this.openDate ? this.openDate.split("/") : null; let openDateWord; if (dateSplit) { openDateWord = monthArray[this.openDate.split("/")[0] - 1] + " " + this.openDate.split("/")[1]; } let contentChart = this.make("div", content, "content-chart"); this.make("div", contentChart, "chart-title").innerText = "Average daily COVID-19 cases per 100,000"; new WorldChart( this.worldData, null, contentChart, [ "cases", "avg", "per", //'raw' or 'per' ], [this.openDate, `Opened for tourism ${openDateWord}`], 20 ).create(); /* let contentDiv = this.make("div", content, "content-div"); let contentTitle = this.make("div", contentDiv, "content-title"); contentTitle.innerText = "Travel notes"; let contentText = this.make("div", contentDiv, "content-text"); contentText.innerHTML = this.text || "No travel notes"; */ } } class WorldChart { constructor( country, rank, parent, metricArray, openArray = null, max = null ) { this.rank = rank; this.parent = parent; this.country = country; this.metricArray = metricArray; this.max = max; this.openDate = openArray ? openArray[0] : null; this.openText = openArray ? openArray[1] : `Opened for tourism on ${this.openDate}`; this.openAlign = openArray ? openArray[2] : "center"; window.addEventListener("resize", () => this.create()); } make(type, parent, CLASS = null, ID = null) { let element = document.createElement(type); CLASS ? element.setAttribute("class", CLASS) : null; ID ? element.setAttribute("id", ID) : null; return parent.appendChild(element); } toDate(excelDate) { let newDate = new Date((excelDate - (25567 + 1)) * 86400 * 1000); let month = newDate.getMonth() + 1; let day = newDate.getDate(); return `${month}/${day}`; } round(number, increment, offset) { return Math.ceil((number - offset) / increment) * increment + offset; } create() { let [m0, m1, m2] = this.metricArray; if (this.parent.querySelector(".chart-div")) { this.parent.querySelector(".chart-div").remove(); } let div = this.make("div", this.parent, "chart-div"); let canvas = this.make("canvas", div, "chart-canvas"); let dpi = window.devicePixelRatio; let W = canvas.offsetWidth * dpi; let H = canvas.offsetHeight * dpi; canvas.width = W; canvas.height = H; let tempMax = this.country.data.reduce((acc, val) => { if (m2 === "per") { return this.country.perCapita(val[m0][m1]) > acc ? this.country.perCapita(val[m0][m1]) : acc; } else { return val[m0][m1] > acc ? val[m0][m1] : acc; } }, 0); if (!this.max) { this.max = tempMax; } else { this.max = Math.max(this.max, this.round(tempMax, 20, 0)); } const pad = { left: 20 * dpi, right: 40 * dpi, top: 40 * dpi, bottom: 14 * dpi, }; let c = canvas.getContext("2d"); c.font = `${10 * dpi}px sans-serif`; c.textAlign = "center"; // Bars + dates this.country.data.forEach((day, i, a) => { let d = m2 === "per" ? this.country.perCapita(day[m0][m1 === "total" ? "total" : "new"]) : day[m0][m1 === "total" ? "total" : "new"]; // Daily Rect c.fillStyle = "#ffbd6955"; c.fillRect( pad.left + (W - pad.right - pad.left) * (i / a.length), H - pad.bottom - pad.top - (H - pad.bottom - pad.top) * (d / this.max) + pad.top, Math.max(Math.round(W / a.length), 1), (H - pad.bottom - pad.top) * (d / this.max) ); //Add dates let dateNumArray = ["3/1", "5/1", "7/1", "9/1", "11/1"]; let dateMonthArray = ["Mar", "May", "Jul", "Sep", "Dec"]; if (dateNumArray.includes(this.toDate(day.date))) { //c.save(); c.fillStyle = "#222"; let monthText = dateMonthArray[dateNumArray.indexOf(this.toDate(day.date))]; let monthX = Math.round( pad.left + (W - pad.right - pad.left) * (i / a.length) ); let monthY = H - 3 * dpi; c.fillText(monthText, monthX, monthY); //c.restore(); } // Opening dash line if (this.openDate && this.toDate(day.date) === this.openDate) { let x = Math.round( pad.left + (W - pad.right - pad.left) * (i / a.length) ); c.beginPath(); c.moveTo(x, H - pad.bottom + 1); c.lineTo(x, 20); c.setLineDash([3 * dpi, 3 * dpi]); c.strokeStyle = "#222"; c.lineWidth = 2; c.stroke(); c.setLineDash([]); c.fillStyle = "#222"; c.textAlign = this.openAlign; c.fillText(this.openText, x + 1, 15 * dpi); } }); // Avg line c.beginPath(); let startHeight = H - pad.bottom - pad.top - ((H - pad.bottom - pad.top) * (m2 === "per" ? this.country.perCapita( this.country.data[0][m0][m1 === "total" ? "total" : "avg"] ) : this.country.data[0][m0][m1 === "total" ? "total" : "avg"])) / this.max + pad.top; c.moveTo(pad.left, startHeight); this.country.data.forEach((day, i, a) => { let d = m2 === "per" ? this.country.perCapita(day[m0][m1 === "total" ? "total" : "avg"]) : day[m0][m1 === "total" ? "total" : "avg"]; c.lineTo( Math.round(pad.left + (W - pad.right - pad.left) * (i / a.length)), H - pad.bottom - pad.top - (H - pad.bottom - pad.top) * (d / this.max) + pad.top ); }); c.strokeStyle = "#2a2a2a"; c.lineWidth = 2 * dpi; c.lineJoin = "round"; c.stroke(); // Baseline c.beginPath(); c.moveTo(pad.left, H - pad.bottom + 0.5); c.lineTo(W - pad.right, H - pad.bottom + 0.5); c.lineWidth = 1; c.strokeStyle = "#222"; c.stroke(); // Axis Line + Numbers c.beginPath(); c.moveTo(W - pad.right - 0.5, H - pad.bottom + 0.5); c.lineTo(W - pad.right - 0.5, pad.top - 0.5); c.lineWidth = 1; c.strokeStyle = "#222"; c.stroke(); let top = pad.top; let bottom = H - pad.bottom + 0.5; let numMarks = this.max === 20 ? 2 : this.max / 20; for (let i = 0; i <= numMarks; i++) { let mark = Math.round(bottom + (i * (top - bottom)) / numMarks) - 0.5; c.beginPath(); c.moveTo(W - pad.right - 0.5, mark); c.lineTo(W - pad.right - 0.5 + 5 * dpi, mark); c.lineWidth = 1; c.strokeStyle = "#222"; c.stroke(); c.fillStyle = this.max * (i / numMarks) === 20 ? "#222" : "#aaa"; c.textAlign = "left"; c.fillText( (this.max * (i / numMarks)).toLocaleString(undefined, { maximumFractionDigits: 1, }), W - pad.right - 0.5 + 7 * dpi, mark + 2 * dpi ); } if (this.country.name === "Canada") { div.classList.add("canada"); } } } window.addEventListener("load", () => new App(".country", "https://beta.ctvnews.ca/content/dam/common/exceltojson/full_data.txt", "https://beta.ctvnews.ca/content/dam/common/exceltojson/Canada%20Travel%20Status.txt").create() );