supce's blog

IFE Task 04

任务目的


  • 在上一任务基础上继续JavaScript的体验
  • 接触更加复杂的表单对象
  • 实现页面上的一个完整交互功能
  • 用DOM实现一个柱状图图表

任务描述

  • 参考以下示例代码,原始数据包含几个城市的空气质量指数数据
  • 用户可以选择查看不同的时间粒度,以选择要查看的空气质量指数是以天为粒度还是以周或月为粒度
  • 天:显示每天的空气质量指数
  • 周:以自然周(周一到周日)为粒度,统计一周7天的平均数为这一周的空气质量数值,如果数据中缺少一个自然周的几天,则按剩余天进行计算
  • 月:以自然月为粒度,统一一个月所有天的平均数为这一个月的空气质量数值
  • 用户可以通过select切换城市
  • 通过在”aqi-chart-wrap”里添加DOM,来模拟一个柱状图图表,横轴是时间,纵轴是空气质量指数,参考图(点击打开)。天、周、月的数据只根据用户的选择显示一种。
  • 天:每天的数据是一个很细的矩形
  • 周:每周的数据是一个矩形
  • 月:每周的数据是一个很粗的矩形
  • 鼠标移动到柱状图的某个柱子时,用title属性提示这个柱子的具体日期和数据

整体思路

首先给相应的表单元素添加事件监听器,当事件触发时,判断选项是否发生了变化,如果发生变化则调用相应的处理函数,否则返回。

在处理函数中,首先根据用户选择的选项对数据进行操作,最后将数据渲染为图表。

最终代码

html

<fieldset id="form-gra-time">
   <legend>请选择日期粒度:</legend>
   <label><input name="gra-time" value="day" type="radio" checked="checked"></label>
   <label><input name="gra-time" value="week" type="radio"></label>
   <label><input name="gra-time" value="month" type="radio"></label>
 </fieldset>
 <fieldset>
   <legend>请选择查看的城市:</legend>
   <select id="city-select">
   </select>
 </fieldset>
 <div class="aqi-chart-wrap">
 </div>
 <script src="js/task17.js"></script>

js

/* 数据格式演示
var aqiSourceData = {
  "北京": {
    "2016-01-01": 10,
    "2016-01-02": 10,
    "2016-01-03": 10,
    "2016-01-04": 10
  }
};
*/
var $ = function(id){
  return document.getElementById(id);
}
function getRadioValue(name){
  var obj = document.getElementsByName(name);
  for(i=0;i<obj.length;i++){
    if(obj[i].checked){
      return obj[i].value;
    }
  }
  return "undefined";
}
// 以下两个函数用于随机模拟生成测试数据
function getDateStr(dat) {
  var y = dat.getFullYear();
  var m = dat.getMonth() + 1;
  m = m < 10 ? '0' + m : m;
  var d = dat.getDate();
  d = d < 10 ? '0' + d : d;
  return y + '-' + m + '-' + d;
}
function randomBuildData(seed) {
  var returnData = {};
  var dat = new Date("2016-01-01");
  var datStr = '';
  for (var i = 1; i < 92; i++) {
    datStr = getDateStr(dat);
    returnData[datStr] = Math.ceil(Math.random() * seed);
    dat.setDate(dat.getDate() + 1);
  }
  return returnData;
}
var aqiSourceData = {
  "北京": randomBuildData(500),
  "上海": randomBuildData(300),
  "广州": randomBuildData(200),
  "深圳": randomBuildData(100),
  "成都": randomBuildData(300),
  "西安": randomBuildData(500),
  "福州": randomBuildData(100),
  "厦门": randomBuildData(100),
  "沈阳": randomBuildData(500)
};
// console.log(aqiSourceData);
// 用于渲染图表的数据
var chartData = {};
// 记录当前页面的表单选项
var pageState = {
  nowSelectCity: "北京",
  nowGraTime: "day"
}
/**
 * 渲染图表
 */
function renderChart() {
  var text = "";
  for(item in chartData){
    var color = "#" + Math.floor(Math.random() * 0xFFFFFF).toString(16);
    text += '<div title="'+item+" : "+chartData[item]+'" style="height:'+chartData[item]+'px;background-color:'+color+'"></div>';
  }
  var aqiChartWrap = document.getElementsByClassName("aqi-chart-wrap")[0];
  // console.log(text);
  aqiChartWrap.innerHTML = text;
}
/**
 * 日、周、月的radio事件点击时的处理函数
 */
function graTimeChange() {
  // 确定是否选项发生了变化 
  var time = getRadioValue("gra-time");
  if(time == pageState["nowGraTime"]){
    return;
  }else{
    // console.log(time);
    pageState["nowGraTime"] = time;
  }

  // 设置对应数据
  initAqiChartData();
  // 调用图表渲染函数
  renderChart();
}
/**
 * select发生变化时的处理函数
 */
function citySelectChange(target) {
  // 确定是否选项发生了变化 
  var citySelect = target.options[target.selectedIndex].value;
  if(citySelect == pageState["nowSelectCity"]){
    return;
  }else{
    // console.log(citySelect);
    pageState["nowSelectCity"] = citySelect;
  }
  // 设置对应数据
  initAqiChartData();
  // 调用图表渲染函数
  renderChart();
}
/**
 * 初始化日、周、月的radio事件,当点击时,调用函数graTimeChange
 */
function initGraTimeForm() {
  var form = $("form-gra-time");
  var times = form.elements["gra-time"];
  for(var i=0;i<times.length;i++){
    times[i].addEventListener("change",graTimeChange,false);
  }
}
/**
 * 初始化城市Select下拉选择框中的选项
 */
function initCitySelector() {
  // 读取aqiSourceData中的城市,然后设置id为city-select的下拉列表中的选项
  for(items in aqiSourceData){
    $("city-select").innerHTML += "<option>"+items+"</option>";
  }
  // 给select设置事件,当选项发生变化时调用函数citySelectChange
  $("city-select").addEventListener("change",function(e){
    citySelectChange(e.target);
  });
}
/**
 * 初始化图表需要的数据格式
 */
function initAqiChartData() {
  // 将原始的源数据处理成图表需要的数据格式
  nowCityData = aqiSourceData[pageState.nowSelectCity];
  switch(pageState.nowGraTime){
    case "day":
      chartData = nowCityData;
      // console.log("day");
      break;
    case "week":
      chartData = {};
      var dataSum = 0,daySum = 0,week = 0;
      for(item in nowCityData){
        dataSum += nowCityData[item];
        daySum++;
        if(new Date(item).getDay() == 6){  //以自然周统计
          week++;
          chartData["第"+week+"周"] = Math.floor(dataSum/daySum);
          dataSum = 0;
          daySum = 0;
        }
      }
      //如果最后不满一周,则按剩余天数计算
      if(daySum != 0){
        week++;
        chartData["第"+week+"周"] = Math.floor(dataSum/daySum);
      }
      // console.log("week");
      break;
    case "month":
      chartData = {};
      var dataSum=0,daySum=0,curMonth = -1;
      for(var item in nowCityData){
        var date = new Date(item);
        var month = date.getMonth();
        if(month !== curMonth){
          if(daySum > 0){
            chartData[curMonth + 1 +"月份"] = Math.floor(dataSum/daySum);
          }
          curMonth = month;
          daySum = 0;
          dataSum = 0;
        }
        dataSum += nowCityData[item];
        daySum++;
      }
      //最后一月按实际天数计算
      if(daySum > 0){
        chartData[month + 1 +"月份"] = Math.floor(dataSum/daySum);
      }
      console.log(chartData);
      break;
  }
}
/**
 * 初始化函数
 */
function init() {
  initGraTimeForm()
  initCitySelector();
  initAqiChartData();
  renderChart();
}
init();

CSS

<style type="text/css">
  .aqi-chart-wrap{
    display: flex;
    justify-content: center;
    align-items: flex-end;
    align-content: center;
    margin: 20px auto;
    padding: 0 10px;
    width: 90%;
    height: 600px;
    border: 1px solid #333;
  }
  .aqi-chart-wrap div{
    flex: 1;
    border: 1px solid #000;
    border-bottom: none;
    margin-right:3px;
  }
</style>

最终效果: