• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
14import m from 'mithril';
15import {
16  ChartAttrs,
17  ChartType,
18  renderChart,
19} from '../../../components/widgets/charts/chart';
20import {Trace} from '../../../public/trace';
21import {Button} from '../../../widgets/button';
22import {Icons} from '../../../base/semantic_icons';
23import {
24  SplitPanel,
25  SplitPanelDrawerVisibility,
26} from '../../../widgets/split_panel';
27import {VisViewSource} from './view_source';
28import {AddChartMenuItem} from '../../../components/widgets/charts/add_chart_menu';
29import {exists} from '../../../base/utils';
30import {DetailsShell} from '../../../widgets/details_shell';
31import {SqlTable} from '../../../components/widgets/sql/table/table';
32import {sqlValueToSqliteString} from '../../../trace_processor/sql_utils';
33import {renderFilters} from '../../../components/widgets/sql/table/filters';
34import {ExplorePageState} from '../explore_page';
35
36export interface DataVisualiserAttrs {
37  trace: Trace;
38  readonly state: ExplorePageState;
39}
40
41export class DataVisualiser implements m.ClassComponent<DataVisualiserAttrs> {
42  private visibility = SplitPanelDrawerVisibility.VISIBLE;
43
44  constructor({attrs}: m.Vnode<DataVisualiserAttrs>) {
45    if (attrs.state.selectedNode === undefined) return;
46
47    attrs.state.activeViewSource = new VisViewSource(
48      attrs.trace,
49      attrs.state.selectedNode,
50    );
51  }
52
53  private renderSqlTable(state: ExplorePageState) {
54    const sqlTableViewState = state.activeViewSource?.visViews?.sqlTableState;
55
56    if (sqlTableViewState === undefined) return;
57
58    const range = sqlTableViewState.getDisplayedRange();
59    const rowCount = sqlTableViewState.getTotalRowCount();
60
61    const navigation = [
62      exists(range) &&
63        exists(rowCount) &&
64        `Showing rows ${range.from}-${range.to} of ${rowCount}`,
65      m(Button, {
66        icon: Icons.GoBack,
67        disabled: !sqlTableViewState.canGoBack(),
68        onclick: () => sqlTableViewState!.goBack(),
69      }),
70      m(Button, {
71        icon: Icons.GoForward,
72        disabled: !sqlTableViewState.canGoForward(),
73        onclick: () => sqlTableViewState!.goForward(),
74      }),
75    ];
76
77    return m(
78      DetailsShell,
79      {
80        title: 'Explore Table',
81        buttons: navigation,
82        fillParent: false,
83      },
84      m('div', renderFilters(sqlTableViewState.filters)),
85      m(SqlTable, {
86        state: sqlTableViewState,
87        addColumnMenuItems: (_, columnAlias) => {
88          const chartAttrs = {
89            data: state.activeViewSource?.data,
90            columns: [columnAlias],
91          };
92
93          return m(AddChartMenuItem, {
94            chartOptions: [
95              {
96                chartType: ChartType.BAR_CHART,
97                ...chartAttrs,
98                onIntervalSelection: (value) => {
99                  const range = `(${value[columnAlias].map(sqlValueToSqliteString).join(', ')})`;
100                  state.activeViewSource?.filters.addFilter({
101                    op: (cols) => `${cols[0]} IN ${range}`,
102                    columns: [columnAlias],
103                  });
104                },
105                onPointSelection: (item) => {
106                  const value = sqlValueToSqliteString(item.datum[columnAlias]);
107                  state.activeViewSource?.filters.addFilter({
108                    op: (cols) => `${cols[0]} = ${value}`,
109                    columns: [columnAlias],
110                  });
111                },
112              },
113              {
114                chartType: ChartType.HISTOGRAM,
115                ...chartAttrs,
116                onIntervalSelection: (value) => {
117                  const range = `${value[columnAlias][0]} AND ${value[columnAlias][1]}`;
118                  state.activeViewSource?.filters.addFilter({
119                    op: (cols) => `${cols[0]} BETWEEN ${range}`,
120                    columns: [columnAlias],
121                  });
122                },
123                onPointSelection: (item) => {
124                  const minValue = item.datum[`bin_maxbins_10_${columnAlias}`];
125                  const maxValue =
126                    item.datum[`bin_maxbins_10_${columnAlias}_end`];
127                  state.activeViewSource?.filters.addFilter({
128                    op: (cols) =>
129                      `${cols[0]} BETWEEN ${minValue} AND ${maxValue}`,
130                    columns: [columnAlias],
131                  });
132                },
133              },
134            ],
135            addChart: (chart) => state.activeViewSource?.addChart(chart),
136          });
137        },
138      }),
139    );
140  }
141
142  private renderRemovableChart(chart: ChartAttrs, state: ExplorePageState) {
143    return m(
144      '.pf-chart-card',
145      {
146        key: `${chart.chartType}-${chart.columns[0]}`,
147      },
148      m(Button, {
149        className: 'pf-chart-card__button',
150        icon: Icons.Close,
151        onclick: () => {
152          state.activeViewSource?.removeChart(chart);
153        },
154      }),
155      m('.pf-chart-card__chart', renderChart(chart)),
156    );
157  }
158
159  view({attrs}: m.Vnode<DataVisualiserAttrs>) {
160    const {state} = attrs;
161
162    return m(
163      SplitPanel,
164      {
165        visibility: this.visibility,
166        onVisibilityChange: (visibility) => {
167          this.visibility = visibility;
168        },
169        drawerContent: m(
170          '.pf-chart-container',
171          state.activeViewSource?.visViews !== undefined &&
172            Array.from(state.activeViewSource?.visViews.charts.values()).map(
173              (chart) => this.renderRemovableChart(chart, state),
174            ),
175        ),
176      },
177      m('.pf-chart-card', this.renderSqlTable(state)),
178    );
179  }
180}
181