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