2020-10-27 02:30:35 +01:00
|
|
|
<!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) => {
|
2020-10-27 15:19:15 +01:00
|
|
|
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));
|
2020-10-27 02:30:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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) {
|
2020-10-27 15:19:15 +01:00
|
|
|
while (true)
|
|
|
|
for (color of colors)
|
|
|
|
yield color;
|
2020-10-27 02:30:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-10-27 15:19:15 +01:00
|
|
|
* Draw a legend on the canvas
|
2020-10-27 02:30:35 +01:00
|
|
|
*
|
|
|
|
* @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) => {
|
2020-10-27 15:19:15 +01:00
|
|
|
drawText(text, canvas.width - 45, y + 15);
|
2020-10-27 02:30:35 +01:00
|
|
|
drawRectangle(canvas.width - 90, y, 40, 20, color);
|
|
|
|
}
|
|
|
|
|
2020-10-27 15:19:15 +01:00
|
|
|
const yOffset = i => canvas.height / 12 + 30 * i;
|
|
|
|
for (i in properties)
|
|
|
|
drawLabel(properties[i], colors.next().value, yOffset(i));
|
2020-10-27 02:30:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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);
|
2020-10-27 15:19:15 +01:00
|
|
|
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));
|
2020-10-27 02:30:35 +01:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2020-10-27 15:19:15 +01:00
|
|
|
for (i in data)
|
2020-10-27 02:30:35 +01:00
|
|
|
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);
|
|
|
|
|
2020-10-27 15:19:15 +01:00
|
|
|
const adjustY = val => canvas.height * ( 1 - val / (range + step));
|
2020-10-27 02:30:35 +01:00
|
|
|
drawColumnSet(data, colorList, adjustY);
|
|
|
|
}
|
|
|
|
|
|
|
|
const taxData = taxForms;
|
|
|
|
taxData.forEach(form => delete form.realName);
|
|
|
|
drawDiagram(taxData, 1000000, 100000);
|
|
|
|
|
|
|
|
</script>
|
|
|
|
</body>
|
|
|
|
</html>
|