Same Value Merge
The Same Value Merge feature automatically combines cells in a column when they contain identical values. This creates a cleaner, more organized view of your data by visually grouping repeated values.
Source code
import { defineCustomElements } from '@revolist/revogrid/loader';
import { SameValueMergePlugin, StickyCellsPlugin } from '@revolist/revogrid-pro';
defineCustomElements();
const regions = ['EMEA', 'AMER', 'APAC'];
const categories = ['Electronics', 'Books', 'Clothing', 'Home'];
const statuses = ['In Stock', 'Back Ordered', 'Discontinued'];
const items = ['Laptop', 'Mouse', 'Keyboard', 'Guide', 'Desk Lamp', 'T-Shirt', 'Notebook', 'Monitor'];
const columns = [
{ prop: 'id', name: 'ID', size: 70, pin: 'colPinStart' },
{ prop: 'region', name: 'Region', merge: { sticky: true }, size: 120, pin: 'colPinStart' },
{ prop: 'category', name: 'Category', size: 140 },
{ prop: 'operationNo', name: 'Operation', size: 120 },
{
prop: 'status',
name: 'Status',
merge: {
mergeGroup: 'operation',
mergeLeader: true,
mergeDependsOn: ['category', 'operationNo'],
},
size: 140,
},
{ prop: 'quantity', name: 'Quantity', merge: { mergeGroup: 'operation' }, size: 110 },
{ prop: 'tool', name: 'Tool', merge: { mergeGroup: 'operation' }, size: 110 },
{ prop: 'name', name: 'Product', size: 160 },
];
const source = Array.from({ length: 60 }, (_, index) => {
const region = regions[Math.floor(index / 20) % regions.length];
const category = categories[Math.floor(index / 5) % categories.length];
const operationNo = 100 + Math.floor(index / 3) % 4;
const status = statuses[Math.floor(index / 2) % statuses.length];
return {
id: index + 1,
region,
category,
operationNo,
status,
quantity: Math.floor(index / 2) % 2 === 0 ? 100 : 250,
tool: ['T-01', 'T-01', 'T-02', 'T-02'][Math.floor(index / 2) % 4],
name: `${items[index % items.length]} ${String(index + 1).padStart(2, '0')}`,
};
});
export function load(parentSelector: string) {
const parent = document.querySelector(parentSelector);
if (!parent) return;
const container = document.createElement('div');
container.style.display = 'grid';
container.style.gap = '8px';
const grid = document.createElement('revo-grid');
grid.plugins = [SameValueMergePlugin, StickyCellsPlugin];
grid.columns = columns;
grid.theme = 'compact';
grid.hideAttribution = true;
container.append(grid);
parent.appendChild(container);
grid.source = source;
return () => container.remove();
}
<template>
<div class="same-value-merge-demo grow flex flex-col gap-2">
<RevoGrid
class="overflow-hidden grow"
:source="rows"
:columns="columns"
:plugins="plugins"
:theme="isDark ? 'darkCompact' : 'compact'"
stretch="all"
hide-attribution
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import RevoGrid from '@revolist/vue3-datagrid';
import { SameValueMergePlugin, StickyCellsPlugin, ColumnStretchPlugin } from '@revolist/revogrid-pro';
import { currentThemeVue } from '../composables/useRandomData';
const { isDark } = currentThemeVue();
const plugins = [SameValueMergePlugin, StickyCellsPlugin, ColumnStretchPlugin];
const columns = [
{ prop: 'id', name: 'ID', size: 70, pin: 'colPinStart' },
{ prop: 'region', name: 'Region', merge: { sticky: true }, size: 120, pin: 'colPinStart' },
{ prop: 'category', name: 'Category', size: 140 },
{ prop: 'operationNo', name: 'Operation', size: 120 },
{
prop: 'status',
name: 'Status',
merge: {
mergeGroup: 'operation',
mergeLeader: true,
mergeDependsOn: ['category', 'operationNo'],
},
size: 140,
},
{ prop: 'quantity', name: 'Quantity', merge: { mergeGroup: 'operation' }, size: 110 },
{ prop: 'tool', name: 'Tool', merge: { mergeGroup: 'operation' }, size: 110 },
{ prop: 'name', name: 'Product', size: 160 },
];
const regions = ['EMEA', 'AMER', 'APAC'];
const categories = ['Electronics', 'Books', 'Clothing', 'Home'];
const statuses = ['In Stock', 'Back Ordered', 'Discontinued'];
const items = ['Laptop', 'Mouse', 'Keyboard', 'Guide', 'Desk Lamp', 'T-Shirt', 'Notebook', 'Monitor'];
const rows = ref(Array.from({ length: 60 }, (_, index) => {
const region = regions[Math.floor(index / 20) % regions.length];
const category = categories[Math.floor(index / 5) % categories.length];
const operationNo = 100 + Math.floor(index / 3) % 4;
const status = statuses[Math.floor(index / 2) % statuses.length];
return {
id: index + 1,
region,
category,
operationNo,
status,
quantity: Math.floor(index / 2) % 2 === 0 ? 100 : 250,
tool: ['T-01', 'T-01', 'T-02', 'T-02'][Math.floor(index / 2) % 4],
name: `${items[index % items.length]} ${String(index + 1).padStart(2, '0')}`,
};
}));
</script>
import React, { useMemo } from 'react';
import { RevoGrid } from '@revolist/react-datagrid';
import { SameValueMergePlugin, StickyCellsPlugin } from '@revolist/revogrid-pro';
const columns = [
{ prop: 'id', name: 'ID', size: 70, pin: 'colPinStart' },
{ prop: 'region', name: 'Region', merge: { sticky: true }, size: 120, pin: 'colPinStart' },
{ prop: 'category', name: 'Category', size: 140 },
{ prop: 'operationNo', name: 'Operation', size: 120 },
{
prop: 'status',
name: 'Status',
merge: {
mergeGroup: 'operation',
mergeLeader: true,
mergeDependsOn: ['category', 'operationNo'],
},
size: 140,
},
{ prop: 'quantity', name: 'Quantity', merge: { mergeGroup: 'operation' }, size: 110 },
{ prop: 'tool', name: 'Tool', merge: { mergeGroup: 'operation' }, size: 110 },
{ prop: 'name', name: 'Product', size: 160 },
];
const regions = ['EMEA', 'AMER', 'APAC'];
const categories = ['Electronics', 'Books', 'Clothing', 'Home'];
const statuses = ['In Stock', 'Back Ordered', 'Discontinued'];
const items = ['Laptop', 'Mouse', 'Keyboard', 'Guide', 'Desk Lamp', 'T-Shirt', 'Notebook', 'Monitor'];
const createRows = () => Array.from({ length: 60 }, (_, index) => {
const region = regions[Math.floor(index / 20) % regions.length];
const category = categories[Math.floor(index / 5) % categories.length];
const operationNo = 100 + Math.floor(index / 3) % 4;
const status = statuses[Math.floor(index / 2) % statuses.length];
return {
id: index + 1,
region,
category,
operationNo,
status,
quantity: Math.floor(index / 2) % 2 === 0 ? 100 : 250,
tool: ['T-01', 'T-01', 'T-02', 'T-02'][Math.floor(index / 2) % 4],
name: `${items[index % items.length]} ${String(index + 1).padStart(2, '0')}`,
};
});
export const SameValueMerged = () => {
const rows = useMemo(createRows, []);
const plugins = useMemo(() => [SameValueMergePlugin, StickyCellsPlugin], []);
return (
<div className="flex grow flex-col gap-2 same-value-merge-demo">
<RevoGrid
className="overflow-hidden grow"
columns={columns}
source={rows}
plugins={plugins}
theme="compact"
hideAttribution
/>
</div>
);
}
import { SameValueMergePlugin, StickyCellsPlugin } from '@revolist/revogrid-pro';
import { Component, ViewEncapsulation } from '@angular/core';
import { RevoGrid } from '@revolist/angular-datagrid';
const regions = ['EMEA', 'AMER', 'APAC'];
const categories = ['Electronics', 'Books', 'Clothing', 'Home'];
const statuses = ['In Stock', 'Back Ordered', 'Discontinued'];
const items = ['Laptop', 'Mouse', 'Keyboard', 'Guide', 'Desk Lamp', 'T-Shirt', 'Notebook', 'Monitor'];
const createRows = () => Array.from({ length: 60 }, (_, index) => {
const region = regions[Math.floor(index / 20) % regions.length];
const category = categories[Math.floor(index / 5) % categories.length];
const operationNo = 100 + Math.floor(index / 3) % 4;
const status = statuses[Math.floor(index / 2) % statuses.length];
return {
id: index + 1,
region,
category,
operationNo,
status,
quantity: Math.floor(index / 2) % 2 === 0 ? 100 : 250,
tool: ['T-01', 'T-01', 'T-02', 'T-02'][Math.floor(index / 2) % 4],
name: `${items[index % items.length]} ${String(index + 1).padStart(2, '0')}`,
};
});
@Component({
selector: 'same-value-merge-grid',
standalone: true,
imports: [RevoGrid],
encapsulation: ViewEncapsulation.None,
template: `
<div class="same-value-merge-demo">
<revo-grid
[source]="rows"
[columns]="columns"
[plugins]="plugins"
[theme]="'compact'"
[hideAttribution]="true"
style="min-height: 400px;"
></revo-grid>
</div>
`,
styles: [`
.same-value-merge-demo {
display: grid;
gap: 8px;
}
`],
})
export class SameValueMergeGridComponent {
plugins = [SameValueMergePlugin, StickyCellsPlugin];
columns = [
{ prop: 'id', name: 'ID', size: 70, pin: 'colPinStart' },
{ prop: 'region', name: 'Region', merge: { sticky: true }, size: 120, pin: 'colPinStart' },
{ prop: 'category', name: 'Category', size: 140 },
{ prop: 'operationNo', name: 'Operation', size: 120 },
{
prop: 'status',
name: 'Status',
merge: {
mergeGroup: 'operation',
mergeLeader: true,
mergeDependsOn: ['category', 'operationNo'],
},
size: 140,
},
{ prop: 'quantity', name: 'Quantity', merge: { mergeGroup: 'operation' }, size: 110 },
{ prop: 'tool', name: 'Tool', merge: { mergeGroup: 'operation' }, size: 110 },
{ prop: 'name', name: 'Product', size: 160 },
];
rows = createRows();
}
Why Use Same Value Merge?
Section titled “Why Use Same Value Merge?”Same Value Merge is particularly useful when you want to:
- Reduce Visual Clutter: Hide repeated values in a column, making the data easier to read
- Highlight Data Patterns: Quickly identify groups of identical values
- Improve Data Presentation: Create a cleaner, more professional look for your grids
Example Use Cases
Section titled “Example Use Cases”- Category Grouping: When displaying items grouped by category, merge cells with the same category name
- Status Display: Merge cells with the same status to make state changes more apparent
- Hierarchical Data: Visually group related items in hierarchical data structures
How It Works
Section titled “How It Works”The Same Value Merge plugin works by:
- Checking each cell against the nearest comparable visible rows above and below it in columns marked with
merge: true - If the values are identical, repeated values are hidden and internal borders are removed
- This creates a visual effect of merged cells while maintaining the original data structure
When row grouping or Pivot row drill-down inserts group rows, those rows participate in the comparison only when they carry the merged column's value. Deeper group rows without that column value are skipped.
Same Value Merge is a visual merge. It does not create real merged grid ranges and does not change selection, focus, copy, or edit behavior. Use the Cell Merge plugin when you need explicit row or column spans.
Configuration
Section titled “Configuration”To enable Same Value Merge for specific columns, set the merge property to true in your column configuration:
const columns = [ { prop: 'id', name: 'ID' }, { prop: 'category', name: 'Category', merge: true }, // Enable merging for this column { prop: 'name', name: 'Name' }];Sticky Merge Start Cells
Section titled “Sticky Merge Start Cells”Use merge: { sticky: true } when the first visible cell in a same-value run should stay visible while users scroll. This marks only the merge start cell as sticky; it does not pin every cell in that row.
import { SameValueMergePlugin, StickyCellsPlugin,} from '@revolist/revogrid-pro';
const columns = [ { prop: 'id', name: 'ID' }, { prop: 'region', name: 'Region', merge: { sticky: true }, }, { prop: 'category', name: 'Category' },];
grid.plugins = [SameValueMergePlugin, StickyCellsPlugin];grid.columns = columns;SameValueMergePlugin can register Sticky Cells automatically for merge.sticky, but adding StickyCellsPlugin explicitly keeps the dependency visible and matches the standalone Sticky Cells setup.
Grouped Merge Boundaries
Section titled “Grouped Merge Boundaries”Some datasets need repeated values to merge only inside another column's run. For example, a quantity column may contain the same value across many rows, but it should stop merging when the controlling workcenter or operation changes.
Use a merge group with one leader column:
const columns = [ { prop: 'batch', name: 'Batch', merge: true }, { prop: 'material', name: 'Material' }, { prop: 'operationNo', name: 'Operation' }, { prop: 'workcenter', name: 'Work Center', merge: { mergeGroup: 'operation', mergeLeader: true, mergeDependsOn: ['material', 'operationNo'], }, }, { prop: 'quantity', name: 'Quantity', merge: { mergeGroup: 'operation' }, }, { prop: 'tool', name: 'Tool', merge: { mergeGroup: 'operation' }, },];workcenter is the leader for the operation group. It merges only while its own value, material, and operationNo all match. quantity and tool are followers: they still require their own values to match, but they cannot merge beyond the leader's boundary.
Merge Configuration
Section titled “Merge Configuration”type SameValueMergeConfig = { mergeGroup?: string; mergeLeader?: boolean; mergeDependsOn?: ColumnProp[]; sticky?: boolean;};mergeGroupconnects columns that should share a leader boundary.mergeLeadermarks the column that defines the boundary for a group.mergeDependsOnadds row fields that must match before the configured column can merge.stickymarks merge start cells for Sticky Cells. Only cells rendered as the start of a merge run become sticky.
If a column uses merge: { mergeGroup: '...' } and no matching leader is found, it falls back to ordinary same-value merge behavior.