diff --git a/src/index.js b/src/index.js index 1176b20..720fad6 100644 --- a/src/index.js +++ b/src/index.js @@ -1,13 +1,36 @@ var d3 = require('d3') import {readxml} from './data.js' -import {render_astronomy, render_temperature} from './render.js' +import {render_calendar, render_nights, render_astronomy, render_temperature} from './render.js' -let canvas = d3.select('svg') +const canvas = d3.select('svg') //const url = 'https://forecast.weather.gov/MapClick.php?lat=39.9243509&lon=-75.1696126&FcstType=digitalDWML' const url = '/forcast.xml' + +const render = function (data) { + data.beginTime = new Date( + data.startTimes[0].getFullYear(), + data.startTimes[0].getMonth(), + data.startTimes[0].getDate(), + 0,0,0 + ) + data.endTime = new Date( + data.endTimes[data.endTimes.length - 1].getFullYear(), + data.endTimes[data.endTimes.length - 1].getMonth(), + data.endTimes[data.endTimes.length - 1].getDate(), + 23,59,59 + ) + + const width = canvas.node().getBoundingClientRect().width + const height = canvas.node().getBoundingClientRect().height + + render_calendar(canvas, width, 20, 0, data) + render_nights(canvas, width, height - 20, 20, data) + render_astronomy(canvas, width, 70, 20, data) + render_temperature(canvas, width, 450, 70, data) +} + d3.xml(url).then(function(xmldoc) { let data = readxml(xmldoc) - render_astronomy(canvas, 80, 18, data) - render_temperature(canvas, 500, 80, data) + render(data) }) diff --git a/src/render.js b/src/render.js index 1863904..acaa5d2 100644 --- a/src/render.js +++ b/src/render.js @@ -3,29 +3,23 @@ var SunCalc = require('suncalc') const lat = 39.9243509 const lon = -75.1696126 +const leftMargin = 75 class Chart { - constructor(canvas, height, width, margin, offset, data, yrange) { + constructor(canvas, width, height, offset, margin, data, yrange) { this.canvas = canvas - this.height = height this.width = width - this.margin = margin + this.height = height this.offset = offset + this.margin = margin this.data = data - this.beginTime = new Date( - data.startTimes[0].getFullYear(), - data.startTimes[0].getMonth(), - data.startTimes[0].getDate(), - 0,0,0 - ) - this.endTime = data.endTimes[data.endTimes.length - 1] this.yRange = d3.scaleLinear() .domain(yrange) - .range([this.height - this.margin.bottom, offset]) + .range([height - margin.bottom, offset]) this.xRange = d3.scaleTime() - .domain([this.beginTime, this.endTime]) - .range([margin.left, width]) + .domain([data.beginTime, data.endTime]) + .range([margin.left, width - margin.right]) } draw_yAxisLine() { @@ -66,13 +60,6 @@ class Chart { .text(label) } - set_weekday(value, label) { - this.canvas.append('text').attr('class', 'axis-label') - .attr('text-anchor', 'middle').attr('dominant-baseline', 'alphabetic') - .attr('x', this.xRange(value)).attr('y', this.offset - 5) - .text(label) - } - set_xAxisTic(value, label, anchor) { const x = this.xRange(value) this.canvas.append('line').attr('class', 'axis') @@ -107,42 +94,133 @@ class Chart { } -const drawNights = function (chart) { - const year = chart.beginTime.getFullYear() - const month = chart.beginTime.getMonth() - for (var day = chart.beginTime.getDate(); day <= chart.endTime.getDate(); day++) { +/** + * Draw days to show time + */ +export function render_calendar(canvas, width, height, offset, data) { + const margin = { + top: 0, + left: leftMargin, + bottom: 0, + right: 0 + } + + const xRange = d3.scaleTime() + .domain([data.beginTime, data.endTime]) + .range([margin.left, width]) + + const calendar = canvas.append('g').attr('id', 'calendar') + const weekdays = calendar.append('g').attr('id', 'weekdays') + const dates = calendar.append('g').attr('id', 'dates') + + let noons = [] + let days = [] + const year = data.beginTime.getFullYear() + const month = data.beginTime.getMonth() + for (var day = data.beginTime.getDate(); day <= data.endTime.getDate(); day++) { + const dayBegin = new Date(year, month, day, 0, 1, 0) + const noon = new Date(year, month, day, 12, 0, 0) + noons.push(noon) + days.push(dayBegin) + } + + weekdays.selectAll('text').data(noons).enter() + .append('text') + .attr('text-anchor', 'middle').attr('dominant-baseline', 'alphabetic') + .attr('x', function(d) {return xRange(d)}) + .attr('y', height - 5) + .text(function (d) { + return d.toLocaleDateString('en-US', {weekday: 'long'}) + }) + + dates.selectAll('text').data(days).enter() + .append('text') + .attr('text-anchor', 'begin').attr('dominant-baseline', 'hanging') + .attr('x', function(d) {return xRange(d) + 3}) + .attr('y', 0) + .text(function (d) { + return d.toLocaleDateString('en-US', {day: 'numeric'}) + }) + dates.selectAll('line').data(days).enter() + .append('line').attr('class', 'axis') + .attr('x1', function(d) {return xRange(d)}).attr('y1', 0) + .attr('x2', function(d) {return xRange(d)}).attr('y2', height) + + calendar.append('line').attr('class', 'axis') + .attr('x1', margin.left).attr('y1', height) + .attr('x2', width - margin.right).attr('y2', height) +} + + +/** + * Draw sky brightness bars + */ +export function render_nights(canvas, width, height, offset, data) { + const margin = { + top: 0, + left: leftMargin, + bottom: 0, + right: 0 + } + + const xRange = d3.scaleTime() + .domain([data.beginTime, data.endTime]) + .range([margin.left, width]) + + const nights = canvas.append('g').attr('id', 'nights') + + const draw_box = function (x1, x2, style) { + const x = xRange(x1) + const width = xRange(x2) - x + nights.append('rect').attr('class', style) + .attr('x', x).attr('y', offset) + .attr('width', width) + .attr('height', height - margin.bottom) + } + + const year = data.beginTime.getFullYear() + const month = data.beginTime.getMonth() + for (var day = data.beginTime.getDate(); day <= data.endTime.getDate(); day++) { const midnight = new Date(year, month, day, 0, 0, 0) const sunEphem = SunCalc.getTimes(new Date(year, month, day, 12, 0, 0), lat, lon) - chart.draw_box(midnight, sunEphem.nauticalDawn, 'night') - chart.draw_box(sunEphem.nauticalDawn, sunEphem.sunrise, 'twilight') - chart.draw_box(sunEphem.sunset, sunEphem.nauticalDusk, 'twilight') - chart.draw_box(sunEphem.nauticalDusk, new Date(year, month, day, 24, 0, 0), 'night') + draw_box(midnight, sunEphem.nauticalDawn, 'night') + draw_box(sunEphem.nauticalDawn, sunEphem.sunrise, 'twilight') + draw_box(sunEphem.sunset, sunEphem.nauticalDusk, 'twilight') + draw_box(sunEphem.nauticalDusk, new Date(year, month, day, 24, 0, 0), 'night') + nights.append('line').attr('class', 'midnight') + .attr('x1', xRange(midnight)).attr('y1', offset) + .attr('x2', xRange(midnight)).attr('y2', height) } } /** * Sun and moon chart */ -export function render_astronomy(canvas, height, offset, data) { - const fullWidth = canvas.node().getBoundingClientRect().width - const margins = { - 'left': 75, - 'bottom': 15 +export function render_astronomy(canvas, width, height, offset, data) { + const margin = { + top: 0, + left: leftMargin, + bottom: 12, + right: 0 } - const yrange = [0, 75 * (Math.PI/180)] - const chart = new Chart(canvas, height, fullWidth, margins, offset, data, yrange) + const yrange = [0, 80 * (Math.PI/180)] + const chart = new Chart(canvas, width, height, offset, margin, data, yrange) + + const x = chart.xRange(data.beginTime) + const box_width = chart.xRange(data.endTime) - x + canvas.append('rect').attr('class', 'blank') + .attr('x', x).attr('y', height - margin.bottom) + .attr('width', box_width) + .attr('height', margin.bottom) chart.set_yAxisTic( 0, '0°') chart.set_yAxisTic(26.6 * (Math.PI/180), '27°') chart.set_yAxisTic(75 * (Math.PI/180), '75°') - drawNights(chart) - - const year = chart.beginTime.getFullYear() - const month = chart.beginTime.getMonth() - let sunData = [] - for (var day = chart.beginTime.getDate(); day <= chart.endTime.getDate(); day++) { + const year = data.beginTime.getFullYear() + const month = data.beginTime.getMonth() + for (var day = data.beginTime.getDate(); day <= data.endTime.getDate(); day++) { const midnight = new Date(year, month, day, 0, 0, 0) // Solar Ephemeris @@ -191,20 +269,8 @@ export function render_astronomy(canvas, height, offset, data) { .attr('class', 'sun') .attr('d', line) - for (var day = chart.beginTime.getDate(); day <= chart.endTime.getDate(); day++) { - const noon = new Date(year, month, day, 12, 0, 0) - const midnight = new Date(year, month, day, 0, 0, 0) - chart.set_weekday(noon, noon.toLocaleDateString('en-US', {weekday: 'long'})) - chart.draw_yGrid(noon, 'noon') - - // Don't draw the first midnight line because that's also our yAxis line - if (day != chart.beginTime.getDate()) { - chart.draw_yGrid(midnight, 'midnight') - } - } - - chart.draw_yAxisLine() - chart.draw_xAxisLineTop() + // chart.draw_yAxisLine() + // chart.draw_xAxisLineTop() chart.draw_xAxisLineBottom() } @@ -212,22 +278,21 @@ export function render_astronomy(canvas, height, offset, data) { /** * Temperature chart */ -export function render_temperature(canvas, height, offset, data) { - const fullWidth = canvas.node().getBoundingClientRect().width - const margins = { - 'left': 75, - 'bottom': 10 +export function render_temperature(canvas, width, height, offset, data) { + const margin = { + top: 0, + left: leftMargin, + bottom: 12, + right: 0 } const yrange = [-15, 45] - const chart = new Chart(canvas, height, fullWidth, margins, offset, data, yrange) + const chart = new Chart(canvas, width, height, offset, margin, data, yrange) const line = d3.line() .x(function(d) { return chart.xRange(d.date) }) .y(function(d) { return chart.yRange(d.temp) }) .curve(d3.curveBasis) - drawNights(chart) - chart.draw_yAxisTitle('Temperature') chart.set_yAxisTic(-10, '-10 °C') chart.draw_xGrid( -10, 'guide') @@ -242,24 +307,12 @@ export function render_temperature(canvas, height, offset, data) { chart.set_yAxisTic( 40, '40 °C') chart.draw_xGrid( 40, 'guide') - const year = chart.beginTime.getFullYear() - const month = chart.beginTime.getMonth() - for (var day = chart.beginTime.getDate(); day <= chart.endTime.getDate(); day++) { - const noon = new Date(year, month, day, 12, 0, 0) - chart.draw_yGrid(noon, 'noon') - // Don't draw the first midnight line because that's also our yAxis line - if (day != chart.beginTime.getDate()) { - const midnight = new Date(year, month, day, 0, 0, 0) - chart.draw_yGrid(midnight, 'midnight') - } - } - canvas.append('path') .data([data.temperature]) .attr('class', 'data') .attr('d', line) - chart.draw_yAxisLine() - chart.draw_xAxisLineTop() + //chart.draw_yAxisLine() + //chart.draw_xAxisLineTop() chart.draw_xAxisLineBottom() } diff --git a/src/style.sass b/src/style.sass index 0bf60cb..6fe8c51 100644 --- a/src/style.sass +++ b/src/style.sass @@ -6,10 +6,13 @@ html, body height: 100% font-family: sans-serif +$primary-font: sans-serif + .wrapper min-height: 100% margin-bottom: -25px position: relative + margin-right: 10px .wrapper:after content: "" @@ -17,11 +20,9 @@ html, body svg position: absolute - background: #f7f7f7 width: 100% height: 90% - footer, .wrapper:after height: 25px @@ -44,6 +45,22 @@ h1.title path, line fill: none +line + shape-rendering: crispEdges + +#calendar .axis + stroke: #bbb + stroke-width: 1px + +#weekdays text + font: bold 12px $primary-font + +#dates text + font: normal 10px $primary-font + +.blank + fill: rgba(255, 255, 255, 0.7) + path.data stroke: #f00 stroke-width: 2px @@ -57,9 +74,6 @@ path.moon stroke: #999 stroke-width: 1px -line - shape-rendering: crispEdges - line.now stroke: #f5f stroke-width: 1px