LCOV - code coverage report
Current view: top level - dashql-app/src/compute - cross_filters.ts Coverage Total Hit
Test: DashQL 915828b Lines: 97.3 % 110 107
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1              : import { ScalarFilter } from './sql/sqlframe_builder.js';
       2              : import { VariantKind } from "../utils/variant.js";
       3              : import { OrdinalGridColumnGroup, StringGridColumnGroup } from "./computation_types.js";
       4              : 
       5            1 : export const HISTOGRAM_FILTER = Symbol("HISTOGRAM_FILTER");
       6            1 : export const MOST_FREQUENT_FILTER = Symbol("MOST_FREQUENT_FILTER");
       7              : 
       8              : export type CrossFilterPredicate =
       9              :     | VariantKind<typeof HISTOGRAM_FILTER, HistogramFilterPredicate>
      10              :     | VariantKind<typeof MOST_FREQUENT_FILTER, MostFrequentFilterPredicate>
      11              :     ;
      12              : 
      13              : export interface HistogramFilterPredicate {
      14              :     /// The selection range
      15              :     selection: [number, number];
      16              :     /// The scalar filters
      17              :     filters: ScalarFilter[];
      18              : }
      19              : 
      20              : export interface MostFrequentFilterPredicate {
      21              :     /// The stable identifier of the selected value
      22              :     valueId: number;
      23              :     /// The scalar filters
      24              :     filters: ScalarFilter[];
      25              : }
      26              : 
      27            1 : export class CrossFilters {
      28              :     /// The column filters
      29            1 :     columnFilters: { [key: number]: CrossFilterPredicate };
      30              : 
      31            1 :     constructor() {
      32            1 :         this.columnFilters = {};
      33            1 :     }
      34              : 
      35            1 :     public clone(): CrossFilters {
      36            1 :         const copy = new CrossFilters();
      37            1 :         copy.columnFilters = {
      38            1 :             ...this.columnFilters
      39            1 :         };
      40            1 :         return copy;
      41            1 :     }
      42              : 
      43            1 :     public equals(other: CrossFilters): boolean {
      44            1 :         if (Object.keys(this.columnFilters).length != Object.keys(other.columnFilters).length) {
      45            1 :             return false;
      46              :         }
      47            1 :         for (const [key, a] of Object.entries(this.columnFilters)) {
      48            1 :             const bFilter = other.columnFilters[+key];
      49            1 :             if (bFilter === undefined || a.type != bFilter.type) {
      50            1 :                 return false;
      51              :             }
      52            1 :             switch (a.type) {
      53            1 :                 case HISTOGRAM_FILTER: {
      54            1 :                     const b = bFilter.value as HistogramFilterPredicate;
      55            1 :                     if (a.value.selection == null) {
      56            0 :                         if (b.selection != null) {
      57            0 :                             return false;
      58              :                         }
      59            1 :                     } else {
      60            1 :                         if (b.selection == null) {
      61            0 :                             return false;
      62              :                         }
      63            1 :                         if (
      64            1 :                             a.value.selection[0] != b.selection[0] ||
      65            1 :                             a.value.selection[1] != b.selection[1]
      66              :                         ) {
      67            1 :                             return false;
      68              :                         }
      69            1 :                     }
      70            1 :                     break;
      71            1 :                 }
      72            1 :                 case MOST_FREQUENT_FILTER: {
      73            1 :                     const b = bFilter.value as MostFrequentFilterPredicate;
      74            1 :                     if (a.value.valueId != b.valueId) {
      75            1 :                         return false;
      76              :                     }
      77            1 :                     break;
      78            1 :                 }
      79            1 :             }
      80            1 :         }
      81            1 :         return true;
      82            1 :     }
      83              : 
      84            1 :     public createFilterTransforms(): ScalarFilter[] {
      85            1 :         const transforms: ScalarFilter[] = [];
      86            1 :         for (const v of Object.values(this.columnFilters)) {
      87            1 :             switch (v.type) {
      88            1 :                 case HISTOGRAM_FILTER:
      89            1 :                     for (const filter of v.value.filters) {
      90            1 :                         transforms.push(filter);
      91              :                     }
      92            1 :                     break;
      93            1 :                 case MOST_FREQUENT_FILTER:
      94            1 :                     for (const filter of v.value.filters) {
      95            1 :                         transforms.push(filter);
      96              :                     }
      97              :                     break;
      98            1 :             }
      99              :         }
     100            1 :         return transforms;
     101            1 :     }
     102              : 
     103            1 :     public containsHistogramFilter(columnGroupId: number, brush: [number, number] | null): boolean {
     104            1 :         const existing = this.columnFilters[columnGroupId];
     105            1 :         if (brush == null) {
     106            1 :             return existing === undefined;
     107              :         }
     108            1 :         if (existing === undefined || existing.type != HISTOGRAM_FILTER) {
     109            1 :             return false;
     110              :         }
     111            1 :         return existing.value.selection[0] == brush[0]
     112            1 :             && existing.value.selection[1] == brush[1];
     113            1 :     }
     114              : 
     115            1 :     public addHistogramFilter(columnGroupId: number, columnGroup: OrdinalGridColumnGroup, brush: [number, number] | null) {
     116            1 :         if (brush == null) {
     117            1 :             delete this.columnFilters[columnGroupId];
     118            1 :             return;
     119            1 :         }
     120            1 :         let filters: ScalarFilter[] = [];
     121            1 :         if (columnGroup.binFieldName != null && brush != null) {
     122            1 :             filters.push({ fieldName: columnGroup.binFieldName, op: ">=", value: brush[0] });
     123            1 :             filters.push({ fieldName: columnGroup.binFieldName, op: "<=", value: brush[1] });
     124            1 :         }
     125            1 :         this.columnFilters[columnGroupId] = {
     126            1 :             type: HISTOGRAM_FILTER,
     127            1 :             value: {
     128            1 :                 selection: brush,
     129            1 :                 filters,
     130            1 :             }
     131            1 :         };
     132            1 :     }
     133              : 
     134            1 :     public containsMostFrequentValueFilter(columnGroupId: number, valueId: number | null): boolean {
     135            1 :         const existing = this.columnFilters[columnGroupId];
     136            1 :         if (valueId == null) {
     137            1 :             return existing === undefined;
     138              :         }
     139            1 :         return existing?.type == MOST_FREQUENT_FILTER && existing.value.valueId == valueId;
     140            1 :     }
     141              : 
     142            1 :     public addMostFrequentValueFilter(columnGroupId: number, columnGroup: StringGridColumnGroup, valueId: number | null) {
     143            1 :         if (valueId == null) {
     144            1 :             delete this.columnFilters[columnGroupId];
     145            1 :             return;
     146            1 :         }
     147            1 :         if (columnGroup.valueIdFieldName == null) {
     148            1 :             return;
     149              :         }
     150            1 :         this.columnFilters[columnGroupId] = {
     151            1 :             type: MOST_FREQUENT_FILTER,
     152            1 :             value: {
     153            1 :                 valueId,
     154            1 :                 filters: [{ fieldName: columnGroup.valueIdFieldName, op: "=", value: valueId }],
     155            1 :             }
     156            1 :         };
     157            1 :     }
     158            1 : }
        

Generated by: LCOV version 2.0-1