Incremental Loading for Large-Scale Statistical Dashboards

To resolve this, we implemented an incremental loading strategy: instead of fetching all statistics in one request, each statistical metric is loaded individually using a unique identifier (item number). This allows partial results to appear progressively, improving perceived performance and responsiveness.

Frontend: Asynchronous Per-Item Data Fetching

The frontend now fetches metadata first—listing all item keys—then sequentially loads detailed statistics for each item. Loading states are dynamically updated per row to provide visual feedback.

loadData() {
  console.log('Fetching item metadata...');
  getCheckItemMetadata({
    year: this.selectedYear,
    month: this.selectedMonth,
    areaNumbers: this.selectedAreaNumber,
    foodTypes: this.selectedFoodTypes,
    itemDepth: this.selectedItemDepth
  }).then(async response => {
    if (response.state === 200) {
      // Initialize table with empty values
      this.tableData = this.initializeTable(response.data);

      const itemKeys = response.data.map(item => item['_ITEMNUMBER']);
      let rowIndex = 0;

      for (const key of itemKeys) {
        // Mark row as loading
        this.tableData[rowIndex].isLoading = true;

        await getPerItemStatistics({
          year: this.selectedYear,
          month: this.selectedMonth,
          areaNumbers: this.selectedAreaNumber,
          itemNumber: key,
          foodTypes: this.selectedFoodTypes,
          itemDepth: this.selectedItemDepth,
          isAreasCondition: this.isAreasCondition
        }).then(result => {
          if (result.state === 200 && result.data.length > 0) {
            const stats = result.data[0];
            const targetRow = this.tableData.find(row => row['_ITEMNUMBER'] === key);
            if (targetRow) {
              targetRow.SUM_COUNT = stats.SUM_COUNT;
              targetRow.TRUE_COUNT = stats.TRUE_COUNT;
              targetRow.FALSE_COUNT = stats.FALSE_COUNT;
              targetRow.MISSING_COUNT = stats.MISSING_COUNT;
              targetRow.TRUE_PCT = stats.TRUE_PCT;
              targetRow.FALSE_PCT = stats.FALSE_PCT;
              targetRow.MISSING_PCT = stats.MISSING_PCT;
            }
          }
        }).finally(() => {
          this.tableData[rowIndex].isLoading = false;
          rowIndex++;
        });
      }
    }
  }).catch(error => {
    console.error('Metadata fetch failed:', error);
  }).finally(() => {
    this.isLoading = false;
  });
}

Backend: Granular Endpoint for Item-Specific Metrics

The backend exposes a new endpoint that acccepts a single itemNumber paramter, enabling targeted queries without scanning the entire dataset.

@RequestMapping(value = "/getPerItemStatistics", method = {RequestMethod.POST})
APIResult<List<Map<String, Object>>> getPerItemStatistics(
    @RequestParam(required = false) String year,
    @RequestParam(required = false) String month,
    @RequestParam(required = false) String areaNumbers,
    @RequestParam(required = true) String itemNumber,
    @RequestParam(required = false) String foodTypes,
    @RequestParam(required = false) Integer itemDepth,
    @RequestParam(required = false) boolean isAreasCondition
);

Dynamic Chart Aggregation

The chart component recalculates aggregate values in real-time as individual items load, ensuring visual consistency without requiring a full page reload.

<div class="chart-box">
  <vue-chart
    :id="chartId"
    :option="chartOptions"
    width="calc(100% - 40px)"
    height="350px"
  />
</div>
computed: {
  chartOptions() {
    const baseOptions = JSON.parse(JSON.stringify(this.dataOption));
    let totalValid = 0;
    let totalInvalid = 0;
    let totalMissing = 0;

    this.tableData.forEach(item => {
      totalValid += item.TRUE_COUNT || 0;
      totalInvalid += item.FALSE_COUNT || 0;
      totalMissing += item.MISSING_COUNT || 0;
    });

    baseOptions.series[0].data = [
      { value: totalValid, name: 'Positive Evaluation' },
      { value: totalInvalid, name: 'Negative Evaluation' },
      { value: totalMissing, name: 'Reasonable Missing' }
    ];

    return baseOptions;
  }
}

Tags: Vue.js javascript API IncrementalLoading StatisticalDashboard

Posted on Thu, 23 Jul 2026 16:55:22 +0000 by kulin