window.addEventListener('load', function () { if (document.getElementById("fullChart")) { document.getElementById("fullChart").classList.add('all-loaded'); getData(); addAnimation(); responsive(); //to resize on window resize (not just load): window.addEventListener("resize", () => { drawChart(nanosJSON); }); } }); function round(value, precision) { var multiplier = Math.pow(10, precision || 0); return (Math.round(value * multiplier) / multiplier).toFixed(precision); } // excel puts date as number (eg "43637") -- function converts to real date function getJsDateFromExcel(excelDate) { const fullDate = new Date((excelDate - (25567 + 1))*86400*1000); return `${fullDate.getMonth() + 1}/${fullDate.getDate()}/${fullDate.getFullYear()}`; } async function getData() { fullChart = document.getElementById("fullChart"); cW = fullChart.offsetWidth; content = document.getElementById("content"); isLoaded = false; breakPoint = 768; //scale the bars to accomodate text... pad + number*shrink divMult = 0.6; divPad = 0; barMult = 35; barPad = 5; decimal = 1; if (manualNumbers === false) { try { //const res = await fetch ('http://localhost:3000/'); const res = await fetch ('https://beta.ctvnews.ca/content/dam/common/exceltojson/nanos.txt'); nanosJSON = await res.json(); //parses all data from the JSON file } catch(err) { let chartBody = document.getElementById('chartBody') chartBody.innerHTML = 'Error fetching data'; chartBody.style.textAlign = 'center'; //fullChart.style.display = 'none'; } } ////console.log('data: ', nanosJSON) //format converted excel data to liking nanosJSON.forEach(line => { if (line[`Liberal`] !== '') { line.poll = {} line.poll['LP'] = line[`Liberal`]; line.poll['CP'] = line[`Conservative`]; line.poll['NDP'] = line[`NDP`]; line.poll['GP'] = line[`Green`]; line.poll['BQ'] = line[`Bloc`]; line.poll['PPC'] = line[`People's`]; delete line[`Liberal`]; delete line[`Conservative`]; delete line[`NDP`]; delete line[`Green`]; delete line[`Bloc`]; delete line[`People's`]; line.date = getJsDateFromExcel((Number(line["Date"]))); } }) let tempJSON = nanosJSON.filter(line => { return line.hasOwnProperty('poll') }) nanosJSON = tempJSON; const update = await drawChart(nanosJSON); //fires update page function } //match up parties with color const partyKey = ["LP", "CP", "NDP", "BQ", "GP", "PPC"]; const colorKey = ["#B91319", "#1A4782", "#F37021", "#33B2CC", "#3D9B35", "#002FA1"]; function color(party) { if (partyKey.indexOf(party) !== -1) { return colorKey[partyKey.indexOf(party)] } else { return "black"; } } //console.log(color("Liberal")) function drawChart(data) { //current ballot is last item in data array, past one is the second last item let ballotCurrent = data[data.length - 1]; let ballotLast = data[data.length - 2]; document.getElementById('chartUpdate').innerText = `Released ${manualNumbers ? updateDate : ballotCurrent.date}`; cW = chartBody.offsetWidth; chartBody.innerHTML = ""; let resultsArray = []; Object.keys(ballotCurrent.poll).forEach((party, i) => { ////console.log(party, ballotCurrent.poll[party]) let obj = {}; obj['name'] = party; obj['current'] = ballotCurrent.poll[party]; obj['last'] = ballotLast.poll[party]; resultsArray.push(obj) }) //console.log('results', resultsArray) sortedResultsArray = resultsArray.slice().sort((a, b) => b.current - a.current); //console.log(sortedResultsArray) sortedResultsArray.forEach((party, i) => { let result = party.current; let pastResult = party.last; let maxParty = sortedResultsArray[0].name; let maxResult = sortedResultsArray[0].current; //create container for each party's section let bigDiv = document.createElement("div"); bigDiv.setAttribute("class", "bigDiv"); chartBody.appendChild(bigDiv); let barChangeDiv = document.createElement('div'); barChangeDiv.setAttribute("class", "barChangeDiv"); bigDiv.appendChild(barChangeDiv) //append main coloured bar let barContainer = document.createElement("div"); barContainer.setAttribute("class", "barContainer"); barChangeDiv.appendChild(barContainer); let bar = document.createElement("div"); isLoaded ? bar.setAttribute("class", "bar") : bar.setAttribute("class", "bar invisible"); bar.setAttribute("style", "width:100%; background:" + color(party.name) + ";"); barContainer.appendChild(bar); let change = `${round((round(result, decimal)-round(pastResult, decimal)), decimal)}`; let arrow = ""; let arrowImg = document.createElement("div"); if (change > 0) { arrowImg.setAttribute("class", "arrowImg upArrow"); } else if (change == 0) { arrowImg.setAttribute("class", "arrowImg neutralArrow"); } else if (change < 0) { arrowImg.setAttribute("class", "arrowImg downArrow"); } let changeText = document.createTextNode(change); let changeTextDiv = document.createElement('div'); changeTextDiv.setAttribute('class', 'changeTextDiv') changeTextDiv.appendChild(changeText); let changeDiv = document.createElement("div"); changeDiv.setAttribute("class", "changeText unselectable"); changeDiv.appendChild(arrowImg); changeDiv.appendChild(changeTextDiv) barChangeDiv.appendChild(changeDiv); let partyNumDiv = document.createElement('div'); partyNumDiv.setAttribute('class', 'partyNumDiv') bigDiv.appendChild(partyNumDiv) let textDiv = document.createElement("div"); textDiv.setAttribute("class", "textDiv unselectable"); let textText = document.createTextNode(party.name); textDiv.appendChild(textText); partyNumDiv.appendChild(textDiv); let numDiv = document.createElement("div"); numDiv.setAttribute("class", "numDiv unselectable"); let numText = document.createTextNode(`${round(result, decimal)}%`); numDiv.appendChild(numText); partyNumDiv.appendChild(numDiv); const lineBreak = document.createElement("br"); }) document.querySelector('#fullChart').offsetWidth <= breakPoint ? positionBarsVertical(nanosJSON) : positionBarsHorizontal(nanosJSON); responsive(); } //function to equally space and center the bar divs (which vary in width) function positionBarsHorizontal(data) { let ballotCurrent = data[data.length - 1]; let ballotLast = data[data.length - 2]; const fullChart = document.getElementById("fullChart"); const bigDiv = document.getElementsByClassName("bigDiv"); const barContainer = document.getElementsByClassName("barContainer"); //this is confusing... let the 'available' chart space be 0.85 of full chart width let chartWidth = fullChart.offsetWidth*0.80; //let the colored bars take up 0.65 of that available space const totalBarWidth = chartWidth*0.60; //calculate the total amount of space left for pad between bars let totalPadWidth = chartWidth-totalBarWidth; //calculate width of individual bars as a fraction of total bar space sortedResultsArray.forEach((party, i) => { let result = party.current; barContainer[i].style.width = totalBarWidth*result/100 + "px"; }) // now calculate how wide the actual party divs are (bar + change number + party label) let totalDivWidth = 0; for (let i = 0; i < bigDiv.length; i++) { totalDivWidth += bigDiv[i].offsetWidth; } //Small numbers result in bars less wide than the party label below //so the total width of the divs isn't just "bar width + space for change number". //This compares the original space alloted for pad compared to the actual space left let gapFix = fullChart.offsetWidth - (totalDivWidth + totalPadWidth); //total pad space is adjusted and then divided by 7 (2 for ends, 5 between bars) let gap = (totalPadWidth + gapFix)/7; //left positioning for the bars is initialized as one "gap" let left = gap; //loop through, adding previous div width + gap to the new left spot for each div for (let i = 0; i < bigDiv.length; i++) { bigDiv[i].style.left = left + "px"; left += bigDiv[i].offsetWidth + gap; } } function positionBarsVertical(data) { let ballotCurrent = data[data.length - 1]; let ballotLast = data[data.length - 2]; let bigDiv = document.getElementsByClassName("bigDiv"); let barContainer = document.getElementsByClassName("barContainer"); const padTop = 20; const padLeft = 20; const padBetween = 15; //let maxParty = Object.keys(ballotCurrent.poll).reduce((a, b) => Number(ballotCurrent.poll[a]) > Number(ballotCurrent.poll[b]) ? a : b) ////console.log(maxParty) sortedResultsArray.forEach((party, i) => { let result = party.current; let maxResult = sortedResultsArray[0].current; barContainer[i].style.width = `${100*(result/maxResult)*0.75}%`; }) } //add animate class on load, remove after animation so it doesn't fire again when it's redrawn on resize function addAnimation() { setTimeout(function() { for (let i = 0; i < document.getElementsByClassName("bar").length; i++) { document.getElementsByClassName("bar")[i].classList.add("animate"); document.getElementsByClassName("bar")[i].classList.remove("invisible"); } isLoaded = true; }, 300); removeAnimation(); }; function removeAnimation() { for (let i = 0; i < document.getElementsByClassName("bar").length; i++) { document.getElementsByClassName("bar")[i].classList.toggle("animate"); } }; //JS responsive code instead of CSS media queries so it resizes based on container function responsive() { let chartTitle = document.querySelector('#chartTitle'); let chartUpdate = document.querySelector('#chartUpdate'); let chartHeader = document.querySelector('#chartHeader'); let barContainer = document.querySelectorAll('.barContainer'); let bar = document.querySelectorAll('.bar'); let barChangeDiv = document.querySelectorAll('.barChangeDiv'); let numDiv = document.querySelectorAll('.numDiv'); let textDiv = document.querySelectorAll('.textDiv'); let partyNumDiv = document.querySelectorAll('.partyNumDiv'); let bigDiv = document.querySelectorAll('.bigDiv'); let chartBody = document.querySelector('#chartBody'); let changeText = document.querySelectorAll('.changeText'); let W = document.querySelector('#fullChart').offsetWidth; chartTitle.style.fontSize = `18px`; chartTitle.style.gridColumn = `2 / 3`; chartTitle.style.textAlign = `center`; chartTitle.style.margin = `0 auto`; chartTitle.style.width = `100%`; chartUpdate.style.gridRow = ``; chartUpdate.style.textAlign = `right`; chartUpdate.style.padding = `5px 0 0 0`; chartHeader.style.gridTemplateColumns = ``; chartHeader.style.gridTemplateRows = ``; chartHeader.style.padding = `15px 20px 25px 20px`; barChangeDiv.forEach(div => div.style.margin = `0 0 0 0`); barChangeDiv.forEach(div => div.style.gridColumn = ``); barChangeDiv.forEach(div => div.style.gridRow = ``); barContainer.forEach(bar => bar.style.height = `13px`); bar.forEach(bar => bar.style.height = `13px`); changeText.forEach(change => change.style.top = '0px') numDiv.forEach(div => div.style.fontSize = `17px`); textDiv.forEach(div => div.style.fontSize = `17px`); textDiv.forEach(div => div.style.width = `auto`); partyNumDiv.forEach(div => div.style.gridColumn = ``); partyNumDiv.forEach(div => div.style.gridRow = ``); bigDiv.forEach(div => div.style.gridTemplateColumns = ``); bigDiv.forEach(div => div.style.gridTemplateRows = `1fr 1fr`); bigDiv.forEach(div => div.style.position = `absolute`); chartBody.style.display = `block`; chartBody.style.gridTemplateColumns = ``; chartBody.style.gridTemplateRows= ``; chartBody.style.gridGap = ``; chartBody.style.height = `36px`; chartBody.style.padding = `0 0 35px 0`; if (W <= 768) { chartTitle.style.gridColumn = `1 / 2`; chartTitle.style.textAlign = `left`; chartTitle.style.margin = `0`; chartHeader.style.gridTemplateColumns = `1fr 140px`; chartHeader.style.padding = `15px 10px 15px 10px`; barChangeDiv.forEach(div => div.style.margin = `5px 0 0 0`); barChangeDiv.forEach(div => div.style.gridColumn = `2 / 3`); barChangeDiv.forEach(div => div.style.gridRow = `1 / 2`); barContainer.forEach(bar => bar.style.height = `10px`); bar.forEach(bar => bar.style.height = `10px`); changeText.forEach(change => change.style.top = '-1px') numDiv.forEach(div => div.style.fontSize = `16px`); textDiv.forEach(div => div.style.fontSize = `17px`); textDiv.forEach(div => div.style.width = `40px`); partyNumDiv.forEach(div => div.style.gridColumn = `1 / 2`); partyNumDiv.forEach(div => div.style.gridRow = `1 / 2`); bigDiv.forEach(div => div.style.width = `100%`); bigDiv.forEach(div => div.style.gridTemplateColumns = `95px 1fr`); bigDiv.forEach(div => div.style.gridTemplateRows = `15px`); bigDiv.forEach(div => div.style.position = `static`); chartBody.style.display = `grid`; chartBody.style.gridTemplateColumns = `1fr`; chartBody.style.gridTemplateRows= `repeat(6, 10px)`; chartBody.style.gridGap = `12px`; chartBody.style.height = `120px`; chartBody.style.padding = `0 10px 35px`; } if (W <= 576) { chartUpdate.style.gridRow = `2 / 3`; chartUpdate.style.textAlign = `left`; chartUpdate.style.padding = `3px 0 0 0`; chartHeader.style.gridTemplateColumns = `1fr`; chartHeader.style.gridTemplateRows = `1fr 1fr`; } if (W <= 340) { chartTitle.style.fontSize = `16px`; } }