Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(app): Result Visualization #679

Draft
wants to merge 56 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
96f657b
vis
yaramt Jul 25, 2024
f756e51
test
yaramt Aug 3, 2024
92a7e39
Add t-test and make textual summary dynamic
yaramt Aug 5, 2024
13a001b
Fix analysis errors
yaramt Aug 5, 2024
4df9a5e
Fix analysis errors
yaramt Aug 5, 2024
9acc910
Fix analysis errors
yaramt Aug 5, 2024
9aaf513
Add t-test extract critical val from csv
yaramt Aug 12, 2024
e58c48d
test commit
yaramt Aug 12, 2024
36f8dda
Merge remote-tracking branch 'origin/dev_results_visualization' into …
yaramt Aug 13, 2024
a4e686f
Merge remote-tracking branch 'upstream/dev_results_visualization' int…
yaramt Aug 13, 2024
3410111
dart format .
yaramt Aug 13, 2024
3b049c3
fix csv reading
yaramt Aug 13, 2024
2a49c0b
Make gauge title dynamic
yaramt Aug 13, 2024
0cb21f3
Merge remote-tracking branch 'origin/dev' into dev_results_visualizat…
yaramt Aug 13, 2024
3d92f1c
fix build errors
yaramt Aug 15, 2024
3944ea9
Use gauge with free license
yaramt Aug 18, 2024
71f6f14
chore: Apply static analysis changes
yaramt Aug 19, 2024
adf0c82
Dynamic descriptive stats
yaramt Aug 19, 2024
c5acbc5
Merge branch 'dev_results_visualization_fork' into dev_results_visual…
yaramt Aug 19, 2024
9d53cb3
Transpose and missing obs descrp stats
yaramt Aug 26, 2024
4ad3905
Merge branch 'dev_results_visualization_fork' into dev_results_visual…
yaramt Aug 26, 2024
d51b728
Add bar plot heights
yaramt Aug 26, 2024
b04c3a1
Merge branch 'dev_results_visualization_fork' into dev_results_visual…
yaramt Aug 26, 2024
eb50560
Add optional bar plots: days,intervention,phase
yaramt Aug 29, 2024
48dff0f
Add line plot for days
yaramt Aug 29, 2024
9740d69
Use one outcome for the text summary instead of two
yaramt Aug 29, 2024
c8c95cb
Add an i as help in textual sum for the pvalue and alpha
yaramt Aug 29, 2024
9370aa4
Merge branch 'dev_results_visualization_fork' into dev_results_visual…
yaramt Aug 29, 2024
ec3e061
t-test reduce to 200 critical values, when degrees of freedom more th…
yaramt Aug 29, 2024
bd84b93
chore: Apply static analysis changes
yaramt Aug 29, 2024
2e5a70e
Fix intervention bar plot colors
yaramt Aug 29, 2024
b5a5bd9
Merge branch 'dev_results_visualization_fork' into dev_results_visual…
yaramt Aug 29, 2024
9028230
Put missing observations in second row
yaramt Aug 29, 2024
e445283
Make line in plot black
yaramt Aug 29, 2024
1487622
dart format .
yaramt Aug 29, 2024
a8e81a0
Add colorless gauge
yaramt Sep 1, 2024
62bf834
line plot background colors
yaramt Sep 4, 2024
4f25a28
dart format .
yaramt Sep 4, 2024
5758626
Darker background colors line plot
yaramt Sep 4, 2024
10300e8
Dynamic pvalue calculations instead of retrieving critical t values f…
yaramt Sep 5, 2024
e323b70
Dynamic pvalue calculations instead of retrieving critical t values f…
yaramt Sep 5, 2024
7557509
Remove performance in final report
yaramt Sep 5, 2024
ab26f29
revert changes
yaramt Sep 5, 2024
69525ea
fix libraray
yaramt Sep 5, 2024
b1dd385
fix libraray
yaramt Sep 8, 2024
5682da1
Add checkbox for colored/colorless gauges
yaramt Sep 8, 2024
eb28b86
Remove variance in descriptive stats
yaramt Sep 10, 2024
94e18c9
fix version
yaramt Sep 15, 2024
ed28413
Merge remote-tracking branch 'origin/dev_results_visualization_fork' …
yaramt Sep 15, 2024
0eac287
fix version
yaramt Sep 15, 2024
1c371a9
Remove critical values file
yaramt Sep 15, 2024
dee846a
fIX VERSIONS
yaramt Sep 15, 2024
05af893
Fix build
yaramt Sep 15, 2024
7a3a0f7
chore: Apply static analysis changes
yaramt Sep 15, 2024
62561d0
Use higher/lower instead of better/worse
yaramt Sep 15, 2024
d963464
Merge remote-tracking branch 'upstream/dev_results_visualization' int…
yaramt Sep 15, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
500 changes: 500 additions & 0 deletions app/assets/data/t_critical_values.csv

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions app/ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objectVersion = 60;
objects = {

/* Begin PBXBuildFile section */
Expand Down Expand Up @@ -491,7 +491,7 @@
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = PG566C9XUK;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 10.14;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down Expand Up @@ -676,7 +676,7 @@
DEVELOPMENT_TEAM = PG566C9XUK;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 10.14;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down Expand Up @@ -704,7 +704,7 @@
DEVELOPMENT_TEAM = PG566C9XUK;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 10.14;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down
62 changes: 58 additions & 4 deletions app/lib/screens/study/report/sections/average_section_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:studyu_app/screens/study/report/report_section_widget.dart';
import 'package:studyu_app/screens/study/report/sections/results_descriptive_statistics.dart';
import 'package:studyu_app/screens/study/report/sections/results_gauge.dart';
import 'package:studyu_app/screens/study/report/sections/results_textual_summary.dart';
import 'package:studyu_app/screens/study/report/util/plot_utilities.dart';
import 'package:studyu_app/theme.dart';
import 'package:studyu_app/util/data_processing.dart';
Expand All @@ -16,6 +19,29 @@ class AverageSectionWidget extends ReportSectionWidget {
@override
Widget build(BuildContext context) {
final data = getAggregatedData().toList();
final aggregatedDataByDay = aggregateDataBy(null).toList();
// Filter out baseline data
final filteredData = aggregatedDataByDay
.where((datum) => datum.intervention != '__baseline');
// Group data by intervention
final interventionGroups =
filteredData.fold<Map<String, List<num>>>({}, (map, datum) {
map.putIfAbsent(datum.intervention, () => []).add(datum.value);
return map;
});
// Extract keys from the map
final keys = interventionGroups.keys.toList();
// Define default empty lists
final List<num> valuesInterventionA =
keys.isNotEmpty ? interventionGroups[keys[0]]! : [];
final List<num> valuesInterventionB =
keys.length > 1 ? interventionGroups[keys[1]]! : [];
final String nameInterventionA = keys.isNotEmpty
? getInterventionNameFromInterventionId(context, keys[0])!
: "";
final String nameInterventionB = keys.length > 1
? getInterventionNameFromInterventionId(context, keys[1])!
: "";
final taskTitle = subject.study.observations
.firstWhereOrNull(
(element) => element.id == section.resultProperty!.task,
Expand All @@ -32,8 +58,21 @@ class AverageSectionWidget extends ReportSectionWidget {
.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
TextualSummaryWidget(valuesInterventionA, valuesInterventionB,
nameInterventionA, nameInterventionB, subject, section), //Row 2
const SizedBox(height: 8),
const GaugeTitleWidget(), //Row 3
const SizedBox(height: 8),
const GaugesWidget(), //Row 4
const SizedBox(height: 8),
getLegend(context, data),
const SizedBox(height: 8),
const ExpansionTile(
title: Text('Descriptive Statistics'),
children: [
DescriptiveStatisticsWidget(),
],
),
AspectRatio(aspectRatio: 1.5, child: getDiagram(context, data)),
],
);
Expand Down Expand Up @@ -250,7 +289,7 @@ class AverageSectionWidget extends ReportSectionWidget {
return subject.getDayOfStudyFor(key) - offset;
}

Iterable<DiagramDatum> getAggregatedData() {
Iterable<DiagramDatum> aggregateDataBy(TemporalAggregation? aggregate) {
final values = section.resultProperty!.retrieveFromResults(subject);
final data = values.entries.map(
(e) => DiagramDatum(
Expand All @@ -260,8 +299,9 @@ class AverageSectionWidget extends ReportSectionWidget {
subject.getInterventionForDate(e.key)!.id,
),
);

if (section.aggregate == TemporalAggregation.day) {
if (aggregate == null) {
return data;
} else if (aggregate == TemporalAggregation.day) {
return data
.groupBy((e) => e.x)
.aggregateWithKey(
Expand All @@ -273,7 +313,7 @@ class AverageSectionWidget extends ReportSectionWidget {
),
)
.map((e) => e.value);
} else if (section.aggregate == TemporalAggregation.phase) {
} else if (aggregate == TemporalAggregation.phase) {
return data
.groupBy((e) => subject.getInterventionIndexForDate(e.timestamp!))
.aggregateWithKey(
Expand Down Expand Up @@ -301,6 +341,10 @@ class AverageSectionWidget extends ReportSectionWidget {
}
}

Iterable<DiagramDatum> getAggregatedData() {
return aggregateDataBy(section.aggregate);
}

Map<String, String?> getInterventionNames(BuildContext context) {
final names = {
for (final intervention in subject.study.interventions)
Expand All @@ -309,6 +353,16 @@ class AverageSectionWidget extends ReportSectionWidget {
names[Study.baselineID] = AppLocalizations.of(context)!.baseline;
return names;
}

String? getInterventionNameFromInterventionId(
BuildContext context, String interventionId) {
for (final intervention in subject.study.interventions) {
if (intervention.id == interventionId) {
return intervention.name;
}
}
return null;
}
}

class DiagramDatum {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import 'package:flutter/material.dart';

// Row 6: Descriptive Statistics
class DescriptiveStatisticsWidget extends StatelessWidget {
const DescriptiveStatisticsWidget({super.key});

@override
Widget build(BuildContext context) {
return GestureDetector(
child: Column(
children: <Widget>[
const Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Icon(
Icons.arrow_drop_up,
),
],
),
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Column(
children: [
_buildStatisticsTable(),
const Padding(
padding: EdgeInsets.only(top: 8.0),
child: Text(
'p-value: 0.01\nLevel of significance: α = 0.05',
style: TextStyle(fontSize: 8, color: Colors.black),
textAlign: TextAlign.center,
),
),
],
),
),
],
),
);
}

Widget _buildStatisticsTable() {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.0),
color: Colors.grey[200],
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: const Offset(0, 3),
),
],
),
child: Table(
border: TableBorder.all(color: Colors.grey),
columnWidths: const {
0: FixedColumnWidth(65),
1: FixedColumnWidth(65),
2: FixedColumnWidth(50),
3: FixedColumnWidth(50),
4: FixedColumnWidth(50),
5: FixedColumnWidth(65),
},
children: [
_buildTableRow(
[
'Intervention',
'Observations',
'Average',
'Min',
'Max',
'Variance'
],
isHeader: true,
),
_buildTableRow(['Tea', '14', '5.0', '4', '3', '1.2']),
_buildTableRow(['No Tea', '14', '7.5', '8', '6', '2']),
],
),
);
}

TableRow _buildTableRow(List<String> cells, {bool isHeader = false}) {
return TableRow(
children: cells.map((cell) {
return _buildTableCell(cell, isHeader: isHeader);
}).toList(),
);
}

Widget _buildTableCell(String text, {bool isHeader = false}) {
return Padding(
padding: const EdgeInsets.all(6.0),
child: Text(
text,
style: TextStyle(
fontSize: isHeader ? 8 : 8,
fontWeight: isHeader ? FontWeight.bold : FontWeight.normal,
color: isHeader ? Colors.black : Colors.grey[800],
),
textAlign: TextAlign.center,
),
);
}
}
126 changes: 126 additions & 0 deletions app/lib/screens/study/report/sections/results_gauge.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_gauges/gauges.dart';

// Row 3: Gauge title
class GaugeTitleWidget extends StatelessWidget {
const GaugeTitleWidget({super.key});

@override
Widget build(BuildContext context) {
return const Center(
child: Text(
'Average Sleep Quality',
style: TextStyle(fontSize: 16, color: Colors.blueAccent),
),
);
}
}

// Row 4: Two gauges
class GaugesWidget extends StatelessWidget {
const GaugesWidget({super.key});

@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Expanded(
child: Column(
children: <Widget>[
SizedBox(
width: 140, // Set the desired width
height: 140, // Set the desired height
child: createGauge(0, 10, 10, 5), // min, max, steps, value
),
const Text('With Tea', style: TextStyle(fontSize: 14)),
],
),
),
Expanded(
child: Column(
children: <Widget>[
SizedBox(
width: 140, // Set the desired width
height: 140, // Set the desired height
child: createGauge(0, 10, 10, 7.5), // min, max, steps, value
),
const Text('Without Tea', style: TextStyle(fontSize: 14)),
],
),
),
],
);
}

// Create gauge with min and max values, steps to switch colors, value to point at
Widget createGauge(double min, double max, int steps, double value) {
// List of colors
final List<Color> colors = [
Colors.red[900]!,
Colors.red[700]!,
Colors.red[500]!,
Colors.orange[900]!,
Colors.orange[700]!,
Colors.yellow[700]!,
Colors.yellow[500]!,
Colors.green[300]!,
Colors.green[500]!,
Colors.green[700]!,
];

// Create gauge ranges based on steps and the color list
List<GaugeRange> createRanges() {
final double stepValue = (max - min) / steps;
return List.generate(steps, (index) {
final double start = min + (index * stepValue);
final double end = start + stepValue;
return GaugeRange(
startValue: start,
endValue: end,
color: colors[index],
);
});
}

return SfRadialGauge(
axes: <RadialAxis>[
RadialAxis(
minimum: min,
maximum: max,
pointers: <GaugePointer>[
NeedlePointer(
value: value,
needleColor: Colors.blue,
needleEndWidth: 7,
),
],
ranges: createRanges(),
annotations: <GaugeAnnotation>[
GaugeAnnotation(
widget: RichText(
text: TextSpan(
children: <TextSpan>[
TextSpan(
text: '$value',
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
TextSpan(
text: '/$max',
style: const TextStyle(fontSize: 10, color: Colors.blue),
),
],
),
),
angle: 90,
positionFactor: 0.8,
),
],
),
],
);
}
}
Loading