feat: prevent overlapping of blips

This commit is contained in:
Mathias Schopmans
2024-02-28 16:27:28 +01:00
committed by Mathias Schopmans
parent 13591b9672
commit 535c9e8a8f
5 changed files with 114 additions and 26 deletions

View File

@@ -6,9 +6,16 @@ import { markedHighlight } from "marked-highlight";
import path from "path";
import config from "../next.config.mjs";
import Positioner from "./positioner";
import { getChartConfig, getQuadrants, getRings } from "@/lib/data";
import { Flag, Item } from "@/lib/types";
const rings = getRings();
const quadrants = getQuadrants();
const { size } = getChartConfig();
const positioner = new Positioner(size, quadrants, rings);
const marked = new Marked(
markedHighlight({
langPrefix: "hljs language-",
@@ -72,6 +79,7 @@ async function parseDirectory(dirPath: string): Promise<Item[]> {
flag: Flag.Default,
tags: data.tags || [],
revisions: [],
position: [0, 0],
};
} else {
items[id].release = releaseDate;
@@ -146,8 +154,7 @@ function postProcessItems(items: Item[]): {
const processedItems = items.map((item) => ({
...item,
// @todo: Maybe we should use a better random number generator to avoid overlapping of blips
random: [Math.sqrt(Math.random()), Math.random()] as [number, number],
position: positioner.getNextPosition(item.quadrant, item.ring),
flag: getFlag(item, latestRelease),
// only keep revision which ring or body is different
revisions: item.revisions

95
scripts/positioner.ts Normal file
View File

@@ -0,0 +1,95 @@
import { Quadrant, Ring } from "@/lib/types";
type Position = [x: number, y: number];
type RingDimension = [innerRadius: number, outerRadius: number];
// Corresponding to positions 1, 2, 3, and 4 respectively
const startAngles = [270, 0, 180, 90];
export default class Positioner {
private readonly centerRadius: number;
private readonly minDistance: number = 20;
private readonly paddingRing: number = 15;
private readonly paddingAngle: number = 10;
private positions: Record<string, Position[]> = {};
private ringDimensions: Record<string, RingDimension> = {};
private quadrantAngles: Record<string, number> = {};
constructor(size: number, quadrants: Quadrant[], rings: Ring[]) {
this.centerRadius = size / 2;
quadrants.forEach((quadrant) => {
this.quadrantAngles[quadrant.id] = startAngles[quadrant.position - 1];
});
rings.forEach((ring, index) => {
const innerRadius =
(rings[index - 1]?.radius ?? 0) * this.centerRadius + this.paddingRing;
const outerRadius =
(ring.radius ?? 1) * this.centerRadius - this.paddingRing;
this.ringDimensions[ring.id] = [innerRadius, outerRadius];
});
}
static getDistance(a: Position, b: Position): number {
const [x1, y1] = a;
const [x2, y2] = b;
return Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2);
}
private isOverlapping(position: Position, positions: Position[]): boolean {
return positions.some(
(p) => Positioner.getDistance(position, p) < this.minDistance,
);
}
private getXYPosition(
quadrantId: string,
ringId: string,
radiusFraction: number,
angleFraction: number,
): Position {
const [innerRadius, outerRadius] = this.ringDimensions[ringId];
const ringWidth = outerRadius - innerRadius;
const absoluteRadius = innerRadius + radiusFraction * ringWidth;
const startAngle = this.quadrantAngles[quadrantId] + this.paddingAngle;
const endAngle = startAngle + 90 - 2 * this.paddingAngle;
const absoluteAngle = startAngle + (endAngle - startAngle) * angleFraction;
const angleInRadians = ((absoluteAngle - 90) * Math.PI) / 180;
return [
Math.round(this.centerRadius + absoluteRadius * Math.cos(angleInRadians)),
Math.round(this.centerRadius + absoluteRadius * Math.sin(angleInRadians)),
];
}
public getNextPosition(quadrantId: string, ringId: string): Position {
this.positions[quadrantId] ??= [];
let tries = 0;
let position: Position;
do {
position = this.getXYPosition(
quadrantId,
ringId,
Math.sqrt(Math.random()),
Math.random(),
);
tries++;
} while (
this.isOverlapping(position, this.positions[quadrantId]) &&
tries < 150
);
if (tries >= 150) {
console.warn(
`Could not find a non-overlapping position for ${quadrantId} in ring ${ringId}`,
);
}
this.positions[quadrantId].push(position);
return position;
}
}