Level 2: Complex Structures

Sub-topic 2.3: Crosstabs, Dynamic Matrix Transformation & Visual Elements

สถาปัตยกรรม Pivot Table อัตโนมัติ, Multi-Dimensional Bucketing, Column Break Pagination และ Chart Integration (JasperReports 7.0.3+)

บทนำเชิงสถาปัตยกรรม (Conceptual Narrative & The Matrix Pivot Problem):

ในรายงานธุรกิจทั่วไป เช่น ตารางรายการสั่งซื้อ ข้อมูลจะขยายตัวในแนวตั้ง (Vertical Stretch) ตามจำนวนแถวจากฐานข้อมูล คอลัมน์ทุกตัวจะคงที่ เช่น รหัส, ชื่อ, จำนวน, ราคา แต่ในงานวิเคราะห์เชิงลึกระดับผู้บริหาร เช่น "รายงานสรุปรายได้แยกตามแผนก (แนวตั้ง) และไตรมาส (แนวนอน)" คอลัมน์แนวนอนจะไม่ได้ถูกกำหนดไว้ตายตัวตั้งแต่ช่วงออกแบบ (Design Time) หากไตรมาสใหม่เกิดขึ้น คอลัมน์จะต้องงอกออกมาเองโดยอัตโนมัติ

หากใช้ SQL แบบดั้งเดิม คุณต้องเขียน CASE WHEN หรือคำสั่ง Pivot ที่ซับซ้อน และถ้ามี Quarter ใหม่เพิ่มเข้ามา คุณต้องแก้ทั้ง SQL และ JRXML! สถาปัตยกรรม Crosstab Matrix Engine ของ JasperReports ออกแบบมาเพื่อแก้ปัญหานี้โดยเฉพาะ: รับ Raw Data Stream แถวเดี่ยวเข้ามา แล้วทำการแตกแกน หมุนข้อมูลเป็นเมทริกซ์ 2 มิติ (X, Y) พร้อมคำนวณผลรวมทั้งแถวและคอลัมน์ให้อัตโนมัติในหน่วยความจำระดับ JVM

ผ่าความหมาย 4 เสาหลักของ Crosstab Architecture

🗂️ 1. Multi-Dimensional Bucketing
กลไกการแยกประเภทข้อมูลลง "ถัง" (Buckets) เครื่องยนต์จะอ่าน Field เข้ามาแล้วกระจายค่าลง Row Groups (แกน Y) และ Column Groups (แกน X) ผ่าน <bucket> โดยจัดเก็บ Index ในรูป Bucket Keys เพื่อจับคู่จุดตัดของแถวและคอลัมน์ได้อย่างรวดเร็ว
📐 2. Measure Computation Cells
เซลล์จุดตัดระหว่างแถวและคอลัมน์ (Intersection Matrix) คือพื้นที่คำนวณของ Measures เช่น ผลรวมยอดขาย หรือค่าเฉลี่ย โดยมีฟังก์ชันรวมค่าในตัว (calculation="Sum", "Count", "Average") พร้อมทั้งสร้างแถว/คอลัมน์ Total อัตโนมัติ
🔄 3. Two-Pass Matrix Buffer
Crosstab ไม่สามารถวาดแบบ Streaming ทีละแถวได้ เครื่องยนต์ต้องดูดข้อมูลทั้งหมดใน Data Source เข้ามาไว้ใน CrosstabBucketingService เพื่อสร้างโครงสร้างเมทริกซ์จนครบทั้งผืนก่อน (Pass 1) แล้วจึงคำนวณขนาดและวาดลงหน้ากระดาษ (Pass 2)
📑 4. Column Break & Overflow Pipeline
เมื่อคอลัมน์ขยายตัวในแนวนอนจนล้น Printable Width (เช่น เกิน 555pt บน A4) ระบบจะไม่ตัดข้อความทิ้ง แต่จะสั่งตัดหน้าในแนวนอน (Column Break) โดยยกคอลัมน์ที่ล้นไปพิมพ์ต่อในหน้าถัดไป พร้อมควบคุมระยะห่างด้วย columnBreakOffset

Step 1: Deep Dive (Crosstab Engine Internals & Memory Architecture)

การทำงานของ Crosstab ภายใน JRVerticalFiller มีความแตกต่างจากการ Fill แบนด์ปกติอย่างสิ้นเชิง โดยมี Flow การทำงานดังนี้:

Crosstab Matrix Architecture Flowchart
รูปที่ 1: แผนผังวงจร Bucketing, Two-Pass Matrix Buffer และ Column Overflow Pagination

วงจรการประมวลผลภายใน JVM (Crosstab Execution Flow):

  1. Data Ingestion: รับ Record แต่ละแถวเข้ามาจาก Data Source
  2. Bucket Key Hashing: ประเมิน Bucket Expressions ของ Row Group และ Column Group เพื่อระบุพิกัด [Row_i, Col_j]
  3. Measure Accumulation: ส่งค่าไปยัง Measure Accumulator ประจำเซลล์พิกัดนั้นๆ เพื่อบวกยอดสะสม
  4. Matrix Dimension Resolution: หลังอ่านข้อมูลจบ คำนวณความกว้างรวมของทุกคอลัมน์และความสูงของทุกแถว
  5. Pagination & Rendering: วาด Header, Detail Cells และ Totals ลงใน Band หากความกว้างเกินหน้ากระดาษ จะกระจายคอลัมน์ข้ามหน้าตามการตั้งค่า columnBreakOffset

ความแตกต่างระหว่าง Table Element และ Crosstab:

คุณสมบัติTable ElementCrosstab Element
โครงสร้างคอลัมน์คงที่ (Static Design Time)ไดนามิกตามข้อมูล (Runtime Driven)
การบริโภค Memoryต่ำ (Streaming ประมวลผลทีละแถว)สูง (ต้อง Buffer Matrix ไว้ทั้งผืน)
การขยายตัวแนวตั้งอย่างเดียว (Vertical)สองมิติพร้อมกัน (Horizontal & Vertical)
จุดตายบน Production (Crosstab Memory Explosion):
เนื่องจาก Crosstab ต้องเก็บพิกัดของ Matrix ทุกเซลล์ไว้ในหน่วยความจำ JVM ก่อนที่จะเริ่มวาดหน้าแรกได้ หาก Data Source มี Cardinality สูงมาก (เช่น 1,000 แถว x 1,000 คอลัมน์ = 1,000,000 เซลล์) จะเกิดสภาวะ Heap Exhaustion ทันที การใช้งาน Crosstab บน Production จะต้องใช้ Virtualizer เสมอ หรือทำการ Aggregation มาจาก SQL ให้เรียบร้อยก่อน

Step 2: Real-world Implementation & Architectural Schema (7.0.x)

ในสถาปัตยกรรม JasperReports 7.0.x การประกาศ Crosstab ได้ถูกปรับปรุงให้อยู่ภายใต้ Unified Element Model โดยใช้แท็ก <element kind="crosstab">:

Quarterly Financial Matrix Production Output
รูปที่ 2: Final Production Output ของรายงานเมทริกซ์การเงินแยกตามไตรมาสและภูมิภาค
<!-- Crosstab Declaration (7.0.x Unified Model) -->
<element kind="crosstab" x="0" y="20" width="555" height="200">
    <!-- 1. Row Group: แกน Y (แตกแถวตามภูมิภาค) -->
    <rowGroup name="RegionGroup" width="120" totalPosition="End">
        <bucket class="java.lang.String">
            <expression><![CDATA[$F{region}]]></expression>
        </bucket>
        <crosstabRowHeader>
            <cellContents>
                <element kind="textField" x="0" y="0" width="120" height="20">
                    <expression><![CDATA[$V{RegionGroup}]]></expression>
                </element>
            </cellContents>
        </crosstabRowHeader>
    </rowGroup>

    <!-- 2. Column Group: แกน X (แตกคอลัมน์ตามไตรมาส) -->
    <columnGroup name="QuarterGroup" height="20" totalPosition="End">
        <bucket class="java.lang.String">
            <expression><![CDATA[$F{quarter}]]></expression>
        </bucket>
        <crosstabColumnHeader>
            <cellContents>
                <element kind="textField" x="0" y="0" width="80" height="20">
                    <expression><![CDATA[$V{QuarterGroup}]]></expression>
                </element>
            </cellContents>
        </crosstabColumnHeader>
    </columnGroup>

    <!-- 3. Measure: ตัวเลขผลรวมตรงจุดตัด -->
    <measure name="TotalRevenue" class="java.math.BigDecimal" calculation="Sum">
        <expression><![CDATA[$F{revenue}]]></expression>
    </measure>
</element>

Hands-on Engineering Lab 2.3.1: Dynamic Matrix Transformation & Bucketing

🛠️ Lab 2.3.1: Crosstab Matrix Architecture & Bucket Hierarchy

ข้อกำหนดทางเทคนิค (Full-Coverage 100%):
1. ใช้แท็กสมัยใหม่ <element kind="crosstab"> ตามมาตรฐาน 7.0.x ห้ามใช้แท็กโบราณ
2. กำหนด <rowGroup name="RegionGroup"> พร้อม totalPosition="End" เพื่อสร้างแถวสรุปผลรวมด้านท้าย
3. กำหนด <columnGroup name="QuarterGroup"> เพื่อแตกคอลัมน์ไตรมาสตามแกนแนวนอน
4. ประกาศ <measure name="TotalAmount" calculation="Sum"> ชนิด `java.math.BigDecimal` เพื่อรวมยอดเงิน

Hands-on Engineering Lab 2.3.2: Multi-Measure Matrix & Chart Visual Integration

🛠️ Lab 2.3.2: Dual Measures & Unified Vector Chart Pipeline

ข้อกำหนดทางเทคนิค (Full-Coverage 100%):
1. ประกาศ Measure 2 ตัวใน Crosstab เดียวกัน: TotalQty (Integer) และ TotalRevenue (BigDecimal)
2. กำหนด columnBreakOffset="15" เพื่อควบคุมระยะตัดหน้าเมื่อคอลัมน์ล้นแนวนอน
3. ฝัง <element kind="chart" chartType="bar"> ใน Summary Band เพื่อแสดงการเปรียบเทียบภาพรวม
4. ผูก <categoryDataset> ให้สอดคล้องกับแกนข้อมูลของ Crosstab

Step 3: Interactive Checkpoint Quiz (Deep-Level Internal Mechanics)

แบบทดสอบ 15 ข้อเชิงลึก วัดความเข้าใจเรื่อง Crosstab Engine, Multi-dimensional Bucketing และ Memory Virtualization:

⚡ Real-time Score Tracker (Level 2.3)
คะแนน: 0 / 15 (ตอบแล้ว: 0 / 15)