pile_row_no field, never a hardcoded number. The playground below lets you punch in any Rows × Columns and verify the leg/dot logic holds for all of them, not just the examples we picked.
Number of legs = pile_row_no (Columns) for that specific pier/abutment — read from that pier's own data, never a fixed number. Each leg is colored independently, using only the piles that fall in its own column of the dot-grid below it — leg 1 ↔ column 1 dots, leg 2 ↔ column 2 dots, and so on, regardless of how many rows or columns exist. A column's own dots decide that column's leg only; it never affects any other leg.
Edit the Rows / Cols boxes on any card (or click a preset), then press Enter or click away — the pier's leg count, dot-grid, and colors all rebuild to match. Wide grids scroll horizontally within the card; legs, dots, and buttons stay column-aligned as one unit.
No backend or data-model change — pileComplete[pierKey] and pile_row_no are already loaded client-side. Two things change: the color decision built before calling drawPile(), and drawPile()'s own loop (fixed count → array of colors). This logic works unchanged for any Rows × Columns — nothing here assumes a particular grid size.
// 1) Caller side — inside drawPier() / drawAbutment(), build one color PER COLUMN
const cols = parseInt(pierData.pile_row_no, 10) || 1; // dynamic per pier, e.g. 3, 4, 5, 9...
const pileComplete = bridgeData.pileComplete[pierKey] || [];
const totalPiles = parseInt(pierData.pile_pier_no, 10) || 0;
const rows = Math.ceil(totalPiles / cols);
const legColors = [];
for (let c = 0; c < cols; c++) {
const columnPiles = [];
for (let row = 0; row < rows; row++) {
const pileIndex = row * cols + c; // matches drawPileFullView()'s own indexing
if (pileIndex < totalPiles) columnPiles.push('pile_' + (pileIndex + 1));
}
const doneInColumn = columnPiles.filter(p => pileComplete.includes(p)).length;
if (doneInColumn === 0) {
legColors.push('#fff9d9'); // not started (unchanged color)
} else if (doneInColumn === columnPiles.length) {
legColors.push('#7ED321'); // complete (unchanged color)
} else {
legColors.push('#D9A526'); // NEW: in progress
}
}
// pass an ARRAY of colors, and cols (not a fixed number) as the bar count:
this.drawPile(x - pileWidth/2, pileY, pileWidth, pileHeight, legColors, id+'-pile-group', cols, pierData, pierKey);
// 2) drawPile() — now takes fillColors[] and draws `numLegs` bars, each its own color
drawPile(x, y, width, height, fillColors, id, numLegs = 0, _pileSource = {}, locationKey = null) {
const pWidth = 6;
const gap = numLegs > 1 ? (width - (numLegs * pWidth)) / (numLegs - 1) : 0;
for (let i = 0; i < numLegs; i++) {
const px = x + i * (pWidth + gap);
const fill = Array.isArray(fillColors) ? fillColors[i] : fillColors;
this.createRect(px, y, pWidth, height, fill, `${id}-sub-${i}`);
}
}
drawPier() — regular piers P1–P11drawAbutment() — abutments A1 / A2drawPile() itself — bar count + per-bar color
drawPileFullView() hardcodes cols = 3 and ignores its own numOfRow param — in single_pier.blade.php, portal_pier.blade.php, double_portal_pier.blade.php. Change it to cols = Number(numOfRow), matching what double_pier.blade.php already does correctly — otherwise the dot-grid and the new legs will disagree on where column boundaries are.
If pile_pier_no isn't evenly divisible by pile_row_no, the last row is simply short a few dots (loop already guards with pileIndex < totalPiles) — no special-casing needed. The playground's "Rows × Cols" inputs always produce a full rectangle (total = rows × cols) so you can freely explore shapes; real data can still be a partial last row and the same logic handles it.
This is a static demo with sample data — not connected to the real database. The grey pier/wall block above each leg row is shown only for context and is unrelated to this change.