← 首页 / 用四个数字框住一块区域 待审核
矩形模型 · 四元组

用四个数字框住一块区域

Excel / Google Sheets 里拖一下就能「合并单元格」,但在代码层面,一个单元格或一块合并区域几乎总是用四个坐标描述:start-row / start-col / end-row / end-col——左上角 + 右下角。本页把这套模型拆开:四种合并情况其实是同一个矩形的四种取值;判定两块区域能不能并存,落到一行矩形相交;最后接到 xlsx 的 "A1:C1" 记法和 HTML 的 rowspan/colspan。每一节都能拖、能点、实时看坐标。

1 · 四个数字,框住任意一块单元格

在代码层面,一个单元格或一块合并区域,通常不靠「它长什么样」来描述,而是靠四个坐标:start-row / start-col / end-row / end-col——左上角行列 + 右下角行列。本节在网格上拖一块区域,看这四个数字实时变化;再点四个预设,看清「单格 / 合并多行 / 合并多列 / 区域合并」其实是同一组坐标的四种取值。

行列号这里都从 1 开始数(和 Excel 行号、A1 记法一致):start-row=2, start-col=1, end-row=4, end-col=1 表示「第 2 行到第 4 行的第 1 列」。在网格里按下拖动框选,或点下面四个预设按钮。

四种情况没有四套数据结构——全是同一个 { sr, sc, er, ec } 四元组。判断属于哪一种,只看两条等式:行相等吗 (sr === er)、列相等吗 (sc === ec)。一个矩形,涵盖四种情况一节进一步说明这层「统一」:单格只是退化成 1×1{1\times 1} 的矩形。

2 · 一个矩形,涵盖四种情况

四个数字,框住任意一块单元格一节说明了四种合并情况都是同一组 { sr, sc, er, ec },那它本质就是网格上的一个矩形:(sr, sc) 是左上角、(er, ec) 是右下角。单个未合并的格子,只是退化成 1×1{1\times 1} 的矩形sr===ersc===ec)——不是另一种东西。本节拖两个角,看矩形怎么变、面积怎么算,以及为什么代码里几乎总要先 normalize 一下。

为什么存「两个角」,而不是「锚点 + 跨度」? 合并区也能记成左上角 + rowspan/colspan(HTML 表格正是这么表达的)。打个比方:描述一场会议,你可以记**「9:00 到 10:00」,也可以记「9:00 起、开 1 小时」——两种都对。但要判断两场会撞不撞钟**,记起止两个时刻能一眼比出来 (A.startB.endB.startA.endA.start \le B.end 且 B.start \le A.end);记「起始 + 时长」,每次都得先把时长加回去算出结束时刻。合并区同理:选左上 + 右下两个角,是因为上面三个核心判定——面积、covers、相交——全都化简成对行、列两条区间的直接比较;若改存锚点 + 跨度,每次判定都要先把跨度加回去、还原出右下角,平白多一步。跨度是输出时才需要的派生量 (rowspan=ersr+1rowspan = er - sr + 1),留到 落地一节 再换算即可。

为什么要 normalize? 用户拖选 / 框选时,起点可能在右下、终点在左上——r1 > r2c1 > c2 完全正常。存进数据结构前先取 min/max 规整成 srersr \le erscecsc \le ec,后面所有逻辑(面积、covers、相交)才能只考虑「左上 ≤ 右下」一种情形。上面把两个角拖反,看红字提示。两块区域如何判断不能重叠,见 两块合并区域,为什么不能重叠 一节。

3 · 两块合并区域,为什么不能重叠

一张表里允许有很多块合并区域,但它们必须互不重叠——同一个物理格子不能同时属于两块合并区(否则「它的值归谁、跨几格」就矛盾了)。所以新建 / 粘贴一块合并区前,代码都要先做一次矩形相交检测。本节拖动 A / B 两块区域,看相交怎么判:当且仅当「行轴区间」和「列轴区间」都重叠,两个矩形才真的相交。

判定只有一行:a.sr ≤ b.er && b.sr ≤ a.er && a.sc ≤ b.ec && b.sc ≤ a.ec。前两个条件管行轴、后两个管列轴;任一轴上分离(一个完全在另一个上方 / 下方 / 左侧 / 右侧),两矩形就不相交。注意:只挨着一条边但不重叠(相邻)是合法的——边界不算相交。四元组在真实代码里如何落地,见 四元组,在真实代码里长什么样 一节。

4 · 四元组,在真实代码里长什么样

前面几节建立了模型:四种合并 = 一个 { sr, sc, er, ec } 矩形(见 坐标模型退化矩形),相交检测保证不重叠(见 矩形相交)。这一节把它接到三处真实代码:锚点单元格(合并后值和样式归谁)、xlsx 的 "A1:C1" 区间记法(序列化时怎么写)、HTML rowspan/colspan(浏览器表格的同一件事,只是换了套坐标)。

4.1 · 锚点单元格:左上角持有一切,其余被覆盖

合并后,这块区域在视觉与数据上都只剩一个真正的单元格——左上角 (sr, sc),称为 anchor(锚点)。它持有值和样式;被覆盖的其余格子不再独立存在(读它们通常拿到空)。在下面输入一个区间(如 B2:D4),看哪个是锚点、哪些被覆盖。

4.2 · 序列化:四元组 ⇄ "A1:C1" 区间字符串

存盘时没人写四个整数字段,而是写成左上 A1 : 右下 A1 的紧凑串。xlsx (OOXML) 的 <mergeCells> 就是一串这样的 ref。读出来再解析回四元组用于计算——两个方向就是 rcToA1 / parseRange

4.3 · 遍历带合并的表:跳过被覆盖的格子

渲染或导出时,逐格扫描会重复输出被合并的区域。做法是先把每块合并区登记到 anchor 上(记下它的 rows×colsrows \times cols 跨度),扫描时遇到锚点按跨度输出、遇到被覆盖的格子直接跳过。这正是把四元组翻译成 HTML rowspan / colspan 的循环。

换套坐标,还是同一件事: HTML 表格用 rowspan / colspan 表达合并——它们正是 rowspan=ersr+1rowspan = er - sr + 1colspan=ecsc+1colspan = ec - sc + 1(也就是前面的 rowsOf / colsOf)。CSS grid 的 grid-row: sr / er+1grid-column: sc / ec+1 也是一回事——本页网格里那个「合并浮层」就是这么定位的。坐标系千变万化,左上 + 右下两个角这套模型不变。

一句话总览:「合并单元格」在代码里不是一种特殊控件,而是网格上的一个轴对齐矩形。单格 = 1×1{1\times 1} 退化矩形、合并多行 = 宽 1、合并多列 = 高 1、区域合并 = 一般矩形——四种情况共用 { sr, sc, er, ec } 一套数据。剩下的全是矩形的基本运算:面积、包含、相交。

相关链接

  • MDN · <td> rowspan / colspan developer.mozilla.org 浏览器表格表达合并的属性;rowspan/colspan = 区域的行 / 列跨度,和四元组一一对应。
  • OOXML (xlsx) · SpreadsheetML wikipedia.org .xlsx 内部就是一堆 XML;worksheet 里的 <mergeCells>"A1:C1" 这种 ref 串记合并区。
  • geometry · 计算几何 vega · playground 本系列的「矩形相交」是轴对齐矩形 (AABB) 的特例;一般几何里的相交 / 包含 / 扫描线看这个系列。
  • range-query · 区间查询 vega · playground 这里是二维区域上的「不重叠」约束;一维区间上的「问一段、改一点」看前缀和 / 树状数组 / 线段树。