下面的代码在识别边框内封闭的区域时存在问题。
如果我将所有“A”单元包围在一个周界内,则它被识别为自己的区域;但如果我先封闭“B”区域,它不会被识别为自己的区域。
const COLOR_PALETTE = ['#FDD', '#DFD', '#DDF', '#FFD', '#DBF', '#BFD', '#FDB', '#BDF'];
const CLICK_DISTANCE_THRESHOLD = 5;
const DATA = [
['A', 'A', 'A', 'B'],
['C', 'C', 'A', 'B'],
['C', 'D', 'B', 'B'],
['C', 'D', 'D', 'D']
];
const $grid = $('#grid');
$grid.append($('<tbody>')
.append(DATA.map((row, rowIndex) => $('<tr>')
.append(row.map((val, colIndex) => $('<td>')
.text(val).data('coords', [rowIndex, colIndex]))))));
$grid.on('click', 'td', onBorderClick);
function onBorderClick(event) {
const $target = $(this);
const rect = this.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
// Determine the closest border and toggle it
if (x < CLICK_DISTANCE_THRESHOLD) {
$target.toggleClass('selected-left');
$target.prev().toggleClass('selected-right');
} else if (x > rect.width - CLICK_DISTANCE_THRESHOLD) {
$target.toggleClass('selected-right');
$target.next().toggleClass('selected-left');
} else if (y < CLICK_DISTANCE_THRESHOLD) {
$target.toggleClass('selected-top');
$target.parent().prev().children().eq($target.index()).toggleClass('selected-bottom');
} else if (y > rect.height - CLICK_DISTANCE_THRESHOLD) {
$target.toggleClass('selected-bottom');
$target.parent().next().children().eq($target.index()).toggleClass('selected-top');
}
distinguishRegions();
}
function distinguishRegions() {
// Clear previous regions
$grid.find('td').css('background-color', '').removeData('region').removeAttr('data-region');
let regionId = 0;
$grid.find('td').each(function() {
const $cell = $(this);
if (!$cell.data('region')) {
regionId++;
floodFill($cell, regionId);
}
});
}
function floodFill($cell, regionId) {
const queue = [$cell];
while (queue.length > 0) {
const $current = queue.shift();
if ($current.length === 0 || $current.data('region')) continue;
$current.data('region', regionId)
.attr('data-region', regionId)
.css('background-color', getRandomColor(regionId));
const [r, c] = $current.data('coords');
const neighbors = [
!$current.hasClass('selected-top') && getCell(r - 1, c),
!$current.hasClass('selected-bottom') && getCell(r + 1, c),
!$current.hasClass('selected-left') && getCell(r, c - 1),
!$current.hasClass('selected-right') && getCell(r, c + 1),
];
queue.push(...neighbors.filter($n => $n && !$n.data('region')));
}
}
function getCell(rowIndex, colIndex) {
return $grid.find(`tr:eq(${rowIndex})`).find(`td:eq(${colIndex})`);
}
function getRandomColor(index) {
return COLOR_PALETTE[index % COLOR_PALETTE.length];
}
table {
border: 2px solid grey;
border-collapse: collapse;
cursor: pointer;
}
td {
width: 2rem;
height: 2rem;
border: 2px dotted grey;
text-align: center;
}
.selected-left { border-left: 2px solid red; }
.selected-right { border-right: 2px solid red; }
.selected-top { border-top: 2px solid red; }
.selected-bottom { border-bottom: 2px solid red; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<table id="grid"></table>
我最终初始化了外边框,以便整个网格作为单个区域开始。
const COLOR_PALETTE = ['#FDD', '#DFD', '#DDF', '#FFD', '#DBF', '#BFD', '#FDB', '#BDF'];
const CLICK_THRESHOLD = 5;
const DATA = [
['A', 'A', 'A', 'B'],
['C', 'C', 'A', 'B'],
['C', 'D', 'B', 'B'],
['C', 'D', 'D', 'D']
];
const $grid = $('#grid');
populateGrid(DATA);
initializeBorders();
$grid.on('click', 'td', onBorderClick);
function populateGrid(data) {
$grid.append($('<tbody>')
.append(data.map((row, rowIndex) => $('<tr>')
.append(row.map((val, colIndex) => $('<td>')
.text(val).data('coords', [rowIndex, colIndex]))))));
}
function initializeBorders() {
$grid.find('tr').each(function(rowIndex, tr) {
$(tr).find('td').each(function(colIndex, td) {
const $cell = $(td);
const neighbors = getNeighbors(rowIndex, colIndex);
if (!neighbors.left.length) $cell.addClass('selected-left');
if (!neighbors.right.length) $cell.addClass('selected-right');
if (!neighbors.top.length) $cell.addClass('selected-top');
if (!neighbors.bottom.length) $cell.addClass('selected-bottom');
});
});
}
function onBorderClick(event) {
const { top, left, width, height } = this.getBoundingClientRect();
const x = event.clientX - left, y = event.clientY - top;
toggleBorder($(this), x, y, width, height);
distinguishRegions();
}
function getNeighbors(rowIndex, colIndex) {
return {
top: getCell(rowIndex - 1, colIndex),
right: getCell(rowIndex, colIndex + 1),
bottom: getCell(rowIndex + 1, colIndex),
left: getCell(rowIndex, colIndex - 1)
};
}
function toggleBorder($cell, x, y, width, height) {
if (x < CLICK_THRESHOLD) toggleLeft($cell);
else if (x > width - CLICK_THRESHOLD) toggleRight($cell);
else if (y < CLICK_THRESHOLD) toggleTop($cell);
else if (y > height - CLICK_THRESHOLD) toggleBottom($cell);
}
function toggleLeft($cell) {
$cell.toggleClass('selected-left');
$cell.prev().toggleClass('selected-right');
}
function toggleRight($cell) {
$cell.toggleClass('selected-right');
$cell.next().toggleClass('selected-left');
}
function toggleTop($cell) {
$cell.toggleClass('selected-top');
$cell.parent().prev().children().eq($cell.index()).toggleClass('selected-bottom');
}
function toggleBottom($cell) {
$cell.toggleClass('selected-bottom');
$cell.parent().next().children().eq($cell.index()).toggleClass('selected-top');
}
function distinguishRegions() {
const $cells = $grid.find('td');
// Clear previous regions
$cells.css('background-color', '').removeData('region').removeAttr('data-region');
let regionId = 0;
$cells.each(function() {
const $cell = $(this);
if (!$cell.data('region')) {
regionId++;
floodFill($cell, regionId);
}
});
}
function floodFill($cell, regionId) {
const queue = [$cell];
while (queue.length > 0) {
const $current = queue.shift();
if ($current.length === 0 || $current.data('region')) continue;
$current.data('region', regionId)
.attr('data-region', regionId)
.css('background-color', getRandomColor(regionId - 1));
const [rowIndex, colIndex] = $current.data('coords');
const neighbors = getNeighbors(rowIndex, colIndex);
const adjacencies = [
!$current.hasClass('selected-top') && neighbors.top,
!$current.hasClass('selected-bottom') && neighbors.bottom,
!$current.hasClass('selected-left') && neighbors.left,
!$current.hasClass('selected-right') && neighbors.right,
];
queue.push(...adjacencies.filter($n => $n && !$n.data('region')));
}
}
function getCell(rowIndex, colIndex) {
const $rows = $grid.find('tr');
if (rowIndex < 0 || rowIndex >= $rows.length) {
return $();
}
const $cells = $rows.eq(rowIndex).find('td');
if (colIndex < 0 || colIndex >= $cells.length) {
return $();
}
return $cells.eq(colIndex);
}
function getRandomColor(index) {
return COLOR_PALETTE[index % COLOR_PALETTE.length];
}
table {
border-collapse: collapse;
cursor: pointer;
}
td {
width: 2rem;
height: 2rem;
border: 2px dotted grey;
text-align: center;
}
.selected-left { border-left: 2px solid black; }
.selected-right { border-right: 2px solid black; }
.selected-top { border-top: 2px solid black; }
.selected-bottom { border-bottom: 2px solid black; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<table id="grid"></table>