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