프로젝트 정리

국비 데이터 분석 2차 프로젝트 결산 - 6

01241 2023. 4. 11. 12:53

Now를 누르면 각 지역별 코로나 현황을 표시하게 끔 하려고 했으나, 시간 이슈로 실패했다. 

그래서 일단 지도만 표현하게끔 해주었다.

아래에는 한국의 코로나 하루 증가량을 파악하기 위한 표를 만들었다.

아래 박스에서 선택을 해주면 각 그래프 모양이 바뀌고, 코로나 하루 증가량과 총 증가량으로 구분지었다.

 

 

 

코로나 지도를 쓰기 위해서 d3.js 의 geojson을 사용했다.

d3 version6이라서 js 코드가 약간 달랐다.

world map으로 canvas를 지정하고 input_corona로 나라가 지정되면 js에서 받아 변경하게 해줬다.

table_now 에서는 chart.js의 canvas로 그려줬다.

  <section class="headline wrap" >
  <body>
    <p>코로나 Now는 매일 15:00에 자동으로 최신화 됩니다. <br>
      글로벌 데이터는 3일전 데이터(GMT 2일전)가 반영됩니다. <br>
      아래 지도는 beta 버전으로 새 지도를 검색시 'clear'를 입력시 초기화 됩니다.
    </p>
    <div id="world_map">
      <h2 id = 'showCase'></h2>
      <input class='input_corona' type="text" placeholder='이곳에 국가별 영어표시를 입력해주세요. ex) KOR, USA'>
      <canvas width="800" height="400"></canvas>
      <svg id="my_dataviz" width="400" height="300"></svg>
      
      
    </div>
    <div id='table_now'>

      <canvas id="myChart1"></canvas>
      <select id="data_choice">
        <option value="all" >한국 코로나 총 수치</option>
        <option value="now" selected>한국 코로나 하루 증가량</option>
      </select>
      <div id='my_total'>
        <p>
          코로나 하루 증가량을 파이그래프로도 확인 가능합니다.</p>
      </div>
      <hr>
      <select id="graph_choice">
        <option value="pie" >파이 그래프</option>
        <option value="bar" selected>바 그래프</option>
      </select>
    </div>
  </body>

이맘 때쯤에 js에서 import로 다른 js와 연결시키는 것을 배웠다.

그래서 now.js 파일에 다른 now2를 연결시켰다.

일단 now.js는 geojson으로 지도를 그려주는 역할과 데이터가 입력되면 지도를 찾아가도록 해주었다.

이후 globalYesterday함수로 그 나라 확진자 수, 신규 확진자 수가 나오게 해줬다.

import {coronaNow} from './now2.js'
// import {globalYesterday} from './now4.js'
// import {enterdata,entData} from './now3.js'


// function DData(width,height,globalScale){
//   this.width = width,
//   this.height = height,
//   this.globalScale = globalScale
// }

let geojson = {};

let projections = [
  {type: 'Mercator', scale: 70},
];
let width, height, globalScale, fillStyle;
width = -6300, height = 2600, globalScale = 4.5,fillStyle = '#FFF';


function updateCanvas(d) {
  
  let context = this.getContext('2d');
  let projection = d3['geo' + d.type]()
    .scale(globalScale * 5 * d.scale) // 크기 정함
    .center([0, 0]) //중앙
    .rotate([0.1, 0, 0]) //위치
    .translate([0.5 * width, 0.5 * height]);  //위치


  let geoGenerator = d3.geoPath()
    .projection(projection)
    .context(context);

  context.lineWidth = 0.5;

  // // Graticule
  // context.strokeStyle = '#ccc';
  // context.fillStyle = 'none';
  // context.setLineDash([1,1]);
  // context.beginPath();
  // geoGenerator(geoGraticule());
  // context.stroke();


  // World
  context.fillStyle = fillStyle;
  context.setLineDash([]);
  context.beginPath();
  geoGenerator({type: 'FeatureCollection', features: geojson.features})
  context.fill();
  context.stroke();

  // // Circles
  // context.strokeStyle = '#888';
  // context.fillStyle = 'none';
  // circles.forEach(function(center) {
  //   geoCircle.center(center);
  //   context.beginPath();
  //   geoGenerator(geoCircle());
  //   context.stroke();
  // });

  // Projection label
  context.fillStyle = '#333';
  context.font = '14px sans-serif';
  context.fillText('geo' + d.type, 6, 17);

}


function update() {
  // console.log(width,height,globalScale)
  let u = d3.select('#world_map')
    .selectAll('canvas')
    .data(projections);

  u.enter()
    .append('canvas')
    .attr('width', width + 'px')
    .attr('height', height + 'px')
    .merge(u)
    .each(updateCanvas);
  u.exit().remove();
  
  
}
// // // load data and display the map on the canvas with country geometries
// d3.json("/static/main/json/NNLL.json")
//   .then(function (json) {
//     geojson = json;
//     update();
//     console.log(geojson)
//   });
d3.json('https://gist.githubusercontent.com/d3indepth/f28e1c3a99ea6d84986f35ac8646fac7/raw/c58cede8dab4673c91a3db702d50f7447b373d98/ne_110m_land.json')
	.then(function(json) {
		geojson = json;
		update();
	});

coronaNow() 

//coronaNow()로 json 불러오기
export async function globalYesterday() {
  const globalY = '../static/main/json/global.json';
  const response = await fetch(globalY);
  const data = await response.json();
  return data;
};
// json 데이터 정리


const enterdata = document.querySelector('.input_corona')
let showCase = document.querySelector("#showCase");
function entData(){
  
  geojson = {};
  
  // update()
  let Countrys


  
  enterdata.addEventListener('keypress', (e)=>{
    
    if(e.key == 'Enter'){

      switch (enterdata.value) {
        case 'clear':{
          width = 1320, height = 1600, globalScale = 10;
          fillStyle ='#000000';
          break         
        }
        case 'KOR':{
          // korData = new DData(-6300,2600,4)
          fillStyle='#FFF'
          width = -6300, height = 2600, globalScale = 4.5;
          Countrys = 'KOR'
          
          break
        }
        case 'USA':{
          fillStyle='#FFF'
          width = 2500, height = 1200, globalScale = 1.6;
          Countrys = 'USA'
          alert('미국은 자료가 없습니다.')
          break
        }
        case 'JPN':{
          fillStyle='#FFF'
          width = -7000, height = 2700, globalScale = 4.5;
          Countrys = 'JPN'
          break
        }
        case 'CHN':{
          fillStyle='#FFF'
          width = -5500, height = 2200, globalScale = 4.5;
          Countrys = 'CHN'
          break
        }
        case 'RUS':{
          fillStyle='#FFF'
          width = 0, height = 1500, globalScale = 1.6;
          Countrys = 'RUS'
          break
        }
        case 'FRA':{
          fillStyle='#FFF'
          width = 800, height = 2400, globalScale = 3.6;
          Countrys = 'FRA'
          break
        }
        case 'GBR':{
          fillStyle='#FFF'
          width = 800, height = 3150, globalScale = 3.6;
          Country = 'GBR'
          break
        }
        default:
          fillStyle ='#000000';
          alert('아직 등록되지 않은 나라입니다.')
          break
      }
      
    }
    globalYesterday().then(data => {
      let newDictCountry = {'Japan':'JPN','Republic of Korea':'KOR','China':'CHN','Russian Federation':'RUS','France':'FRA','The United Kingdom':'GBR'}
      let New_cases
      let New_deaths
      for (let i in data){
        let Country = data[i].Country
        
        if (newDictCountry[Country]){
          New_cases = data[i].New_cases
          New_deaths = data[i].New_deaths
          // console.log(New_cases,New_deaths,newDictCountry[Country])
          if (Countrys == newDictCountry[Country]){
            // console.log('aa')
            showCase.innerText =`나라명 : ${Country} // 새 확진자 수 : ${New_cases} // 새 사망자 수 : ${New_deaths}`
          }
        }
      }
    })
    update();
    
  })
}
entData()

now2.js는 국내 데이터 차트이다.

chart.js는 이전과 비슷하게 사용했고, 카테고리 선택 함수, 랜덤 색 뽑는 함수 정도만 더 추가해봤다.

//coronaNow()로 json 불러오기
export async function coronaNow() {
  const newCorona = '../static/main/json/newCorona.json';
  const response = await fetch(newCorona);
  const data = await response.json();
  return data;
};
// json 데이터 정리
coronaNow().then(data => {

  //초기화
  let new_loc_list = []
  let new_up_list = []
  let new_all_list = []


  for (let i in data){
    let loc = data[i].지역
    let all_people = data[i].총인원
    let up_people = data[i].증가값

    // chart 가져올때 숫자화
    let all_people_num = all_people.replace(/,/g,'')
    all_people_num = all_people_num *1
    up_people = up_people.replace(/[\(\)]/g,'')
    let up_people_num = up_people.replace(/[\+]/g,'')
    up_people_num = up_people_num * 1

    // chart data가 배열형태로 넣어야함
    new_loc_list.push(loc)
    new_up_list.push(up_people_num)
    new_all_list.push(all_people_num)

    // 나중에 시간나면 테이블 만들기
    // let tr = document.createElement('tr');
    // let th = document.createElement('th');
    // let td = document.createElement('td');

  }
  // console.log(Object.keys(data).length)

  //index 0 총합은 따로 빼서 다른 곳에서 사용하기
  new_loc_list.shift();
  const up_total = new_up_list.shift();
  new_all_list.shift();

  // div 밑에 p tag 넣어서 매번 자동으로 바뀌게 해주기
  const my_total_div = document.querySelector('#my_total');
  let p = document.createElement('p');
  p.innerHTML=(
    `국내 코로나 총 확진자 수 는 ${data[0].총인원}명 입니다. <br/>
    국내 지난 하루 확진자 증가 수는 ${up_total}명 입니다.`
    );
  p.style.fontSize = 'x-large'
  my_total_div.appendChild(p)
  
  // 16진법으로 #00ff00 이런식으로 랜덤으로 뽑기
  let color_list = []
  function backRandom1() {
    let colorCode = '#' + Math.round(Math.random() * 0xffffff).toString(16);
    color_list.push(colorCode)
  }

  // Object.keys(data).length -> 데이터가 object 타입으로 나옴, 이때는 이렇게 해주면 길이 찍힘
  // 랜덤 색 뽑기
  for (let i=0; i<Object.keys(data).length; i++){
    backRandom1();
  }

  //그래프 선택시 수정하도록 설정
  const data_choice = document.querySelector('#data_choice');
  const graph_choice = document.querySelector('#graph_choice');

  //selectData() 함수
  function selectData() {

    let select_value = data_choice.value;
    let graph_choice_value = graph_choice.value;

    // 각 데이터 고를 때 선택하도록
    const graphset_choice = ( graph_choice_value === 'bar' ? 'bar' : 'pie' )
    const dataset_choice = (select_value === 'now' ?
                            {
                              label: '한국 코로나 하루 증가량',
                              backgroundColor: color_list,
                              borderColor: 'rgb(255, 255, 255)',
                              data: new_up_list,
                              pointHoverBorderColor : 'rgb(0,255,255)',
                              pointHoverBorderWidth : 30,
                            }:
                            {
                              label: '한국 코로나 총 수치',
                              backgroundColor: color_list,
                              borderColor: 'rgb(255, 255, 255)',
                              data: new_all_list,
                              pointHoverBorderColor : 'rgb(0,255,255)',
                              pointHoverBorderWidth : 30,
                            })
      // data 입력
  const datas1 = {
    labels : new_loc_list,
    datasets: [
      dataset_choice
    ]
  }
  const ctx1 = document.getElementById('myChart1').getContext('2d');
  const chart1 = new Chart(ctx1, {
      // 만들기 원하는 차트의 유형
      type: graphset_choice,

      // 데이터 집합을 위한 데이터
      data: datas1,
      
      // 설정은 여기서 하세요
      options: {
        legend: {
          labels: {
            fontColor : 'white',
            fontSize : 18
          }
        },
        aspectRatio: 1,
        scales: {
          xAxes: [{
            ticks:{
              fontColor : "rgba(251, 203, 9, 1)",
              fontSize : 20,
            },
            gridLines:{
              color: "rgba(87, 152, 23, 1)",
              lineWidth: 1
            }
          }],
          x: {
            type: 'linear',
            position: 'bottom'
          },

          yAxes: [{
            ticks: {
                beginAtZero: true,
                // stepSize : 1,
                fontColor : "rgba(251, 203, 9, 1)",
                fontSize : 20,
            },
            gridLines:{
              color: 'rgba(166, 201, 226, 1)',
              lineWidth:3
            }
          }]
        }
      }
  })
  }
  // 선택하게 해서 결정시키기
  data_choice.addEventListener('change', () =>{
    selectData()
  })
  graph_choice.addEventListener('change', () =>{
    selectData()
  })

  // 초기에 실행
  selectData()

})

원래 계획은 aws에 연결 후 linux와 schedule을 이용해 매일 15시에 최신화 시키려 했으나, aws 배포에 실패해서, schedule만 자동으로 돌렸다.

아래는 자동화 코드이다.

import schedule
import time
import requests
import pandas as pd
def doCorona():
    address = 'https://ncov.kdca.go.kr/bdBoardList_Real.do?brdId=1&brdGubun=13&ncvContSeq=&contSeq=&board_id=&gubun='
    res = requests.get(address)
    res.encoding = None
    from bs4 import BeautifulSoup as bs
    soup = bs(res.text)
    sb = soup.select('.rpsa_map > .rpsam_graph button')
    newList=[]

    for s in sb:
        newDict={}
        newDict['지역'] = s.span.text
        newDict['총인원'] = s.find('span','num').text
        newDict['증가값'] = s.find('span','before').text
        newList.append(newDict)
    pd.DataFrame(newList).to_json('./main/static/main/json/newCorona.json', orient = 'index', indent = 4)
    # print('3초마다 실행되는지 확인')
# 매일 특정 HH:MM 및 다음 HH:MM:SS에 작업 실행
schedule.every(3).seconds.do(doCorona)
schedule.every().day.at("15:00").do(doCorona)
while True:
    schedule.run_pending()
    time.sleep(1)

후기는 나중에 써야지