任务目的
- 在上一任务基础上继续JavaScript的体验
- 接触更加复杂的表单对象
- 实现页面上的一个完整交互功能
- 用DOM实现一个柱状图图表
任务描述
- 参考以下示例代码,原始数据包含几个城市的空气质量指数数据
- 用户可以选择查看不同的时间粒度,以选择要查看的空气质量指数是以天为粒度还是以周或月为粒度
- 天:显示每天的空气质量指数
- 周:以自然周(周一到周日)为粒度,统计一周7天的平均数为这一周的空气质量数值,如果数据中缺少一个自然周的几天,则按剩余天进行计算
- 月:以自然月为粒度,统一一个月所有天的平均数为这一个月的空气质量数值
- 用户可以通过select切换城市
- 通过在"aqi-chart-wrap"里添加DOM,来模拟一个柱状图图表,横轴是时间,纵轴是空气质量指数,参考图(点击打开)。天、周、月的数据只根据用户的选择显示一种。
- 天:每天的数据是一个很细的矩形
- 周:每周的数据是一个矩形
- 月:每周的数据是一个很粗的矩形
- 鼠标移动到柱状图的某个柱子时,用title属性提示这个柱子的具体日期和数据
task.html
<!DOCTYPE> <html> <head> <meta charset="utf-8"> <title>IFE JavaScript Task 01</title> <script src="task.js"></script> </head> <body> <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"> <option>北京</option> </select> </fieldset> <div class="aqi-chart-wrap"> </div> </body> </html>
task.js
/* 数据格式演示 var aqiSourceData = { "北京": { "2016-01-01": 10, "2016-01-02": 10, "2016-01-03": 10, "2016-01-04": 10 } }; */ // 以下两个函数用于随机模拟生成测试数据 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) }; // 用于渲染图表的数据 var chartData = {}; // 记录当前页面的表单选项 var pageState = { nowSelectCity: -1, nowGraTime: "day" } /** * 渲染图表 */ function renderChart() { } /** * 日、周、月的radio事件点击时的处理函数 */ function graTimeChange() { // 确定是否选项发生了变化 // 设置对应数据 // 调用图表渲染函数 } /** * select发生变化时的处理函数 */ function citySelectChange() { // 确定是否选项发生了变化 // 设置对应数据 // 调用图表渲染函数 } /** * 初始化日、周、月的radio事件,当点击时,调用函数graTimeChange */ function initGraTimeForm() { } /** * 初始化城市Select下拉选择框中的选项 */ function initCitySelector() { // 读取aqiSourceData中的城市,然后设置id为city-select的下拉列表中的选项 // 给select设置事件,当选项发生变化时调用函数citySelectChange } /** * 初始化图表需要的数据格式 */ function initAqiChartData() { // 将原始的源数据处理成图表需要的数据格式 // 处理好的数据存到 chartData 中 } /** * 初始化函数 */ function init() { initGraTimeForm() initCitySelector(); initAqiChartData(); } init();
任务注意事项
- 实现简单功能的同时,请仔细学习JavaScript基本语法、事件、DOM相关的知识
- 请注意代码风格的整齐、优雅
- 代码中含有必要的注释
- 示例图仅为参考,不需要完全一致
- 点击select或者radio选项时,如果没有发生变化,则图表不需要重新渲染
- 建议不使用任何第三方库、框架
- 示例代码仅为示例,可以直接使用,也可以完全自己重写
任务完成与总结:
我看不懂的JS代码:
// 以下两个函数用于随机模拟生成测试数据 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) }; var colors = ['#16324a', '#24385e', '#393f65', '#4e4a67', '#5a4563', '#b38e95', '#edae9e', '#c1b9c2', '#bec3cb', '#9ea7bb', '#99b4ce', '#d7f0f8']; // 用于渲染图表的数据 var chartData = {}; // 记录当前页面的表单选项 var pageState = { nowSelectCity: -1, nowGraTime: "day" } function getWidth(width, len) { var posObj = {}; posObj.width = Math.floor(width / (len*2)); posObj.left = Math.floor(width / len); posObj.offsetLeft = (width - posObj.left * (len - 1) - posObj.width) / 2; return posObj; } function getHintLfeft(posObj, i){ if (posObj.left * i + posObj.offsetLeft + posObj.width / 2 - 60 <= 0) { return 5; } else if (posObj.left * i + posObj.offsetLeft + posObj.width / 2 + 60 >= 1200) { return (posObj.left * i + posObj.offsetLeft + posObj.width / 2 - 110); } else { return (posObj.left * i + posObj.offsetLeft + posObj.width / 2 - 60); } } function getTitle() { switch (pageState.nowGraTime) { case "day": return "每日"; case "week": return "周平均"; case "month": return "月平均"; } } /** * addEventHandler方法 * 跨浏览器实现事件绑定 */ function addEventHandler(ele, event, hanlder) { if (ele.addEventListener) { ele.addEventListener(event, hanlder, false); } else if (ele.attachEvent) { ele.attachEvent("on"+event, hanlder); } else { ele["on" + event] = hanlder; } } /** * 渲染图表 */ function renderChart() { var innerHTML = "", i = 0; var wrapper = document.getElementById("aqi-chart-wrap"); var width = wrapper.clientWidth; var selectedData = chartData[pageState.nowGraTime][pageState.nowSelectCity]; var len = Object.keys(selectedData).length; var posObj = getWidth(width, len); innerHTML += "<div class='title'>" + pageState.nowSelectCity + "市01-03月"+ getTitle() +"空气质量报告</div>" for (var key in selectedData) { innerHTML += "<div class='aqi-bar " + pageState.nowGraTime + "' style='height:" + selectedData[key] + "px; " + posObj.width +"px; left:" + (posObj.left * i + posObj.offsetLeft) + "px; background-color:" + colors[Math.floor(Math.random() * 11)] + "'></div>" innerHTML += "<div class='aqi-hint' style='bottom: " + (selectedData[key] + 10) + "px; left:" + getHintLfeft(posObj, i++) + "px'>" + key + "<br/> [AQI]: " + selectedData[key] + "</div>" } wrapper.innerHTML = innerHTML; } /** * 日、周、月的radio事件点击时的处理函数 */ function graTimeChange(radio) { // 确定是否选项发生了变化 var value = radio.value; var item = radio.previousElementSibling; var items = document.getElementsByTagName('span'); for (var i = 0; i < items.length; i++) { items[i].className = ""; } item.className = "selected"; if (value !== pageState.nowGraTime) { // 设置对应数据 pageState.nowGraTime = value; // 调用图表渲染函数 renderChart(); } } /** * select发生变化时的处理函数 */ function citySelectChange() { // 确定是否选项发生了变化 var city = this.value; if (city !== pageState.nowSelectCity) { // 设置对应数据 pageState.nowSelectCity = city; // 调用图表渲染函数 renderChart(); } } /** * 初始化日、周、月的radio事件,当点击时,调用函数graTimeChange */ function initGraTimeForm() { var radio = document.getElementsByName('gra-time'); for (var i = 0; i < radio.length; i++) { (function (m) { addEventHandler(radio[m], 'click', function () { graTimeChange(radio[m]) }) })(i); } addEventHandler(document, 'mouseover', function(event){ var ele = event.target; ele.className += " show"; }); addEventHandler(document, 'mouseout', function(event){ var ele = event.target; ele.className = ele.className.replace(/show/, ""); }); } /** * 初始化城市Select下拉选择框中的选项 */ function initCitySelector() { // 读取aqiSourceData中的城市,然后设置id为city-select的下拉列表中的选项 var select = document.getElementById("city-select"); var cityArr = Object.getOwnPropertyNames(aqiSourceData); var htmlArr = cityArr.map(function(item) { return "<option>" + item + "</option>"; }); pageState.nowSelectCity = cityArr[0]; select.innerHTML = htmlArr.join(""); // 给select设置事件,当选项发生变化时调用函数citySelectChange addEventHandler(select, 'change', citySelectChange); } /** * 初始化图表需要的数据格式 */ function initAqiChartData() { // 将原始的源数据处理成图表需要的数据格式 var week = {}, count = 0, singleWeek = {}, month = {}, mcount = 0, singleMonth = {}; for (var key in aqiSourceData) { var tempCity = aqiSourceData[key] var keyArr = Object.getOwnPropertyNames(tempCity); var tempMonth = keyArr[0].slice(5, 7); var weekInit = 4, weekCount = 0; for (var i = 0; i < keyArr.length; i++, weekInit++) { count += tempCity[keyArr[i]]; mcount += tempCity[keyArr[i]]; weekCount++; if ((weekInit+1) % 7 == 0 || i == keyArr.length - 1 || keyArr[i+1].slice(5, 7) !== tempMonth) { var tempKey = keyArr[i].slice(0, 7) + "月第" + (Math.floor(weekInit / 7) + 1) + "周"; singleWeek[tempKey] = Math.floor(count / weekCount); if (i != keyArr.length - 1 && keyArr[i+1].slice(5, 7) !== tempMonth) { weekInit = weekCount % 7; } count = 0; weekCount = 0; if (i == keyArr.length - 1 || keyArr[i+1].slice(5, 7) !== tempMonth) { tempMonth = (i == keyArr.length - 1) ? keyArr[i].slice(5, 7) : keyArr[i+1].slice(5, 7); var tempMKey = keyArr[i].slice(0, 7); var tempDays = keyArr[i].slice(-2); singleMonth[tempMKey] = Math.floor(mcount / tempDays); mcount = 0; } } } week[key] = singleWeek; month[key] = singleMonth; singleWeek = {}; singleMonth = {}; } // 处理好的数据存到 chartData 中 chartData.day = aqiSourceData; chartData.week = week; chartData.month = month; renderChart(); } /** * 初始化函数 */ function init() { initGraTimeForm(); initCitySelector(); initAqiChartData(); } init()
到了这步,有点小绝望,心里有些打击。看来得继续加油,好好加油!