10000 Optimization: Various HTML5 Canvas Optimizations · KyleSmith0905/Sorting-Algorithm@99c2443 · GitHub
[go: up one dir, main page]

Skip to content

Commit 99c2443

Browse files
committed
Optimization: Various HTML5 Canvas Optimizations
This includes: Using pixelated interpolation, Render graph at least once every 30 seconds, disabling the alpha layer, rounding values instead of anti-aliasing, history graph now combines straight lines into one.
1 parent 98ff1f4 commit 99c2443

File tree

10 files changed

+105
-90
lines changed

10 files changed

+105
-90
lines changed

src/app/_components/submenu.component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export class submenuComponent {
4747

4848
const textElement = document.getElementById('GraphTypeText');
4949
if (textElement instanceof HTMLElement) textElement.innerText = 'Graph Type: ' + val;
50-
RenderGraph(this.Coordinates);
50+
RenderGraph(this.Coordinates, 'mustRender');
5151
}
5252

5353
SetGraphColor(object: Event | number) {
@@ -78,7 +78,7 @@ export class submenuComponent {
7878
return 0;
7979
});
8080

81-
RenderGraph(this.Coordinates);
81+
RenderGraph(this.Coordinates, 'mustRender');
8282

8383
const textElement = document.getElementById('GraphColorText');
8484
if (textElement instanceof HTMLElement) textElement.innerText = 'Graph Color: ' + val;

src/app/home/home.component.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Component } from '@angular/core';
2-
import { GetCookie, SetCookie } from '../../shared/cookies';
2+
import { GetCookie } from '../../shared/cookies';
33
import { IDataPoint } from '../../shared/interfaces';
44
import { sortGraph } from '../../shared/sortGraph';
55
import { RenderGraph } from '../../shared/renderGraph';
@@ -62,19 +62,19 @@ export class HomeComponent {
6262
if (graph.style.width === width) return;
6363
graph.style.width = width;
6464
graph.width = parseFloat(width);
65-
RenderGraph(this.Coordinates);
65+
RenderGraph(this.Coordinates, 'mustRender');
6666
}
6767
else {
6868
graph.width = window.innerWidth * 0.8;
69-
RenderGraph(this.Coordinates);
69+
RenderGraph(this.Coordinates, 'mustRender');
7070
if (graph.style.width === '80%') return;
7171
graph.style.width = '80%';
7272
}
7373
}
7474

7575
ResetGraph = () => {
7676
if (this.SortingInterval !== undefined) window.clearInterval(this.SortingInterval);
77-
77+
7878
sortGraph(this);
7979
};
8080
}

src/settings/graphType/barGraph.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,31 @@ import { IDataPoint } from 'src/shared/interfaces';
22
import { graphColors, settings } from '../';
33

44
export const name = 'Bar Graph';
5-
export const algorithm = (array: IDataPoint[], canvas: HTMLCanvasElement, data: any) => {
5+
export const render = (array: IDataPoint[], canvas: HTMLCanvasElement, data: any) => {
66

77
const graphColor = graphColors.find(e => e.name === settings.GraphColor);
88
if (graphColor === undefined) return;
99

10-
const context = canvas.getContext('2d');
10+
const context = canvas.getContext('2d', { alpha: false });
1111
if (!context) return;
12-
context.clearRect(0, 0, canvas.width, canvas.height);
12+
context.fillStyle = '#ffffff';
13+
context.fillRect(0, 0, canvas.width, canvas.height);
1314

1415
const dataLength = array.length;
15-
const barWidth = canvas.width / dataLength + 1;
16+
const barWidth = Math.round(canvas.width / dataLength + 1);
1617
const graphYScalar = (canvas.height - 50) / 100;
1718

1819
for (let i = 0; i < dataLength; i++) {
1920
const currentPoint = array[i];
2021
if (currentPoint.highlight === true) context.fillStyle = graphColor.highlightColor();
2122
else context.fillStyle = currentPoint.color;
2223

23-
context.fillRect((i / dataLength) * canvas.width, canvas.height - 50 - (currentPoint.height * graphYScalar), barWidth, (currentPoint.height * graphYScalar) + 50);
24+
context.fillRect(
25+
Math.round((i / dataLength) * canvas.width),
26+
Math.round(canvas.height - 50 - (currentPoint.height * graphYScalar)),
27+
barWidth,
28+
Math.round(currentPoint.height * graphYScalar) + 50
29+
);
2430
}
25-
};
31+
};
32+
export const recordData = () => null;

src/settings/graphType/circleGraph.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ import { IDataPoint } from 'src/shared/interfaces';
22
import { graphColors, settings } from '../';
33

44
export const name = 'Circle Graph';
5-
export const algorithm = (array: IDataPoint[], canvas: HTMLCanvasElement, data: any) => {
5+
export const render = (array: IDataPoint[], canvas: HTMLCanvasElement, data: any) => {
66

77
const graphColor = graphColors.find(e => e.name === settings.GraphColor);
88
if (graphColor === undefined) return;
99

10-
const context = canvas.getContext('2d');
10+
const context = canvas.getContext('2d', { alpha: false });
1111
if (!context) return;
12-
context.clearRect(0, 0, canvas.width, canvas.height);
12+
context.fillStyle = '#ffffff';
13+
context.fillRect(0, 0, canvas.width, canvas.height);
1314

1415
const circumference = Math.PI * 2;
1516
const dataLength = array.length;
@@ -26,4 +27,5 @@ export const algorithm = (array: IDataPoint[], canvas: HTMLCanvasElement, data:
2627
context.lineTo(radiusX, radiusY);
2728
context.fill();
2829
}
29-
};
30+
};
31+
export const recordData = () => null;

src/settings/graphType/dotGraph.ts

Lines changed: 12 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,18 @@ import { IDataPoint } from 'src/shared/interfaces';
22
import { graphColors, settings } from '../';
33

44
export const name = 'Dot Graph';
5-
export const algorithm = (array: IDataPoint[], canvas: HTMLCanvasElement, data: any) => {
5+
export const render = (array: IDataPoint[], canvas: HTMLCanvasElement, data: any) => {
66

77
const graphColor = graphColors.find(e => e.name === settings.GraphColor);
88
if (graphColor === undefined) return;
99

10-
const context = canvas.getContext('2d');
10+
const context = canvas.getContext('2d', { alpha: false });
1111
if (!context) return;
12-
context.clearRect(0, 0, canvas.width, canvas.height);
12+
context.fillStyle = '#ffffff';
13+
context.fillRect(0, 0, canvas.width, canvas.height);
1314

1415
const dataLength = array.length;
15-
const dotSize = canvas.height * Math.pow(200 + array.length, -0.8);
16+
const dotSize = Math.round(canvas.height * Math.pow(200 + array.length, -0.8) * 2) / 2;
1617
const graphYScalar = (canvas.height - (dotSize * 2)) / 100;
1718

1819
if (dotSize > 2) {
@@ -36,30 +37,14 @@ export const algorithm = (array: IDataPoint[], canvas: HTMLCanvasElement, data:
3637
else context.fillStyle = currentPoint.color;
3738

3839
context.beginPath();
39-
context.fillRect((i / dataLength) * canvas.width, canvas.height - (currentPoint.height * graphYScalar), dotSize * 2, dotSize * 2);
40+
context.fillRect(
41+
Math.round((i / dataLength) * canvas.width),
42+
Math.round(canvas.height - (currentPoint.height * graphYScalar)),
43+
dotSize * 2,
44+
dotSize * 2
45+
);
4046
}
4147
}
4248
context.closePath();
4349
};
44-
45-
export const colorCircle = (array: IDataPoint[], canvas: HTMLCanvasElement, activeColor: () => string) => {
46-
const context = canvas.getContext('2d');
47-
if (!context) return;
48-
context.clearRect(0, 0, canvas.width, canvas.height);
49-
50-
const circumference = Math.PI * 2;
51-
const dataLength = array.length;
52-
const offset = Math.PI * 1.5;
53-
const radiusX = canvas.width / 2;
54-
const radiusY = canvas.height / 2;
55-
56-
for (let i = 0; i < dataLength; i++) {
57-
const currentPoint = array[i];
58-
if (currentPoint.highlight === true) context.fillStyle = activeColor();
59-
else context.fillStyle = currentPoint.color;
60-
context.beginPath();
61-
context.arc(radiusX, radiusY, radiusY, (i / dataLength) * circumference + offset, ((i + 1) / dataLength) * circumference + 0.003 + offset);
62-
context.lineTo(radiusX, radiusY);
63-
context.fill();
64-
}
65-
};
50+
export const recordData = () => null;

src/settings/graphType/historyGraph.ts

Lines changed: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,33 @@ import { IDataPoint } from 'src/shared/interfaces';
22
import { graphColors, settings } from '../';
33

44
export const name = 'History Graph';
5-
6-
export const algorithm = (array: IDataPoint[], canvas: HTMLCanvasElement, data: any) => {
7-
5+
export const render = (array: IDataPoint[], canvas: HTMLCanvasElement, data: any) => {
86
const graphColor = graphColors.find(e => e.name === settings.GraphColor);
97
if (graphColor === undefined) return;
108

11-
const context = canvas.getContext('2d');
9+
const context = canvas.getContext('2d', { alpha: false });
1210
if (!context) return;
13-
const highlight: number[] = [];
14-
15-
if (data.interval === undefined) {
16-
data.interval = 1;
17-
data.intervalLength = 1;
18-
data.reference = array.map(e => e.id);
19-
data.memoryArray = [Array.from(Array(array.length).keys())];
20-
}
11+
context.fillStyle = '#ffffff';
12+
context.fillRect(0, 0, canvas.width, canvas.height);
13+
14+
if (data.interval === undefined) recordData(array, data);
2115

16+
const highlight = [];
2217
const currentLocation: number[] = Array.from(Array(data.memoryArray[0].length));
2318
for (let i = 0; i < array.length; i++) {
2419
const index = array.findIndex((e) => e.id === data.reference[i]);
2520
if (array[i].highlight === true) highlight.push(i);
2621
if (index !== -1) currentLocation[i] = index;
2722
}
2823

29-
data.interval = ++data.interval % data.intervalLength;
30-
if (data.interval === 0) {
31-
data.memoryArray.push(currentLocation);
32-
}
33-
if (data.memoryArray.length * data.memoryArray[0].length > 65536) {
34-
data.intervalLength *= 2;
35-
for (let i = 2; i <= data.memoryArray.length; i += 2) data.memoryArray.splice(i, 1);
36-
}
37-
3824
const arrayLengthX = data.memoryArray.length;
3925
const arrayLengthY = data.memoryArray[0].length;
4026
context.lineWidth = (canvas.height - 100) / (arrayLengthY + 2);
41-
const halfWidth = context.lineWidth / 2;
27+
const halfLineWidth = context.lineWidth / 2;
4228
const adjustedLength = arrayLengthX + (data.interval / data.intervalLength) - 1;
4329

44-
context.clearRect(0, 0, canvas.width, canvas.height);
4530
for (let i = 0; i < arrayLengthY; i++) {
46-
if (highlight.some(e => e === currentLocation[i]) === true) context.strokeStyle = graphColor?.highlightColor();
31+
if (highlight.some((e: number) => e === currentLocation[i]) === true) context.strokeStyle = graphColor?.highlightColor();
4732
else context.strokeStyle = graphColor.color(data.reference.length, i, data.reference[i]);
4833

4934
context.beginPath();
@@ -56,12 +41,39 @@ export const algorithm = (array: IDataPoint[], canvas: HTMLCanvasElement, data:
5641
}
5742
else if (pickedUp === true) {
5843
pickedUp = false;
59-
context.moveTo(((j - 1) / adjustedLength) * canvas.width, (point / arrayLengthY) * canvas.height + halfWidth);
44+
context.moveTo(((j - 1) / adjustedLength) * canvas.width, (point / arrayLengthY) * canvas.height + halfLineWidth);
45+
}
46+
else if (
47+
data.memoryArray[j - 1] === undefined || data.memoryArray[j + 1] === undefined ||
48+
data.memoryArray[j - 1][i] !== -1 || data.memoryArray[j + 1][i] !== point
49+
) {
50+
context.lineTo((j / adjustedLength) * canvas.width, (point / arrayLengthY) * canvas.height + halfLineWidth);
6051
}
61-
context.lineTo((j / adjustedLength) * canvas.width, (point / arrayLengthY) * canvas.height + halfWidth);
6252
}
63-
if (data.interval !== data.intervalCount) context.lineTo(canvas.width, (currentLocation[i] / arrayLengthY) * canvas.height + halfWidth);
53+
54+
if (data.interval !== data.intervalLength) context.lineTo(canvas.width, (currentLocation[i] / arrayLengthY) * canvas.height + halfLineWidth);
6455
context.stroke();
65-
context.closePath();
56+
}
57+
};
58+
export const recordData = (array: IDataPoint[], data: any) => {
59+
if (data.interval === undefined) {
60+
data.interval = 1;
61+
data.intervalLength = 1;
62+
data.reference = array.map(e => e.id);
63+
data.memoryArray = [Array.from(Array(array.length).keys())];
64+
}
65+
66+
data.interval = ++data.interval % data.intervalLength;
67+
if (data.interval === 0) {
68+
const currentLocation: number[] = Array.from(Array(data.memoryArray[0].length));
69+
for (let i = 0; i < array.length; i++) {
70+
const index = array.findIndex((e) => e.id === data.reference[i]);
71+
if (index !== -1) currentLocation[i] = index;
72+
}
73+
data.memoryArray.push(currentLocation);
74+
}
75+
if (data.memoryArray.length * data.memoryArray[0].length > 131072) {
76+
data.intervalLength *= 2;
77+
for (let i = 2; i <= data.memoryArray.length; i += 2) data.memoryArray.splice(i, 1);
6678
}
6779
};

src/settings/graphType/lineGraph.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ import { IDataPoint } from 'src/shared/interfaces';
22
import { graphColors, settings } from '../';
33

44
export const name = 'Line Graph';
5-
export const algorithm = (array: IDataPoint[], canvas: HTMLCanvasElement, data: any) => {
5+
export const render = (array: IDataPoint[], canvas: HTMLCanvasElement, data: any) => {
66

77
const graphColor = graphColors.find(e => e.name === settings.GraphColor);
88
if (graphColor === undefined) return;
99

10-
const context = canvas.getContext('2d');
10+
const context = canvas.getContext('2d', { alpha: false });
1111
if (!context) return;
12-
context.clearRect(0, 0, canvas.width, canvas.height);
12+
context.fillStyle = '#ffffff';
13+
context.fillRect(0, 0, canvas.width, canvas.height);
1314

1415
context.lineWidth = (canvas.height + 100) / (array.length);
1516

@@ -28,4 +29,5 @@ export const algorithm = (array: IDataPoint[], canvas: HTMLCanvasElement, data:
2829
context.stroke();
2930
}
3031
context.closePath();
31-
};
32+
};
33+
export const recordData = () => null;

src/shared/renderGraph.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,28 @@
11
import { IDataPoint } from './interfaces';
22
import { graphTypes, settings } from '../settings';
33

4+
let lastRender = Date.now();
5+
let backupGraphData: any;
6+
type renderAction = 'regularRender' | 'mustRender';
7+
48
/**
59
* Renders the graph's canvas element.
610
* @param {IDataPoint[]} coordinates - The coordinates's of the graph.
11+
* @param {string} action - Whether or not to record the data.
12+
* @param {any} GraphData - Persistant data used by the graph.
713
*/
8-
export const RenderGraph = (coordinates: IDataPoint[] | undefined, graphData?: any) => {
9-
if (coordinates === undefined) return;
10-
11-
const graph = <HTMLCanvasElement>document.getElementById('DataGraph');
14+
export const RenderGraph = (coordinates: IDataPoint[] | undefined, action: renderAction, graphData?: any) => {
1215
const graphType = graphTypes.find(e => e.name === settings.GraphType);
16+
if (coordinates === undefined || graphType === undefined) return;
1317

14-
if (graphType === undefined || graph === undefined) return;
18+
if (action === 'regularRender') graphType.recordData(coordinates, graphData);
19+
20+
// Only render the image 30 times a second unless required.
21+
if (Date.now() - lastRender < 33 && action !== 'mustRender') return;
22+
if (graphData !== undefined) backupGraphData = graphData;
23+
24+
lastRender = Date.now();
25+
const graph = <HTMLCanvasElement>document.getElementById('DataGraph');
1526

16-
graphType.algorithm(coordinates, graph, graphData ?? {});
27+
if (graph) graphType.render(coordinates, graph, graphData ?? backupGraphData);
1728
};

src/shared/sortGraph.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const suspendSorting = (app: HomeComponent, graphData: any, message?: string) =>
1414
if (typeof message === 'string') app.ErrorMessage = 'Error: ' + message;
1515
app.Oscillator?.stop();
1616
window.clearInterval(app.SortingInterval);
17-
RenderGraph(app.Coordinates, graphData);
17+
RenderGraph(app.Coordinates, 'mustRender', graphData);
1818
return;
1919
};
2020

@@ -59,7 +59,7 @@ export const sortGraph = (app: HomeComponent) => {
5959

6060
app.ErrorMessage = undefined;
6161

62-
RenderGraph(app.Coordinates);
62+
RenderGraph(app.Coordinates, 'regularRender', graphData);
6363

6464
const sortingAlgorithm = sortingAlgorithms.find(e => e.name === settings.SortingAlgorithm);
6565
if (sortingAlgorithm === undefined) return suspendSorting(app, {}, 'Sorting Algorithm not found');
@@ -76,7 +76,7 @@ export const sortGraph = (app: HomeComponent) => {
7676
speed *= 2;
7777
speedPass++;
7878
}
79-
79+
8080
let lowestValue = Number.POSITIVE_INFINITY;
8181
let highestValue = Number.NEGATIVE_INFINITY;
8282
for (let i = 0; i < app.Coordinates.length; i++) {
@@ -124,7 +124,7 @@ export const sortGraph = (app: HomeComponent) => {
124124
app.Coordinates[data.highlight[i]].highlight = true;
125125
}
126126

127-
RenderGraph(app.Coordinates, graphData);
127+
RenderGraph(app.Coordinates, 'regularRender', graphData);
128128

129129
for (let i = 0; i < data.highlight.length; i++) {
130130
if (app.Coordinates[data.highlight[i]] === undefined) continue;

src/styles.css

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,7 @@ header {
104 76B1 104
margin-left: auto;
105105
margin-right: auto;
106106
padding: 1em;
107-
}
108-
109-
#DataGraph canvas{
110-
border: var(--border);
111-
border-radius: var(--border-radius);
107+
image-rendering: pixelated;
112108
}
113109

114110
#BottomMenu {

0 commit comments

Comments
 (0)
0