Lattice Grid Buy a licence

demo D222

Chaining, three levels deep

sales → revenue per rep → top three and a profile, with tiles reading the middle level; one filter moves all of it

source: { mode: 'derived', from, groupBy, profile } · createStat

Building…
Loading a live grid…

The configuration

import * as ng from '@angular/core';
import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { bootstrapApplication } from '@angular/platform-browser';
import { createGrid, createStat } from '@toclocoinc/lattice-grid';
import '@toclocoinc/lattice-grid/css';
import createLatticeGrid from '@toclocoinc/lattice-grid/modules/angular';

const { LatticeGridComponent } = createLatticeGrid({ ng, createGrid });

const USD = { style: 'currency', currency: 'USD', maximumFractionDigits: 0 };
const WHOLE = { maximumFractionDigits: 0 };
const bookColumns = [
  { field: 'rep', title: 'Rep' },
  { field: 'region', title: 'Region', filter: { type: 'set' } },
  { field: 'amount', title: 'Deal', type: 'number', format: USD, total: 'sum' },
];
const byRepColumns = [
  { field: 'rep', title: 'Rep' },
  { field: 'revenue', title: 'Revenue', type: 'number', format: USD },
  { field: 'deals', title: 'Deals', type: 'number' },
];
const top3Columns = [
  { field: 'rep', title: 'Rep' },
  { field: 'revenue', title: 'Revenue', type: 'number', format: USD },
];
const profileColumns = [
  { field: 'column', title: 'Measure' },
  { field: 'mean', title: 'Mean', type: 'number', format: WHOLE },
  { field: 'stddev', title: 'Spread', type: 'number', format: WHOLE },
  { field: 'min', title: 'Weakest', type: 'number', format: WHOLE },
  { field: 'max', title: 'Strongest', type: 'number', format: WHOLE },
];

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, LatticeGridComponent],
  template:
    '<div #tiles style="display:grid;grid-template-columns:repeat(5, 1fr);gap:12px;margin-bottom:16px"></div>' +
    '<lattice-grid #book [config]="bookGrid" style="display:block;height:320px"></lattice-grid>' +
    '<div style="display:flex;gap:16px;margin-top:16px">' +
    '  <lattice-grid #byRepGrid *ngIf="byRep" [config]="byRep" (ready)="onByRep()" style="flex:1"></lattice-grid>' +
    '  <lattice-grid *ngIf="top3" [config]="top3" style="flex:1"></lattice-grid>' +
    '  <lattice-grid *ngIf="profile" [config]="profile" style="flex:1"></lattice-grid>' +
    '</div>',
})
export class AppComponent implements AfterViewInit {
  @ViewChild('book') bookRef!: LatticeGridComponent;
  @ViewChild('byRepGrid') byRepRef!: LatticeGridComponent;
  @ViewChild('tiles') tilesEl!: ng.ElementRef<HTMLElement>;
  byRep: any = null;
  top3: any = null;
  profile: any = null;

  bookGrid = { rowKey: 'id', toolPanel: { side: 'left', panels: ['filters', 'columns'] }, columns: bookColumns, rows: [/* e.g. { id: 'S0', rep: 'Ana Bright', region: 'EMEA', amount: 2140 } */] };

  // Level 1: revenue per rep, derived from the book and following its filter.
  ngAfterViewInit() {
    const book = this.bookRef.grid;
    this.byRep = { autoHeight: true, columns: byRepColumns, source: { mode: 'derived', from: book, follow: 'filtered', refresh: 'live', groupBy: 'rep',
      select: { revenue: { of: 'amount', fn: 'sum' }, deals: { fn: 'count' } },
      sort: [{ col: 'revenue', dir: 'desc' }] } };
  }

  // Level 2: derived from byRep, not from the book, so the chain composes. Runs
  // once byRep is a live instance, which its ready event announces.
  onByRep() {
    const rep = this.byRepRef.grid;
    this.top3 = { autoHeight: true, columns: top3Columns, source: { mode: 'derived', from: rep, refresh: 'live', sort: [{ col: 'revenue', dir: 'desc' }], limit: 3 } };
    this.profile = { autoHeight: true, columns: profileColumns, source: { mode: 'derived', from: rep, profile: ['revenue'], refresh: 'live' } };
    // Each tile is one reduced figure over byRep. show: 'rep' turns max into an
    // argmax, naming the rep behind the figure.
    const tile = () => this.tilesEl.nativeElement.appendChild(document.createElement('div'));
    createStat({ grid: rep, container: tile(), title: 'Revenue concentration', of: 'revenue', fn: 'gini', goodWhen: 'down', footer: '0 = even, 1 = one rep has it all' });
    createStat({ grid: rep, container: tile(), title: 'Best rep', of: 'revenue', fn: 'max', show: 'rep' });
    createStat({ grid: rep, container: tile(), title: 'Weakest rep', of: 'revenue', fn: 'min', show: 'rep' });
    createStat({ grid: rep, container: tile(), title: 'Spread across reps', of: 'revenue', fn: 'stddev' });
    createStat({ grid: rep, container: tile(), title: 'Reps selling', fn: 'count' });
  }
}

bootstrapApplication(AppComponent);