@veltra/sheet 2.3.4 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -17
- package/dist/index.js +2 -13
- package/dist/types/index.d.ts +1 -3
- package/package.json +5 -6
- package/src/index.ts +1 -27
- package/src/types/index.ts +0 -2
- package/dist/components/report/binding-badge.js +0 -112
- package/dist/components/report/designer/cell-coords.js +0 -46
- package/dist/components/report/designer/col-widths.js +0 -29
- package/dist/components/report/designer/conditional-rules/helpers.js +0 -139
- package/dist/components/report/designer/drop-highlight-overlay.js +0 -76
- package/dist/components/report/designer/float-panel.js +0 -422
- package/dist/components/report/designer/role.js +0 -17
- package/dist/components/report/designer/rule-preview.js +0 -33
- package/dist/components/report/designer/rule-row.js +0 -253
- package/dist/components/report/designer/rules-dialog.js +0 -118
- package/dist/components/report/designer/topology-overlay.js +0 -122
- package/dist/components/report/designer/topology.js +0 -64
- package/dist/components/report/designer/use-grid-overlay.js +0 -75
- package/dist/components/report/designer-hub.js +0 -177
- package/dist/components/report/field-panel-helpers.js +0 -33
- package/dist/components/report/field-panel.js +0 -132
- package/dist/components/report/filter-bar.js +0 -74
- package/dist/components/report/hub-connection-form.js +0 -145
- package/dist/components/report/hub-dataset-editor.js +0 -409
- package/dist/components/report/preview-grid-size.js +0 -32
- package/dist/components/report/report-designer.js +0 -393
- package/dist/components/report/report-viewer.js +0 -124
- package/dist/components/report/style.d.ts +0 -17
- package/dist/components/report/style.js +0 -18
- package/dist/components/report/style2.css +0 -874
- package/dist/components/report/use-report-designer.js +0 -386
- package/dist/components/report/use-report-viewer.js +0 -55
- package/dist/report/binding.d.ts +0 -83
- package/dist/report/binding.js +0 -334
- package/dist/report/connector.d.ts +0 -66
- package/dist/report/connector.js +0 -159
- package/dist/report/export-xlsx.d.ts +0 -13
- package/dist/report/export-xlsx.js +0 -13
- package/dist/report/filter-bar.d.ts +0 -10
- package/dist/report/filter-bar.js +0 -31
- package/dist/report/index.d.ts +0 -12
- package/dist/report/params.d.ts +0 -13
- package/dist/report/params.js +0 -61
- package/dist/report/render/aggregate.js +0 -169
- package/dist/report/render/builder.d.ts +0 -12
- package/dist/report/render/builder.js +0 -139
- package/dist/report/render/coordinate.d.ts +0 -25
- package/dist/report/render/coordinate.js +0 -733
- package/dist/report/render/index.d.ts +0 -14
- package/dist/report/render/index.js +0 -21
- package/dist/report/render/style-resolver.js +0 -50
- package/dist/report/render/template-index.d.ts +0 -24
- package/dist/report/render/template-index.js +0 -137
- package/dist/report/rules/evaluate.d.ts +0 -25
- package/dist/report/rules/evaluate.js +0 -82
- package/dist/report/rules/index.d.ts +0 -2
- package/dist/report/template.d.ts +0 -63
- package/dist/report/template.js +0 -83
- package/dist/report/types.d.ts +0 -85
- package/dist/types/report-designer.d.ts +0 -38
- package/dist/types/report-viewer.d.ts +0 -38
- package/src/components/report/__test__/binding-badge.test.ts +0 -92
- package/src/components/report/__test__/conditional-rules-helpers.test.ts +0 -88
- package/src/components/report/__test__/field-panel-helpers.test.ts +0 -65
- package/src/components/report/__test__/float-panel-position.test.ts +0 -45
- package/src/components/report/__test__/preview-grid-size.test.ts +0 -63
- package/src/components/report/__test__/report-designer.test.ts +0 -464
- package/src/components/report/__test__/report-viewer.test.ts +0 -357
- package/src/components/report/__test__/topology.test.ts +0 -143
- package/src/components/report/__test__/use-report-designer.test.ts +0 -697
- package/src/components/report/binding-badge.ts +0 -117
- package/src/components/report/designer/cell-coords.ts +0 -95
- package/src/components/report/designer/col-widths.ts +0 -56
- package/src/components/report/designer/conditional-rules/helpers.ts +0 -189
- package/src/components/report/designer/drop-highlight-overlay.vue +0 -75
- package/src/components/report/designer/float-panel.vue +0 -432
- package/src/components/report/designer/role.ts +0 -32
- package/src/components/report/designer/rule-preview.vue +0 -40
- package/src/components/report/designer/rule-row.vue +0 -252
- package/src/components/report/designer/rules-dialog.vue +0 -110
- package/src/components/report/designer/topology-overlay.vue +0 -128
- package/src/components/report/designer/topology.ts +0 -124
- package/src/components/report/designer/use-grid-overlay.ts +0 -114
- package/src/components/report/designer-hub.vue +0 -198
- package/src/components/report/field-panel-helpers.ts +0 -44
- package/src/components/report/field-panel.vue +0 -136
- package/src/components/report/filter-bar.vue +0 -77
- package/src/components/report/hub-connection-form.vue +0 -124
- package/src/components/report/hub-dataset-editor.vue +0 -387
- package/src/components/report/index.ts +0 -2
- package/src/components/report/preview-grid-size.ts +0 -33
- package/src/components/report/report-designer.vue +0 -421
- package/src/components/report/report-viewer.vue +0 -136
- package/src/components/report/style-designer.scss +0 -587
- package/src/components/report/style-hub.scss +0 -292
- package/src/components/report/style-viewer.scss +0 -104
- package/src/components/report/style.scss +0 -3
- package/src/components/report/style.ts +0 -20
- package/src/components/report/use-report-designer.ts +0 -602
- package/src/components/report/use-report-viewer.ts +0 -88
- package/src/report/__test__/binding.test.ts +0 -320
- package/src/report/__test__/connector.test.ts +0 -239
- package/src/report/__test__/export-xlsx.test.ts +0 -96
- package/src/report/__test__/filter-bar.test.ts +0 -58
- package/src/report/__test__/params.test.ts +0 -156
- package/src/report/__test__/render.test.ts +0 -856
- package/src/report/__test__/rules.test.ts +0 -131
- package/src/report/__test__/template.test.ts +0 -366
- package/src/report/binding.ts +0 -474
- package/src/report/connector.ts +0 -206
- package/src/report/export-xlsx.ts +0 -15
- package/src/report/filter-bar.ts +0 -36
- package/src/report/index.ts +0 -13
- package/src/report/params.ts +0 -96
- package/src/report/render/__test__/aggregate.test.ts +0 -16
- package/src/report/render/__test__/coordinate.test.ts +0 -405
- package/src/report/render/aggregate.ts +0 -261
- package/src/report/render/builder.ts +0 -211
- package/src/report/render/coordinate.ts +0 -1239
- package/src/report/render/index.ts +0 -30
- package/src/report/render/style-resolver.ts +0 -68
- package/src/report/render/template-index.ts +0 -200
- package/src/report/rules/evaluate.ts +0 -113
- package/src/report/rules/index.ts +0 -7
- package/src/report/template.ts +0 -145
- package/src/report/types.ts +0 -96
- package/src/types/report-designer.ts +0 -38
- package/src/types/report-viewer.ts +0 -37
|
@@ -1,1239 +0,0 @@
|
|
|
1
|
-
import type { CellAddress, CellRange } from '@veltra/sheet-core'
|
|
2
|
-
import { cellKey, createRange } from '@veltra/sheet-core'
|
|
3
|
-
|
|
4
|
-
import type { DatasetRecords, ReportBinding } from '../types'
|
|
5
|
-
import { sameAddress, type TemplateIndex } from './template-index'
|
|
6
|
-
|
|
7
|
-
/** 扩展实例:携带祖先父格过滤条件 */
|
|
8
|
-
export interface ExpansionInstance {
|
|
9
|
-
index: number
|
|
10
|
-
/** `group` 聚合时的分组值 */
|
|
11
|
-
value?: unknown
|
|
12
|
-
/** 同数据集内生效的字段过滤 */
|
|
13
|
-
filter: Record<string, unknown>
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/** 逻辑格 → 物理区间映射项(纯几何,不含取值) */
|
|
17
|
-
export interface PhysicalPlacement {
|
|
18
|
-
logical: CellAddress
|
|
19
|
-
physical: CellRange
|
|
20
|
-
rowPath: readonly number[]
|
|
21
|
-
colPath: readonly number[]
|
|
22
|
-
mergeSpan: boolean
|
|
23
|
-
binding?: ReportBinding
|
|
24
|
-
/** `list` 明细实例下标;非 list 时为 0 */
|
|
25
|
-
listIndex: number
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** 扩展坐标系计算结果 */
|
|
29
|
-
export interface ExpansionLayout {
|
|
30
|
-
rowCount: number
|
|
31
|
-
colCount: number
|
|
32
|
-
placements: PhysicalPlacement[]
|
|
33
|
-
/** 模板合并区域映射到物理网格后的区间 */
|
|
34
|
-
mappedMerges: CellRange[]
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function isRowExpanding(binding: ReportBinding): boolean {
|
|
38
|
-
return (
|
|
39
|
-
binding.expand === 'down' && (binding.aggregate === 'group' || binding.aggregate === 'list')
|
|
40
|
-
)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function isColExpanding(binding: ReportBinding): boolean {
|
|
44
|
-
return (
|
|
45
|
-
binding.expand === 'right' && (binding.aggregate === 'group' || binding.aggregate === 'list')
|
|
46
|
-
)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function mergeSpanEnabled(binding: ReportBinding | undefined): boolean {
|
|
50
|
-
return binding?.mergeSpan !== false
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function filterRows(
|
|
54
|
-
rows: Record<string, unknown>[],
|
|
55
|
-
filter: Record<string, unknown>
|
|
56
|
-
): Record<string, unknown>[] {
|
|
57
|
-
if (Object.keys(filter).length === 0) return rows
|
|
58
|
-
return rows.filter((row) =>
|
|
59
|
-
Object.entries(filter).every(([field, value]) => row[field] === value)
|
|
60
|
-
)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function distinctFieldValues(rows: Record<string, unknown>[], field: string): unknown[] {
|
|
64
|
-
const seen = new Set<string>()
|
|
65
|
-
const values: unknown[] = []
|
|
66
|
-
for (const row of rows) {
|
|
67
|
-
const key = JSON.stringify(row[field])
|
|
68
|
-
if (seen.has(key)) continue
|
|
69
|
-
seen.add(key)
|
|
70
|
-
values.push(row[field])
|
|
71
|
-
}
|
|
72
|
-
return values
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function sortValues(values: unknown[], sort: ReportBinding['sort']): unknown[] {
|
|
76
|
-
if (sort === 'asc') return [...values].sort((a, b) => String(a).localeCompare(String(b), 'zh-CN'))
|
|
77
|
-
if (sort === 'desc')
|
|
78
|
-
return [...values].sort((a, b) => String(b).localeCompare(String(a), 'zh-CN'))
|
|
79
|
-
return values
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function singleCellRange(row: number, col: number): CellRange {
|
|
83
|
-
return { start: { row, col }, end: { row, col } }
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function rangeRows(range: CellRange): number {
|
|
87
|
-
return range.end.row - range.start.row + 1
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function rangeCols(range: CellRange): number {
|
|
91
|
-
return range.end.col - range.start.col + 1
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function mergeFilters(
|
|
95
|
-
rowFilter: Record<string, unknown>,
|
|
96
|
-
colFilter: Record<string, unknown>
|
|
97
|
-
): Record<string, unknown> {
|
|
98
|
-
return { ...rowFilter, ...colFilter }
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function parentFilterForBinding(
|
|
102
|
-
binding: ReportBinding,
|
|
103
|
-
index: TemplateIndex,
|
|
104
|
-
rowFilter: Record<string, unknown>,
|
|
105
|
-
colFilter: Record<string, unknown>
|
|
106
|
-
): Record<string, unknown> {
|
|
107
|
-
const filter: Record<string, unknown> = {}
|
|
108
|
-
if (binding.rowParent) {
|
|
109
|
-
const parent = index.bindingAt(binding.rowParent)
|
|
110
|
-
if (parent && parent.dataset === binding.dataset) Object.assign(filter, rowFilter)
|
|
111
|
-
}
|
|
112
|
-
if (binding.colParent) {
|
|
113
|
-
const parent = index.bindingAt(binding.colParent)
|
|
114
|
-
if (parent && parent.dataset === binding.dataset) Object.assign(filter, colFilter)
|
|
115
|
-
}
|
|
116
|
-
return filter
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/** 枚举扩展格实例 */
|
|
120
|
-
export function enumerateExpansionInstances(
|
|
121
|
-
binding: ReportBinding,
|
|
122
|
-
data: DatasetRecords,
|
|
123
|
-
filter: Record<string, unknown>
|
|
124
|
-
): ExpansionInstance[] {
|
|
125
|
-
const rows = filterRows(data[binding.dataset] ?? [], filter)
|
|
126
|
-
|
|
127
|
-
if (!isRowExpanding(binding) && !isColExpanding(binding)) {
|
|
128
|
-
return [{ index: 0, filter }]
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (binding.aggregate === 'group') {
|
|
132
|
-
const values = sortValues(distinctFieldValues(rows, binding.field), binding.sort)
|
|
133
|
-
return values.map((value, index) => ({
|
|
134
|
-
index,
|
|
135
|
-
value,
|
|
136
|
-
filter: { ...filter, [binding.field]: value }
|
|
137
|
-
}))
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (binding.aggregate === 'list') {
|
|
141
|
-
return rows.map((_, index) => ({ index, filter }))
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return [{ index: 0, filter }]
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function isRowSubtreeRoot(binding: ReportBinding, index: TemplateIndex): boolean {
|
|
148
|
-
if (!isRowExpanding(binding)) return false
|
|
149
|
-
if (!binding.rowParent) return true
|
|
150
|
-
const parent = index.bindingAt(binding.rowParent)
|
|
151
|
-
return !parent || !isRowExpanding(parent)
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function isColSubtreeRoot(binding: ReportBinding, index: TemplateIndex): boolean {
|
|
155
|
-
if (!isColExpanding(binding)) return false
|
|
156
|
-
if (!binding.colParent) return true
|
|
157
|
-
const parent = index.bindingAt(binding.colParent)
|
|
158
|
-
return !parent || !isColExpanding(parent)
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function isNestedRowExpander(binding: ReportBinding, index: TemplateIndex): boolean {
|
|
162
|
-
return (
|
|
163
|
-
isRowExpanding(binding) &&
|
|
164
|
-
binding.aggregate === 'group' &&
|
|
165
|
-
!!binding.rowParent &&
|
|
166
|
-
!isRowSubtreeRoot(binding, index)
|
|
167
|
-
)
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function isNestedColExpander(binding: ReportBinding, index: TemplateIndex): boolean {
|
|
171
|
-
return (
|
|
172
|
-
isColExpanding(binding) &&
|
|
173
|
-
binding.aggregate === 'group' &&
|
|
174
|
-
!!binding.colParent &&
|
|
175
|
-
!isColSubtreeRoot(binding, index)
|
|
176
|
-
)
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function rowSubtreeRows(rootAddr: CellAddress, index: TemplateIndex): number[] {
|
|
180
|
-
let maxRow = rootAddr.row
|
|
181
|
-
const walk = (addr: CellAddress): void => {
|
|
182
|
-
maxRow = Math.max(maxRow, addr.row)
|
|
183
|
-
for (const child of index.rowChildren.get(cellKey(addr)) ?? []) walk(child)
|
|
184
|
-
}
|
|
185
|
-
walk(rootAddr)
|
|
186
|
-
return index.logicalRows.filter((row) => row >= rootAddr.row && row <= maxRow)
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function colSubtreeCols(rootAddr: CellAddress, index: TemplateIndex): number[] {
|
|
190
|
-
let maxCol = rootAddr.col
|
|
191
|
-
const walk = (addr: CellAddress): void => {
|
|
192
|
-
maxCol = Math.max(maxCol, addr.col)
|
|
193
|
-
for (const child of index.colChildren.get(cellKey(addr)) ?? []) walk(child)
|
|
194
|
-
}
|
|
195
|
-
walk(rootAddr)
|
|
196
|
-
return index.logicalCols.filter((col) => col >= rootAddr.col && col <= maxCol)
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function columnsOnRow(index: TemplateIndex, row: number): number[] {
|
|
200
|
-
const cols = new Set<number>()
|
|
201
|
-
for (const entry of index.bindingsOnRow(row)) cols.add(entry.addr.col)
|
|
202
|
-
for (const cell of index.staticCells.values()) {
|
|
203
|
-
if (cell.row === row) cols.add(cell.col)
|
|
204
|
-
}
|
|
205
|
-
return [...cols].sort((a, b) => a - b)
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function findRowSubtreeRootOnRow(index: TemplateIndex, row: number): CellAddress | null {
|
|
209
|
-
for (const entry of index.bindingsOnRow(row)) {
|
|
210
|
-
if (isRowSubtreeRoot(entry.binding, index)) return entry.addr
|
|
211
|
-
}
|
|
212
|
-
return null
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/** 扩展带终止行:新的纵向子树根,或无父格的汇总行(如总计) */
|
|
216
|
-
function isExpansionBandTerminatorRow(index: TemplateIndex, row: number, rootRow: number): boolean {
|
|
217
|
-
if (row <= rootRow) return false
|
|
218
|
-
const rowRoot = findRowSubtreeRootOnRow(index, row)
|
|
219
|
-
if (rowRoot && rowRoot.row !== rootRow) return true
|
|
220
|
-
return !isCompanionRow(index, row)
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/** 扩展带内随行:全部绑定均带 rowParent(纯静态行不算带内行) */
|
|
224
|
-
function isCompanionRow(index: TemplateIndex, row: number): boolean {
|
|
225
|
-
const bindings = index.bindingsOnRow(row)
|
|
226
|
-
if (bindings.length === 0) return false
|
|
227
|
-
return bindings.every(({ binding }) => binding.rowParent !== undefined)
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/** 纵向扩展带覆盖的模板行:子树行 + 紧随其后的带内汇总行 */
|
|
231
|
-
function expansionBandRows(rootAddr: CellAddress, index: TemplateIndex): number[] {
|
|
232
|
-
const subtreeRows = rowSubtreeRows(rootAddr, index)
|
|
233
|
-
const maxSubtreeRow = Math.max(...subtreeRows)
|
|
234
|
-
const rows = new Set(subtreeRows)
|
|
235
|
-
for (const row of index.logicalRows) {
|
|
236
|
-
if (row <= maxSubtreeRow) continue
|
|
237
|
-
if (isExpansionBandTerminatorRow(index, row, rootAddr.row)) break
|
|
238
|
-
if (!isCompanionRow(index, row)) break
|
|
239
|
-
rows.add(row)
|
|
240
|
-
}
|
|
241
|
-
return index.logicalRows.filter((row) => rows.has(row))
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function findNestedColChild(rootAddr: CellAddress, index: TemplateIndex): CellAddress | null {
|
|
245
|
-
for (const child of index.colChildren.get(cellKey(rootAddr)) ?? []) {
|
|
246
|
-
const binding = index.bindingAt(child)
|
|
247
|
-
if (binding && isColExpanding(binding) && child.row > rootAddr.row) return child
|
|
248
|
-
}
|
|
249
|
-
return null
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
interface LayoutContext {
|
|
253
|
-
rowFilter: Record<string, unknown>
|
|
254
|
-
colFilter: Record<string, unknown>
|
|
255
|
-
rowPath: number[]
|
|
256
|
-
colPath: number[]
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
interface SegmentResult {
|
|
260
|
-
rowSpan: number
|
|
261
|
-
colSpan: number
|
|
262
|
-
placements: PhysicalPlacement[]
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
class CoordinateEngine {
|
|
266
|
-
constructor(
|
|
267
|
-
private readonly index: TemplateIndex,
|
|
268
|
-
private readonly data: DatasetRecords
|
|
269
|
-
) {}
|
|
270
|
-
|
|
271
|
-
compute(): ExpansionLayout {
|
|
272
|
-
const placements: PhysicalPlacement[] = []
|
|
273
|
-
const consumedRows = new Set<number>()
|
|
274
|
-
let physRow = 0
|
|
275
|
-
let maxCol = 0
|
|
276
|
-
|
|
277
|
-
for (const logicalRow of this.index.logicalRows) {
|
|
278
|
-
if (consumedRows.has(logicalRow)) continue
|
|
279
|
-
|
|
280
|
-
const rowRoot = findRowSubtreeRootOnRow(this.index, logicalRow)
|
|
281
|
-
if (rowRoot) {
|
|
282
|
-
const result = this.layoutRowSubtree(rowRoot, emptyCtx(), physRow, 0)
|
|
283
|
-
placements.push(...result.placements)
|
|
284
|
-
physRow += result.rowSpan
|
|
285
|
-
maxCol = Math.max(maxCol, result.colSpan)
|
|
286
|
-
for (const row of expansionBandRows(rowRoot, this.index)) consumedRows.add(row)
|
|
287
|
-
continue
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const result = this.layoutStandaloneRow(logicalRow, emptyCtx(), physRow, 0)
|
|
291
|
-
placements.push(...result.placements)
|
|
292
|
-
physRow += result.rowSpan
|
|
293
|
-
maxCol = Math.max(maxCol, result.colSpan)
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
return {
|
|
297
|
-
rowCount: physRow,
|
|
298
|
-
colCount: Math.max(maxCol, this.baseColCount()),
|
|
299
|
-
placements,
|
|
300
|
-
mappedMerges: mapTemplateMerges(this.index, placements)
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
private baseColCount(): number {
|
|
305
|
-
if (this.index.logicalCols.length === 0) return 0
|
|
306
|
-
return Math.max(...this.index.logicalCols) + 1
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
private layoutRowSubtree(
|
|
310
|
-
rootAddr: CellAddress,
|
|
311
|
-
ctx: LayoutContext,
|
|
312
|
-
physRowStart: number,
|
|
313
|
-
physColStart: number
|
|
314
|
-
): SegmentResult {
|
|
315
|
-
const rootBinding = this.index.bindingAt(rootAddr)!
|
|
316
|
-
const parentFilter = parentFilterForBinding(
|
|
317
|
-
rootBinding,
|
|
318
|
-
this.index,
|
|
319
|
-
ctx.rowFilter,
|
|
320
|
-
ctx.colFilter
|
|
321
|
-
)
|
|
322
|
-
const instances = enumerateExpansionInstances(
|
|
323
|
-
rootBinding,
|
|
324
|
-
this.data,
|
|
325
|
-
mergeFilters(parentFilter, ctx.rowFilter)
|
|
326
|
-
)
|
|
327
|
-
const bandRows = expansionBandRows(rootAddr, this.index)
|
|
328
|
-
|
|
329
|
-
if (instances.length === 0) return { rowSpan: 0, colSpan: 0, placements: [] }
|
|
330
|
-
|
|
331
|
-
if (rootBinding.aggregate === 'list') {
|
|
332
|
-
const listCtx: LayoutContext = {
|
|
333
|
-
rowFilter: mergeFilters(parentFilter, ctx.rowFilter),
|
|
334
|
-
colFilter: ctx.colFilter,
|
|
335
|
-
rowPath: ctx.rowPath,
|
|
336
|
-
colPath: ctx.colPath
|
|
337
|
-
}
|
|
338
|
-
const rowResult = this.layoutTemplateRow(
|
|
339
|
-
rootAddr.row,
|
|
340
|
-
listCtx,
|
|
341
|
-
physRowStart,
|
|
342
|
-
physColStart,
|
|
343
|
-
rootAddr,
|
|
344
|
-
instances.length
|
|
345
|
-
)
|
|
346
|
-
const placements = [...rowResult.placements]
|
|
347
|
-
let totalRowSpan = instances.length
|
|
348
|
-
|
|
349
|
-
for (const templateRow of bandRows) {
|
|
350
|
-
if (templateRow === rootAddr.row) continue
|
|
351
|
-
const companionResult = this.layoutTemplateRow(
|
|
352
|
-
templateRow,
|
|
353
|
-
listCtx,
|
|
354
|
-
physRowStart + totalRowSpan,
|
|
355
|
-
physColStart,
|
|
356
|
-
null,
|
|
357
|
-
1
|
|
358
|
-
)
|
|
359
|
-
placements.push(...companionResult.placements)
|
|
360
|
-
totalRowSpan += companionResult.rowSpan
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
return { rowSpan: totalRowSpan, colSpan: rowResult.colSpan, placements }
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
const placements: PhysicalPlacement[] = []
|
|
367
|
-
let totalRowSpan = 0
|
|
368
|
-
let maxColSpan = 0
|
|
369
|
-
|
|
370
|
-
for (const instance of instances) {
|
|
371
|
-
const instanceCtx: LayoutContext = {
|
|
372
|
-
rowFilter: instance.filter,
|
|
373
|
-
colFilter: ctx.colFilter,
|
|
374
|
-
rowPath: [...ctx.rowPath, instance.index],
|
|
375
|
-
colPath: ctx.colPath
|
|
376
|
-
}
|
|
377
|
-
const instanceHeight = this.measureSubtreeInstanceHeight(bandRows, rootAddr, instanceCtx)
|
|
378
|
-
const groupMergeHeight = this.measureTemplateRowHeight(rootAddr.row, instanceCtx, null)
|
|
379
|
-
|
|
380
|
-
if (this.bandHasMultiRowList(bandRows, instanceCtx)) {
|
|
381
|
-
const bandResult = this.layoutMultiRowListBand(
|
|
382
|
-
bandRows,
|
|
383
|
-
rootAddr,
|
|
384
|
-
instanceCtx,
|
|
385
|
-
physRowStart + totalRowSpan,
|
|
386
|
-
physColStart,
|
|
387
|
-
groupMergeHeight
|
|
388
|
-
)
|
|
389
|
-
placements.push(...bandResult.placements)
|
|
390
|
-
maxColSpan = Math.max(maxColSpan, bandResult.colSpan)
|
|
391
|
-
totalRowSpan += instanceHeight
|
|
392
|
-
continue
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
let rowOffset = 0
|
|
396
|
-
|
|
397
|
-
for (const templateRow of bandRows) {
|
|
398
|
-
const rowHeight = this.measureTemplateRowHeight(templateRow, instanceCtx, rootAddr)
|
|
399
|
-
const mergeHeight =
|
|
400
|
-
templateRow === rootAddr.row ? groupMergeHeight : instanceHeight - rowOffset
|
|
401
|
-
const rowResult = this.layoutTemplateRow(
|
|
402
|
-
templateRow,
|
|
403
|
-
instanceCtx,
|
|
404
|
-
physRowStart + totalRowSpan + rowOffset,
|
|
405
|
-
physColStart,
|
|
406
|
-
rootAddr,
|
|
407
|
-
mergeHeight
|
|
408
|
-
)
|
|
409
|
-
placements.push(...rowResult.placements)
|
|
410
|
-
rowOffset += rowHeight
|
|
411
|
-
maxColSpan = Math.max(maxColSpan, rowResult.colSpan)
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
totalRowSpan += instanceHeight
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
return { rowSpan: totalRowSpan, colSpan: maxColSpan, placements }
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
private measureSubtreeInstanceHeight(
|
|
421
|
-
bandRows: number[],
|
|
422
|
-
rootAddr: CellAddress,
|
|
423
|
-
ctx: LayoutContext
|
|
424
|
-
): number {
|
|
425
|
-
let listDrivenHeight = 0
|
|
426
|
-
let companionHeight = 0
|
|
427
|
-
|
|
428
|
-
for (const row of bandRows) {
|
|
429
|
-
const listCount = this.listInstanceCountOnRow(row, ctx)
|
|
430
|
-
if (listCount > 0) {
|
|
431
|
-
listDrivenHeight = Math.max(listDrivenHeight, listCount)
|
|
432
|
-
continue
|
|
433
|
-
}
|
|
434
|
-
if (row === rootAddr.row) continue
|
|
435
|
-
companionHeight += this.measureTemplateRowHeight(row, ctx, rootAddr)
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
if (listDrivenHeight === 0) {
|
|
439
|
-
return this.measureTemplateRowHeight(rootAddr.row, ctx, null)
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
return listDrivenHeight + companionHeight
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
private bandHasMultiRowList(bandRows: number[], ctx: LayoutContext): boolean {
|
|
446
|
-
let count = 0
|
|
447
|
-
for (const row of bandRows) {
|
|
448
|
-
if (this.listInstanceCountOnRow(row, ctx) > 0) count++
|
|
449
|
-
if (count > 1) return true
|
|
450
|
-
}
|
|
451
|
-
return false
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
private layoutMultiRowListBand(
|
|
455
|
-
bandRows: number[],
|
|
456
|
-
rootAddr: CellAddress,
|
|
457
|
-
ctx: LayoutContext,
|
|
458
|
-
physRowStart: number,
|
|
459
|
-
physColStart: number,
|
|
460
|
-
groupMergeHeight: number
|
|
461
|
-
): SegmentResult {
|
|
462
|
-
const listRows = bandRows.filter((row) => this.listInstanceCountOnRow(row, ctx) > 0)
|
|
463
|
-
const companionRows = bandRows.filter((row) => !listRows.includes(row))
|
|
464
|
-
const listCount = Math.max(...listRows.map((row) => this.listInstanceCountOnRow(row, ctx)), 1)
|
|
465
|
-
|
|
466
|
-
const placements: PhysicalPlacement[] = []
|
|
467
|
-
let maxColSpan = 0
|
|
468
|
-
|
|
469
|
-
for (let listIndex = 0; listIndex < listCount; listIndex++) {
|
|
470
|
-
for (const templateRow of listRows) {
|
|
471
|
-
const rowResult = this.layoutTemplateRow(
|
|
472
|
-
templateRow,
|
|
473
|
-
ctx,
|
|
474
|
-
physRowStart + listIndex,
|
|
475
|
-
physColStart,
|
|
476
|
-
templateRow === rootAddr.row ? rootAddr : null,
|
|
477
|
-
groupMergeHeight,
|
|
478
|
-
false,
|
|
479
|
-
listIndex,
|
|
480
|
-
listCount
|
|
481
|
-
)
|
|
482
|
-
placements.push(...rowResult.placements)
|
|
483
|
-
maxColSpan = Math.max(maxColSpan, rowResult.colSpan)
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
let rowOffset = listCount
|
|
488
|
-
for (const templateRow of companionRows) {
|
|
489
|
-
const rowResult = this.layoutTemplateRow(
|
|
490
|
-
templateRow,
|
|
491
|
-
ctx,
|
|
492
|
-
physRowStart + rowOffset,
|
|
493
|
-
physColStart,
|
|
494
|
-
null,
|
|
495
|
-
1
|
|
496
|
-
)
|
|
497
|
-
placements.push(...rowResult.placements)
|
|
498
|
-
maxColSpan = Math.max(maxColSpan, rowResult.colSpan)
|
|
499
|
-
rowOffset += rowResult.rowSpan
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
return { rowSpan: rowOffset, colSpan: maxColSpan, placements }
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
private measureTemplateRowHeight(
|
|
506
|
-
templateRow: number,
|
|
507
|
-
ctx: LayoutContext,
|
|
508
|
-
rootAddr: CellAddress | null
|
|
509
|
-
): number {
|
|
510
|
-
for (const entry of this.index.bindingsOnRow(templateRow)) {
|
|
511
|
-
if (isNestedRowExpander(entry.binding, this.index)) {
|
|
512
|
-
return this.measureNestedRowBandHeight(entry.addr, entry.binding, templateRow, ctx)
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
const listCount = this.listInstanceCountOnRow(templateRow, ctx)
|
|
517
|
-
if (listCount > 0) return listCount
|
|
518
|
-
|
|
519
|
-
if (rootAddr && templateRow === rootAddr.row) return 0
|
|
520
|
-
return 1
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
private measureNestedRowBandHeight(
|
|
524
|
-
groupAddr: CellAddress,
|
|
525
|
-
groupBinding: ReportBinding,
|
|
526
|
-
templateRow: number,
|
|
527
|
-
ctx: LayoutContext
|
|
528
|
-
): number {
|
|
529
|
-
const parentFilter = parentFilterForBinding(
|
|
530
|
-
groupBinding,
|
|
531
|
-
this.index,
|
|
532
|
-
ctx.rowFilter,
|
|
533
|
-
ctx.colFilter
|
|
534
|
-
)
|
|
535
|
-
const instances = enumerateExpansionInstances(
|
|
536
|
-
groupBinding,
|
|
537
|
-
this.data,
|
|
538
|
-
mergeFilters(parentFilter, ctx.rowFilter)
|
|
539
|
-
)
|
|
540
|
-
if (instances.length === 0) return 0
|
|
541
|
-
|
|
542
|
-
let total = 0
|
|
543
|
-
for (const instance of instances) {
|
|
544
|
-
const instanceCtx: LayoutContext = {
|
|
545
|
-
...ctx,
|
|
546
|
-
rowFilter: instance.filter,
|
|
547
|
-
rowPath: [...ctx.rowPath, instance.index]
|
|
548
|
-
}
|
|
549
|
-
total += this.listInstanceCountOnRow(templateRow, instanceCtx) || 1
|
|
550
|
-
}
|
|
551
|
-
return total
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
private layoutStandaloneRow(
|
|
555
|
-
templateRow: number,
|
|
556
|
-
ctx: LayoutContext,
|
|
557
|
-
physRowStart: number,
|
|
558
|
-
physColStart: number
|
|
559
|
-
): SegmentResult {
|
|
560
|
-
return this.layoutTemplateRow(
|
|
561
|
-
templateRow,
|
|
562
|
-
ctx,
|
|
563
|
-
physRowStart,
|
|
564
|
-
physColStart,
|
|
565
|
-
null,
|
|
566
|
-
1,
|
|
567
|
-
!this.rowUsesPackedColumns(templateRow)
|
|
568
|
-
)
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
private rowUsesPackedColumns(templateRow: number): boolean {
|
|
572
|
-
for (const entry of this.index.bindingsOnRow(templateRow)) {
|
|
573
|
-
if (isColSubtreeRoot(entry.binding, this.index)) return true
|
|
574
|
-
if (isNestedColExpander(entry.binding, this.index)) return true
|
|
575
|
-
if (entry.binding.colParent) {
|
|
576
|
-
const parent = this.index.bindingAt(entry.binding.colParent)
|
|
577
|
-
if (parent && isColExpanding(parent)) return true
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
return false
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
private layoutTemplateRow(
|
|
584
|
-
templateRow: number,
|
|
585
|
-
ctx: LayoutContext,
|
|
586
|
-
physRowStart: number,
|
|
587
|
-
physColStart: number,
|
|
588
|
-
rowSubtreeRoot: CellAddress | null,
|
|
589
|
-
groupMergeHeight: number,
|
|
590
|
-
preserveLogicalCols = false,
|
|
591
|
-
fixedListIndex?: number,
|
|
592
|
-
mergeListCount?: number
|
|
593
|
-
): SegmentResult {
|
|
594
|
-
for (const entry of this.index.bindingsOnRow(templateRow)) {
|
|
595
|
-
if (isNestedRowExpander(entry.binding, this.index)) {
|
|
596
|
-
return this.layoutNestedRowBand(
|
|
597
|
-
templateRow,
|
|
598
|
-
entry.addr,
|
|
599
|
-
entry.binding,
|
|
600
|
-
ctx,
|
|
601
|
-
physRowStart,
|
|
602
|
-
physColStart
|
|
603
|
-
)
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
const cols = columnsOnRow(this.index, templateRow)
|
|
608
|
-
const placements: PhysicalPlacement[] = []
|
|
609
|
-
let physCol = physColStart
|
|
610
|
-
const packedCols = !preserveLogicalCols && this.rowUsesPackedColumns(templateRow)
|
|
611
|
-
|
|
612
|
-
const listCount = this.listInstanceCountOnRow(templateRow, ctx)
|
|
613
|
-
const rowSpan = fixedListIndex !== undefined ? 1 : Math.max(listCount, 1)
|
|
614
|
-
const leadingGap = cols.length > 0 && cols[0]! > physColStart
|
|
615
|
-
|
|
616
|
-
for (const col of cols) {
|
|
617
|
-
const addr = { row: templateRow, col }
|
|
618
|
-
const binding = this.index.bindingAt(addr)
|
|
619
|
-
const targetCol = packedCols && !leadingGap ? physCol : col
|
|
620
|
-
|
|
621
|
-
if (binding && isColSubtreeRoot(binding, this.index)) {
|
|
622
|
-
const colResult = this.layoutColSubtree(addr, ctx, physRowStart, targetCol)
|
|
623
|
-
placements.push(...colResult.placements)
|
|
624
|
-
if (packedCols) {
|
|
625
|
-
physCol = leadingGap ? targetCol + colResult.colSpan : physCol + colResult.colSpan
|
|
626
|
-
}
|
|
627
|
-
continue
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
if (binding && isNestedColExpander(binding, this.index)) {
|
|
631
|
-
const colResult = this.layoutNestedColBand(addr, binding, ctx, physRowStart, targetCol)
|
|
632
|
-
placements.push(...colResult.placements)
|
|
633
|
-
if (packedCols) {
|
|
634
|
-
physCol = leadingGap ? targetCol + colResult.colSpan : physCol + colResult.colSpan
|
|
635
|
-
}
|
|
636
|
-
continue
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
if (binding && this.isInlineColChild(templateRow, col)) continue
|
|
640
|
-
|
|
641
|
-
if (binding && binding.colParent && !isColExpanding(binding)) {
|
|
642
|
-
const colResult = this.layoutColParentAlignedCell(
|
|
643
|
-
addr,
|
|
644
|
-
binding,
|
|
645
|
-
ctx,
|
|
646
|
-
physRowStart,
|
|
647
|
-
targetCol,
|
|
648
|
-
rowSpan
|
|
649
|
-
)
|
|
650
|
-
placements.push(...colResult.placements)
|
|
651
|
-
if (packedCols) {
|
|
652
|
-
physCol = leadingGap ? targetCol + colResult.colSpan : physCol + colResult.colSpan
|
|
653
|
-
}
|
|
654
|
-
continue
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
if (binding) {
|
|
658
|
-
const cellResult = this.layoutBindingCell(
|
|
659
|
-
addr,
|
|
660
|
-
binding,
|
|
661
|
-
ctx,
|
|
662
|
-
physRowStart,
|
|
663
|
-
targetCol,
|
|
664
|
-
rowSpan,
|
|
665
|
-
rowSubtreeRoot,
|
|
666
|
-
groupMergeHeight,
|
|
667
|
-
fixedListIndex,
|
|
668
|
-
mergeListCount
|
|
669
|
-
)
|
|
670
|
-
placements.push(...cellResult.placements)
|
|
671
|
-
if (packedCols) {
|
|
672
|
-
physCol = leadingGap ? targetCol + cellResult.colSpan : physCol + cellResult.colSpan
|
|
673
|
-
}
|
|
674
|
-
continue
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
const staticCell = this.index.cellAt(addr)
|
|
678
|
-
if (staticCell) {
|
|
679
|
-
if (rowSpan > 1) {
|
|
680
|
-
for (let i = 0; i < rowSpan; i++) {
|
|
681
|
-
placements.push(
|
|
682
|
-
this.makePlacement(
|
|
683
|
-
addr,
|
|
684
|
-
singleCellRange(physRowStart + i, targetCol),
|
|
685
|
-
ctx,
|
|
686
|
-
undefined,
|
|
687
|
-
i,
|
|
688
|
-
false
|
|
689
|
-
)
|
|
690
|
-
)
|
|
691
|
-
}
|
|
692
|
-
} else {
|
|
693
|
-
placements.push(
|
|
694
|
-
this.makePlacement(
|
|
695
|
-
addr,
|
|
696
|
-
singleCellRange(physRowStart, targetCol),
|
|
697
|
-
ctx,
|
|
698
|
-
undefined,
|
|
699
|
-
0,
|
|
700
|
-
false
|
|
701
|
-
)
|
|
702
|
-
)
|
|
703
|
-
}
|
|
704
|
-
if (packedCols) {
|
|
705
|
-
physCol = leadingGap ? targetCol + 1 : physCol + 1
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
const effectiveColSpan = packedCols
|
|
711
|
-
? leadingGap && cols.length > 0
|
|
712
|
-
? Math.max(...cols) + 1 - physColStart
|
|
713
|
-
: physCol - physColStart
|
|
714
|
-
: cols.length > 0
|
|
715
|
-
? Math.max(...cols) + 1 - physColStart
|
|
716
|
-
: 0
|
|
717
|
-
|
|
718
|
-
const effectiveRowSpan =
|
|
719
|
-
rowSubtreeRoot && templateRow === rowSubtreeRoot.row && listCount === 0 ? 0 : rowSpan
|
|
720
|
-
return { rowSpan: effectiveRowSpan, colSpan: effectiveColSpan, placements }
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
private layoutNestedRowBand(
|
|
724
|
-
templateRow: number,
|
|
725
|
-
groupAddr: CellAddress,
|
|
726
|
-
groupBinding: ReportBinding,
|
|
727
|
-
ctx: LayoutContext,
|
|
728
|
-
physRowStart: number,
|
|
729
|
-
physColStart: number
|
|
730
|
-
): SegmentResult {
|
|
731
|
-
const parentFilter = parentFilterForBinding(
|
|
732
|
-
groupBinding,
|
|
733
|
-
this.index,
|
|
734
|
-
ctx.rowFilter,
|
|
735
|
-
ctx.colFilter
|
|
736
|
-
)
|
|
737
|
-
const instances = enumerateExpansionInstances(
|
|
738
|
-
groupBinding,
|
|
739
|
-
this.data,
|
|
740
|
-
mergeFilters(parentFilter, ctx.rowFilter)
|
|
741
|
-
)
|
|
742
|
-
if (instances.length === 0) return { rowSpan: 0, colSpan: 0, placements: [] }
|
|
743
|
-
|
|
744
|
-
const placements: PhysicalPlacement[] = []
|
|
745
|
-
let rowOffset = 0
|
|
746
|
-
let maxColSpan = 0
|
|
747
|
-
|
|
748
|
-
const colOffsets = this.rowLogicalColOffsets(templateRow, physColStart)
|
|
749
|
-
|
|
750
|
-
for (const instance of instances) {
|
|
751
|
-
const instanceCtx: LayoutContext = {
|
|
752
|
-
...ctx,
|
|
753
|
-
rowFilter: instance.filter,
|
|
754
|
-
rowPath: [...ctx.rowPath, instance.index]
|
|
755
|
-
}
|
|
756
|
-
const detailCount = this.listInstanceCountOnRow(templateRow, instanceCtx) || 1
|
|
757
|
-
|
|
758
|
-
if (mergeSpanEnabled(groupBinding)) {
|
|
759
|
-
placements.push(
|
|
760
|
-
this.makePlacement(
|
|
761
|
-
groupAddr,
|
|
762
|
-
createRange(
|
|
763
|
-
{ row: physRowStart + rowOffset, col: physColStart },
|
|
764
|
-
{ row: physRowStart + rowOffset + detailCount - 1, col: physColStart }
|
|
765
|
-
),
|
|
766
|
-
instanceCtx,
|
|
767
|
-
groupBinding,
|
|
768
|
-
0,
|
|
769
|
-
true
|
|
770
|
-
)
|
|
771
|
-
)
|
|
772
|
-
} else {
|
|
773
|
-
for (let i = 0; i < detailCount; i++) {
|
|
774
|
-
placements.push(
|
|
775
|
-
this.makePlacement(
|
|
776
|
-
groupAddr,
|
|
777
|
-
singleCellRange(physRowStart + rowOffset + i, physColStart),
|
|
778
|
-
instanceCtx,
|
|
779
|
-
groupBinding,
|
|
780
|
-
i,
|
|
781
|
-
false
|
|
782
|
-
)
|
|
783
|
-
)
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
for (const entry of this.index.bindingsOnRow(templateRow)) {
|
|
788
|
-
if (sameAddress(entry.addr, groupAddr)) continue
|
|
789
|
-
if (!isRowExpanding(entry.binding) || entry.binding.aggregate !== 'list') continue
|
|
790
|
-
for (let i = 0; i < detailCount; i++) {
|
|
791
|
-
placements.push(
|
|
792
|
-
this.makePlacement(
|
|
793
|
-
entry.addr,
|
|
794
|
-
singleCellRange(physRowStart + rowOffset + i, colOffsets.get(entry.addr.col)!),
|
|
795
|
-
instanceCtx,
|
|
796
|
-
entry.binding,
|
|
797
|
-
i,
|
|
798
|
-
false
|
|
799
|
-
)
|
|
800
|
-
)
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
for (const col of columnsOnRow(this.index, templateRow)) {
|
|
805
|
-
if (this.index.bindingAt({ row: templateRow, col })) continue
|
|
806
|
-
const staticCell = this.index.cellAt({ row: templateRow, col })
|
|
807
|
-
if (!staticCell) continue
|
|
808
|
-
for (let i = 0; i < detailCount; i++) {
|
|
809
|
-
placements.push(
|
|
810
|
-
this.makePlacement(
|
|
811
|
-
{ row: templateRow, col },
|
|
812
|
-
singleCellRange(physRowStart + rowOffset + i, colOffsets.get(col)!),
|
|
813
|
-
instanceCtx,
|
|
814
|
-
undefined,
|
|
815
|
-
i,
|
|
816
|
-
false
|
|
817
|
-
)
|
|
818
|
-
)
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
maxColSpan = Math.max(maxColSpan, colOffsets.size)
|
|
823
|
-
rowOffset += detailCount
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
return { rowSpan: rowOffset, colSpan: maxColSpan, placements }
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
private layoutBindingCell(
|
|
830
|
-
addr: CellAddress,
|
|
831
|
-
binding: ReportBinding,
|
|
832
|
-
ctx: LayoutContext,
|
|
833
|
-
physRowStart: number,
|
|
834
|
-
physColStart: number,
|
|
835
|
-
rowSpan: number,
|
|
836
|
-
rowSubtreeRoot: CellAddress | null,
|
|
837
|
-
groupMergeHeight: number,
|
|
838
|
-
fixedListIndex?: number,
|
|
839
|
-
mergeListCount?: number
|
|
840
|
-
): SegmentResult {
|
|
841
|
-
if (
|
|
842
|
-
rowSubtreeRoot &&
|
|
843
|
-
isRowExpanding(binding) &&
|
|
844
|
-
binding.aggregate === 'group' &&
|
|
845
|
-
sameAddress(addr, rowSubtreeRoot)
|
|
846
|
-
) {
|
|
847
|
-
if (fixedListIndex !== undefined && fixedListIndex > 0) {
|
|
848
|
-
return { rowSpan: 0, colSpan: 1, placements: [] }
|
|
849
|
-
}
|
|
850
|
-
const mergeRows = Math.max(
|
|
851
|
-
fixedListIndex !== undefined ? (mergeListCount ?? groupMergeHeight) : groupMergeHeight,
|
|
852
|
-
1
|
|
853
|
-
)
|
|
854
|
-
if (!mergeSpanEnabled(binding)) {
|
|
855
|
-
const placements = Array.from({ length: mergeRows }, (_, listIndex) =>
|
|
856
|
-
this.makePlacement(
|
|
857
|
-
addr,
|
|
858
|
-
singleCellRange(physRowStart + listIndex, physColStart),
|
|
859
|
-
ctx,
|
|
860
|
-
binding,
|
|
861
|
-
listIndex,
|
|
862
|
-
false
|
|
863
|
-
)
|
|
864
|
-
)
|
|
865
|
-
return { rowSpan: 0, colSpan: 1, placements }
|
|
866
|
-
}
|
|
867
|
-
const range = createRange(
|
|
868
|
-
{ row: physRowStart, col: physColStart },
|
|
869
|
-
{ row: physRowStart + mergeRows - 1, col: physColStart }
|
|
870
|
-
)
|
|
871
|
-
return {
|
|
872
|
-
rowSpan: 0,
|
|
873
|
-
colSpan: 1,
|
|
874
|
-
placements: [this.makePlacement(addr, range, ctx, binding, 0, true)]
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
if (isRowExpanding(binding) && binding.aggregate === 'list') {
|
|
879
|
-
if (fixedListIndex !== undefined) {
|
|
880
|
-
return {
|
|
881
|
-
rowSpan: 0,
|
|
882
|
-
colSpan: 1,
|
|
883
|
-
placements: [
|
|
884
|
-
this.makePlacement(
|
|
885
|
-
addr,
|
|
886
|
-
singleCellRange(physRowStart, physColStart),
|
|
887
|
-
ctx,
|
|
888
|
-
binding,
|
|
889
|
-
fixedListIndex,
|
|
890
|
-
false
|
|
891
|
-
)
|
|
892
|
-
]
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
const placements = Array.from({ length: rowSpan }, (_, listIndex) =>
|
|
896
|
-
this.makePlacement(
|
|
897
|
-
addr,
|
|
898
|
-
singleCellRange(physRowStart + listIndex, physColStart),
|
|
899
|
-
ctx,
|
|
900
|
-
binding,
|
|
901
|
-
listIndex,
|
|
902
|
-
false
|
|
903
|
-
)
|
|
904
|
-
)
|
|
905
|
-
return { rowSpan: 0, colSpan: 1, placements }
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
return {
|
|
909
|
-
rowSpan: 0,
|
|
910
|
-
colSpan: 1,
|
|
911
|
-
placements: [
|
|
912
|
-
this.makePlacement(
|
|
913
|
-
addr,
|
|
914
|
-
expandRows(singleCellRange(physRowStart, physColStart), rowSpan),
|
|
915
|
-
ctx,
|
|
916
|
-
binding,
|
|
917
|
-
0,
|
|
918
|
-
mergeSpanEnabled(binding) && rowSpan > 1
|
|
919
|
-
)
|
|
920
|
-
]
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
private layoutColSubtree(
|
|
925
|
-
rootAddr: CellAddress,
|
|
926
|
-
ctx: LayoutContext,
|
|
927
|
-
physRowStart: number,
|
|
928
|
-
physColStart: number
|
|
929
|
-
): SegmentResult {
|
|
930
|
-
const rootBinding = this.index.bindingAt(rootAddr)!
|
|
931
|
-
const parentFilter = parentFilterForBinding(
|
|
932
|
-
rootBinding,
|
|
933
|
-
this.index,
|
|
934
|
-
ctx.rowFilter,
|
|
935
|
-
ctx.colFilter
|
|
936
|
-
)
|
|
937
|
-
const instances = enumerateExpansionInstances(
|
|
938
|
-
rootBinding,
|
|
939
|
-
this.data,
|
|
940
|
-
mergeFilters(parentFilter, ctx.colFilter)
|
|
941
|
-
)
|
|
942
|
-
|
|
943
|
-
if (instances.length === 0) return { rowSpan: 0, colSpan: 0, placements: [] }
|
|
944
|
-
|
|
945
|
-
const placements: PhysicalPlacement[] = []
|
|
946
|
-
let totalColSpan = 0
|
|
947
|
-
|
|
948
|
-
for (const instance of instances) {
|
|
949
|
-
const instanceCtx: LayoutContext = {
|
|
950
|
-
rowFilter: ctx.rowFilter,
|
|
951
|
-
colFilter: instance.filter,
|
|
952
|
-
rowPath: ctx.rowPath,
|
|
953
|
-
colPath: [...ctx.colPath, instance.index]
|
|
954
|
-
}
|
|
955
|
-
const width = this.colInstanceWidth(rootAddr, instanceCtx)
|
|
956
|
-
const range = createRange(
|
|
957
|
-
{ row: physRowStart, col: physColStart + totalColSpan },
|
|
958
|
-
{ row: physRowStart, col: physColStart + totalColSpan + width - 1 }
|
|
959
|
-
)
|
|
960
|
-
placements.push(
|
|
961
|
-
this.makePlacement(
|
|
962
|
-
rootAddr,
|
|
963
|
-
range,
|
|
964
|
-
instanceCtx,
|
|
965
|
-
rootBinding,
|
|
966
|
-
rootBinding.aggregate === 'list' ? instance.index : 0,
|
|
967
|
-
mergeSpanEnabled(rootBinding)
|
|
968
|
-
)
|
|
969
|
-
)
|
|
970
|
-
totalColSpan += width
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
return { rowSpan: 1, colSpan: totalColSpan, placements }
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
private layoutNestedColBand(
|
|
977
|
-
addr: CellAddress,
|
|
978
|
-
binding: ReportBinding,
|
|
979
|
-
ctx: LayoutContext,
|
|
980
|
-
physRowStart: number,
|
|
981
|
-
physColStart: number
|
|
982
|
-
): SegmentResult {
|
|
983
|
-
const parentAddr = binding.colParent!
|
|
984
|
-
const parentBinding = this.index.bindingAt(parentAddr)!
|
|
985
|
-
const parentFilter = parentFilterForBinding(
|
|
986
|
-
parentBinding,
|
|
987
|
-
this.index,
|
|
988
|
-
ctx.rowFilter,
|
|
989
|
-
ctx.colFilter
|
|
990
|
-
)
|
|
991
|
-
const parentInstances = enumerateExpansionInstances(
|
|
992
|
-
parentBinding,
|
|
993
|
-
this.data,
|
|
994
|
-
mergeFilters(parentFilter, ctx.colFilter)
|
|
995
|
-
)
|
|
996
|
-
|
|
997
|
-
const placements: PhysicalPlacement[] = []
|
|
998
|
-
let colOffset = 0
|
|
999
|
-
|
|
1000
|
-
for (const parentInstance of parentInstances) {
|
|
1001
|
-
const bandCtx: LayoutContext = {
|
|
1002
|
-
...ctx,
|
|
1003
|
-
colFilter: parentInstance.filter,
|
|
1004
|
-
colPath: [...ctx.colPath, parentInstance.index]
|
|
1005
|
-
}
|
|
1006
|
-
const childFilter = parentFilterForBinding(
|
|
1007
|
-
binding,
|
|
1008
|
-
this.index,
|
|
1009
|
-
bandCtx.rowFilter,
|
|
1010
|
-
bandCtx.colFilter
|
|
1011
|
-
)
|
|
1012
|
-
const childInstances = enumerateExpansionInstances(
|
|
1013
|
-
binding,
|
|
1014
|
-
this.data,
|
|
1015
|
-
mergeFilters(childFilter, bandCtx.colFilter)
|
|
1016
|
-
)
|
|
1017
|
-
|
|
1018
|
-
for (const childInstance of childInstances) {
|
|
1019
|
-
const childCtx: LayoutContext = {
|
|
1020
|
-
...bandCtx,
|
|
1021
|
-
colFilter: childInstance.filter,
|
|
1022
|
-
colPath: [...bandCtx.colPath, childInstance.index]
|
|
1023
|
-
}
|
|
1024
|
-
placements.push(
|
|
1025
|
-
this.makePlacement(
|
|
1026
|
-
addr,
|
|
1027
|
-
singleCellRange(physRowStart, physColStart + colOffset),
|
|
1028
|
-
childCtx,
|
|
1029
|
-
binding,
|
|
1030
|
-
0,
|
|
1031
|
-
false
|
|
1032
|
-
)
|
|
1033
|
-
)
|
|
1034
|
-
colOffset++
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
return { rowSpan: 1, colSpan: colOffset, placements }
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
private colInstanceWidth(rootAddr: CellAddress, ctx: LayoutContext): number {
|
|
1042
|
-
const nestedChild = findNestedColChild(rootAddr, this.index)
|
|
1043
|
-
if (nestedChild) {
|
|
1044
|
-
const binding = this.index.bindingAt(nestedChild)!
|
|
1045
|
-
const parentFilter = parentFilterForBinding(binding, this.index, ctx.rowFilter, ctx.colFilter)
|
|
1046
|
-
const instances = enumerateExpansionInstances(
|
|
1047
|
-
binding,
|
|
1048
|
-
this.data,
|
|
1049
|
-
mergeFilters(parentFilter, ctx.colFilter)
|
|
1050
|
-
)
|
|
1051
|
-
return Math.max(instances.length, 1)
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
const rootBinding = this.index.bindingAt(rootAddr)!
|
|
1055
|
-
if (!mergeSpanEnabled(rootBinding)) return 1
|
|
1056
|
-
|
|
1057
|
-
let width = 0
|
|
1058
|
-
for (const col of colSubtreeCols(rootAddr, this.index)) {
|
|
1059
|
-
if (col <= rootAddr.col) continue
|
|
1060
|
-
if (
|
|
1061
|
-
this.index.bindingAt({ row: rootAddr.row, col }) ||
|
|
1062
|
-
this.index.cellAt({ row: rootAddr.row, col })
|
|
1063
|
-
) {
|
|
1064
|
-
width++
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
return Math.max(width, 1)
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
private layoutColParentAlignedCell(
|
|
1071
|
-
addr: CellAddress,
|
|
1072
|
-
binding: ReportBinding,
|
|
1073
|
-
ctx: LayoutContext,
|
|
1074
|
-
physRowStart: number,
|
|
1075
|
-
physColStart: number,
|
|
1076
|
-
rowSpan: number
|
|
1077
|
-
): SegmentResult {
|
|
1078
|
-
const colParent = binding.colParent!
|
|
1079
|
-
const parentBinding = this.index.bindingAt(colParent)!
|
|
1080
|
-
const parentFilter = parentFilterForBinding(
|
|
1081
|
-
parentBinding,
|
|
1082
|
-
this.index,
|
|
1083
|
-
ctx.rowFilter,
|
|
1084
|
-
ctx.colFilter
|
|
1085
|
-
)
|
|
1086
|
-
const instances = enumerateExpansionInstances(
|
|
1087
|
-
parentBinding,
|
|
1088
|
-
this.data,
|
|
1089
|
-
mergeFilters(parentFilter, ctx.colFilter)
|
|
1090
|
-
)
|
|
1091
|
-
|
|
1092
|
-
if (instances.length === 0) return { rowSpan: 0, colSpan: 0, placements: [] }
|
|
1093
|
-
|
|
1094
|
-
const placements: PhysicalPlacement[] = []
|
|
1095
|
-
let colOffset = 0
|
|
1096
|
-
|
|
1097
|
-
for (const instance of instances) {
|
|
1098
|
-
const instanceCtx: LayoutContext = {
|
|
1099
|
-
rowFilter: ctx.rowFilter,
|
|
1100
|
-
colFilter: instance.filter,
|
|
1101
|
-
rowPath: ctx.rowPath,
|
|
1102
|
-
colPath: [...ctx.colPath, instance.index]
|
|
1103
|
-
}
|
|
1104
|
-
const width = this.colInstanceWidth(colParent, instanceCtx)
|
|
1105
|
-
placements.push(
|
|
1106
|
-
this.makePlacement(
|
|
1107
|
-
addr,
|
|
1108
|
-
expandRows(singleCellRange(physRowStart, physColStart + colOffset), rowSpan),
|
|
1109
|
-
instanceCtx,
|
|
1110
|
-
binding,
|
|
1111
|
-
0,
|
|
1112
|
-
mergeSpanEnabled(binding) && rowSpan > 1
|
|
1113
|
-
)
|
|
1114
|
-
)
|
|
1115
|
-
colOffset += width
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
return { rowSpan: 0, colSpan: colOffset, placements }
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
private isInlineColChild(row: number, col: number): boolean {
|
|
1122
|
-
const binding = this.index.bindingAt({ row, col })
|
|
1123
|
-
if (!binding?.colParent) return false
|
|
1124
|
-
const parent = this.index.bindingAt(binding.colParent)
|
|
1125
|
-
if (!parent || !isColExpanding(parent)) return false
|
|
1126
|
-
return binding.colParent.row === row
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
private listInstanceCountOnRow(templateRow: number, ctx: LayoutContext): number {
|
|
1130
|
-
let count = 0
|
|
1131
|
-
for (const entry of this.index.bindingsOnRow(templateRow)) {
|
|
1132
|
-
if (!isRowExpanding(entry.binding) || entry.binding.aggregate !== 'list') continue
|
|
1133
|
-
const parentFilter = parentFilterForBinding(
|
|
1134
|
-
entry.binding,
|
|
1135
|
-
this.index,
|
|
1136
|
-
ctx.rowFilter,
|
|
1137
|
-
ctx.colFilter
|
|
1138
|
-
)
|
|
1139
|
-
const instances = enumerateExpansionInstances(
|
|
1140
|
-
entry.binding,
|
|
1141
|
-
this.data,
|
|
1142
|
-
mergeFilters(parentFilter, ctx.rowFilter)
|
|
1143
|
-
)
|
|
1144
|
-
count = Math.max(count, instances.length)
|
|
1145
|
-
}
|
|
1146
|
-
return count
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
private rowLogicalColOffsets(templateRow: number, physColStart: number): Map<number, number> {
|
|
1150
|
-
const offsets = new Map<number, number>()
|
|
1151
|
-
let physCol = physColStart
|
|
1152
|
-
for (const col of columnsOnRow(this.index, templateRow)) {
|
|
1153
|
-
offsets.set(col, physCol)
|
|
1154
|
-
physCol++
|
|
1155
|
-
}
|
|
1156
|
-
return offsets
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
private makePlacement(
|
|
1160
|
-
logical: CellAddress,
|
|
1161
|
-
physical: CellRange,
|
|
1162
|
-
ctx: LayoutContext,
|
|
1163
|
-
binding: ReportBinding | undefined,
|
|
1164
|
-
listIndex: number,
|
|
1165
|
-
mergeSpan: boolean
|
|
1166
|
-
): PhysicalPlacement {
|
|
1167
|
-
return {
|
|
1168
|
-
logical,
|
|
1169
|
-
physical,
|
|
1170
|
-
rowPath: ctx.rowPath,
|
|
1171
|
-
colPath: ctx.colPath,
|
|
1172
|
-
mergeSpan,
|
|
1173
|
-
binding,
|
|
1174
|
-
listIndex
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
function emptyCtx(): LayoutContext {
|
|
1180
|
-
return { rowFilter: {}, colFilter: {}, rowPath: [], colPath: [] }
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
|
-
function expandRows(range: CellRange, rowSpan: number): CellRange {
|
|
1184
|
-
if (rowSpan <= 1) return range
|
|
1185
|
-
return createRange(range.start, { row: range.start.row + rowSpan - 1, col: range.end.col })
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
/** 将模板合并区域映射到物理网格(基于锚点格的物理落点) */
|
|
1189
|
-
export function mapTemplateMerges(
|
|
1190
|
-
index: TemplateIndex,
|
|
1191
|
-
placements: PhysicalPlacement[]
|
|
1192
|
-
): CellRange[] {
|
|
1193
|
-
const layout: ExpansionLayout = { rowCount: 0, colCount: 0, placements, mappedMerges: [] }
|
|
1194
|
-
const mapped: CellRange[] = []
|
|
1195
|
-
for (const merge of index.merges) {
|
|
1196
|
-
const mappedRange = mapLogicalMerge(merge, layout)
|
|
1197
|
-
if (mappedRange) mapped.push(mappedRange)
|
|
1198
|
-
}
|
|
1199
|
-
return mapped
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
|
-
function mapLogicalMerge(merge: CellRange, layout: ExpansionLayout): CellRange | null {
|
|
1203
|
-
const startHits = placementsAt(layout, merge.start)
|
|
1204
|
-
if (startHits.length === 0) return null
|
|
1205
|
-
const startPlacement = startHits[0]!
|
|
1206
|
-
const endHits = placementsAt(layout, merge.end)
|
|
1207
|
-
if (endHits.length > 0) {
|
|
1208
|
-
return createRange(startPlacement.physical.start, endHits[endHits.length - 1]!.physical.end)
|
|
1209
|
-
}
|
|
1210
|
-
const logicalRowSpan = merge.end.row - merge.start.row
|
|
1211
|
-
const logicalColSpan = merge.end.col - merge.start.col
|
|
1212
|
-
return createRange(startPlacement.physical.start, {
|
|
1213
|
-
row: startPlacement.physical.start.row + logicalRowSpan,
|
|
1214
|
-
col: startPlacement.physical.start.col + logicalColSpan
|
|
1215
|
-
})
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
/** 计算扩展坐标系:逻辑格 → 物理区间映射(纯计算、无快照输出) */
|
|
1219
|
-
export function computeExpansionLayout(
|
|
1220
|
-
index: TemplateIndex,
|
|
1221
|
-
data: DatasetRecords
|
|
1222
|
-
): ExpansionLayout {
|
|
1223
|
-
return new CoordinateEngine(index, data).compute()
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
/** 按逻辑地址查找全部物理落点 */
|
|
1227
|
-
export function placementsAt(layout: ExpansionLayout, addr: CellAddress): PhysicalPlacement[] {
|
|
1228
|
-
return layout.placements.filter((p) => sameAddress(p.logical, addr))
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
|
-
/** 物理区间行高(含合并) */
|
|
1232
|
-
export function physicalRowSpan(placement: PhysicalPlacement): number {
|
|
1233
|
-
return rangeRows(placement.physical)
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
/** 物理区间列宽(含合并) */
|
|
1237
|
-
export function physicalColSpan(placement: PhysicalPlacement): number {
|
|
1238
|
-
return rangeCols(placement.physical)
|
|
1239
|
-
}
|