Skip to content

Commit

Permalink
feat: frequency map
Browse files Browse the repository at this point in the history
  • Loading branch information
rei1024 committed Oct 19, 2024
1 parent f705ffa commit 9adf6fb
Show file tree
Hide file tree
Showing 9 changed files with 263 additions and 136 deletions.
77 changes: 58 additions & 19 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@
<body>
<header>
<h1>Oscilloscope</h1>
<div style="text-align: center; margin-bottom: 2px">
<small style="text-align: center; margin: auto"
><a
href="https://conwaylife.com/wiki/Oscillator"
target="_blank"
rel="noopener"
>Oscillator</a
>
analyzer</small
>
</div>
</header>
<main>
<div class="box">
Expand Down Expand Up @@ -59,6 +70,28 @@ <h2>
<div class="box" id="message" style="display: none"></div>
<div id="map-box" class="box" style="display: none">
<h2>Map</h2>
<div>
<label for="map-period-select" style="user-select: none">
<input
id="map-period-select"
value="period"
name="map-type-select"
type="radio"
checked
/>
Period
</label>

<label for="map-frequency-select" style="user-select: none">
<input
id="map-frequency-select"
value="frequency"
name="map-type-select"
type="radio"
/>
Frequency
</label>
</div>
<canvas id="canvas"></canvas>
<div>
<input
Expand All @@ -74,16 +107,20 @@ <h2>Map</h2>
<pre
style="display: inline"
><span id="generation" class="tabular-nums"></span></pre>
<pre style="display: inline" id="hover-info"><span></span></pre>
</div>
<table id="period-coolor-table" style="margin-right: auto"></table>
<details open>
<summary style="user-select: none; cursor: pointer">Colors</summary>
<table id="color-table" style="margin-right: auto"></table>
</details>
</div>

<div id="data-box" class="box" style="display: none">
<h2>Data</h2>
<table id="output-table" style="display: none">
<tr>
<th>Period</th>
<td id="output-period">32</td>
<td id="output-period"></td>
</tr>
<tr>
<th>Population</th>
Expand Down Expand Up @@ -116,23 +153,25 @@ <h2>Data</h2>
</div>
</main>
<footer>
<h2>Reference</h2>
<ul>
<li>
<a href="https://github.com/rei1024/oscilloscope"
>Source Code | GitHub</a
>
</li>
<li>
<a href="https://conwaylife.com/wiki/Map">Map | LifeWiki</a>
</li>
<li>
<a
href="https://conwaylife.com/wiki/Comparison_of_oscillator_analysis_tools"
>Comparison of oscillator analysis tools | LifeWiki</a
>
</li>
</ul>
<section>
<h2>Reference</h2>
<ul>
<li>
<a href="https://github.com/rei1024/oscilloscope"
>Source Code | GitHub</a
>
</li>
<li>
<a href="https://conwaylife.com/wiki/Map">Map | LifeWiki</a>
</li>
<li>
<a
href="https://conwaylife.com/wiki/Comparison_of_oscillator_analysis_tools"
>Comparison of oscillator analysis tools | LifeWiki</a
>
</li>
</ul>
</section>
</footer>
</body>
</html>
79 changes: 58 additions & 21 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import {
$animFrequencyLabel,
$dataBox,
$generation,
$hoverInfo,
$mapBox,
} from "./bind";
import { setPeriodColorTable } from "./ui/periodColorTable";
import { setColorTable } from "./ui/colorTable";

const cellSize = 10;
const innerCellSize = 6;
Expand All @@ -20,12 +21,12 @@ const frequencyList = [
300, 400, 500,
];

export function periodToColor(periodList: number[], period: number) {
const index = periodList.findIndex((t) => t === period) ?? 0;
export function dataToColor(list: number[], data: number) {
const index = list.findIndex((t) => t === data) ?? 0;
// 80% 0.1
return `oklch(92% 0.35 ${(index * 360) / periodList.length})`;
// return `lch(70% 70 ${(index * 360) / periodList.length})`;
// return `hsl(${(index * 360) / periodList.length} 100% 70%)`;
return `oklch(92% 0.35 ${(index * 360) / list.length})`;
// return `lch(70% 70 ${(index * 360) / list.length})`;
// return `hsl(${(index * 360) / list.length} 100% 70%)`;
}

export class App {
Expand All @@ -34,7 +35,8 @@ export class App {
private ctx: CanvasRenderingContext2D;
private gen = 0;
private valve: Valve;
private periodRows: HTMLTableRowElement[] = [];
private colorTableRows: HTMLTableRowElement[] = [];
private mapType: "period" | "frequency" = "period";
constructor(private $canvas: HTMLCanvasElement) {
const ctx = this.$canvas.getContext("2d", { colorSpace: "display-p3" });
if (ctx == null) {
Expand Down Expand Up @@ -77,12 +79,13 @@ export class App {
const dx = this.data.bitGridData.minX;
const dy = this.data.bitGridData.minY;

const periodList = this.data.periodMap.periodList;
for (const [y, row] of this.data.periodMap.data.entries()) {
const mapData = this.getMapData();
const list = mapData.list;
for (const [y, row] of mapData.data.entries()) {
for (const [x, p] of row.entries()) {
if (p >= 1) {
ctx.beginPath();
ctx.fillStyle = periodToColor(periodList, p);
ctx.fillStyle = dataToColor(list, p);
ctx.rect(
(x - dx + safeArea) * cellSize,
(y - dy + safeArea) * cellSize,
Expand Down Expand Up @@ -150,16 +153,28 @@ export class App {

this.updateFrequency();

this.periodRows = setPeriodColorTable(data);
this.colorTableRows = setColorTable(this.getMapData(), this.mapType);
}

private getMapData() {
if (this.data == null) {
throw null;
}
return this.mapType === "frequency"
? this.data.frequencyMap
: this.data.periodMap;
}

updateFrequency() {
this.valve.frequency = frequencyList[Number($animFrequency.value)];
}

renderPeriodTableHighlight(pixelPosition: { x: number; y: number }) {
if (!this.data?.periodMap) {
return;
private getMapIndexAt(pixelPosition: {
x: number;
y: number;
}): { cellData: number; index: number } | undefined {
if (!this.data) {
return undefined;
}
const dx = this.data.bitGridData.minX;
const dy = this.data.bitGridData.minY;
Expand All @@ -178,16 +193,38 @@ export class App {
(this.data.boundingBox.sizeY + safeArea * 2)
);

const period = this.data.periodMap.data[y][x];
const index = this.data.periodMap.periodList
const mapData = this.getMapData();
const cellData = mapData.data[y][x];
const index = mapData.list
.filter((x) => x !== 0)
.findIndex((x) => x === period);
console.log(x, y, index, this.periodRows);
for (const row of this.periodRows) {
.findIndex((x) => x === cellData);
if (index === -1) {
return undefined;
}
return { cellData, index };
}

renderColorTableHighlight(pixelPosition: { x: number; y: number }) {
for (const row of this.colorTableRows) {
row.style.backgroundColor = "";
}
if (index !== -1) {
this.periodRows[index].style.backgroundColor = "#0000FF22";

const pointData = this.getMapIndexAt(pixelPosition);
if (pointData !== undefined) {
this.colorTableRows[pointData.index].style.backgroundColor = "#0000FF22";

$hoverInfo.textContent =
" " +
(this.mapType === "period" ? "period" : "frequency") +
" = " +
pointData.cellData;
} else {
$hoverInfo.textContent = "";
}
}

updateMapType(mapType: "period" | "frequency") {
this.mapType = mapType;
this.colorTableRows = setColorTable(this.getMapData(), this.mapType);
}
}
11 changes: 9 additions & 2 deletions src/bind.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
export const $message = document.querySelector("#message") as HTMLElement;

export const $mapBox = document.querySelector("#map-box") as HTMLElement;

export const $mapTypeSelect = [
...document.querySelectorAll('[name="map-type-select"]'),
] as HTMLInputElement[];

export const $dataBox = document.querySelector("#data-box") as HTMLElement;

export const $outputTable = document.querySelector(
Expand Down Expand Up @@ -59,6 +64,8 @@ export const $exampleOscillators = document.querySelector(

export const $canvas = document.querySelector("#canvas") as HTMLCanvasElement;

export const $hoverInfo = document.querySelector("#hover-info") as HTMLElement;

export const $animFrequency = document.querySelector(
"#anim-frequency"
) as HTMLInputElement;
Expand All @@ -69,6 +76,6 @@ export const $animFrequencyLabel = document.querySelector(

export const $generation = document.querySelector("#generation") as HTMLElement;

export const $periodColorTable = document.querySelector(
"#period-coolor-table"
export const $colorTable = document.querySelector(
"#color-table"
) as HTMLTableElement;
21 changes: 13 additions & 8 deletions src/lib/analyzeOscillator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { average, max, median, min } from "./collection";
import { rectToSize } from "./rect";
import { BitGrid } from "@ca-ts/algo/bit";
import { runOscillator, type RunOscillatorConfig } from "./runOscillator";
import { periodMap, periodMapUnique } from "./periodMap";
import { getMap } from "./getMap";

function getOrAndGrid(histories: BitGrid[]) {
if (histories.length === 0) {
Expand Down Expand Up @@ -74,14 +74,21 @@ export type AnalyzeResult = {
and: BitGridData;
};
/**
* [Period Map](https://conwaylife.com/wiki/Map#Period_map)
* [Period map](https://conwaylife.com/wiki/Map#Period_map)
*/
periodMap: {
/**
* Period of each cells
*/
data: number[][];
periodList: number[];
list: number[];
};
/**
* [Frequency map](https://conwaylife.com/wiki/Map#Frequency_map)
*/
frequencyMap: {
data: number[][];
list: number[];
};
};

Expand Down Expand Up @@ -117,7 +124,7 @@ export function analyzeOscillator(
const width = world.histories[0]?.bitGrid.getWidth() ?? 0;
const height = world.histories[0]?.bitGrid.getHeight() ?? 0;

const periodMapData = periodMap({
const { periodMap, frequencyMap } = getMap({
width,
height,
or,
Expand Down Expand Up @@ -145,9 +152,7 @@ export function analyzeOscillator(
minX: boundingBox.minX,
minY: boundingBox.minY,
},
periodMap: {
data: periodMapData,
periodList: periodMapUnique(periodMapData),
},
periodMap,
frequencyMap,
};
}
65 changes: 65 additions & 0 deletions src/lib/getMap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import type { BitGrid } from "@ca-ts/algo/bit";
import { findPeriod } from "./findPeriod";

export function getMap({
width,
height,
or,
histories,
}: {
width: number;
height: number;
or: BitGrid;
histories: BitGrid[];
}): {
periodMap: { data: number[][]; list: number[] };
frequencyMap: { data: number[][]; list: number[] };
} {
const periodArray = Array(height)
.fill(0)
.map(() => 0)
.map(() =>
Array(width)
.fill(0)
.map(() => 0)
);

const frequencyArray = Array(height)
.fill(0)
.map(() => 0)
.map(() =>
Array(width)
.fill(0)
.map(() => 0)
);

const historiesArray = histories.map((h) => h.getArray());

or.forEachAlive((x, y) => {
const states: (0 | 1)[] = [];
for (const h of historiesArray) {
const cell = h[y][x];
states.push(cell);
if (cell !== 0) {
frequencyArray[y][x]++;
}
}
periodArray[y][x] = findPeriod(states);
});

return {
periodMap: {
data: periodArray,
list: mapUnique(periodArray),
},
frequencyMap: {
data: frequencyArray,
list: mapUnique(frequencyArray),
},
};
}

function mapUnique(periodMap: number[][]): number[] {
const set = new Set(periodMap.flat());
return [...set].sort((a, b) => a - b);
}
Loading

0 comments on commit 9adf6fb

Please sign in to comment.