xref: /aosp_15_r20/external/perfetto/ui/src/components/widgets/charts/histogram/state.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1// Copyright (C) 2024 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14// import {stringifyJsonWithBigints} from '../../../../base/json_utils';
15import {raf} from '../../../../core/raf_scheduler';
16import {Engine} from '../../../../trace_processor/engine';
17import {Row} from '../../../../trace_processor/query_result';
18import {ChartData, ChartState, VegaLiteChartSpec} from '../chart';
19
20export interface HistogramChartConfig extends VegaLiteChartSpec {
21  binAxisType: 'nominal' | 'quantitative';
22  binAxis: 'x' | 'y';
23  countAxis: 'x' | 'y';
24  sort: string;
25  isBinned: boolean;
26  labelLimit?: number;
27}
28
29export class HistogramState implements ChartState {
30  data?: ChartData;
31  spec?: VegaLiteChartSpec;
32
33  constructor(
34    readonly engine: Engine,
35    readonly query: string,
36    readonly columns: string[],
37    private aggregationType?: 'nominal' | 'quantitative',
38  ) {
39    this.loadData();
40  }
41
42  createHistogramVegaSpec(): VegaLiteChartSpec {
43    const binAxisEncoding = {
44      bin: this.aggregationType !== 'nominal',
45      field: this.columns[0],
46      type: this.aggregationType,
47      title: this.columns[0],
48      sort: this.aggregationType === 'nominal' && {
49        op: 'count',
50        order: 'descending',
51      },
52      axis: {
53        labelLimit: 500,
54      },
55    };
56
57    const countAxisEncoding = {
58      aggregate: 'count',
59      title: 'Count',
60    };
61
62    const spec: VegaLiteChartSpec = {
63      $schema: 'https://vega.github.io/schema/vega-lite/v5.json',
64      width: 'container',
65      mark: 'bar',
66      data: {
67        values: this.data?.rows,
68      },
69      encoding: {
70        x:
71          this.aggregationType !== 'nominal'
72            ? binAxisEncoding
73            : countAxisEncoding,
74        y:
75          this.aggregationType !== 'nominal'
76            ? countAxisEncoding
77            : binAxisEncoding,
78      },
79    };
80
81    return spec;
82  }
83
84  async loadData() {
85    const res = await this.engine.query(`
86      SELECT ${this.columns[0]}
87      FROM (
88        ${this.query}
89      )
90    `);
91
92    const rows: Row[] = [];
93
94    let hasQuantitativeData = false;
95
96    for (const it = res.iter({}); it.valid(); it.next()) {
97      const rowVal = it.get(this.columns[0]);
98      if (typeof rowVal === 'bigint') {
99        hasQuantitativeData = true;
100      }
101
102      rows.push({
103        [this.columns[0]]: rowVal,
104      });
105    }
106
107    if (this.aggregationType === undefined) {
108      this.aggregationType = hasQuantitativeData ? 'quantitative' : 'nominal';
109    }
110
111    this.data = {
112      rows,
113    };
114
115    this.spec = this.createHistogramVegaSpec();
116    raf.scheduleFullRedraw();
117  }
118
119  isLoading(): boolean {
120    return this.data === undefined;
121  }
122}
123