xref: /aosp_15_r20/development/tools/motion/motion_test_watcher_app/src/app/feature.ts (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
1import { Derivate } from './feature_derivate';
2import {
3  DataPoint,
4  DataPointObject,
5  isNotFound,
6  MotionGoldenFeature,
7} from './golden';
8import {
9  DataPointVisualization,
10  LineGraphVisualization,
11  PROBE_COLORS,
12  Visualization,
13} from './visualization';
14
15export class Feature {
16  constructor(
17    readonly id: string,
18    readonly name: string,
19    readonly type: string,
20    readonly dataPoints: Array<DataPoint>,
21  ) {}
22
23  private _visualization: Visualization | undefined;
24
25  get visualization(): Visualization {
26    if (this._visualization === undefined) {
27      this._visualization = createVisualization(this);
28    }
29
30    return this._visualization;
31  }
32
33  private _subFeatures: Array<Feature> | undefined;
34
35  get subFeatures(): Array<Feature> {
36    if (this._subFeatures === undefined) {
37      this._subFeatures = createSubFeatures(this);
38    }
39
40    return this._subFeatures!!;
41  }
42
43  private _derivates: Array<Derivate> | undefined;
44
45  get derivates(): Array<Derivate> {
46    if (this._derivates === undefined) {
47      this._derivates = createDerivatives(this);
48    }
49    return this._derivates;
50  }
51}
52
53export function recordedFeatureFactory(
54  featureData: MotionGoldenFeature,
55): Feature {
56  return new Feature(
57    featureData.name,
58    featureData.name,
59    featureData.type,
60    featureData.data_points,
61  );
62}
63
64function createVisualization(feature: Feature): Visualization {
65  const { name, type } = feature;
66
67  let color: string | null = null;
68
69  if (name === 'input') {
70    color = PROBE_COLORS[0];
71  } else if (name === 'output_target') {
72    color = PROBE_COLORS[1];
73  } else if (name === 'output') {
74    color = PROBE_COLORS[2];
75  }
76
77  if (name === 'alpha' && type === 'float') {
78    return new LineGraphVisualization(/* minValue */ 0, /*maxValue*/ 1, color);
79  }
80
81  if (['float', 'int', 'dp'].includes(type)) {
82    const numericValues = feature.dataPoints.filter(
83      (it): it is number => typeof it === 'number',
84    );
85    const minValue = Math.min(...numericValues) ?? 0;
86    let maxValue = Math.max(...numericValues) ?? 1;
87
88    if (minValue === maxValue) {
89      maxValue += 1;
90    }
91
92    return new LineGraphVisualization(minValue, maxValue, color);
93  }
94
95  return new DataPointVisualization();
96}
97
98function createDerivatives(feature: Feature): Derivate[] {
99  const { name, type } = feature;
100
101  const result: Derivate[] = [];
102
103  return result;
104}
105
106function createSubFeatures(feature: Feature): Feature[] {
107  const { name, type } = feature;
108
109  switch (type) {
110    case 'intSize':
111    case 'dpSize':
112      return [
113        createSubFeature(feature, 'width', 'float', (point) => point['width']),
114        createSubFeature(
115          feature,
116          'height',
117          'float',
118          (point) => point['height'],
119        ),
120      ];
121    case 'intOffset':
122    case 'dpOffset':
123    case 'offset':
124      return [
125        createSubFeature(feature, 'x', 'float', (point) => point['x']),
126        createSubFeature(feature, 'y', 'float', (point) => point['y']),
127      ];
128
129    case 'animatedVisibilityTransitions':
130      return [
131        ...new Set(
132          feature.dataPoints.flatMap((it) =>
133            Object.keys(it as DataPointObject),
134          ),
135        ),
136      ]
137        .sort()
138        .map((it) =>
139          createSubFeature(
140            feature,
141            it,
142            'animatedVisibilityValues',
143            (point) => point[it],
144          ),
145        );
146
147    case 'animatedVisibilityValues':
148      return [
149        ...new Set(
150          feature.dataPoints.flatMap((it) =>
151            it ?
152            Object.keys(it as DataPointObject) : [],
153          ),
154        ),
155      ]
156        .sort()
157        .flatMap((name) => {
158          let type: string;
159
160          switch (name) {
161            case 'alpha':
162              type = 'float';
163              break;
164            case 'slide':
165              type = 'intOffset';
166              break;
167            case 'scale':
168              type = 'float';
169              break;
170            case 'size':
171              type = 'intSize';
172              break;
173            default:
174              return [];
175          }
176
177          return [createSubFeature(feature, name, type, (point) => point[name])];
178        })
179  }
180
181  return [];
182}
183
184function createSubFeature(
185  parent: Feature,
186  key: string,
187  type: string,
188  extract: (dataPoint: DataPointObject) => DataPoint,
189): Feature {
190  return new Feature(
191    `${parent.id}::${key}`,
192    `${parent.name}[${key}]`,
193    type,
194    parent.dataPoints.map((it) => {
195      if (!isNotFound(it) && it instanceof Object)
196        return extract(it as DataPointObject);
197
198      return { type: 'not_found' };
199    }),
200  );
201}
202