diff --git a/frontend/projects/upgrade/src/app/features/dashboard/home/components/enrollment-over-time/enrollment-over-time.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/home/components/enrollment-over-time/enrollment-over-time.component.ts index e51f0b41bd..6aeee93c63 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/home/components/enrollment-over-time/enrollment-over-time.component.ts +++ b/frontend/projects/upgrade/src/app/features/dashboard/home/components/enrollment-over-time/enrollment-over-time.component.ts @@ -45,7 +45,22 @@ export class EnrollmentOverTimeComponent implements OnChanges, OnInit, OnDestroy isInitialLoad = true; showLabelOfxAxis = true; - colors = ['#31e8dd', '#7dc7fb', '#fedb64', '#51ed8f', '#ddaaf8', '#fd9099', '#14c9be']; + colors = [ + '#31e8dd', + '#7dc7fb', + '#fedb64', + '#51ed8f', + '#ddaaf8', + '#fd9099', + '#14c9be', + '#57ff6d', + '#3dffec', + '#fedb64', + '#0a6de6', + '#da0766', + '#cd8014', + '#fa59a1', + ]; colorScheme = { domain: this.colors, }; diff --git a/frontend/projects/upgrade/src/app/features/dashboard/home/components/experiment-participants/experiment-participants.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/home/components/experiment-participants/experiment-participants.component.ts index 60db5f6582..ecb5fb770c 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/home/components/experiment-participants/experiment-participants.component.ts +++ b/frontend/projects/upgrade/src/app/features/dashboard/home/components/experiment-participants/experiment-participants.component.ts @@ -175,7 +175,6 @@ export class ExperimentParticipantsComponent implements OnInit { this.members1.removeAt(0); this.members2.removeAt(0); - this.updateView1(); this.updateView2(); 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 6a70b66b0e..15c851dbb9 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 @@ -13,20 +13,20 @@
- - - - -
{{ factor | uppercase}}
-
-
-
+
+
+
+ + + + +
{{ factor | uppercase }}
- - {{ model.name }} -
- {{ model.value }} -
- ( n = {{ model.extra }} ) -
+ + {{ model.name }} +
+ {{ model.value }} +
+ ( n = {{ model.extra }} ) +
+
-
- +
+ + +
+
+ + +
+ {{ model.name }} -
+
{{ model.value }} -
+
( n = {{ model.extra }} )
-
-
+
-
- - -
- - - {{ model.name }} -
- {{ model.value }} -
- ( n = {{ model.extra }} ) -
-
-
-
-
-

@@ -120,62 +97,50 @@
- - - - -
{{ factor | uppercase}}
- - - - -
({{ factor | uppercase}})
-
-
- - - {{ model.series }} -
- {{ model.value }} -
- ( n = {{ model.participantsLogged }} ) -
-
+
+
+
+ + + + + + + +
+ {{ factor | uppercase }} +
+ ({{ factor | uppercase }}) +
-
- +
+
- - {{ model.series }} -
- {{ model.value }} -
- ( n = {{ model.participantsLogged }} ) -
- + + + {{ model.series }} +
+ {{ model.value }} +
+ ( n = {{ model.participantsLogged }} ) +
+
+
@@ -184,14 +149,36 @@
-
- - - - -
- +
+ + + + + {{ column.label | uppercase }} + {{ row[column.name] }} + + + + + {{ column.label | uppercase }} + {{ row[column.name] }} + + + + + + + +
- -
+ + + + +
+ +
+
+
+
diff --git a/frontend/projects/upgrade/src/app/features/dashboard/home/components/experiment-query-result/experiment-query-result.component.scss b/frontend/projects/upgrade/src/app/features/dashboard/home/components/experiment-query-result/experiment-query-result.component.scss index 072c0c8528..c371f73ba5 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/home/components/experiment-query-result/experiment-query-result.component.scss +++ b/frontend/projects/upgrade/src/app/features/dashboard/home/components/experiment-query-result/experiment-query-result.component.scss @@ -18,18 +18,47 @@ 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: 190px; - padding-right: 280px; + padding-left: 150px; + padding-right: 330px; } .factor-name2 { color: var(--grey-3); margin-bottom: -50px; - padding-left: 185px; - padding-right: 275px; + 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-header-cell { + justify-content: left; + } + + .mat-cell { + justify-content: left; + } + } } ::ng-deep .ngx-charts { @@ -41,14 +70,9 @@ } } - ::ng-deep .ngx-charts - g.line-chart - > g:last-of-type - > g:nth-child(n) - g.line-series - > path { + ::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 { 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 2c3ea42d35..40acc41a2e 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 @@ -14,6 +14,21 @@ import { AnalysisService } from '../../../../../core/analysis/analysis.service'; import { Subscription } from 'rxjs'; import { filter } from 'rxjs/operators'; import { EXPERIMENT_TYPE } from 'upgrade_types'; +import { ExperimentFactorData } from '../../../../../core/experiment-design-stepper/store/experiment-design-stepper.model'; + +interface FactorColumnDef { + name: string; + label: string; +} + +interface QueryColumnDef { + name: string; + label: string; +} + +interface RowData { + [key: string]: any; +} @Component({ selector: 'home-experiment-query-result', @@ -29,13 +44,12 @@ export class ExperimentQueryResultComponent implements OnInit, OnDestroy { }; queryResults = {}; - queryFactorResults1 = {}; - queryFactorResults2 = {}; - interactionEffectQueryFactorResults1 = {}; - interactionEffectQueryFactorResults2 = {}; + queryFactorResults = []; + interactionEffectQueryFactorResults = []; queryResultsSub: Subscription; isQueryExecuting$ = this.analysisService.isQueryExecuting$; - factors = []; + factors: string[] = []; + queries: string[] = []; displayedColumns: string[] = []; factorialData = {}; experimentType: string = null; @@ -43,6 +57,9 @@ export class ExperimentQueryResultComponent implements OnInit, OnDestroy { 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": @@ -66,6 +83,11 @@ export class ExperimentQueryResultComponent implements OnInit, OnDestroy { 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 { @@ -83,10 +105,69 @@ export class ExperimentQueryResultComponent implements OnInit, OnDestroy { this.populateMainEffectGraphData(result); // interactive effect graph data - this.populateInteractionGraphData(result); + 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, levelIndex) => { + 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() @@ -95,43 +176,47 @@ export class ExperimentQueryResultComponent implements OnInit, OnDestroy { populateMainEffectGraphData(result: QueryResult[]) { result.forEach((res) => { - let resultData: MainEffectGraphData[] = []; - let resultData1: MainEffectGraphData[] = []; - let resultData2: MainEffectGraphData[] = []; + 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) => { - const factorIndex = this.getFactorIndex(data.levelId); + factorIndex = this.getFactorIndex(data.levelId); const resData = { name: this.getLevelName(data.levelId), value: Math.round(Number(data.result) * 100) / 100, extra: Number(data.participantsLogged), }; - factorIndex === 0 ? resultData1.push(resData) : resultData2.push(resData); + 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], + }; }); - resultData1 = this.formatEmptyBar(resultData1); - this.queryFactorResults1 = { - ...this.queryFactorResults1, - [res.id]: resultData1, - }; - resultData2 = this.formatEmptyBar(resultData2); - this.queryFactorResults2 = { - ...this.queryFactorResults2, - [res.id]: resultData2, - }; } else { - resultData = res.mainEffect.map((data) => ({ + simpleExperimentResultData = res.mainEffect.map((data) => ({ name: this.getConditionCode(data.conditionId), value: Math.round(Number(data.result) * 100) / 100, extra: Number(data.participantsLogged), })); - resultData = this.formatEmptyBar(resultData); + simpleExperimentResultData = this.formatEmptyBar(simpleExperimentResultData); this.queryResults = { ...this.queryResults, - [res.id]: resultData, + [res.id]: simpleExperimentResultData, }; } return { - [res.id]: resultData, + [res.id]: simpleExperimentResultData, }; }); } @@ -160,22 +245,21 @@ export class ExperimentQueryResultComponent implements OnInit, OnDestroy { emptySeries2 = this.prepareEmptySeriesInteractionGraphData(resultData2, resultData1); // fill the result values for each query: - let resData1 = emptySeries1; - let resData2 = emptySeries2; + const resData = []; res.interactionEffect.forEach((data) => { // levels of the condition: const levels: LevelCombinationElement[] = this.getLevels(data.conditionId); - resData1 = this.populateLineChartSeries(resData1, data, levels, 1); - resData2 = this.populateLineChartSeries(resData2, data, levels, 0); + 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], + }; }); - this.interactionEffectQueryFactorResults1 = { - ...this.interactionEffectQueryFactorResults1, - [res.id]: resData1, - }; - this.interactionEffectQueryFactorResults2 = { - ...this.interactionEffectQueryFactorResults2, - [res.id]: resData2, - }; } }); } @@ -235,6 +319,7 @@ export class ExperimentQueryResultComponent implements OnInit, OnDestroy { 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) { @@ -304,6 +389,28 @@ export class ExperimentQueryResultComponent implements OnInit, OnDestroy { 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.analysisService.setQueryResult(null); diff --git a/frontend/projects/upgrade/src/assets/i18n/en.json b/frontend/projects/upgrade/src/assets/i18n/en.json index 36219a603c..eb276f7fdb 100644 --- a/frontend/projects/upgrade/src/assets/i18n/en.json +++ b/frontend/projects/upgrade/src/assets/i18n/en.json @@ -239,7 +239,7 @@ "home.view-experiment.experiment-payloads.column-label.site": "SITE", "home.view-experiment.experiment-payloads.column-label.target": "TARGET", "home.view-experiment.experiment-payloads.column-label.condition": "CONDITION", - "home.view-experiment.experiment-payloads.column-label.payload": "Payload", + "home.view-experiment.experiment-payloads.column-label.payload": "PAYLOAD", "home.view-experiment.graph-type.text": "Type", "home.view-experiment.graph-conditions.text": "Conditions", "home.view-experiment.graph-decision-point.text": "Sites",