Learn how to make a Simple App showing Temperatures for the next seven days. We learn about SVG and API fetching.
Before we get into it, I want to stress that I am no JavaScript Pro; therefore, it may be that my practices are not the best or even the worst. Feel free to contact me in any way and tell me better ways to do things!
Idea
Today we will make a little Website that shows us the hourly temperatures in the next seven days for a certain choosable city. We will make the graph ourselves using the power of SVG. We get the data from the Open Meteo API (Licence). Before we get into coding, we look at how SVG will help us and what kind of data the API returns.
SVG
Scalable Vector Graphics are fantastic. They look good in all sizes and can easily be made programmatically because they are HTML tags. Below you see a simple SVG tag that will show a polygon. We will use this tag for our graph and line
and text
. In the program, we add the points.
<svg height="210" width="500">
<polygon points="200,10 250,190 160,210"/>
</svg>
API
We will request the hourly temperatures in the next seven days from the weather temperatures. We request an URL that looks something like this. The coordinates will be inserted.
https://api.open-meteo.com/v1/forecast?latitude=30.0&longitude=30.0&hourly=temperature_2m
Such a request will return a JSON string with the information we need.
{
"latitude": 30.0,
"longitude": 30.0,
"generationtime_ms": 1.3890266418457031,
"utc_offset_seconds": 0,
"elevation": 137.0,
"hourly_units": {
"time": "iso8601",
"temperature_2m": "°C"
},
"hourly": {
"time": [
"2022-08-03T00:00",
"2022-08-03T01:00",
"2022-08-03T02:00",
...
],
"temperature_2m": [
24.7,
24.3,
23.9,
...
]
}
}
Let's get into coding!
HTML
Let us start with the HTML of our website. As always, we use the default scaffolding that can be expanded in VS-code with the emmet abbreviation !
.
In the head, we import our stylesheet.
<link rel="stylesheet" href="style.css">
In our body, we have two div
elements and the script tag for the js of the website. The first div holds the header and the select
element for the choosable cities, and the other contains the SVG and the link to the API.
<div class="header-container">
<div class="header">
...
</div>
</div>
<div class="container graph-container">
<svg height="400" id="graph-svg">
...
</svg>
<div>
<a href="https://open-meteo.com" target="_blank">Weather Data Provided By Open-Meteo</a>
</div>
</div>
<script src="app.js"></script>
Inside the header, we set an h1
and a select
element. Each option represents a location; therefore, we give each of them a custom data-lon
and data-lat
attribute, which are the coordinates of the given area. We later access these attributes in the JS code.
<h1>Weather App</h1>
<div>
<span>Choose Location: </span>
<select name="" class="countrySelect">
<option value="Berlin" selected data-lat="52.5235" data-lon="13.4115">Berlin</option>
<option value="Paris" data-lat="48.8567" data-lon="2.3510">Paris</option>
<option value="London" data-lat="51.5002" data-lon="-0.1262">London</option>
<option value="Madrid" data-lat="40.4167" data-lon="-3.7033">Madrid</option>
...
</select>
</div>
Inside the svg
element, we first make a gradient texture that can be later used in visible SVG elements. Then we add two SVG groups with the element g
. The first one holds all the labels and the second one the graph itself. We will insert the other SVG that's through the JS code.
<defs>
<linearGradient id="gradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#5591c2 ;stop-opacity:1" />
<stop offset="50%" style="stop-color:#5591c2 ;stop-opacity:0.5" />
<stop offset="100%" style="stop-color:rgba(44, 44, 44, 0);stop-opacity:0" />
</linearGradient>
</defs>
<g class="lables"></g>
<g class="data"></g>
Now we add the functionalities with JavaScript!
JavaScript
Let's get to the JS of the Program. We must remember that we must redraw the graph every time the window resizes or when the user changes the location.
Setup
Let's setup up some variables. The first one currentData
acts just as a cache, so we don't have to get the data each time the window resizes. The following three constants are used for the styling of the graph and the labels. We need to add a little padding to the left inside the SVG element because our temperature scale will be there. We also exaggerate the temperatures, so they have a more apparent difference. And we also define how far apart each step on the temperature scale is.
let currentData = {}
const paddingLeftGraph = 60
const height_exxagaration = 7
const LabelStepsY = 5
After that, we save some of the needed elements in variables. For this we use querySelector
and getElementById
. continuing, we get the dimensions of the SVG element minus the padding; this is important so the graph will span the full width of the container. Last but not least, we define a step multiplier. This value will be used in tandem with the width.
The API will always return 168
Datapoints, but it may be that the SVG is 500 Pixels wide, and we want each of the Datapoints evenly spread out in these 500 Pixels. These last three variables will always be overwritten when the window size changes.
const dataContainer = document.querySelector('.data')
const lableContainer = document.querySelector('.lables')
const citySelect = document.querySelector('.countrySelect')
const svgElement = document.getElementById('graph-svg')
let container_width = svgElement.getBoundingClientRect().width - paddingLeftGraph
let container_height = svgElement.getBoundingClientRect().height
let step_multiplier = container_width / 168
Functions
Let us go over the function used on our website.
The first one is pretty simple. It will just fetch a provided URL and return the parsed json.
async function request(url) {
const response = await fetch(url);
const data = await response.json();
return data;
}
The next one is also simple as it will just set the new width and step multiplier and redraw the data and labels as soon as the window size changes.
function setSize() {
let size = svgElement.getBoundingClientRect()
container_width = size.width - paddingLeftGraph
step_multiplier = container_width / 168
DrawData()
drawLables()
}
The getWeekDay
function will return the name of the weekday from a given date.
function getWeekDay(date) {
let days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
return days[date.getDay()];
}
The changeLocation
function will first get the option element where the value is the same as the select itself. This returned element has the data-lon
and data-lat
attributes which we will get from it using getAttribute()
. After that we build the request string where insert these coordinates and we call the request()
function. As soon as the request is finished we set the currentData
variable to the returned data and we redraw the graph with DrawData()
function changeLocation() {
let currentCityEl = citySelect.querySelector(`[value="${citySelect.value}"]`)
let lat = currentCityEl.getAttribute('data-lat')
let lon = currentCityEl.getAttribute('data-lon')
let requestString = `https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}&hourly=temperature_2m`
request(requestString).then((data) => {
currentData = data
DrawData(currentData)
})
}
Now let us get to the DrawData()
function. We start by making a string in which we build a polygon
element. The thing about SVG polygons is that they always have to be closed, that is why we insert the positions of the start and end. We also add a placeholder INSERT_POINTS
which will later be replaced with the points of the temperatures. When making the start and end point we take the padding into account.
Then we define three variables one will hold the concatenated string of all the points and the other hold the hourly temperatures we got from the request. The last one will be the x index position.
Then we loop over the hourly temperatures and we add the temperature and index to the points. We have to keep in mind the step multiplier and that the coordinates in a polygon start at the top left of the SVG element. Last but not least we insert the points string we just made into the string we made in the first place and we set it to be the inner HTML of the dataContainer
which is a group in our SVG.
function DrawData() {
let poly = `<polygon fill="url(#gradient)" points="${paddingLeftGraph},${container_height} INSERT_POINTS ${container_width + paddingLeftGraph},${container_height}"></polygon>`
let positions = ''
const temps = currentData.hourly.temperature_2m
let index = 0
temps.forEach(temperature => {
positions = positions + ` ${index * step_multiplier + paddingLeftGraph},${container_height - temperature * height_exxagaration}`
index++
})
poly = poly.replace('INSERT_POINTS', positions)
dataContainer.innerHTML = poly
}
Let's all so go over the drawLables
function. We do it pretty similar here, we build a string of text
and line
elements and we set them to be the inner HTML of the lableContainer
. We make a loop where we count by our LabelStepsY
variable. We do this for the X and Y axis. For the horizontal axis, we insert the weekdays but we set today and tomorrow.
function drawLables() {
let verticalLinesString = ''
for (let index = 0; index < 50; index += LabelStepsY) {
let height = container_height - index * height_exxagaration
verticalLinesString = verticalLinesString + `<line x1="0" y1="${height}" x2="1000" y2="${height}" class="line" /><text x="0" y="${height}">${index} °C</text>`
}
let dayIndex = 0
for (let index = 0; index < 169; index += 24) {
let daytext
if (dayIndex == 0) {
daytext = 'Today'
}
else if (dayIndex == 1) {
daytext = 'Tomorrow'
}
else {
var date = new Date();
date.setDate(date.getDate() + dayIndex);
daytext = getWeekDay(date)
}
let width = index * step_multiplier + paddingLeftGraph
verticalLinesString = verticalLinesString + `<line x1="${width}" y1="50" x2="${width}" y2="1000" class="line"/><text x="${width}" y="${container_height - 5}">${daytext}</text>`
dayIndex++
}
lableContainer.innerHTML = verticalLinesString
}
Connecting Events
After making the functions we connect the change event from the selection to the changeLocation
function and the resize event from the document to setSize
. Last but not least we call changeLocation
and drawLables
so the graph is drawn for the firs time after the page loads.
citySelect.addEventListener('change', changeLocation)
window.addEventListener('resize', setSize, false);
changeLocation()
drawLables()
CSS / SASS
Our Web App will now work but it won't look good, so let's add some styling to it!
In the sass file, we first add four variables that hold the color palette and we define a mixin
which is a reusable component that we can later include anywhere with @include container
.
$color: #3e6b8f
$bg: rgb(65, 65, 65)
$lines: rgb(90, 90, 90)
$text: #f3f3f3
@mixin container()
max-width: 900px
margin: 0 auto
padding: 1em
After that, we style the body by setting the font and color. We also make a class container
that solely consists of the mixin.
body
box-sizing: border-box
font-family: 'Segoe UI', sans-serif
margin: 0
background-color: $bg
color: $text
.container
@include container
Then we make the header, where we also use the mixin
.header-container
background-color: $color
border-bottom: 6px solid darken($color, 5)
.header
@include container
padding: 1em
display: flex
justify-content: space-between
align-items: center
h1
margin: 0
continuing we style the SVG to be full width and we set the outline color of the lines with the stroke
property and the color of the text with fill
. Last but not least we style the link and we use the lighten function that will as its name suggests lighten the color by the amount given as a second argument.
.graph-container
margin-top: 1em
svg
width: 100%
.line
stroke: $lines
text
fill: $text
a
color: lighten($color, 20)
text-decoration: none
padding: 0.5em
display: block
text-align: center
Showcase
Below you see the Website in action.
Weather App (maximmaeder.com)
GitHub
Conclusion
Excellent! You have successfully created a Weather using Web Technologies! See how you can add more features to this program, such as rain prediction or custom locations.
Keep in mind that I am also just a beginner, so it could be that my way of solving these problems is not the best or that I use functions or features that aren't advised. Always ask questions and try to solve problems your way!
Attribution
Open Meteo
Licenceand try to solve problems your way!
Top comments (0)