Virtual scrolling in Vue Pivot Table component

27 Sep 202424 minutes to read

Virtual Scrolling

The virtual scrolling option allows you to load the large amounts of data without performance degradation by rendering rows and columns only in the content viewport. The data will refresh dynamically on vertical or horizontal scroll. This feature can be enabled by setting the enableVirtualization property to true.

To use the virtual scrolling feature, inject the VirtualScroll module in to the pivot table.

<template>
  <div id="app">
    <ejs-pivotview :dataSourceSettings="dataSourceSettings" :height="height" :enableVirtualization="enableVirtualization">
    </ejs-pivotview>
  </div>
</template>
<script setup>
import { provide } from "vue";
import { PivotViewComponent as EjsPivotview, VirtualScroll } from "@syncfusion/ej2-vue-pivotview";

function data(count) {
  let result = [];
  let dt = 0;
  for (let i = 1; i < count + 1; i++) {
    dt++;
    let round;
    let toString = i.toString();
    if (toString.length === 1) {
      round = "0000" + i;
    } else if (toString.length === 2) {
      round = "000" + i;
    } else if (toString.length === 3) {
      round = "00" + i;
    } else if (toString.length === 4) {
      round = "0" + i;
    } else {
      round = toString;
    }
    result.push({
      ProductID: "PRO-" + round,
      Year: "FY " + (dt + 2013),
      Price: Math.round(Math.random() * 5000) + 5000,
      Sold: Math.round(Math.random() * 80) + 10
    });
    if (dt / 4 == 1) {
      dt = 0;
    }
  }
  return result;
}

const dataSourceSettings = {
  dataSource: data(1000),
  enableSorting: false,
  expandAll: true,
  formatSettings: [{ name: 'Price', format: 'C0' }],
  rows: [{ name: 'ProductID' }],
  columns: [{ name: 'Year' }],
  values: [{ name: 'Price', caption: 'Unit Price' }, { name: 'Sold', caption: 'Unit Sold' }]
};
const height = 350;
const enableVirtualization = true;
provide('pivotview', [VirtualScroll]);

</script>
<style>
@import "../node_modules/@syncfusion/ej2-vue-pivotview/styles/material.css";
</style>
<template>
  <div id="app">
    <ejs-pivotview :dataSourceSettings="dataSourceSettings" :height="height" :enableVirtualization="enableVirtualization">
    </ejs-pivotview>
  </div>
</template>

<script>

import { PivotViewComponent, VirtualScroll } from "@syncfusion/ej2-vue-pivotview";

function data(count) {
  let result = [];
  let dt = 0;
  for (let i = 1; i < count + 1; i++) {
    dt++;
    let round;
    let toString = i.toString();
    if (toString.length === 1) {
      round = "0000" + i;
    } else if (toString.length === 2) {
      round = "000" + i;
    } else if (toString.length === 3) {
      round = "00" + i;
    } else if (toString.length === 4) {
      round = "0" + i;
    } else {
      round = toString;
    }
    result.push({
      ProductID: "PRO-" + round,
      Year: "FY " + (dt + 2013),
      Price: Math.round(Math.random() * 5000) + 5000,
      Sold: Math.round(Math.random() * 80) + 10
    });
    if (dt / 4 == 1) {
      dt = 0;
    }
  }
  return result;
}

export default {
  name: "App",
  components: {
    "ejs-pivotview": PivotViewComponent
  },
  data() {
    return {
      dataSourceSettings: {
        dataSource: data(1000),
        enableSorting: false,
        expandAll: true,
        formatSettings: [{ name: 'Price', format: 'C0' }],
        rows: [{ name: 'ProductID' }],
        columns: [{ name: 'Year' }],
        values: [{ name: 'Price', caption: 'Unit Price' }, { name: 'Sold', caption: 'Unit Sold' }]
      },
      height: 350,
      enableVirtualization: true
    }
  },
  provide: {
    pivotview: [VirtualScroll]
  }
}
</script>
<style>
@import "../node_modules/@syncfusion/ej2-vue-pivotview/styles/material.css";
</style>

Virtual scrolling with single page mode

When virtual scrolling is enabled, the pivot table renders not only the current view page, but also the previous and next pages by default. This default behavior, however, can cause performance delays when dealing with a large number of rows and columns. This is because the same number of rows and columns from adjacent pages are also processed, resulting in additional computational load. This performance constraint can be avoided by setting the allowSinglePage property to true within the virtualScrollSettings.

Enabling this property causes the pivot table to render only the rows and columns that are relevant to the current view page during virtual scrolling. This optimization significantly improves the performance of the pivot table during initial rendering and when performing UI actions such as drill up/down, sorting, filtering, and more.

<template>
  <div id="app">
    <ejs-pivotview :dataSourceSettings="dataSourceSettings" :width="width" :height="height"
      :virtualScrollSettings="virtualScrollSettings" :enableVirtualization="enableVirtualization">
    </ejs-pivotview>
  </div>
</template>

<script setup>
import { provide } from "vue";
import { PivotViewComponent as EjsPivotview, VirtualScroll } from "@syncfusion/ej2-vue-pivotview";



/* tslint:disable */
function data(count) {
  let result = [];
  let dt = 0;
  for (let i = 1; i < count + 1; i++) {
    dt++;
    let round;
    let toString = i.toString();
    if (toString.length === 1) {
      round = "0000" + i;
    } else if (toString.length === 2) {
      round = "000" + i;
    } else if (toString.length === 3) {
      round = "00" + i;
    } else if (toString.length === 4) {
      round = "0" + i;
    } else {
      round = toString;
    }
    result.push({
      ProductID: "PRO-" + round,
      Year: "FY " + (dt + 2013),
      Price: Math.round(Math.random() * 5000) + 5000,
      Sold: Math.round(Math.random() * 80) + 10
    });
    if (dt / 4 == 1) {
      dt = 0;
    }
  }
  return result;
}
const dataSourceSettings = {
  dataSource: data(1000),
  enableSorting: false,
  expandAll: true,
  formatSettings: [{ name: 'Price', format: 'C0' }],
  rows: [{ name: 'ProductID' }],
  columns: [{ name: 'Year' }],
  values: [{ name: 'Price', caption: 'Unit Price' },
  { name: 'Sold', caption: 'Unit Sold' }
  ]
};
const width = '100%';
const height = 350;
const enableVirtualization = true;
const virtualScrollSettings = {
  allowSinglePage: true
};

provide('pivotview', [VirtualScroll]);

</script>

<style>
@import "../node_modules/@syncfusion/ej2-vue-pivotview/styles/material.css";
</style>
<template>
  <div id="app">
    <ejs-pivotview :dataSourceSettings="dataSourceSettings" :width="width" :height="height"
      :virtualScrollSettings="virtualScrollSettings" :enableVirtualization="enableVirtualization">
    </ejs-pivotview>
  </div>
</template>
<script>

  import { PivotViewComponent, VirtualScroll } from "@syncfusion/ej2-vue-pivotview";

  /* tslint:disable */
  function data(count) {
    let result = [];
    let dt = 0;
    for (let i = 1; i < count + 1; i++) {
      dt++;
      let round;
      let toString = i.toString();
      if (toString.length === 1) {
        round = "0000" + i;
      } else if (toString.length === 2) {
        round = "000" + i;
      } else if (toString.length === 3) {
        round = "00" + i;
      } else if (toString.length === 4) {
        round = "0" + i;
      } else {
        round = toString;
      }
      result.push({
        ProductID: "PRO-" + round,
        Year: "FY " + (dt + 2013),
        Price: Math.round(Math.random() * 5000) + 5000,
        Sold: Math.round(Math.random() * 80) + 10
      });
      if (dt / 4 == 1) {
        dt = 0;
      }
    }
    return result;
  }

  export default {
name: "App",
components: {
"ejs-pivotview":PivotViewComponent
},
    data () {
      return {
        dataSourceSettings: {
          dataSource: data(1000),
          enableSorting: false,
          expandAll: true,
          formatSettings: [{ name: 'Price', format: 'C0' }],
          rows: [{ name: 'ProductID' }],
          columns: [{ name: 'Year' }],
          values: [{ name: 'Price', caption: 'Unit Price' },
            { name: 'Sold', caption: 'Unit Sold' }
          ]
        },
        width: '100%',
        height: 350,
        enableVirtualization: true,
        virtualScrollSettings: {
          allowSinglePage: true
        }
      }
    },
    provide: {
      pivotview: [ VirtualScroll ]
    }
  }
</script>

<style>
  @import "../node_modules/@syncfusion/ej2-vue-pivotview/styles/material.css";
</style>

Limitations for virtual scrolling

  • In virtual scrolling, the columnWidth property in gridSettings should be in pixels, and percentage values are not accepted.
  • Features such as auto fit, column resizing, text wrapping, and setting specific column widths through events can dynamically affect the row height and column width in the pivot table at runtime. However, these changes are not considered in the scroller calculations, particularly with large datasets. This can lead to performance issues and problems with UI functionality during scrolling. Therefore, it is not recommended to use these features alongside virtualization in the pivot table.
  • Grouping, which takes additional time to splitting the raw items into the provided format.
  • Date Formatting, which takes additional time to convert date format.
  • Date Formatting with sorting, here additionally full date time format should be framed to perform sorting along with the provided date format which lags the performance.
  • When using OLAP data, subtotals and grand totals are only displayed when measures are bound at the last position in the rows or columns axis. Otherwise, the data from the pivot table will be shown without summary totals.
  • Even if virtual scrolling is enabled, not only is the current view port data retrieved, but also the data for the immediate previous page and the immediate next page. As a result, when the end user scrolls slightly ahead or behind, the next or previous page data is displayed immediately without requiring a refresh. Note: If the pivot table’s width and height are large, the loading data count in the current, previous, and next viewport (pages) will also increase, affecting performance.

Virtual scrolling for static field list

Virtual scrolling automatically works with “Popup” field list on setting the enableVirtualization property in the Pivot Table to true. Incase of static field list, which act as a separate component, user need to enable enableVirtualization property in the Pivot Table and also pass the report information to pivot table instance via the load event of the field list.

<template>
  <div id="app">
    <ejs-pivotview id="pivotview_flist" :height="height" :gridSettings="gridSettings" :enginePopulated="enginePopulated"
      :width="width" :enableVirtualization="enableVirtualization"></ejs-pivotview>
    <ejs-pivotfieldlist id="pivotfieldlist1" :dataSourceSettings="dataSourceSettings"
      :enginePopulated="fieldEnginePopulated" :load="load" :dataBound="dataBound"
      :allowCalculatedField="allowCalculatedField" :renderMode="renderMode"></ejs-pivotfieldlist>
  </div>
</template>
<script setup>
import { provide } from "vue";
import { PivotViewComponent as EjsPivotview, PivotFieldListComponent as EjsPivotfieldlist, FieldList, CalculatedField, VirtualScroll } from "@syncfusion/ej2-vue-pivotview";
import { pivotData } from './pivotData.js';

const dataSourceSettings = {
  dataSource: pivotData,
  expandAll: false,
  columns: [{ name: 'Year', caption: 'Production Year' }, { name: 'Quarter' }],
  values: [{ name: 'Sold', caption: 'Units Sold' }, { name: 'Amount', caption: 'Sold Amount' }],
  rows: [{ name: 'Country' }, { name: 'Products' }],
  formatSettings: [{ name: 'Amount', format: 'C0' }],
  filters: []
};
const height = 350;
const renderMode = "Fixed";
const gridSettings = { columnWidth: 140 };
const allowCalculatedField = true;
const enableVirtualization = true;
const width = "99%";

const fieldEnginePopulated = () => {
  let fieldListObj = document.getElementById('pivotfieldlist1').ej2_instances[0];
  let pivotGridObj = document.getElementById('pivotview_flist').ej2_instances[0];
  fieldListObj.updateView(pivotGridObj);
};
const dataBound = () => {
  let pivotObj = document.getElementById('pivotview_flist').ej2_instances[0];
  pivotObj.tooltip.destroy();
  pivotObj.refresh();
};
const load = () => {
  let pivotObj = document.getElementById('pivotview_flist').ej2_instances[0];
  let fieldListObj = document.getElementById('pivotfieldlist1').ej2_instances[0];
  pivotObj.dataSourceSettings = fieldListObj.dataSourceSettings;
  pivotObj.updatePageSettings(true);
  fieldListObj.pageSettings = pivotObj.pageSettings;
};
const enginePopulated = () => {
  let pivotObj = document.getElementById('pivotview_flist').ej2_instances[0];
  let fieldListObj = document.getElementById('pivotfieldlist1').ej2_instances[0];
  if (fieldListObj && pivotObj) {
    fieldListObj.update(pivotObj);
  }
};

provide('pivotview', [CalculatedField, FieldList, VirtualScroll],);
</script>
<style>
@import "../node_modules/@syncfusion/ej2-vue-pivotview/styles/material.css";

#pivotfieldlist1 {
  width: 400px;
  margin-top: 20px;
}
</style>
<template>
  <div id="app">
    <ejs-pivotview id="pivotview_flist" :height="height" :gridSettings="gridSettings" :enginePopulated="enginePopulated"
      :width="width" :enableVirtualization="enableVirtualization"></ejs-pivotview>
    <ejs-pivotfieldlist id="pivotfieldlist1" :dataSourceSettings="dataSourceSettings"
      :enginePopulated="fieldEnginePopulated" :load="load" :dataBound="dataBound"
      :allowCalculatedField="allowCalculatedField" :renderMode="renderMode"></ejs-pivotfieldlist>
  </div>
</template>
<script>
import { PivotViewComponent, PivotFieldListComponent, FieldList, CalculatedField, VirtualScroll } from "@syncfusion/ej2-vue-pivotview";
import { pivotData } from './pivotData.js';

export default {
  name: "App",
  components: {
    "ejs-pivotview": PivotViewComponent,
    "ejs-pivotfieldlist": PivotFieldListComponent
  },
  data() {
    return {
      dataSourceSettings: {
        dataSource: pivotData,
        expandAll: false,
        columns: [{ name: 'Year', caption: 'Production Year' }, { name: 'Quarter' }],
        values: [{ name: 'Sold', caption: 'Units Sold' }, { name: 'Amount', caption: 'Sold Amount' }],
        rows: [{ name: 'Country' }, { name: 'Products' }],
        formatSettings: [{ name: 'Amount', format: 'C0' }],
        filters: []
      },
      height: 350,
      renderMode: "Fixed",
      gridSettings: { columnWidth: 140 },
      allowCalculatedField: true,
      enableVirtualization: true,
      width: "99%",
    }
  },
  methods: {
    fieldEnginePopulated: function () {
      let fieldListObj = document.getElementById('pivotfieldlist1').ej2_instances[0];
      let pivotGridObj = document.getElementById('pivotview_flist').ej2_instances[0];
      fieldListObj.updateView(pivotGridObj);
    },
    dataBound: function () {
      let pivotObj = document.getElementById('pivotview_flist').ej2_instances[0];
      pivotObj.tooltip.destroy();
      pivotObj.refresh();
    },
    load: function () {
      let pivotObj = document.getElementById('pivotview_flist').ej2_instances[0];
      let fieldListObj = document.getElementById('pivotfieldlist1').ej2_instances[0];
      pivotObj.dataSourceSettings = fieldListObj.dataSourceSettings;
      pivotObj.updatePageSettings(true);
      fieldListObj.pageSettings = pivotObj.pageSettings;
    },
    enginePopulated: function () {
      let pivotObj = document.getElementById('pivotview_flist').ej2_instances[0];
      let fieldListObj = document.getElementById('pivotfieldlist1').ej2_instances[0];
      if (fieldListObj && pivotObj) {
        fieldListObj.update(pivotObj);
      }
    }
  },
  provide: {
    pivotview: [CalculatedField, FieldList, VirtualScroll],
  },
}
</script>
<style>
@import "../node_modules/@syncfusion/ej2-vue-pivotview/styles/material.css";

#pivotfieldlist1 {
  width: 400px;
  margin-top: 20px;
}
</style>

See also