Skip to content

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
TypeScript ts
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();
}
Vue vue
<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>
React tsx
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>
  );
}
Angular ts
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();
}

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
  • 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

The Same Value Merge plugin works by:

  1. Checking each cell against the nearest comparable visible rows above and below it in columns marked with merge: true
  2. If the values are identical, repeated values are hidden and internal borders are removed
  3. 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.

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' }
];

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.

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.

type SameValueMergeConfig = {
mergeGroup?: string;
mergeLeader?: boolean;
mergeDependsOn?: ColumnProp[];
sticky?: boolean;
};
  • mergeGroup connects columns that should share a leader boundary.
  • mergeLeader marks the column that defines the boundary for a group.
  • mergeDependsOn adds row fields that must match before the configured column can merge.
  • sticky marks 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.