diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 7a152080fd..af27cbfda7 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -27,7 +27,7 @@
"@ngrx/store-devtools": "^20.1.0",
"@ngx-translate/core": "^15.0.0",
"@ngx-translate/http-loader": "^8.0.0",
- "@swimlane/ngx-charts": "^20.5.0",
+ "@swimlane/ngx-charts": "^23.1.0",
"ang-jsoneditor": "^3.1.1",
"angular2-notifications": "^16.0.1",
"ansi-to-html": "^0.7.2",
@@ -6540,36 +6540,36 @@
"license": "ISC"
},
"node_modules/@swimlane/ngx-charts": {
- "version": "20.5.0",
- "resolved": "https://registry.npmjs.org/@swimlane/ngx-charts/-/ngx-charts-20.5.0.tgz",
- "integrity": "sha512-PNBIHdu/R3ceD7jnw1uCBVOj4k3T6IxfdW6xsDsglGkZyoWMEEq4tLoEurjLEKzmDtRv9c35kVNOXy0lkOuXeA==",
+ "version": "23.1.0",
+ "resolved": "https://registry.npmjs.org/@swimlane/ngx-charts/-/ngx-charts-23.1.0.tgz",
+ "integrity": "sha512-f2lBc8M9164PtylLDLd98XqO6owSw4LQMT9L/vQovT3nIjMO7t5l9JnytyOB8s5KImo3p+7x+1/HYxBZ5iOVWA==",
"license": "MIT",
"dependencies": {
- "d3-array": "^3.1.1",
+ "d3-array": "^3.2.0",
"d3-brush": "^3.0.0",
"d3-color": "^3.1.0",
"d3-ease": "^3.0.1",
"d3-format": "^3.1.0",
- "d3-hierarchy": "^3.1.0",
+ "d3-hierarchy": "^3.1.2",
"d3-interpolate": "^3.0.1",
"d3-sankey": "^0.12.3",
"d3-scale": "^4.0.2",
"d3-selection": "^3.0.0",
"d3-shape": "^3.2.0",
- "d3-time-format": "^3.0.0",
+ "d3-time-format": "^4.1.0",
"d3-transition": "^3.0.1",
- "rfdc": "^1.3.0",
- "tslib": "^2.0.0"
+ "gradient-path": "^2.3.0",
+ "tslib": "^2.3.1"
},
"peerDependencies": {
- "@angular/animations": ">=12.0.0",
- "@angular/cdk": ">=12.0.0",
- "@angular/common": ">=12.0.0",
- "@angular/core": ">=12.0.0",
- "@angular/forms": ">=12.0.0",
- "@angular/platform-browser": ">=12.0.0",
- "@angular/platform-browser-dynamic": ">=12.0.0",
- "rxjs": "^6.5.3 || ^7.4.0"
+ "@angular/animations": "18.x || 19.x || 20.x",
+ "@angular/cdk": "18.x || 19.x || 20.x",
+ "@angular/common": "18.x || 19.x || 20.x",
+ "@angular/core": "18.x || 19.x || 20.x",
+ "@angular/forms": "18.x || 19.x || 20.x",
+ "@angular/platform-browser": "18.x || 19.x || 20.x",
+ "@angular/platform-browser-dynamic": "18.x || 19.x || 20.x",
+ "rxjs": "7.x"
}
},
"node_modules/@tootallnate/once": {
@@ -7053,6 +7053,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/tinycolor2": {
+ "version": "1.4.6",
+ "resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.6.tgz",
+ "integrity": "sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw==",
+ "license": "MIT"
+ },
"node_modules/@types/tough-cookie": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
@@ -9984,38 +9990,17 @@
}
},
"node_modules/d3-time-format": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-3.0.0.tgz",
- "integrity": "sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==",
- "license": "BSD-3-Clause",
- "dependencies": {
- "d3-time": "1 - 2"
- }
- },
- "node_modules/d3-time-format/node_modules/d3-array": {
- "version": "2.12.1",
- "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
- "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
- "license": "BSD-3-Clause",
- "dependencies": {
- "internmap": "^1.0.0"
- }
- },
- "node_modules/d3-time-format/node_modules/d3-time": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz",
- "integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==",
- "license": "BSD-3-Clause",
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
+ "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
+ "license": "ISC",
"dependencies": {
- "d3-array": "2"
+ "d3-time": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
}
},
- "node_modules/d3-time-format/node_modules/internmap": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz",
- "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==",
- "license": "ISC"
- },
"node_modules/d3-timer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
@@ -12149,6 +12134,15 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/gradient-path": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/gradient-path/-/gradient-path-2.3.0.tgz",
+ "integrity": "sha512-vZdF/Z0EpqUztzWXFjFC16lqcialHacYoRonslk/bC6CuujkuIrqx7etlzdYHY4SnUU94LRWESamZKfkGh7yYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "tinygradient": "^1.0.0"
+ }
+ },
"node_modules/gzip-size": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz",
@@ -18185,6 +18179,7 @@
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
+ "dev": true,
"license": "MIT"
},
"node_modules/rimraf": {
@@ -20195,6 +20190,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/tinycolor2": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz",
+ "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==",
+ "license": "MIT"
+ },
"node_modules/tinyglobby": {
"version": "0.2.14",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
@@ -20212,6 +20213,16 @@
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
+ "node_modules/tinygradient": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/tinygradient/-/tinygradient-1.1.5.tgz",
+ "integrity": "sha512-8nIfc2vgQ4TeLnk2lFj4tRLvvJwEfQuabdsmvDdQPT0xlk9TaNtpGd6nNRxXoK6vQhN6RSzj+Cnp5tTQmpxmbw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/tinycolor2": "^1.4.0",
+ "tinycolor2": "^1.0.0"
+ }
+ },
"node_modules/tmpl": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index ec21da473c..ff7d2afc97 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -56,7 +56,7 @@
"@ngrx/store-devtools": "^20.1.0",
"@ngx-translate/core": "^15.0.0",
"@ngx-translate/http-loader": "^8.0.0",
- "@swimlane/ngx-charts": "^20.5.0",
+ "@swimlane/ngx-charts": "^23.1.0",
"ang-jsoneditor": "^3.1.1",
"angular2-notifications": "^16.0.1",
"ansi-to-html": "^0.7.2",
diff --git a/frontend/projects/upgrade/src/app/features/dashboard/experiments/pages/experiment-details-page/experiment-details-page-content/experiment-metrics-data-section-card/experiment-metrics-data-section-card.component.html b/frontend/projects/upgrade/src/app/features/dashboard/experiments/pages/experiment-details-page/experiment-details-page-content/experiment-metrics-data-section-card/experiment-metrics-data-section-card.component.html
index 0f9d57df43..160fc1fea0 100644
--- a/frontend/projects/upgrade/src/app/features/dashboard/experiments/pages/experiment-details-page/experiment-details-page-content/experiment-metrics-data-section-card/experiment-metrics-data-section-card.component.html
+++ b/frontend/projects/upgrade/src/app/features/dashboard/experiments/pages/experiment-details-page/experiment-details-page-content/experiment-metrics-data-section-card/experiment-metrics-data-section-card.component.html
@@ -16,5 +16,7 @@
-
+
diff --git a/frontend/projects/upgrade/src/app/features/dashboard/experiments/pages/experiment-details-page/experiment-details-page-content/experiment-metrics-data-section-card/experiment-metrics-data-section-card.component.scss b/frontend/projects/upgrade/src/app/features/dashboard/experiments/pages/experiment-details-page/experiment-details-page-content/experiment-metrics-data-section-card/experiment-metrics-data-section-card.component.scss
index e69de29bb2..112cbe419e 100644
--- a/frontend/projects/upgrade/src/app/features/dashboard/experiments/pages/experiment-details-page/experiment-details-page-content/experiment-metrics-data-section-card/experiment-metrics-data-section-card.component.scss
+++ b/frontend/projects/upgrade/src/app/features/dashboard/experiments/pages/experiment-details-page/experiment-details-page-content/experiment-metrics-data-section-card/experiment-metrics-data-section-card.component.scss
@@ -0,0 +1,3 @@
+.data-container {
+ padding: 32px;
+}
diff --git a/frontend/projects/upgrade/src/app/features/dashboard/experiments/pages/experiment-details-page/experiment-details-page-content/experiment-metrics-data-section-card/experiment-metrics-data-section-card.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/experiments/pages/experiment-details-page/experiment-details-page-content/experiment-metrics-data-section-card/experiment-metrics-data-section-card.component.ts
index bdbb70b6f8..0a7490792d 100644
--- a/frontend/projects/upgrade/src/app/features/dashboard/experiments/pages/experiment-details-page/experiment-details-page-content/experiment-metrics-data-section-card/experiment-metrics-data-section-card.component.ts
+++ b/frontend/projects/upgrade/src/app/features/dashboard/experiments/pages/experiment-details-page/experiment-details-page-content/experiment-metrics-data-section-card/experiment-metrics-data-section-card.component.ts
@@ -6,7 +6,7 @@ import {
} from '../../../../../../../shared-standalone-component-lib/components';
import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
-import { ExperimentMetricsDataComponent } from './experiment-metrics-data/experiment-metrics-data.component';
+import { ExperimentQueryResultComponent } from './experiment-query-result/experiment-query-result.component';
import { ExperimentService } from '../../../../../../../core/experiments/experiments.service';
import { AuthService } from '../../../../../../../core/auth/auth.service';
@@ -17,7 +17,7 @@ import { AuthService } from '../../../../../../../core/auth/auth.service';
CommonSectionCardComponent,
CommonSectionCardTitleHeaderComponent,
CommonSectionCardActionButtonsComponent,
- ExperimentMetricsDataComponent,
+ ExperimentQueryResultComponent,
TranslateModule,
],
templateUrl: './experiment-metrics-data-section-card.component.html',
diff --git a/frontend/projects/upgrade/src/app/features/dashboard/experiments/pages/experiment-details-page/experiment-details-page-content/experiment-metrics-data-section-card/experiment-metrics-data/experiment-metrics-data.component.html b/frontend/projects/upgrade/src/app/features/dashboard/experiments/pages/experiment-details-page/experiment-details-page-content/experiment-metrics-data-section-card/experiment-metrics-data/experiment-metrics-data.component.html
deleted file mode 100644
index a97653791f..0000000000
--- a/frontend/projects/upgrade/src/app/features/dashboard/experiments/pages/experiment-details-page/experiment-details-page-content/experiment-metrics-data-section-card/experiment-metrics-data/experiment-metrics-data.component.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
experiment-metrics-data works!
-
diff --git a/frontend/projects/upgrade/src/app/features/dashboard/experiments/pages/experiment-details-page/experiment-details-page-content/experiment-metrics-data-section-card/experiment-metrics-data/experiment-metrics-data.component.scss b/frontend/projects/upgrade/src/app/features/dashboard/experiments/pages/experiment-details-page/experiment-details-page-content/experiment-metrics-data-section-card/experiment-metrics-data/experiment-metrics-data.component.scss
deleted file mode 100644
index 112cbe419e..0000000000
--- a/frontend/projects/upgrade/src/app/features/dashboard/experiments/pages/experiment-details-page/experiment-details-page-content/experiment-metrics-data-section-card/experiment-metrics-data/experiment-metrics-data.component.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-.data-container {
- padding: 32px;
-}
diff --git a/frontend/projects/upgrade/src/app/features/dashboard/experiments/pages/experiment-details-page/experiment-details-page-content/experiment-metrics-data-section-card/experiment-metrics-data/experiment-metrics-data.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/experiments/pages/experiment-details-page/experiment-details-page-content/experiment-metrics-data-section-card/experiment-metrics-data/experiment-metrics-data.component.ts
deleted file mode 100644
index 3fc9dc4651..0000000000
--- a/frontend/projects/upgrade/src/app/features/dashboard/experiments/pages/experiment-details-page/experiment-details-page-content/experiment-metrics-data-section-card/experiment-metrics-data/experiment-metrics-data.component.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { ChangeDetectionStrategy, Component } from '@angular/core';
-import { CommonModule } from '@angular/common';
-import { TranslateModule } from '@ngx-translate/core';
-
-@Component({
- selector: 'app-experiment-metrics-data',
- imports: [CommonModule, TranslateModule],
- templateUrl: './experiment-metrics-data.component.html',
- styleUrl: './experiment-metrics-data.component.scss',
- changeDetection: ChangeDetectionStrategy.OnPush,
-})
-export class ExperimentMetricsDataComponent {
- // TODO: Implement metrics data functionality
-}
diff --git a/frontend/projects/upgrade/src/app/features/dashboard/experiments/pages/experiment-details-page/experiment-details-page-content/experiment-metrics-data-section-card/experiment-query-result/experiment-query-result.component.html b/frontend/projects/upgrade/src/app/features/dashboard/experiments/pages/experiment-details-page/experiment-details-page-content/experiment-metrics-data-section-card/experiment-query-result/experiment-query-result.component.html
new file mode 100644
index 0000000000..cedb025f2f
--- /dev/null
+++ b/frontend/projects/upgrade/src/app/features/dashboard/experiments/pages/experiment-details-page/experiment-details-page-content/experiment-metrics-data-section-card/experiment-query-result/experiment-query-result.component.html
@@ -0,0 +1,182 @@
+
+
+ {{
+ 'home.experiment-query-result.main-effect.title.text' | translate
+ }}
+
+
+
+
+ {{ query.name }}
+
+
+
+
+
+
+
+
+
+
+ | {{ factor | uppercase }} |
+
+
+
+
+ {{ model.name }}
+
+ {{ model.value }}
+
+ ( n = {{ model.extra }} )
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ model.name }}
+
+ {{ model.value }}
+
+ ( n = {{ model.extra }} )
+
+
+
+
+
+
+
+
+
+
+
{{
+ 'home.experiment-query-result.interaction-effect.title.text' | translate
+ }}
+
+
+
+
+ {{ query.name }}
+
+
+
+
+
+
+
+
+
+ |
+ {{ factor | uppercase }}
+ |
+
+
+ |
+ ({{ factor | uppercase }})
+ |
+
+
+
+
+
+
+
+
+ {{ model.series }}
+
+ {{ model.value }}
+
+ ( n = {{ model.participantsLogged }} )
+
+
+
+
+
+
+
+
+
+
+
+
= 3">
+
+
+
+
+ {{ column.label | uppercase }}
+ {{ row[column.name] }}
+
+
+
+
+ {{ column.label | uppercase }}
+ {{ row[column.name] }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/projects/upgrade/src/app/features/dashboard/experiments/pages/experiment-details-page/experiment-details-page-content/experiment-metrics-data-section-card/experiment-query-result/experiment-query-result.component.scss b/frontend/projects/upgrade/src/app/features/dashboard/experiments/pages/experiment-details-page/experiment-details-page-content/experiment-metrics-data-section-card/experiment-query-result/experiment-query-result.component.scss
new file mode 100644
index 0000000000..ac36338cb1
--- /dev/null
+++ b/frontend/projects/upgrade/src/app/features/dashboard/experiments/pages/experiment-details-page/experiment-details-page-content/experiment-metrics-data-section-card/experiment-query-result/experiment-query-result.component.scss
@@ -0,0 +1,98 @@
+.experiment-queries-result {
+ .metric-data-text {
+ display: block;
+ }
+
+ .main-effect-data-text {
+ display: block;
+ }
+
+ .interaction-effect-data-text {
+ display: block;
+ }
+
+ .metric-data-detail-text {
+ color: var(--grey-3);
+ margin-bottom: 10px;
+ }
+
+ .factor-name-main {
+ color: var(--grey-3);
+ margin-top: 10px;
+ padding-left: 150px;
+ }
+
+ .factor-name1 {
+ color: var(--grey-3);
+ margin-top: 10px;
+ padding-left: 150px;
+ padding-right: 330px;
+ }
+
+ .factor-name2 {
+ color: var(--grey-3);
+ margin-bottom: -50px;
+ padding-left: 150px;
+ padding-right: 330px;
+ }
+
+ .table-container {
+ padding: 24px 34px;
+
+ .table {
+ mat-cell,
+ mat-header-cell {
+ justify-content: flex-start !important;
+ }
+ }
+
+ &-query .query-table {
+ justify-content: space-between;
+
+ .mat-mdc-header-cell {
+ justify-content: left;
+ }
+
+ .mat-mdc-cell {
+ justify-content: left;
+ }
+ }
+ }
+
+ ::ng-deep .ngx-charts {
+ ::ng-deep text {
+ font-family: 'Open Sans';
+ font-weight: 600;
+ display: flex;
+ flex-grow: 1;
+ }
+ }
+
+ ::ng-deep .ngx-charts g.line-chart > g:last-of-type > g:nth-child(n) g.line-series > path {
+ stroke-width: 5px;
+ }
+
+ ::ng-deep {
+ .legend-labels {
+ background: none !important;
+ margin-left: 50px;
+ }
+ }
+
+ // ::ng-deep{
+ // .legend-label-text{
+ // color: white;
+ // }
+ // }
+
+ .loading-container {
+ display: flex;
+ flex-grow: 1;
+ align-items: center;
+ justify-content: center;
+ }
+
+ ::ng-deep .mat-expansion-panel-body {
+ padding: 0 0 16px;
+ }
+}
diff --git a/frontend/projects/upgrade/src/app/features/dashboard/experiments/pages/experiment-details-page/experiment-details-page-content/experiment-metrics-data-section-card/experiment-query-result/experiment-query-result.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/experiments/pages/experiment-details-page/experiment-details-page-content/experiment-metrics-data-section-card/experiment-query-result/experiment-query-result.component.ts
new file mode 100644
index 0000000000..68bd54ed5f
--- /dev/null
+++ b/frontend/projects/upgrade/src/app/features/dashboard/experiments/pages/experiment-details-page/experiment-details-page-content/experiment-metrics-data-section-card/experiment-query-result/experiment-query-result.component.ts
@@ -0,0 +1,432 @@
+import { Component, OnInit, Input, OnDestroy } from '@angular/core';
+import {
+ ExperimentFactor,
+ ExperimentVM,
+ InteractionEffectGraphData,
+ InteractionEffectLineChartSeriesData,
+ InteractionEffectResult,
+ LevelCombinationElement,
+ LevelsMap,
+ MainEffectGraphData,
+ QueryResult,
+} from '../../../../../../../../core/experiments/store/experiments.model';
+import { AnalysisService } from '../../../../../../../../core/analysis/analysis.service';
+import { Subscription } from 'rxjs';
+import { filter } from 'rxjs/operators';
+import { EXPERIMENT_STATE, EXPERIMENT_TYPE } from 'upgrade_types';
+import { ExperimentFactorData } from '../../../../../../../../core/experiment-design-stepper/store/experiment-design-stepper.model';
+import { CommonModule } from '@angular/common';
+import { TranslateModule } from '@ngx-translate/core';
+import { NgxChartsModule } from '@swimlane/ngx-charts';
+import { MatTableModule } from '@angular/material/table';
+import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
+
+interface FactorColumnDef {
+ name: string;
+ label: string;
+}
+
+interface QueryColumnDef {
+ name: string;
+ label: string;
+}
+
+interface RowData {
+ [key: string]: any;
+}
+
+@Component({
+ selector: 'app-experiment-query-result',
+ templateUrl: './experiment-query-result.component.html',
+ styleUrls: ['./experiment-query-result.component.scss'],
+ imports: [CommonModule, TranslateModule, NgxChartsModule, MatTableModule, MatProgressSpinnerModule],
+})
+export class ExperimentQueryResultComponent implements OnInit, OnDestroy {
+ @Input() experiment: ExperimentVM;
+
+ // chart options
+ colorScheme = {
+ domain: ['#31e8dd', '#7dc7fb', '#fedb64', '#51ed8f', '#ddaaf8', '#fd9099', '#14c9be'],
+ };
+
+ queryResults = {};
+ queryFactorResults = [];
+ interactionEffectQueryFactorResults = [];
+ queryResultsSub: Subscription;
+ analysisSub: Subscription;
+ isQueryExecuting$ = this.analysisService.isQueryExecuting$;
+ factors: string[] = [];
+ queries: string[] = [];
+ displayedColumns: string[] = [];
+ factorialData = {};
+ experimentType: string = null;
+ experimentState: EXPERIMENT_STATE;
+ data: { name: string; series: { name: string; value: number }[]; dot: boolean }[];
+ meanData2: { name: string; value: number }[];
+ meanData1: { name: string; value: number }[];
+ maxLevelCount = 0;
+ factorColumnDefs: FactorColumnDef[] = [];
+ queryColumnDefs: QueryColumnDef[] = [];
+ dataSource: RowData[] = [];
+
+ /**
+ * What we want is a flat map of "id: Level":
+ * {
+ * 'abc1': Level,
+ * 'd6f3': Level,
+ * 'a2b5': Level
+ * }
+ */
+ levels: LevelsMap = {};
+
+ constructor(private analysisService: AnalysisService) {}
+
+ ngOnInit() {
+ const queryIds = [];
+ this.experimentType = this.experiment.type;
+
+ if (this.experimentType === EXPERIMENT_TYPE.FACTORIAL) {
+ this.setMaxLevelsCount();
+ // sort the factors:
+ this.experiment.factors = this.sortFactorsByOrderAscending(this.experiment.factors);
+ this.experiment.factors.map((factor) => {
+ this.factors.push(factor?.name);
+ this.displayedColumns.push(factor?.name);
+ });
+ this.experiment.queries.forEach((query) => {
+ this.queries.push(query?.name);
+ this.displayedColumns.push(query?.name);
+ });
+ this.levels = this.createLevelsMap(this.experiment.factors); // make a flat lookup map one time
+ } else {
+ this.setConditionCount();
+ }
+ this.queryResults = this.experiment.queries.map((query) => {
+ queryIds.push(query.id);
+ return {
+ [query.id]: [],
+ };
+ });
+
+ this.analysisSub = this.analysisService.experimentQueryResult$(this.experiment.id).subscribe((queryResults) => {
+ if (queryResults && queryResults.length) {
+ const queryIds = queryResults.map((queryResult) => queryResult.id);
+ this.analysisService.executeQuery(queryIds);
+ }
+ });
+ this.queryResultsSub = this.analysisService.queryResult$.pipe(filter((result) => !!result)).subscribe((result) => {
+ // main effect graph data
+ this.populateMainEffectGraphData(result);
+
+ // interactive effect graph data
+ if (this.factors.length <= 2) {
+ this.populateInteractionGraphData(result);
+ } else {
+ this.createMultiFactorQueryTableData(result);
+ }
+ });
+ }
+
+ createMultiFactorQueryTableData(result) {
+ const levelCombinationTable = this.factorDataToConditions(this.experiment.factors);
+ result.forEach((res) => {
+ // fill the result values for each query:
+ res.interactionEffect.forEach((data) => {
+ // levels of the condition:
+ const levels: LevelCombinationElement[] = this.getLevels(data.conditionId);
+ });
+ });
+ // Define factor columns dynamically
+ this.factors.forEach((factor, factorIndex) => {
+ const columnName = `factor${factorIndex + 1}`;
+ const columnLabel = factor;
+ this.factorColumnDefs.push({ name: columnName, label: columnLabel });
+ });
+
+ // Define query columns dynamically
+ this.queries.forEach((query, queryIndex) => {
+ const columnName = `query${queryIndex + 1}`;
+ const columnLabel = query;
+ this.queryColumnDefs.push({ name: columnName, label: columnLabel });
+ });
+
+ // Define data rows dynamically
+ levelCombinationTable.forEach((levels) => {
+ const rowData: RowData = {};
+ this.factorColumnDefs.forEach((factorColumnDef, factorColumnDefIndex) => {
+ rowData[factorColumnDef.name] = levels[factorColumnDefIndex].level;
+ });
+ this.dataSource.push(rowData);
+ });
+
+ result.forEach((res) => {
+ res.interactionEffect.forEach((data, dataIndex) => {
+ const rowData: RowData = {};
+ this.queryColumnDefs.forEach((queryColumnDef) => {
+ rowData[queryColumnDef.name] = data.result;
+ });
+ this.dataSource[dataIndex] = { ...this.dataSource[dataIndex], ...rowData };
+ });
+ });
+ }
+
+ // Get an array of all columns for the table header
+ get headerColumns(): string[] {
+ const factorColumnNames = this.factorColumnDefs.map((column) => column.name);
+ const queryColumnNames = this.queryColumnDefs.map((column) => column.name);
+ return [...factorColumnNames, ...queryColumnNames];
+ }
+
+ // Get an array of all columns for the data rows
+ get dataColumns(): string[] {
+ return this.headerColumns;
+ }
+
+ sortFactorsByOrderAscending(factors: ExperimentFactor[]): ExperimentFactor[] {
+ return factors
+ .slice()
+ .sort((factorA, factorB) => (factorA.order > factorB.order ? 1 : factorB.order > factorA.order ? -1 : 0));
+ }
+
+ populateMainEffectGraphData(result: QueryResult[]) {
+ result.forEach((res) => {
+ let simpleExperimentResultData: MainEffectGraphData[] = [];
+ const factorialExperimentResultData: MainEffectGraphData[][] = [];
+ let factorIndex;
+ this.experiment.factors.forEach((factor, factorIndex) => {
+ factorialExperimentResultData[factorIndex] = [];
+ });
+ if (this.experimentType === EXPERIMENT_TYPE.FACTORIAL) {
+ res.mainEffect.forEach((data) => {
+ factorIndex = this.getFactorIndex(data.levelId);
+ const resData = {
+ name: this.getLevelName(data.levelId),
+ value: Math.round(Number(data.result) * 100) / 100,
+ extra: Number(data.participantsLogged),
+ };
+ factorialExperimentResultData[factorIndex].push(resData);
+ });
+
+ factorialExperimentResultData.forEach((factorialExperimentResData, index) => {
+ factorialExperimentResultData[index] = this.formatEmptyBar(factorialExperimentResData);
+ });
+
+ this.factors.forEach((factor, factorIndex) => {
+ this.queryFactorResults[factorIndex] = {
+ ...this.queryFactorResults[factorIndex],
+ [res.id]: factorialExperimentResultData[factorIndex],
+ };
+ });
+ } else {
+ simpleExperimentResultData = res.mainEffect.map((data) => ({
+ name: this.getConditionCode(data.conditionId),
+ value: Math.round(Number(data.result) * 100) / 100,
+ extra: Number(data.participantsLogged),
+ }));
+ simpleExperimentResultData = this.formatEmptyBar(simpleExperimentResultData);
+ this.queryResults = {
+ ...this.queryResults,
+ [res.id]: simpleExperimentResultData,
+ };
+ }
+ return {
+ [res.id]: simpleExperimentResultData,
+ };
+ });
+ }
+
+ populateInteractionGraphData(result: QueryResult[]) {
+ result.forEach((res) => {
+ const resultData1: string[] = [];
+ const resultData2: string[] = [];
+ let emptySeries1: InteractionEffectGraphData[] = [];
+ let emptySeries2: InteractionEffectGraphData[] = [];
+ if (this.experimentType === EXPERIMENT_TYPE.FACTORIAL) {
+ // prepare all combination series with 0 result
+ // sort the factors:
+ this.experiment.factors = this.sortFactorsByOrderAscending(this.experiment.factors);
+ this.experiment.factors.map((factor, index) => {
+ factor.levels.map((level) => {
+ const levelName = level.name;
+ // collect level names in 2 list
+ index === 0 ? resultData1.push(levelName) : resultData2.push(levelName);
+ });
+ });
+
+ // factor 1 with factor 2
+ emptySeries1 = this.prepareEmptySeriesInteractionGraphData(resultData1, resultData2);
+ // factor 2 with factor 1
+ emptySeries2 = this.prepareEmptySeriesInteractionGraphData(resultData2, resultData1);
+
+ // fill the result values for each query:
+ const resData = [];
+ res.interactionEffect.forEach((data) => {
+ // levels of the condition:
+ const levels: LevelCombinationElement[] = this.getLevels(data.conditionId);
+ resData[0] = emptySeries1;
+ resData[1] = emptySeries2;
+ resData[0] = this.populateLineChartSeries(emptySeries1, data, levels, 1);
+ resData[1] = this.populateLineChartSeries(emptySeries2, data, levels, 0);
+ });
+ this.factors.forEach((factor, factorIndex) => {
+ this.interactionEffectQueryFactorResults[factorIndex] = {
+ ...this.interactionEffectQueryFactorResults[factorIndex],
+ [res.id]: resData[factorIndex],
+ };
+ });
+ }
+ });
+ }
+
+ prepareEmptySeriesInteractionGraphData(resultData1: string[], resultData2: string[]): InteractionEffectGraphData[] {
+ const emptySeries: InteractionEffectGraphData[] = [];
+ resultData1.forEach((level1) => {
+ const series: InteractionEffectLineChartSeriesData[] = [];
+ resultData2.forEach((level2) => {
+ series.push({
+ name: level2,
+ value: 0,
+ participantsLogged: 0,
+ });
+ });
+ emptySeries.push({
+ name: level1,
+ series: series,
+ dot: true,
+ });
+ });
+ return emptySeries;
+ }
+
+ populateLineChartSeries(
+ resData: InteractionEffectGraphData[],
+ data: InteractionEffectResult,
+ levels: LevelCombinationElement[],
+ factorNumber: number
+ ): InteractionEffectGraphData[] {
+ const alternateFactorNumber = factorNumber === 0 ? 1 : 0;
+ resData.map((result) => {
+ const factorIndex = result.name === levels[factorNumber].level.name ? alternateFactorNumber : factorNumber;
+ return result.series.map((level) => {
+ if (level.name === levels[factorIndex].level.name) {
+ level.value = Math.round(Number(data.result) * 100) / 100;
+ level.participantsLogged = Number(data.participantsLogged);
+ }
+ });
+ });
+ return resData;
+ }
+
+ setMaxLevelsCount() {
+ this.experiment.factors.forEach((factor) => {
+ const levelCount = factor.levels.length;
+ if (levelCount > this.maxLevelCount) {
+ this.maxLevelCount = levelCount;
+ }
+ });
+ }
+
+ setConditionCount() {
+ this.maxLevelCount = this.experiment.conditions.length;
+ }
+
+ getFactorIndex(levelId: string): number {
+ let factorIndex;
+ this.experiment.factors = this.sortFactorsByOrderAscending(this.experiment.factors);
+ this.experiment.factors.forEach((factor, index) => {
+ factor.levels.forEach((level) => {
+ if (level.id === levelId) {
+ factorIndex = index;
+ }
+ });
+ });
+ return factorIndex;
+ }
+
+ isResultExist(queryId: string): boolean {
+ let result = this.queryResults[queryId];
+ if (result) {
+ result = result.filter((res) => typeof res.name === 'string');
+ return result.length === 0;
+ }
+ return false;
+ }
+
+ getConditionCode(conditionId: string) {
+ return this.experiment.conditions.reduce(
+ (acc, condition) => (condition.id === conditionId ? (acc = condition.conditionCode as any) : acc),
+ null
+ );
+ }
+
+ getLevels(conditionId: string): LevelCombinationElement[] {
+ return this.experiment.conditions.reduce(
+ (acc, condition) => (condition.id === conditionId ? (acc = condition.levelCombinationElements as any) : acc),
+ null
+ );
+ }
+
+ createLevelsMap(factors: ExperimentFactor[]): LevelsMap {
+ return factors.reduce((levelsMap, factor) => {
+ factor.levels.forEach((level) => {
+ levelsMap[level.id] = level;
+ });
+ return levelsMap;
+ }, {});
+ }
+
+ getLevelName(levelId: string): string {
+ const level = this.levels[levelId];
+ return level?.name || '';
+ }
+
+ // remove empty series data labels
+ formateXAxisLabel(value: number) {
+ return !isNaN(value) ? '' : value;
+ }
+
+ formateYAxisLabel(value: number) {
+ return value === 0.5 || value === 1.5 ? '' : value;
+ }
+
+ formatEmptyBar(data: MainEffectGraphData[]) {
+ const emptyBars: MainEffectGraphData[] = [];
+ // Decide number of bars by inserting empty bars in case data not present:
+ for (let i = 0; i < this.maxLevelCount - data.length; i++) {
+ emptyBars.push({
+ name: i.toString(),
+ value: 0,
+ extra: 0,
+ });
+ }
+ return [...data, ...emptyBars];
+ }
+
+ factorDataToConditions(factorsData: ExperimentFactorData[], levelsCombinationData: any[] = []) {
+ // return if no data in factors
+ if (factorsData.length === 0) {
+ return [levelsCombinationData];
+ } else {
+ // taking the 1st factor
+ const currentFactor = factorsData[0];
+ const levelPermutations = [];
+
+ for (let i = 0; i < currentFactor.levels.length; i++) {
+ const levelName = currentFactor.levels[i].name;
+ // taking level of current factor and processing on other factors
+ const remainingLevelsPermutations = this.factorDataToConditions(factorsData.slice(1), [
+ ...levelsCombinationData,
+ { level: levelName },
+ ]);
+ levelPermutations.push(...remainingLevelsPermutations);
+ }
+ return levelPermutations;
+ }
+ }
+
+ ngOnDestroy() {
+ this.queryResultsSub.unsubscribe();
+ this.analysisSub.unsubscribe();
+ this.analysisService.setQueryResult(null);
+ }
+}
diff --git a/frontend/projects/upgrade/src/app/features/dashboard/home/components/experiment-query-result/experiment-query-result.component.html b/frontend/projects/upgrade/src/app/features/dashboard/home/components/experiment-query-result/experiment-query-result.component.html
index 54e2c7e4db..baa2076a30 100644
--- a/frontend/projects/upgrade/src/app/features/dashboard/home/components/experiment-query-result/experiment-query-result.component.html
+++ b/frontend/projects/upgrade/src/app/features/dashboard/home/components/experiment-query-result/experiment-query-result.component.html
@@ -1,8 +1,4 @@
- {{ 'home.experiment-query-result.title.text' | translate }}
- {{ 'home.experiment-query-result.detail.text' | translate }}
-
-
{{
'home.experiment-query-result.main-effect.title.text' | translate
}}
diff --git a/frontend/projects/upgrade/src/app/features/dashboard/home/components/experiment-query-result/experiment-query-result.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/home/components/experiment-query-result/experiment-query-result.component.ts
index fa8aac2150..c1edd091ed 100644
--- a/frontend/projects/upgrade/src/app/features/dashboard/home/components/experiment-query-result/experiment-query-result.component.ts
+++ b/frontend/projects/upgrade/src/app/features/dashboard/home/components/experiment-query-result/experiment-query-result.component.ts
@@ -15,6 +15,9 @@ import { Subscription } from 'rxjs';
import { filter } from 'rxjs/operators';
import { EXPERIMENT_STATE, EXPERIMENT_TYPE } from 'upgrade_types';
import { ExperimentFactorData } from '../../../../../core/experiment-design-stepper/store/experiment-design-stepper.model';
+import { CommonModule } from '@angular/common';
+import { TranslateModule } from '@ngx-translate/core';
+import { NgxChartsModule } from '@swimlane/ngx-charts';
interface FactorColumnDef {
name: string;
@@ -31,10 +34,11 @@ interface RowData {
}
@Component({
- selector: 'home-experiment-query-result',
+ selector: 'experiment-query-result',
templateUrl: './experiment-query-result.component.html',
styleUrls: ['./experiment-query-result.component.scss'],
- standalone: false,
+ standalone: true,
+ imports: [CommonModule, TranslateModule, NgxChartsModule],
})
export class ExperimentQueryResultComponent implements OnInit, OnDestroy {
@Input() experiment: ExperimentVM;
diff --git a/frontend/projects/upgrade/src/app/features/dashboard/home/home.module.ts b/frontend/projects/upgrade/src/app/features/dashboard/home/home.module.ts
index 99add7cc08..377ad6301b 100644
--- a/frontend/projects/upgrade/src/app/features/dashboard/home/home.module.ts
+++ b/frontend/projects/upgrade/src/app/features/dashboard/home/home.module.ts
@@ -27,7 +27,6 @@ import { QueriesModalComponent } from './components/modal/queries-modal/queries-
import { CreateQueryComponent } from './components/create-query/create-query.component';
import { OperationPipe } from '../../../shared/pipes/operation.pipe';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
-import { ExperimentQueryResultComponent } from './components/experiment-query-result/experiment-query-result.component';
import { ExperimentEndCriteriaComponent } from './components/modal/experiment-end-criteria/experiment-end-criteria.component';
import { RepeatedMeasurePipe } from './pipes/repeated-measure.pipe';
import { ImportExperimentComponent } from './components/modal/import-experiment/import-experiment.component';
@@ -56,7 +55,6 @@ import { MoocletPolicyEditorComponent } from './components/experiment-design/moo
ImportExperimentComponent,
QueriesModalComponent,
CreateQueryComponent,
- ExperimentQueryResultComponent,
ExperimentEndCriteriaComponent,
RepeatedMeasurePipe,
StateTimeLogsComponent,
diff --git a/frontend/projects/upgrade/src/styles.scss b/frontend/projects/upgrade/src/styles.scss
index d34efb7a2d..df223db785 100755
--- a/frontend/projects/upgrade/src/styles.scss
+++ b/frontend/projects/upgrade/src/styles.scss
@@ -37,7 +37,6 @@
@import './app/features/dashboard/home/components/modal/queries-modal/queries-modal.theme.scss';
@import './app/shared/components/query-result/query-result.theme.scss';
@import './app/features/dashboard/profile/components/metrics/metrics.theme.scss';
-@import './app/features/dashboard/home/components/experiment-query-result/experiment-query-result.theme.scss';
@import './app/features/dashboard/home/components/modal/experiment-end-criteria/experiment-ending-criteria.theme.scss';
@import './app/features/dashboard/home/components/modal/import-experiment/import-experiment.theme.scss';
@import './app/features/dashboard/home/components/modal/state-time-logs/state-time-logs.theme.scss';
@@ -72,7 +71,6 @@
@include queries-modal-component-theme($theme);
@include query-result-component-theme($theme);
@include metrics-component-theme($theme);
- @include experiment-query-result-component-theme($theme);
@include experiment-ending-criteria-component-theme($theme);
@include import-experiment-component-theme($theme);
@include state-time-logs-modal-component-theme($theme);