Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c18355e9e5 | |||
| b335ec2b16 | |||
| 61aba5e9e4 | |||
| d6acf623c0 | |||
| 0983b959ab | |||
| 58ffcec09b |
@@ -17,14 +17,10 @@ console.log('PART 3')
|
||||
|
||||
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
|
||||
|
||||
/* A function that returns eplekake, eple, kake or n based on what n can be divided by */
|
||||
const eplekakeCheck = (n) => {
|
||||
/* if it's divisible by 15 (which is 3*5), it's also divisible by both 3 and 5 */
|
||||
if (i % 15 == 0) return 'eplekake';
|
||||
else if (i % 3 == 0) return 'eple';
|
||||
else if (i % 5 == 0) return 'kake';
|
||||
else return String(n);
|
||||
}
|
||||
/* A function that returns eple and/or kake if it's divisible by 3 or 5, but defaults
|
||||
* to n if it's not (an empty string combined with || will return the latter element)
|
||||
*/
|
||||
const eplekakeCheck = (n) => (n % 3 ? '' : 'eple') + (n % 5 ? '' : 'kake') || n;
|
||||
|
||||
/* Print the output of eplekakeCheck for each element in numbers */
|
||||
for (i of numbers) {
|
||||
|
||||
BIN
Exercise 6/images/calculate-tax.gif
Normal file
BIN
Exercise 6/images/calculate-tax.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
BIN
Exercise 6/images/gif.mov
Normal file
BIN
Exercise 6/images/gif.mov
Normal file
Binary file not shown.
76
Exercise 6/index.html
Normal file
76
Exercise 6/index.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Tax form</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="./style.css">
|
||||
<script src="./script.js" defer></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
|
||||
<header>
|
||||
<h1>Hero tax</h1>
|
||||
</header>
|
||||
|
||||
<form action="http://folk.ntnu.no/michaedm/norwegian-tax-administration/receive.php" method="post">
|
||||
<label for="nameInput">Real name</label>
|
||||
<input type="text" name="name" id="nameInput" pattern="[A-Za-z]+" title="Please use only alphanumeric characters" autofocus required>
|
||||
|
||||
<!-- Another option for genders would've been radio buttons -->
|
||||
|
||||
<label for="genderInput">Gender</label>
|
||||
<select name="gender" id="genderInput" required>
|
||||
<option value="">None</option>
|
||||
<option value="male">Male</option>
|
||||
<option value="female">Female</option>
|
||||
</select>
|
||||
|
||||
<label for="emailInput">E-mail</label>
|
||||
<input type="email" name="email" id="emailInput" required>
|
||||
|
||||
<!-- This might not return the value in the DD.MM.YYYY format, but it's usually considered to be the safest and most correct method for date inputs. Also, even though the html validator doesn't like it, the regexp is only used as a fallback field if the browser has no native date picker (see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date#Handling_browser_support). If you need conversion for something like a database, this would usually be done in javascript -->
|
||||
|
||||
<label for="birthdayInput">Birthdate</label>
|
||||
<input type="date" name="birthday" id="birthdayInput" max="1987-12-31" pattern="\d{2}\.\d{2}\.\d{4}" required>
|
||||
|
||||
<label for="heroInput">Hero name</label>
|
||||
<input type="text" name="hero" id="heroInput" required>
|
||||
|
||||
<div class="inline">
|
||||
<label for="wearsSpandexInput">Do you wear spandex?</label>
|
||||
<input type="checkbox" name="spandex" id="wearsSpandexInput" required>
|
||||
</div>
|
||||
|
||||
<div id="sliders">
|
||||
|
||||
<!-- I made these inputs as sliders because I felt it made more sense considering the "floatish" nature of these properties. If we had been allowed to use javascript for this task, I probably also would've added some kind of a number that would show the slider value. If it's really important that these values could be specified as an exact value, you would only need to change the type from "range" to "number" (and maybe add a required-attribute). Min, max and step will work in the same way. -->
|
||||
|
||||
<label for="strengthInput">Strength</label>
|
||||
<input type="range" name="strength" id="strengthInput" min="1" max="10" step="1">
|
||||
<label for="speedInput">Speed</label>
|
||||
<input type="range" name="speed" id="speedInput" min="1" max="10" step="1">
|
||||
|
||||
<label for="intelligenceInput">Intelligence</label>
|
||||
<input type="range" name="intelligence" id="intelligenceInput" min="1" max="10" step="1">
|
||||
</div>
|
||||
|
||||
<div id="taxFields">
|
||||
<label for="incomeInput">Income</label>
|
||||
<input type="number" name="income" id="incomeInput" min="0" step="1" required>
|
||||
|
||||
<label for="wealthInput">Wealth</label>
|
||||
<input type="number" name="wealth" id="wealthInput" min="0" step="1" required>
|
||||
|
||||
<label for="taxInput">Tax</label>
|
||||
<input type="number" name="tax" id="taxInput" readonly>
|
||||
</div>
|
||||
|
||||
<input type="submit" value="Submit" id="submitButton">
|
||||
</form>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
23
Exercise 6/questions.txt
Normal file
23
Exercise 6/questions.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
# Questions
|
||||
|
||||
1. Why does the expression `0.1 + 0.2 === 0.3` evaluate to `false` in javascript?
|
||||
|
||||
Because of how most programming languages represent fractions, not all numbers can be represented correctly. Just like how we can't represent 1/3 in our base 10 number system, neither 0.1 nor 0.2 can't be represented without recurring digits, and therefore we end up with an approximation. `0.1 + 0.2` is a pretty common example of this, and it resolves to `0.30000000000000004`
|
||||
|
||||
2. What does the `method` attribute on a `form` element decide?
|
||||
|
||||
The `method` of on a `form` decides what kind of HTTP request to send on submission. `POST` and `ADD` are the most normal ones to use for this. You might use `GET` if the data isn't sensitive.
|
||||
|
||||
3. Explain why the form fields appear in the URL when the `method` on `form` element is changed to `GET`?
|
||||
|
||||
There are several types of HTTP requests. Some of them are quite similar but named differently for semantic purposes. `GET` and `POST` however, are quite different. `POST` is a generic request used for sending data to a server. Therefore, the packets contain data fields, and it can even be encrypted. `GET` however is only supposed to be used to fetch information from the server. `GET` requests have no data fields. If you want to include any modifiers or data in the request, it has to go in the URL of the request (with a few exceptions for metadata such as some kinds of system information, cookies, authentication certificates etc.)
|
||||
|
||||
4. Why is it considered bad practise to only validate form input with HTML5 and/or Javascript?
|
||||
|
||||
You can't run all kinds of validation in HTML. There are only basic, but often used options for HTML validation. Javascript however can do a lot more advanced validation, but using this to reimplement everything that the browser already does for you is bad practise. You would essentially be reinventing the wheel. It also means you'd have to take care of things like giving the user feedback on what's wrong. In the end, you would also want backend validation on a real project so no one can just bypass the html and javascript validation with sending an http request directly containing bad or even malicious data.
|
||||
|
||||
5. When is it appropriate to use `textarea` over `input`?
|
||||
|
||||
`textarea` is used whenever you have a long text, such as a tweet, a paragraph or really just anything longer than a few words. If the user is going to write more than one sentence, textarea is usually easier to work with than `input`. While it is still possible to force `input` to act as some kind of textfield, it's bad practise to do so. The text type `input` tag is really only meant for short entries such as names, usernames, food preferences etc.
|
||||
|
||||
[//]: # vim: set syntax=markdown:
|
||||
15
Exercise 6/script.js
Normal file
15
Exercise 6/script.js
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
const [incomeInput, wealthInput, taxInput] = ["incomeInput", 'wealthInput', 'taxInput']
|
||||
.map(id => document.getElementById(id));
|
||||
|
||||
const tax = (income, wealth) => (0.35 * income) + (0.25 * wealth);
|
||||
|
||||
const updateTax = () => {
|
||||
const [income, wealth] = [incomeInput.value, wealthInput.value]
|
||||
.map(value => parseInt(value))
|
||||
.map(value => isNaN(value) ? 0 : value);
|
||||
taxInput.value = tax(income, wealth);
|
||||
}
|
||||
|
||||
incomeInput.addEventListener('input', updateTax);
|
||||
wealthInput.addEventListener('input', updateTax);
|
||||
58
Exercise 6/style.css
Normal file
58
Exercise 6/style.css
Normal file
@@ -0,0 +1,58 @@
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background-color: grey;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
color: #2b68d8;
|
||||
padding: 10px;
|
||||
border-radius: 20px;
|
||||
background-color: #DFDFDF;
|
||||
margin: 30px 10%;
|
||||
}
|
||||
|
||||
form {
|
||||
margin: 0 10%;
|
||||
background-color: #DFDFDF;
|
||||
padding: 50px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
input, select {
|
||||
display: block;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
input[type=text], input[type=number], input[type=email], input[type=date], select {
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
input[type=submit] {
|
||||
font-size: large;
|
||||
border-radius: 5px;
|
||||
color: #2b68d8;
|
||||
background-color: #DDDDDD;
|
||||
}
|
||||
|
||||
input[type=submit]:hover {
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
|
||||
#sliders input, #taxFields input {
|
||||
display: inline;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
#sliders {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
#taxFields {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.inline * {
|
||||
display: inline;
|
||||
}
|
||||
7
Exercise 6/table.txt
Normal file
7
Exercise 6/table.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
The PDF didn't want to cooperate so here you have the table for part 6.
|
||||
|
||||
| Real name | Gender | E-Mail | Birthyear | Hero name | Spandex | Strength | Speed | Intelligence | Wealth | Income | Tax
|
||||
|-------------|--------|----------------------------|-----------|-----------|---------|----------|-------|--------------|----------- |-----------|-----
|
||||
| Nick Nedd | Male | nick@tick.com | 1976 | The Tick | Yes | 9 | 7 | 5 | 0 | 90 287 | 29 794
|
||||
| Bruce Wayne | Male | bruce@wayneenterprises.com | 1974 | Batman | No | 7 | 4 | 9 | 50 144 501 | 3 343 891 | 11 132 384
|
||||
| Selina Kyle | Female | selina@whiskas.com | 1977 | Catwoman | Yes | 6 | 6 | 9 | 2 987 323 | 0 | 597 464
|
||||
45
Exercise 6/taxes.xml
Normal file
45
Exercise 6/taxes.xml
Normal file
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<people>
|
||||
<person>
|
||||
<name>Nick Nedd</name>
|
||||
<gender>Male</gender>
|
||||
<email>nick@tick.com</email>
|
||||
<birthyear>1976</birthyear>
|
||||
<hero>The Tick</hero>
|
||||
<spandex>True</spandex>
|
||||
<strength>9</strength>
|
||||
<speed>7</speed>
|
||||
<intelligence>5</intelligence>
|
||||
<wealth>0</wealth>
|
||||
<income>90287</income>
|
||||
<tax>29794</tax>
|
||||
</person>
|
||||
<person>
|
||||
<name>Bruce Wayne</name>
|
||||
<gender>Male</gender>
|
||||
<email>bruce@wayneenterprises.com</email>
|
||||
<birthyear>1974</birthyear>
|
||||
<hero>Batman</hero>
|
||||
<spandex>False</spandex>
|
||||
<strength>7</strength>
|
||||
<speed>4</speed>
|
||||
<intelligence>9</intelligence>
|
||||
<wealth>50144501</wealth>
|
||||
<income>3343891</income>
|
||||
<tax>11132384</tax>
|
||||
</person>
|
||||
<person>
|
||||
<name>Selina Kyle</name>
|
||||
<gender>Female</gender>
|
||||
<email>selina@whiskas.com</email>
|
||||
<birthdate>1977</birthdate>
|
||||
<hero>Catwoman</hero>
|
||||
<spandex>True</spandex>
|
||||
<strength>6</strength>
|
||||
<speed>6</speed>
|
||||
<intelligence>9</intelligence>
|
||||
<wealth>2987323</wealth>
|
||||
<income>0</income>
|
||||
<tax>597464</tax>
|
||||
</person>
|
||||
<people>
|
||||
71
Exercise 7/todo.css
Normal file
71
Exercise 7/todo.css
Normal file
@@ -0,0 +1,71 @@
|
||||
body {
|
||||
background-color: #272822;
|
||||
color: #F8F8F8;
|
||||
margin: 5% 20%;
|
||||
}
|
||||
|
||||
#title {
|
||||
text-align: center;
|
||||
color: #A6E22E;
|
||||
font-size: 4em;
|
||||
}
|
||||
|
||||
#todoField {
|
||||
display: block;
|
||||
padding: 1em;
|
||||
margin: 0 auto;
|
||||
color: #F8F8F8;
|
||||
background-color: #494b41;
|
||||
font-size: 2em;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
#todoButton {
|
||||
display: block;
|
||||
padding: 1em;
|
||||
margin: 1em auto;
|
||||
color: black;
|
||||
background-color: #FD971F;
|
||||
font-size: 1.5em;
|
||||
filter: drop-shadow(0.5em 0.5em 2px #191a16);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#todoSummary {
|
||||
font-size: 3em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#todoList {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
input[type=checkbox] {
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
margin-right: 2em;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#todoList>li>span {
|
||||
display: inline-block;
|
||||
font-size: 3em;
|
||||
}
|
||||
|
||||
#todoList>li {
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
|
||||
/* Monokai rainbow list :DDD */
|
||||
#todoList>li:nth-child(7n+1) {color: #F92672;}
|
||||
#todoList>li:nth-child(7n+2) {color: #FD971F;}
|
||||
#todoList>li:nth-child(7n+3) {color: #E69F66;}
|
||||
#todoList>li:nth-child(7n+4) {color: #E6DB74;}
|
||||
#todoList>li:nth-child(7n+5) {color: #A6E22E;}
|
||||
#todoList>li:nth-child(7n+6) {color: #66D9EF;}
|
||||
#todoList>li:nth-child(7n) {color: #AE81FF;}
|
||||
|
||||
.strikethrough {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
19
Exercise 7/todo.html
Normal file
19
Exercise 7/todo.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Todo</title>
|
||||
<link rel="stylesheet" href="./todo.css">
|
||||
<script src="./todo.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1 id="title">Todo-list</h1>
|
||||
<textarea id="todoField" cols="60" rows="2" autofocus ></textarea>
|
||||
<button id="todoButton">Add task (Enter)</button>
|
||||
<div id="todoSummary"></div>
|
||||
<ul id="todoList"></ul>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
91
Exercise 7/todo.js
Normal file
91
Exercise 7/todo.js
Normal file
@@ -0,0 +1,91 @@
|
||||
const todoField = document.getElementById("todoField");
|
||||
const todoButton = document.getElementById("todoButton");
|
||||
const todoSummary = document.getElementById("todoSummary");
|
||||
const todoList = document.getElementById("todoList");
|
||||
|
||||
/**
|
||||
* A task for the todolist
|
||||
* @typedef {Object} Task
|
||||
* @property {number} createdAt - Timestamp of the time that the task was created.
|
||||
* @property {string} description - Description of the task.
|
||||
* @property {boolean} isCompleted - Whether or not the task is completed.
|
||||
*/
|
||||
|
||||
const tasks = [];
|
||||
|
||||
/**
|
||||
* Add task to both tasks and DOM
|
||||
*/
|
||||
addTask = () => {
|
||||
tasks.push({
|
||||
createdAt: Date.now(),
|
||||
description: todoField.value,
|
||||
isCompleted: false
|
||||
});
|
||||
|
||||
const taskElement = createTaskElement(tasks[tasks.length - 1]);
|
||||
todoList.prepend(taskElement);
|
||||
|
||||
todoField.value = '';
|
||||
todoField.focus();
|
||||
|
||||
updateSummary();
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate HTML element for a task
|
||||
* @param {Task} task - The task to be converted into an html element
|
||||
* @return {HTMLLIElement} The <li> element
|
||||
*/
|
||||
createTaskElement = (task) => {
|
||||
const listItem = document.createElement('li');
|
||||
|
||||
const checkbox = document.createElement('input');
|
||||
checkbox.type = 'checkbox';
|
||||
checkbox.name = tasks.length - 1
|
||||
checkbox.addEventListener('change', updateCheckbox);
|
||||
listItem.appendChild(checkbox);
|
||||
|
||||
const description = document.createElement('span');
|
||||
description.innerText = task.description;
|
||||
listItem.appendChild(description);
|
||||
|
||||
return listItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to react to change on a checkbox
|
||||
* @param {Event} event - the event raised by the eventlistener
|
||||
*/
|
||||
updateCheckbox = (event) => {
|
||||
const index = tasks.length - event.target.name - 1;
|
||||
tasks[index].isCompleted = event.target.checked;
|
||||
|
||||
const descriptionSpanClasses = todoList
|
||||
.children[index]
|
||||
.getElementsByTagName('span')[0]
|
||||
.classList
|
||||
|
||||
if (event.target.checked) descriptionSpanClasses.add('strikethrough');
|
||||
else descriptionSpanClasses.remove('strikethrough');
|
||||
|
||||
updateSummary();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the summary span with new task values
|
||||
*/
|
||||
updateSummary = () => {
|
||||
const tasksCompleted = tasks.filter(task => task.isCompleted).length;
|
||||
todoSummary.innerText = `${tasksCompleted}/${tasks.length} completed.`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the global keypresses and react on certain ones.
|
||||
*/
|
||||
handleKeys = (event) => {
|
||||
if (event.which === 13 && document.activeElement === todoField) addTask();
|
||||
}
|
||||
|
||||
todoButton.addEventListener('click', addTask);
|
||||
document.addEventListener('keyup', handleKeys);
|
||||
184
Exercise 8/bar-chart-with-bars.html
Normal file
184
Exercise 8/bar-chart-with-bars.html
Normal file
@@ -0,0 +1,184 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Tax forms: Bar chart with lines</title>
|
||||
<meta charset="UTF-8">
|
||||
<script src="taxForms.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<canvas id="chart" width="700" height="550"></canvas>
|
||||
|
||||
<script>
|
||||
const canvas = document.getElementById('chart');
|
||||
const context = canvas.getContext('2d');
|
||||
|
||||
/* Draw a line from (fromX, fromY) to (toX, toY) */
|
||||
function drawLine(fromX, fromY, toX, toY) {
|
||||
context.beginPath();
|
||||
context.moveTo(toX, toY);
|
||||
context.lineTo(fromX, fromY);
|
||||
context.stroke();
|
||||
}
|
||||
|
||||
/* Draw a text (string) on (x, y) */
|
||||
function drawText(text, x, y) {
|
||||
context.fillStyle = 'black';
|
||||
context.fillText(text, x, y);
|
||||
}
|
||||
|
||||
/* Draw a text and with a line to its right */
|
||||
function drawLineWithText(text, fromX, fromY, toX, toY) {
|
||||
drawText(text, fromX - 50, fromY + 10);
|
||||
drawLine(fromX, fromY, toX, toY);
|
||||
}
|
||||
|
||||
/* Insert your code here. */
|
||||
|
||||
/**
|
||||
* Draw lines on the y axis of the canvas
|
||||
*
|
||||
* @param {number} n - Number of lines to draw
|
||||
* @param {number} step - Number to add to y axis
|
||||
*/
|
||||
const drawNLines = (n, step) => {
|
||||
const yOffset = i => canvas.height * (n - i + 1 ) / (n+1);
|
||||
for (let i = 1; i <= n; i++)
|
||||
drawLineWithText(step * i, 50, yOffset(i), canvas.width - 100, yOffset(i));
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a rectangle onto the canvas
|
||||
*
|
||||
* @param {number} x - x value of the upper left corner of the rectangle
|
||||
* @param {number} y - y value of the upper left corner of the rectangle
|
||||
* @param {number} width - Width of the rectangle
|
||||
* @param {number} height - Height of the rectangle (DOWNWARDS)
|
||||
* @param {string} color - Color of the rectangle
|
||||
*/
|
||||
const drawRectangle = (x, y, width, height, color='red') => {
|
||||
context.fillStyle = color;
|
||||
context.fillRect(x, y, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cycle a list of colors
|
||||
*
|
||||
* @generator
|
||||
*
|
||||
* @param {string[]} colors - List of colors to cycle
|
||||
* @yields {string} A color
|
||||
*/
|
||||
function* colorCycler(colors) {
|
||||
while (true)
|
||||
for (color of colors)
|
||||
yield color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a legend on the canvas
|
||||
*
|
||||
* @param {string[]} properties - An array of labels
|
||||
* @param {[string[]} colorList - An array of colors to cycle when making labels
|
||||
*/
|
||||
const drawLabels = (properties, colorList) => {
|
||||
const colors = colorCycler(colorList);
|
||||
|
||||
const drawLabel = (text, color, y) => {
|
||||
drawText(text, canvas.width - 45, y + 15);
|
||||
drawRectangle(canvas.width - 90, y, 40, 20, color);
|
||||
}
|
||||
|
||||
const yOffset = i => canvas.height / 12 + 30 * i;
|
||||
for (i in properties)
|
||||
drawLabel(properties[i], colors.next().value, yOffset(i));
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a subset of bars on the canvas
|
||||
*
|
||||
* param {{
|
||||
* x: number,
|
||||
* width: number,
|
||||
* adjustY: {val: number}=>number,
|
||||
* object: Object.<string, number>,
|
||||
* sep: number,
|
||||
* colorList: string[]
|
||||
* }} config -
|
||||
* x: x value of where to start drawing bars
|
||||
* width: Width of all the bars to draw collected
|
||||
* adjustY: A function to adjust the y value of each bar to the upper left corner
|
||||
* object: The object containing the values to draw
|
||||
* sep: The amount of pixels between each bar
|
||||
* colorList: A list of colors to cycle when creating bars
|
||||
*/
|
||||
const drawBars = ({x, width, adjustY, object, sep, colorList}) => {
|
||||
const colors = colorCycler(colorList);
|
||||
const values = Object.values(object);
|
||||
|
||||
const drawBar = (value, x) =>
|
||||
drawRectangle(
|
||||
x,
|
||||
adjustY(value),
|
||||
width / (values.length) - sep,
|
||||
canvas.height - adjustY(value),
|
||||
colors.next().value
|
||||
);
|
||||
|
||||
|
||||
const xOffset = i => x + i * (sep + width / (values.length));
|
||||
for (let i=0; i<values.length; i++)
|
||||
drawBar(values[i], xOffset(i));
|
||||
|
||||
colors.return();
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw an array of objects as a diagram onto the canvas
|
||||
*
|
||||
* @param {Array.<Object.<string, number>>} data - An array of objects with graphable properties
|
||||
* @param {string[]} colorList - An array of colors to cycle when drawing bars
|
||||
* @param {{val: number}=>number} adjustY - A function to adjust the y value of
|
||||
* each bar to the upper left corner
|
||||
*/
|
||||
const drawColumnSet = (data, colorList, adjustY) => {
|
||||
const lineWidth = canvas.width - 150;
|
||||
const columnSetWidth = lineWidth / (2 * data.length + 1) - data.length + 1;
|
||||
const initialXOffset = 50 + columnSetWidth;
|
||||
const xOffset = 2*columnSetWidth;
|
||||
|
||||
for (i in data)
|
||||
drawBars({
|
||||
x: initialXOffset + i*xOffset,
|
||||
width: columnSetWidth,
|
||||
adjustY: adjustY,
|
||||
object: data[i],
|
||||
sep: 5,
|
||||
colorList: colorList
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a bar chart diagram
|
||||
*
|
||||
* @param {Array.<Object.<string, number>>} data - An array of objects with graphable properties
|
||||
* @param {number} range - The upper range of the graph (the top line)
|
||||
* @param {number} step - The number to increase by for every unit on the axis
|
||||
*/
|
||||
const drawDiagram = (data, range, step) => {
|
||||
drawNLines(range/step, step);
|
||||
|
||||
const colorList = ['red', 'blue'];
|
||||
drawLabels(Object.getOwnPropertyNames(data[0]), colorList);
|
||||
|
||||
const adjustY = val => canvas.height * ( 1 - val / (range + step));
|
||||
drawColumnSet(data, colorList, adjustY);
|
||||
}
|
||||
|
||||
const taxData = taxForms;
|
||||
taxData.forEach(form => delete form.realName);
|
||||
drawDiagram(taxData, 1000000, 100000);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
109
Exercise 8/bar-chart-with-labels.html
Normal file
109
Exercise 8/bar-chart-with-labels.html
Normal file
@@ -0,0 +1,109 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Tax forms: Bar chart with lines</title>
|
||||
<meta charset="UTF-8">
|
||||
<script src="taxForms.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<canvas id="chart" width="700" height="550"></canvas>
|
||||
|
||||
<script>
|
||||
const canvas = document.getElementById('chart');
|
||||
const context = canvas.getContext('2d');
|
||||
|
||||
/* Draw a line from (fromX, fromY) to (toX, toY) */
|
||||
function drawLine(fromX, fromY, toX, toY) {
|
||||
context.beginPath();
|
||||
context.moveTo(toX, toY);
|
||||
context.lineTo(fromX, fromY);
|
||||
context.stroke();
|
||||
}
|
||||
|
||||
/* Draw a text (string) on (x, y) */
|
||||
function drawText(text, x, y) {
|
||||
context.fillStyle = 'black';
|
||||
context.fillText(text, x, y);
|
||||
}
|
||||
|
||||
/* Draw a text and with a line to its right */
|
||||
function drawLineWithText(text, fromX, fromY, toX, toY) {
|
||||
drawText(text, fromX - 50, fromY + 10);
|
||||
drawLine(fromX, fromY, toX, toY);
|
||||
}
|
||||
|
||||
/* Insert your code here. */
|
||||
|
||||
/**
|
||||
* Draw lines on the y axis of the canvas
|
||||
*
|
||||
* @param {number} n - Number of lines to draw
|
||||
* @param {number} step - Number to add to y axis
|
||||
*/
|
||||
const drawNLines = (n, step) => {
|
||||
for (let i = 1; i <= n; i++) {
|
||||
const y = canvas.height * (n - i + 1 ) / (n+1);
|
||||
const text = step * i;
|
||||
drawLineWithText(text, 50, y, canvas.width - 100, y);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a rectangle onto the canvas
|
||||
*
|
||||
* @param {number} x - x value of the upper left corner of the rectangle
|
||||
* @param {number} y - y value of the upper left corner of the rectangle
|
||||
* @param {number} width - Width of the rectangle
|
||||
* @param {number} height - Height of the rectangle (DOWNWARDS)
|
||||
* @param {string} color - Color of the rectangle
|
||||
*/
|
||||
const drawRectangle = (x, y, width, height, color='red') => {
|
||||
context.fillStyle = color;
|
||||
context.fillRect(x, y, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cycle a list of colors
|
||||
*
|
||||
* @generator
|
||||
*
|
||||
* @param {string[]} colors - List of colors to cycle
|
||||
* @yields {string} A color
|
||||
*/
|
||||
function* colorCycler(colors) {
|
||||
let i = 0;
|
||||
while (true) {
|
||||
yield colors[i];
|
||||
i = (i + 1) % colors.length;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a legend on the canvas
|
||||
*
|
||||
* @param {string[]} properties - An array of labels
|
||||
* @param {[string[]} colorList - An array of colors to cycle when making labels
|
||||
*/
|
||||
const drawLabels = (properties, colorList) => {
|
||||
const colors = colorCycler(colorList);
|
||||
|
||||
const drawLabel = (text, color, y) => {
|
||||
drawText(text, canvas.width-45, y + 15)
|
||||
drawRectangle(canvas.width - 90, y, 40, 20, color);
|
||||
}
|
||||
|
||||
const yOffset = canvas.height / 12
|
||||
for (i in properties) {
|
||||
drawLabel(properties[i], colors.next().value, yOffset+30*i);
|
||||
}
|
||||
}
|
||||
|
||||
drawNLines(10, 100000);
|
||||
const taxData = taxForms;
|
||||
taxData.forEach(form => delete form.realName);
|
||||
drawLabels(Object.getOwnPropertyNames(taxData[0]), ['red', 'blue']);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
55
Exercise 8/bar-chart-with-lines.html
Normal file
55
Exercise 8/bar-chart-with-lines.html
Normal file
@@ -0,0 +1,55 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Tax forms: Bar chart with lines</title>
|
||||
<meta charset="UTF-8">
|
||||
<script src="taxForms.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<canvas id="chart" width="700" height="550"></canvas>
|
||||
|
||||
<script>
|
||||
const canvas = document.getElementById('chart');
|
||||
const context = canvas.getContext('2d');
|
||||
|
||||
/* Draw a line from (fromX, fromY) to (toX, toY) */
|
||||
function drawLine(fromX, fromY, toX, toY) {
|
||||
context.beginPath();
|
||||
context.moveTo(toX, toY);
|
||||
context.lineTo(fromX, fromY);
|
||||
context.stroke();
|
||||
}
|
||||
|
||||
/* Draw a text (string) on (x, y) */
|
||||
function drawText(text, x, y) {
|
||||
context.fillStyle = 'black';
|
||||
context.fillText(text, x, y);
|
||||
}
|
||||
|
||||
/* Draw a text and with a line to its right */
|
||||
function drawLineWithText(text, fromX, fromY, toX, toY) {
|
||||
drawText(text, fromX - 50, fromY + 10);
|
||||
drawLine(fromX, fromY, toX, toY);
|
||||
}
|
||||
|
||||
/* Insert your code here. */
|
||||
|
||||
/**
|
||||
* Draw lines on the y axis of the canvas
|
||||
*
|
||||
* @param {number} n - Number of lines to draw
|
||||
*/
|
||||
const drawNLines = (n) => {
|
||||
for (let i = 1; i <= n; i++) {
|
||||
const y = canvas.height * (n - i + 0.5) / n;
|
||||
const text = 100000 * i;
|
||||
drawLineWithText(text, 50, y, canvas.width, y);
|
||||
}
|
||||
}
|
||||
|
||||
drawNLines(10);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
13
Exercise 8/income-greater-than-500k.html
Normal file
13
Exercise 8/income-greater-than-500k.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Income greather than 500 000</title>
|
||||
<meta charset="UTF-8">
|
||||
<script src="taxForms.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
console.log(taxForms.filter(form => form.income > 500000));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
46
Exercise 8/questions.txt
Normal file
46
Exercise 8/questions.txt
Normal file
@@ -0,0 +1,46 @@
|
||||
# Questions
|
||||
|
||||
1. When calling `getContext('2d')` on a Canvas element, it will return a drawing context (i.e. the type of canvas) - in this case a two-dimensional context. How can we get a three-dimensional context instead?
|
||||
|
||||
You could use any version of WebGL. Webgl is an API for rendering graphics on the web, with the help of things like hardware acceleration and compiled function libraries which will run a lot faster than interpreting everything as javascript. Most modern browsers have an implementation of WebGL.
|
||||
|
||||
Example of usage:
|
||||
```javascript
|
||||
const ctx = canvas.getContext('webgl');
|
||||
```
|
||||
|
||||
2. How would you create a blue circle using the Canvas element? Explain with words or code.
|
||||
|
||||
```javascript
|
||||
const drawCircle = (x, y, radius) => {
|
||||
context.beginPath();
|
||||
context.fillStyle('blue');
|
||||
context.arc(x,y,0, Math.PI*2);
|
||||
context.fill();
|
||||
}
|
||||
|
||||
drawCircle();
|
||||
```
|
||||
|
||||
3. What is a more terse (also known as shorter) way of writing the loop in the following usingthe Array.filter method?
|
||||
|
||||
```javascript
|
||||
const countries = ['Norway', 'Sweden', 'Denmark', 'New Zealand'];
|
||||
const countriesStartingWithN = [];
|
||||
|
||||
for (let i = 0; i < countries.length; i++) {
|
||||
let country = countries[i];
|
||||
if (country.startsWith('N')) {
|
||||
countriesStartingWithN.push(country);
|
||||
}
|
||||
}
|
||||
```
|
||||
```javascript
|
||||
const countries = ['Norway', 'Sweden', 'Denmark', 'New Zealand'];
|
||||
|
||||
const countriesStartingWithN = countries.filter(
|
||||
country => country.startsWith('n')
|
||||
);
|
||||
```
|
||||
|
||||
[//]: # vim: set syntax=markdown:
|
||||
17
Exercise 8/taxForms.js
Normal file
17
Exercise 8/taxForms.js
Normal file
@@ -0,0 +1,17 @@
|
||||
const taxForms = [
|
||||
{
|
||||
realName: "Bruce Wayne",
|
||||
income: 750000,
|
||||
wealth: 300000
|
||||
},
|
||||
{
|
||||
realName: "John Blake",
|
||||
income: 440000,
|
||||
wealth: 832000
|
||||
},
|
||||
{
|
||||
realName: "Selina Kyle",
|
||||
income: 640000,
|
||||
wealth: 432000
|
||||
}
|
||||
];
|
||||
Reference in New Issue
Block a user