This code does the following:
-
Put square sections on a grid (right in the middle of a dot)
-
Regard those sections as a single component (they have different colors)
-
Calculate the outer edges of that component
-
Add a stroke to those edges
-
Set the stroke to the same color as the background (to fake shrinking the whole component inwardly).
The result:
The issue: when a component is getting inside another component, it will be hidden by the stroke. For example, in the photo above, the red component is being hidden by the yellow component’s stroke.
How to achieve the edge’s shrinking effect without this issue?
The code:
import p5 from 'p5';
const convertPosition = (position, numCols, numRows) => {
const col = position.charCodeAt(0) - 'A'.charCodeAt(0);
const row = parseInt(position.slice(1), 10) - 1;
if (col >= numCols || row >= numRows) {
throw new Error(`Invalid position: ${position}`);
}
return { col, row };
};
const createConfigFromStrings = (inputConfig, numCols, numRows) => {
const components = inputConfig.components.map((component) => {
const sections = component.sections.map((section) => {
const { col, row } = convertPosition(section.position, numCols, numRows);
return { ...section, col, row };
});
const { col, row } = convertPosition(component.position, numCols, numRows);
return { ...component, col, row, sections };
});
return {
...inputConfig,
numCols,
numRows,
components,
};
};
const inputConfig = {
numRows: 6,
numCols: 14,
spacing: 40,
dotSize: 2,
components: [
{
position: "C3",
shrinkPixels: 34,
label: "B3",
sections: [
{ position: "B6", color: "#ff628c", label: "" },
{ position: "C6", color: "#ff628c", label: "" },
{ position: "D6", color: "#ff628c", label: "" },
{ position: "E6", color: "#ff628c", label: "" },
{ position: "F6", color: "#ff628c", label: "" },
{ position: "G6", color: "#ff628c", label: "" },
{ position: "G5", color: "#ff628c", label: "" },
{ position: "G4", color: "#ff628c", label: "" },
],
},
{
position: "A1",
shrinkPixels: 10,
label: "A1",
sections: [
{ position: "A4", color: "#fad000", label: "" },
{ position: "A5", color: "#fad000", label: "" },
{ position: "A6", color: "#fad000", label: "" },
{ position: "B4", color: "#fad000", label: "DP1" },
{ position: "B5", color: "#fad000", label: "CC1" },
{ position: "B6", color: "#fad000", label: "VCC" },
{ position: "C4", color: "#fad000", label: "DN2" },
{ position: "C5", color: "#fad000", label: "USB2" },
{ position: "C6", color: "#fad000", label: "VCC" },
{ position: "D4", color: "#fad000", label: "" },
{ position: "D5", color: "#fad000", label: "" },
{ position: "D6", color: "#fad000", label: "" },
],
},
],
};
const sketch = (p) => {
let config;
p.setup = () => {
config = createConfigFromStrings(inputConfig, inputConfig.numCols, inputConfig.numRows);
p.createCanvas(window.innerWidth, window.innerHeight);
p.noLoop();
};
p.draw = () => {
p.background("#2d2b55");
p.fill("#7c76a7");
p.noStroke();
const startX = (p.width - (config.numCols - 1) * config.spacing) / 2;
const startY = (p.height - (config.numRows - 1) * config.spacing) / 2;
drawGrid(startX, startY);
drawLabels(startX, startY);
drawRectangles(startX, startY);
};
p.windowResized = () => {
p.resizeCanvas(window.innerWidth, window.innerHeight);
p.draw();
};
const drawGrid = (startX, startY) => {
for (let i = 0; i < config.numCols; i++) {
for (let j = 0; j < config.numRows; j++) {
const x = startX + i * config.spacing;
const y = startY + j * config.spacing;
p.ellipse(x, y, config.dotSize, config.dotSize);
}
}
};
const drawLabels = (startX, startY) => {
p.textAlign(p.CENTER, p.CENTER);
p.textSize(12);
p.fill("#7c76a7");
for (let i = 0; i < config.numCols; i++) {
const x = startX + i * config.spacing;
p.text(String.fromCharCode(65 + i), x, startY - config.spacing);
}
for (let j = 0; j < config.numRows; j++) {
const y = startY + j * config.spacing;
p.text(j + 1, startX - config.spacing, y);
}
};
const drawRectangles = (startX, startY) => {
config.components.forEach(({ shrinkPixels, label, sections }) => {
const minCol = Math.min(...sections.map((section) => section.col));
const minRow = Math.min(...sections.map((section) => section.row));
const maxCol = Math.max(...sections.map((section) => section.col));
const maxRow = Math.max(...sections.map((section) => section.row));
const rectX = startX + minCol * config.spacing - config.spacing / 2;
const rectY = startY + minRow * config.spacing - config.spacing / 2;
p.noStroke();
sections.forEach((section) => {
const sectionColor = p.color(section.color);
sectionColor.setAlpha(255);
p.fill(sectionColor);
p.rect(
rectX + (section.col - minCol) * config.spacing,
rectY + (section.row - minRow) * config.spacing,
config.spacing,
config.spacing,
);
p.fill("#fbf7ff");
p.noStroke();
p.text(
section.label,
rectX + (section.col - minCol) * config.spacing + config.spacing / 2,
rectY + (section.row - minRow) * config.spacing + config.spacing / 2,
);
});
p.noFill();
p.stroke("#2d2b55");
p.strokeWeight(shrinkPixels);
p.strokeCap(p.PROJECT);
p.strokeJoin(p.BEVEL);
const edges = [];
sections.forEach((section) => {
const x = rectX + (section.col - minCol) * config.spacing;
const y = rectY + (section.row - minRow) * config.spacing;
const neighbors = {
top: sections.some((s) => s.col === section.col && s.row === section.row - 1),
right: sections.some((s) => s.col === section.col + 1 && s.row === section.row),
bottom: sections.some((s) => s.col === section.col && s.row === section.row + 1),
left: sections.some((s) => s.col === section.col - 1 && s.row === section.row),
};
if (!neighbors.top) {
edges.push([x, y, x + config.spacing, y]);
}
if (!neighbors.right) {
edges.push([x + config.spacing, y, x + config.spacing, y + config.spacing]);
}
if (!neighbors.bottom) {
edges.push([x, y + config.spacing, x + config.spacing, y + config.spacing]);
}
if (!neighbors.left) {
edges.push([x, y, x, y + config.spacing]);
}
});
edges.forEach(([x1, y1, x2, y2]) => {
p.line(x1, y1, x2, y2);
});
const componentCenterX = rectX + ((maxCol - minCol + 1) * config.spacing) / 2;
const componentCenterY = rectY + ((maxRow - minRow + 1) * config.spacing) / 2;
p.fill("#fbf7ff");
p.noStroke();
p.text(label, componentCenterX, componentCenterY);
});
};
};
new p5(sketch, document.getElementById('sketch'));
The live code
Also posted here.