data-dashboard
Version:
A data-driven dashboard console for report widgets
3 lines (2 loc) • 27.8 kB
JavaScript
//! data-dashboard v0.5.4 ~~ https://data-dashboard.js.org ~~ MIT License
var webApp=function(dnaEngine,fetchJson,webIgnition,chart_js,simpleDatatables$1,prettyPrintJson){"use strict";const webAppConfig={widgets:[{code:"fin-rate-intraday",header:"Exchange rate intraday"},{code:"fin-rate-moving-avg",header:"Exchange rate moving average"},{code:"network-endpoints",header:"REST endpoints"},{code:"network-log",header:"Network log"},{code:"network-rest-tool",header:"REST tool"},{code:"project-contributors",header:"dna-engine contributors"},{code:"project-json-questions",header:"JSON questions"},{code:"space-starships",header:"Starship data"},{code:"space-vehicles",header:"Vehicle data"},{code:"spacex-books",header:"SpaceX books"},{code:"trans-bart-departures",header:"BART departures"},{code:"trans-bart-stations",header:"BART stations"},{code:"trans-f1-top-countries",header:"F1 top countries"},{code:"trans-nyc-bike-stations",header:"NYC bike stations"}],panels:[{code:"starships",header:"Starships",display:true,widgets:["space-starships","space-vehicles"]},{code:"space",header:"Space",display:true,widgets:["spacex-books"]},{code:"trans",header:"Transportation",display:true,widgets:["trans-nyc-bike-stations","trans-bart-stations","trans-bart-departures","trans-f1-top-countries"]},{code:"finance",header:"Finance",display:true,widgets:["fin-rate-moving-avg","fin-rate-intraday"]},{code:"project",header:"Project",display:true,widgets:["project-contributors","project-json-questions"]},{code:"tbd",header:"TBD",display:false,widgets:["wip-widget"]},{code:"network",header:"Network",display:true,widgets:["network-log","network-endpoints","network-rest-tool"]}],chartColors:[{code:"red",value:"rgba(255, 99, 132, 0.7)"},{code:"blue",value:"rgba( 54, 162, 235, 0.7)"},{code:"yellow",value:"rgba(255, 206, 86, 0.7)"},{code:"green",value:"rgba( 75, 192, 192, 0.7)"},{code:"purple",value:"rgba(153, 102, 255, 0.7)"},{code:"orange",value:"rgba(255, 159, 64, 0.7)"},{code:"teal",value:"rgba( 0, 200, 230, 0.7)"}]};const webAppLookup={widgets:webAppConfig.widgets,panels:webAppConfig.panels,chartColors:webAppConfig.chartColors,widget:dna.array.toMap(webAppConfig.widgets),panel:dna.array.toMap(webAppConfig.panels),chartColor:dna.array.toMap(webAppConfig.chartColors)};const webAppUtil={lookupChartColor(i){return webAppLookup.chartColors[i%webAppLookup.chartColors.length].value},addChartColors(datasets,startIndex=0){const colorize=(dataset,i)=>{dataset.fill=false;dataset.borderColor=webAppUtil.lookupChartColor(startIndex+i);dataset.backgroundColor=webAppUtil.lookupChartColor(startIndex+i)};datasets.forEach(colorize);return datasets},narrowScreenSaver(chartInfo,options){const defaults={maxPoints:200,screenWidth:700};const settings={...defaults,...options};const shrinkRatio=Math.ceil(chartInfo.data.labels.length/settings.maxPoints);const shrinkNow=()=>{const shrink=points=>points.filter((_point,i)=>i%shrinkRatio===0);chartInfo.data.labels=shrink(chartInfo.data.labels);chartInfo.data.datasets.forEach(dataset=>dataset.data=shrink(dataset.data))};if(shrinkRatio>1&&globalThis.window.innerWidth<settings.screenWidth)shrinkNow();return chartInfo},secsToStr(epocSeconds){return new Date(epocSeconds*1e3).toISOString().replace("T","+").substring(0,19)},spinnerStart(widgetElem){const elem=widgetElem.closest("web-app-widget");const create=()=>{const options={html:"<i data-icon=yin-yang class=fa-spin>"};const spinner=dna.dom.create("web-app-widget-spinner",options);spinner.style.paddingTop=String(elem.clientHeight/2-50)+"px";elem.appendChild(webIgnition.libX.ui.makeIcons(spinner));return spinner};const spinnerElem=elem.querySelector("web-app-widget-spinner")||create();elem.classList.add("waiting");dna.ui.fadeIn(dna.ui.hide(spinnerElem));return widgetElem},spinnerStop(widgetElem){const elem=widgetElem.closest("web-app-widget");const spinner=elem.querySelector("web-app-widget-spinner");elem.classList.remove("waiting");dna.ui.fadeOut(spinner).then(dna.ui.hide);return elem}};const webAppNetwork={logName:"network-log",logEvent(...eventItems){console.info(eventItems.join(" - "));const maxLogEvents=250;const log=webAppNetwork.getLog();log.push(eventItems);while(log.length>maxLogEvents)log.shift();localStorage.setItem(webAppNetwork.logName,JSON.stringify(log))},getLog(){return JSON.parse(localStorage.getItem(webAppNetwork.logName)||"[]")}};const webAppTransformer={dataTablesNormalizer(rows,numColumns,indexRemoveColumn){const size=numColumns?numColumns:rows.length?rows[0].length:0;const normalize=row=>{const toStr=(value,i)=>row[i]=["boolean","number"].includes(typeof value)?String(value):"";row.forEach((value,i)=>typeof value!=="string"&&toStr(value,i));if(indexRemoveColumn!==undefined)row.splice(indexRemoveColumn,1);while(row.length<size)row.push("");while(row.length>size)row.pop()};rows.forEach(normalize);return rows}};const webAppWidgetFinRateIntraday={displayDataChart(widgetElem,rawData){const transform=rawData=>{const metadata=rawData["Meta Data"];const timeSeries=rawData["Time Series FX (5min)"];const timestamps=Object.keys(timeSeries).sort();const symbols=metadata["2. From Symbol"]+"/"+metadata["3. To Symbol"];return{title:metadata["1. Information"],subtitle:symbols+" "+metadata["4. Last Refreshed"],labels:timestamps.map(timestamp=>timestamp.substring(11,16)),lows:timestamps.map(timestamp=>parseFloat(timeSeries[timestamp]["3. low"])),highs:timestamps.map(timestamp=>parseFloat(timeSeries[timestamp]["2. high"]))}};const data=transform(rawData);const datasets=[{label:"Low",data:data.lows},{label:"High",data:data.highs}];const chartInfo={type:"line",data:{labels:data.labels,datasets:webAppUtil.addChartColors(datasets)},options:{maintainAspectRatio:false,plugins:{title:{display:true,text:[data.title,data.subtitle]}}}};const canvas=widgetElem.querySelector("canvas");dna.dom.state(widgetElem).chart=new chart_js.Chart(canvas,chartInfo)},show(widgetElem){const handleData=rawData=>{webAppUtil.spinnerStop(widgetElem);if(!rawData||rawData["Error Message"]||rawData["Information"])console.error(url,rawData);else webAppWidgetFinRateIntraday.displayDataChart(widgetElem,rawData)};const url="https://www.alphavantage.co/query";const params={function:"FX_INTRADAY",from_symbol:"EUR",to_symbol:"USD",interval:"5min",outputsize:"full",apikey:"demo"};webAppUtil.spinnerStart(widgetElem);fetchJson.fetchJson.get(url,params).then(handleData)}};const webAppWidgetFinRateMovingAvg={displayDataChart(widgetElem,rawData){const transform=rawData=>{const metadata=rawData["Meta Data"];const timeSeries=rawData["Technical Analysis: SMA"];const timestamps=Object.keys(timeSeries).sort();return{title:metadata["2: Indicator"],subtitle:metadata["3: Last Refreshed"],set:metadata["1: Symbol"],labels:timestamps,values:timestamps.map(timestamp=>parseFloat(timeSeries[timestamp].SMA))}};const data=transform(rawData);const dataset={label:data.set,data:data.values,borderColor:webAppLookup.chartColor.purple.value,backgroundColor:webAppLookup.chartColor.purple.value};const chartInfo={type:"line",data:{labels:data.labels,datasets:[dataset]},options:{maintainAspectRatio:false,plugins:{title:{display:true,text:[data.title,data.subtitle]}}}};const canvas=widgetElem.querySelector("canvas");dna.dom.state(widgetElem).chart=new chart_js.Chart(canvas,chartInfo)},show(widgetElem){const handleData=rawData=>{webAppUtil.spinnerStop(widgetElem);if(!rawData||rawData["Error Message"]||rawData["Information"])console.error(url,rawData);else webAppWidgetFinRateMovingAvg.displayDataChart(widgetElem,rawData)};const url="https://www.alphavantage.co/query";const params={function:"SMA",symbol:"USDEUR",interval:"weekly",time_period:10,series_type:"open",apikey:"demo"};webAppUtil.spinnerStart(widgetElem);fetchJson.fetchJson.get(url,params).then(handleData)}};const webAppWidgetNetworkEndpoints={show(widgetElem){const model=dnaEngine.dna.getModel(widgetElem);model.endpoints=[{name:"Alpha Vantage API",base:"https://www.alphavantage.co/query",docs:"https://www.alphavantage.co/documentation"},{name:"BART API",base:"https://api.bart.gov/api",docs:"https://api.bart.gov/docs/overview/examples.aspx"},{name:"Citi Bike",base:"https://gbfs.citibikenyc.com",docs:"https://www.citibikenyc.com/system-data"},{name:"Ergast Developer API",base:"https://ergast.com/api/f1",docs:"https://ergast.com/mrd/"},{name:"GitHub REST API",base:"https://api.github.com",docs:"https://developer.github.com/v3"},{name:"Google Books APIs",base:"https://www.googleapis.com/books",docs:"https://developers.google.com/books"},{name:"Flickr API",base:"https://api.flickr.com/services",docs:"https://www.flickr.com/services/feeds/docs/photos_public"},{name:"Stack Exchange API",base:"https://api.stackexchange.com",docs:"https://api.stackexchange.com/docs"},{name:"The Star Wars API",base:"https://swapi.py4e.com/api",docs:"https://swapi.py4e.com/documentation"}];return dnaEngine.dna.refresh(widgetElem)}};const webAppWidgetNetworkLog={show(widgetElem){const tableElem=widgetElem.querySelector("figure table");const options={perPageSelect:[10,25,50,100]};const dataTable=new simpleDatatables.DataTable(tableElem,options);const headers=fetchJson.fetchJson.getLogHeaders();const log=webAppNetwork.getLog().reverse();const delColumn=fetchJson.fetchJson.getLogHeaderIndexMap().domain;headers.splice(delColumn,1);webAppTransformer.dataTablesNormalizer(log,headers.length,delColumn);dataTable.insert({headings:headers,data:log});dna.dom.state(widgetElem).table=dataTable}};const webAppWidgetNetworkRestTool={elem:null,get(button){const elem=webAppWidgetNetworkRestTool.elem;const model=dnaEngine.dna.getModel(elem.widget);const handleData=data=>{model.restError=!!data.error;model.jsonHtml=prettyPrintJson.prettyPrintJson.toHtml(data);dnaEngine.dna.refresh(elem.widget,{html:true});webAppUtil.spinnerStop(elem.widget);elem.button.disabled=false;if(button)elem.input.focus();return data};const handleError=error=>{const rawData=error;handleData({error:true,name:rawData.name,message:rawData.message})};webAppUtil.spinnerStart(elem.widget);model.url=elem.input.value;fetchJson.fetchJson.get(model.url).then(handleData).catch(handleError)},show(widgetElem){const defaultRestUrl="https://dna-engine.org/api/books/1/";const elem={widget:widgetElem,input:widgetElem.querySelector("input"),button:widgetElem.querySelector("button")};webAppWidgetNetworkRestTool.elem=elem;elem.input.value=defaultRestUrl;webAppWidgetNetworkRestTool.get()}};const webAppWidgetProjectContributors={show(widgetElem){const url="https://api.github.com/repos/dna-engine/dna-engine/contributors";const handleData=data=>{webAppUtil.spinnerStop(widgetElem);const model=dnaEngine.dna.getModel(widgetElem);model.contributors=data;dnaEngine.dna.refresh(widgetElem)};webAppUtil.spinnerStart(widgetElem);fetchJson.fetchJson.get(url).then(handleData)}};const webAppWidgetProjectJsonQuestions={displayDataChart(widgetElem,data){const numItems=webAppLookup.chartColors.length;const title="Active JSON Questions";const subtitle=`Page views of ${numItems} most recently active JSON questions`;const mostRecent=data.slice(0,numItems).sort((a,b)=>b.view_count-a.view_count);const dataset={backgroundColor:webAppLookup.chartColors.map(color=>color.value),data:mostRecent.map(item=>item.view_count)};const chartInfo={type:"pie",data:{labels:mostRecent.map(item=>item.owner.display_name),datasets:[dataset]},options:{maintainAspectRatio:false,plugins:{title:{display:true,text:[title,subtitle]}}}};const canvas=widgetElem.querySelector("canvas");dna.dom.state(widgetElem).chart=new chart_js.Chart(canvas,chartInfo);webIgnition.libX.ui.normalize(widgetElem)},displayDataTable(widgetElem,data){const tableElem=widgetElem.querySelector("figure table");const dataTable=new simpleDatatables.DataTable(tableElem);data.forEach(item=>item.timestamp=webAppUtil.secsToStr(item.last_activity_date));data.forEach(item=>item.link=`<span data-href="${item.link}">${item.title}</span>`);const headers=["Last activity","Owner","Answered","Views","Score","Title"];const rows=data.map(item=>[item.timestamp??"",item.owner.display_name,item.is_answered,item.view_count,item.score||0,item.link]);webAppTransformer.dataTablesNormalizer(rows);dataTable.insert({headings:headers,data:rows});dna.dom.state(widgetElem).table=dataTable},show(widgetElem){const url="https://api.stackexchange.com/2.2/search";const params={order:"desc",sort:"activity",intitle:"json",site:"stackoverflow"};const handleData=data=>{webAppUtil.spinnerStop(widgetElem);webAppWidgetProjectJsonQuestions.displayDataChart(widgetElem,data.items);webAppWidgetProjectJsonQuestions.displayDataTable(widgetElem,data.items)};webAppUtil.spinnerStart(widgetElem);fetchJson.fetchJson.get(url,params).then(handleData)}};const webAppWidgetSpaceStarships={displayDataChart(widgetElem,starships){starships.forEach(item=>item.chart={passengers:Number(item.passengers)||0,crew:Number(item.crew)||0});starships.forEach(item=>item.chart.total=item.chart.passengers+item.chart.crew);starships.sort((itemA,itemB)=>itemA.chart.total-itemB.chart.total);const chartStarships=starships.slice(-11,-3);const datasets=[{label:"Passengers",data:chartStarships.map(item=>item.chart.passengers)},{label:"Crew",data:chartStarships.map(item=>item.chart.crew)}];const chartInfo={type:"bar",data:{labels:chartStarships.map(item=>item.name),datasets:webAppUtil.addChartColors(datasets,4)},options:{maintainAspectRatio:false,scales:{x:{stacked:true},y:{stacked:true}},plugins:{title:{display:true,text:["Larger Starships","Passengers and crew capacity"]}}}};const canvas=widgetElem.querySelector("canvas");dna.dom.state(widgetElem).chart=new chart_js.Chart(canvas,chartInfo)},displayDataTable(widgetElem,starships){const tableElem=widgetElem.querySelector("figure table");const dataTable=new simpleDatatables.DataTable(tableElem);const headers=["Name","Model","Length","Crew","Passengers","MGLT","Class"];const tableStarships=starships.map(starship=>[starship.name,starship.model,starship.length,starship.crew,starship.passengers,starship.MGLT,starship.starship_class]);dataTable.insert({headings:headers,data:tableStarships});dna.dom.state(widgetElem).table=dataTable},show(widgetElem){const starships=[];const displayData=()=>{webAppUtil.spinnerStop(widgetElem);webAppWidgetSpaceStarships.displayDataChart(widgetElem,starships);webAppWidgetSpaceStarships.displayDataTable(widgetElem,starships)};const handleData=data=>{starships.push(...data.results);if(data.next)fetchJson.fetchJson.get(data.next.replace("http://","https://")).then(handleData);else displayData()};const url="https://swapi.py4e.com/api/starships/";const params={format:"json"};webAppUtil.spinnerStart(widgetElem);fetchJson.fetchJson.get(url,params).then(handleData)}};const webAppWidgetSpaceVehicles={displayDataChart(widgetElem,vehicles){vehicles.forEach(item=>item.chart={passengers:Number(item.passengers)||0,crew:Number(item.crew)||0});vehicles.forEach(item=>item.chart.total=item.chart.passengers+item.chart.crew);vehicles.sort((itemA,itemB)=>itemA.chart.total-itemB.chart.total);const chartVehicles=vehicles.slice(-12,-4);const datasets=[{label:"Passengers",data:chartVehicles.map(item=>item.chart.passengers)},{label:"Crew",data:chartVehicles.map(item=>item.chart.crew)}];const chartInfo={type:"bar",data:{labels:chartVehicles.map(item=>item.name),datasets:webAppUtil.addChartColors(datasets,1)},options:{maintainAspectRatio:false,scales:{x:{stacked:true},y:{stacked:true}},plugins:{title:{display:true,text:["Larger Vehicles","Passengers and crew capacity"]}}}};const canvas=widgetElem.querySelector("canvas");dna.dom.state(widgetElem).chart=new chart_js.Chart(canvas,chartInfo)},displayDataTable(widgetElem,vehicles){const tableElem=widgetElem.querySelector("figure table");const dataTable=new simpleDatatables.DataTable(tableElem);const headers=["Name","Model","Length","Crew","Passengers","Class"];const tableVehicles=vehicles.map(vehicle=>[vehicle.name,vehicle.model,vehicle.length,vehicle.crew,vehicle.passengers,vehicle.vehicle_class]);dataTable.insert({headings:headers,data:tableVehicles});dna.dom.state(widgetElem).table=dataTable},show(widgetElem){const vehicles=[];const displayData=()=>{webAppUtil.spinnerStop(widgetElem);webAppWidgetSpaceVehicles.displayDataChart(widgetElem,vehicles);webAppWidgetSpaceVehicles.displayDataTable(widgetElem,vehicles)};const handleData=data=>{vehicles.push(...data.results);if(data.next)fetchJson.fetchJson.get(data.next.replace("http://","https://")).then(handleData);else displayData()};const url="https://swapi.py4e.com/api/vehicles/";const params={format:"json"};webAppUtil.spinnerStart(widgetElem);fetchJson.fetchJson.get(url,params).then(handleData)}};const webAppWidgetSpacexBooks={show(widgetElem){const url="https://www.googleapis.com/books/v1/volumes";const params={q:"spacex"};const hasCover=book=>!!book.volumeInfo.imageLinks;const fixHttpProtocol=book=>book.volumeInfo.imageLinks.thumbnail=book.volumeInfo.imageLinks.thumbnail.replace("http:","https:");const handleData=data=>{webAppUtil.spinnerStop(widgetElem);const model=dnaEngine.dna.getModel(widgetElem);model.books=data.items.filter(hasCover);model.books.forEach(fixHttpProtocol);dnaEngine.dna.refresh(widgetElem)};webAppUtil.spinnerStart(widgetElem);fetchJson.fetchJson.get(url,params).then(handleData)}};const webAppWidgetTransBartDepartures={displayDataChart(widgetElem,timestamp,station){const title=`${station.abbr} -- Upcoming departures from ${station.name}`;const subtitle=timestamp;const yAxesLabel="Direction";const xAxesLabel="Estimated minutes until departure";const etd=station.etd??[];etd.forEach(dest=>dest.estimate.forEach(est=>est.destination=dest.destination));const toChartData=item=>({direction:item.direction,minutes:Number(item.minutes)||0,label:"Platform #"+item.platform+" to "+item.destination});const compareMinutes=(a,b)=>a.minutes-b.minutes;const estimates=etd.map(destination=>destination.estimate).reduce((a,b)=>a.concat(b),[]).map(toChartData).sort(compareMinutes);const onlyUnique=(direction,i,array)=>array.indexOf(direction)===i;const directions=estimates.map(item=>item.direction).filter(onlyUnique);const directionEstimates=directions.map(direction=>estimates.filter(item=>item.direction===direction));const calcDelta=(estimate,i,estimates)=>estimate.delta=estimates[i].minutes-(i?estimates[i-1].minutes:0)+1;directionEstimates.forEach(de=>{de.forEach(calcDelta)});const maxEstimates=Math.max(...directionEstimates.map(estimate=>estimate.length));const padEstimates=estimates=>{while(estimates.length<maxEstimates)estimates.push({direction:"",minutes:0,label:"",delta:0})};directionEstimates.forEach(padEstimates);const datasets=[];while(datasets.length<maxEstimates)datasets.push({label:"Train "+String(datasets.length+1),labels:directionEstimates.map(estimates=>estimates[datasets.length].label),data:directionEstimates.map(estimates=>estimates[datasets.length].delta)});const scales={x:{stacked:true,scaleLabel:{display:true,labelString:xAxesLabel}},y:{stacked:true,scaleLabel:{display:true,labelString:yAxesLabel}}};const makeTooltip=item=>`${item.dataset.label}: ${item.dataset.labels[item.dataIndex]}`;const chartInfo={type:"bar",data:{labels:directions,datasets:webAppUtil.addChartColors(datasets)},options:{indexAxis:"y",maintainAspectRatio:false,scales:scales,plugins:{title:{display:true,text:[title,subtitle]},tooltip:{callbacks:{label:makeTooltip}}}}};const canvas=widgetElem.querySelector("canvas");dna.dom.state(widgetElem).chart=new chart_js.Chart(canvas,chartInfo)},show(widgetElem){const handleData=data=>{webAppUtil.spinnerStop(widgetElem);const timestamp=data.root.date+" "+data.root.time;const station=data.root.station[0];if(station.message?.error)console.info(url,station.message.error);webAppWidgetTransBartDepartures.displayDataChart(widgetElem,timestamp,station)};const url="https://api.bart.gov/api/etd.aspx";const params={cmd:"etd",orig:"embr",key:"MW9S-E7SL-26DU-VV8V",json:"y"};webAppUtil.spinnerStart(widgetElem);fetchJson.fetchJson.get(url,params).then(handleData)}};const webAppWidgetTransBartStations={displayDataChart(widgetElem,stations){const dataset={label:"Geolocation",backgroundColor:webAppLookup.chartColor.green.value,data:stations.map(item=>({x:parseFloat(item.gtfs_longitude),y:parseFloat(item.gtfs_latitude),label:item.abbr+" ("+item.name+")"}))};const latLong=item=>{const lat=item.parsed.y;const long=item.parsed.x;const fixed=degrees=>Math.abs(degrees).toFixed(2);return`${fixed(lat)}°${lat>0?"N":"S"} ${fixed(long)}°${long>0?"E":"W"}`};const makeTooltip=item=>`${item.dataset.data[item.dataIndex].label} ${latLong(item)}`;const chartInfo={type:"scatter",data:{datasets:[dataset]},options:{maintainAspectRatio:false,plugins:{title:{display:true,text:["BART Stations","San Francisco Bay Area"]},tooltip:{callbacks:{label:makeTooltip}}}}};const canvas=widgetElem.querySelector("canvas");dna.dom.state(widgetElem).chart=new chart_js.Chart(canvas,chartInfo)},displayDataTable(widgetElem,stations){const tableElem=widgetElem.querySelector("figure table");const dataTable=new simpleDatatables.DataTable(tableElem);const headers=["Name","Code","Latitude","Longitude","City","County"];const rows=stations.map(station=>[station.name,station.abbr,station.gtfs_latitude,station.gtfs_longitude,station.city,station.county]);dataTable.insert({headings:headers,data:rows});dna.dom.state(widgetElem).table=dataTable},show(widgetElem){const handleData=data=>{webAppUtil.spinnerStop(widgetElem);const stations=data.root.stations.station;webAppWidgetTransBartStations.displayDataChart(widgetElem,stations);webAppWidgetTransBartStations.displayDataTable(widgetElem,stations)};const url="https://api.bart.gov/api/stn.aspx";const params={cmd:"stns",key:"MW9S-E7SL-26DU-VV8V",json:"y"};webAppUtil.spinnerStart(widgetElem);fetchJson.fetchJson.get(url,params).then(handleData)}};const webAppWidgetTransF1TopCountries={displayDataChart(widgetElem,race){const topFinishes=10;const title="Nationalities of Top F1 Drivers and Constructors";const subtitle=`${race.season} ${race.raceName} top ${topFinishes} finishes`;const round=Number(race.round);const addResult=(totalsMap,result)=>{const setupNationality=nationality=>{if(!totalsMap[nationality])totalsMap[nationality]={nationality:nationality,numDrivers:0,numConstructors:0}};[result.Driver.nationality,result.Constructor.nationality].forEach(setupNationality);totalsMap[result.Driver.nationality].numDrivers++;totalsMap[result.Constructor.nationality].numConstructors++;return totalsMap};const totals=race.Results.slice(0,topFinishes).reduce(addResult,{});const data=Object.keys(totals).map(nationality=>totals[nationality]);data.sort((a,b)=>a.numDrivers+a.numConstructors-b.numDrivers-b.numConstructors||a.nationality.localeCompare(b.nationality));const datasets=[{label:"Driver",data:data.map(item=>item.numDrivers)},{label:"Constructor",data:data.map(item=>item.numConstructors)}];const chartInfo={type:"bar",data:{labels:data.map(item=>item.nationality),datasets:webAppUtil.addChartColors(datasets)},options:{maintainAspectRatio:false,scales:{x:{stacked:true},y:{stacked:true}},plugins:{title:{display:true,text:[title,subtitle]}}}};const canvas=widgetElem.querySelectorAll("canvas")[round-1];dna.dom.state(widgetElem).chart=new chart_js.Chart(canvas,chartInfo)},show(widgetElem){const raceYear=(new Date).getFullYear()-1;const handleData=data=>{webAppUtil.spinnerStop(widgetElem);const race=data.MRData.RaceTable.Races[0];webAppWidgetTransF1TopCountries.displayDataChart(widgetElem,race)};webAppUtil.spinnerStart(widgetElem);const display=(canvas,index)=>{const round=index+1;const url=`https://ergast.com/api/f1/${raceYear}/${round}/results.json`;fetchJson.fetchJson.get(url).then(handleData)};widgetElem.querySelectorAll("web-app-widget-body >figure >canvas").forEach(display)}};const webAppWidgetTransNycBikeStations={displayDataChart(widgetElem,data){const title="NYC Bike Stations";const subtitle="Capacity on "+new Date(data.last_updated*1e3).toLocaleString();const stations=data.data.stations;stations.forEach(station=>station.capacity=station.num_docks_available+station.num_bikes_available+station.num_bikes_disabled);stations.sort((a,b)=>a.capacity-b.capacity);stations.forEach(station=>station.reservedBikes=station.totalDocks-station.availableDocks-station.availableBikes);const datasets=[{label:"Available docks",data:stations.map(station=>station.num_docks_available)},{label:"Available bikes",data:stations.map(station=>station.num_bikes_available)},{label:"Disabled bikes",data:stations.map(station=>station.num_bikes_disabled)}];const chartInfo={type:"bar",data:{labels:Array.from({length:stations.length},(value,i)=>i+1),datasets:webAppUtil.addChartColors(datasets,3)},options:{maintainAspectRatio:false,scales:{x:{stacked:true},y:{stacked:true}},plugins:{title:{display:true,text:[title,subtitle]}}}};webAppUtil.narrowScreenSaver(chartInfo);const canvas=widgetElem.querySelector("canvas");dna.dom.state(widgetElem).chart=new chart_js.Chart(canvas,chartInfo)},show(widgetElem){const url="https://gbfs.citibikenyc.com/gbfs/en/station_status.json";const handleData=data=>{webAppUtil.spinnerStop(widgetElem);webAppWidgetTransNycBikeStations.displayDataChart(widgetElem,data)};webAppUtil.spinnerStart(widgetElem);fetchJson.fetchJson.get(url).then(handleData)}};const webAppWidgets={finRateIntraday:webAppWidgetFinRateIntraday,finRateMovingAvg:webAppWidgetFinRateMovingAvg,networkEndpoints:webAppWidgetNetworkEndpoints,networkLog:webAppWidgetNetworkLog,networkRestTool:webAppWidgetNetworkRestTool,projectContributors:webAppWidgetProjectContributors,projectJsonQuestions:webAppWidgetProjectJsonQuestions,spaceStarships:webAppWidgetSpaceStarships,spaceVehicles:webAppWidgetSpaceVehicles,spacexBooks:webAppWidgetSpacexBooks,transBartDepartures:webAppWidgetTransBartDepartures,transBartStations:webAppWidgetTransBartStations,transF1TopCountries:webAppWidgetTransF1TopCountries,transNycBikeStations:webAppWidgetTransNycBikeStations};const webAppController={showPanel(panelElem){globalThis.window.scrollTo({top:0});const webAppWidgetsElem=panelElem.querySelector("web-app-widgets");const showWidget=widgetElem=>{const widget=dnaEngine.dna.getModel(widgetElem)??null;const msg={missingWidget:"Missing widget, index: %s, panel: %s",missingController:"Widget controller missing: %s"};if(!widget)throw new Error("[data-dashboard] "+dnaEngine.dna.util.printf(msg.missingWidget,panelElem.dataset.hash));widgetElem.querySelector("web-app-widget-body")?.remove();widgetElem.appendChild(dnaEngine.dna.clone(widget.code,{}));const webAppWidgetsKey=dnaEngine.dna.util.toCamel(widget.code);const widgetController=webAppWidgets[webAppWidgetsKey];if(!widgetController)throw new Error("[data-dashboard] "+dnaEngine.dna.util.printf(msg.missingController,widget.code));widgetController.show(widgetElem)};dnaEngine.dna.dom.forEach(webAppWidgetsElem.children,showWidget);return panelElem},setup(){webIgnition.libX.ui.autoDisableButtons();dnaEngine.dna.registerInitializer(webIgnition.libX.bubbleHelp.setup);fetchJson.fetchJson.enableLogger(webAppNetwork.logEvent);webAppLookup.panels.forEach(panel=>panel.widgetList=panel.widgets.map(code=>webAppLookup.widget[code]));const displayedPanels=webAppLookup.panels.filter(panel=>panel.display);const onLoadSetup=()=>{dnaEngine.dna.clone("web-app-menu-item",displayedPanels);dnaEngine.dna.clone("web-app-panel",displayedPanels)};dnaEngine.dna.dom.onReady(onLoadSetup)}};const webApp={config:webAppConfig,controller:webAppController,util:webAppUtil,network:webAppNetwork,transformer:webAppTransformer,lookup:webAppLookup,widgets:webAppWidgets,setup(){console.info("DataDashboard");console.info("Widgets:",webAppConfig.widgets.map(widget=>widget.code));webApp.controller.setup()}};webApp.setup();return webApp}(globalThis,globalThis,globalThis,globalThis,globalThis,globalThis);