mirror of
https://github.com/hamaluik/timecop.git
synced 2025-05-17 16:56:05 +08:00
make average daily a stacked bar chart instead
This commit is contained in:
13
.vscode/launch.json
vendored
Normal file
13
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Flutter",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
}
|
||||
]
|
||||
}
|
@ -29,22 +29,22 @@ import 'package:timecop/l10n.dart';
|
||||
import 'package:timecop/screens/dashboard/DashboardScreen.dart';
|
||||
import 'package:timecop/themes.dart';
|
||||
|
||||
import 'package:timecop/data_providers/database_provider.dart';
|
||||
/*import 'package:timecop/data_providers/database_provider.dart';
|
||||
import 'package:timecop/data_providers/shared_prefs_settings_provider.dart';
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
final SettingsProvider settings = await SharedPrefsSettingsProvider.load();
|
||||
final DataProvider data = await DatabaseProvider.open();
|
||||
await runMain(settings, data);
|
||||
}
|
||||
}*/
|
||||
|
||||
/*import 'data_providers/mock_data_provider.dart';
|
||||
import 'data_providers/mock_data_provider.dart';
|
||||
import 'data_providers/mock_settings_provider.dart';
|
||||
Future<void> main() async {
|
||||
final SettingsProvider settings = MockSettingsProvider();
|
||||
final DataProvider data = MockDataProvider(Locale.fromSubtags(languageCode: "en"));
|
||||
await runMain(settings, data);
|
||||
}*/
|
||||
}
|
||||
|
||||
Future<void> runMain(SettingsProvider settings, DataProvider data) async {
|
||||
// setup intl date formats?
|
||||
|
@ -12,25 +12,35 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:timecop/blocs/projects/bloc.dart';
|
||||
import 'package:timecop/blocs/timers/bloc.dart';
|
||||
import 'package:timecop/components/ProjectColour.dart';
|
||||
import 'package:timecop/l10n.dart';
|
||||
import 'package:timecop/models/project.dart';
|
||||
import 'package:timecop/models/timer_entry.dart';
|
||||
import 'package:timecop/models/start_of_week.dart';
|
||||
import 'dart:math';
|
||||
|
||||
class WeekdayAverages extends StatelessWidget {
|
||||
final DateTime startDate;
|
||||
final DateTime endDate;
|
||||
final List<double> _daysData;
|
||||
final LinkedHashMap<int, LinkedHashMap<int, double>> _daysData;
|
||||
|
||||
static List<double> calculateData(BuildContext context, DateTime startDate, DateTime endDate) {
|
||||
static LinkedHashMap<int, LinkedHashMap<int, double>> calculateData(BuildContext context, DateTime startDate, DateTime endDate) {
|
||||
final TimersBloc timers = BlocProvider.of<TimersBloc>(context);
|
||||
|
||||
DateTime firstDate = DateTime.now();
|
||||
DateTime lastDate = DateTime(1970);
|
||||
List<double> daySums = <double>[0, 0, 0, 0, 0, 0, 0];
|
||||
LinkedHashMap<int, LinkedHashMap<int, double>> daySums = LinkedHashMap();
|
||||
for(int i = 0; i < 7; i++) {
|
||||
daySums.putIfAbsent(i, () => LinkedHashMap<int, double>());
|
||||
}
|
||||
|
||||
for(
|
||||
TimerEntry timer in timers.state.timers
|
||||
.where((timer) => timer.endTime != null)
|
||||
@ -40,7 +50,10 @@ class WeekdayAverages extends StatelessWidget {
|
||||
double hours = timer.endTime.difference(timer.startTime).inSeconds.toDouble() / 3600.0;
|
||||
int weekday = timer.startTime.weekday;
|
||||
if(weekday == 7) weekday = 0;
|
||||
daySums[weekday] += hours;
|
||||
|
||||
//daySums[weekday] += hours;
|
||||
daySums[weekday].update(timer.projectID, (double sum) => sum + hours, ifAbsent: () => hours);
|
||||
|
||||
if(timer.startTime.isBefore(firstDate)) {
|
||||
firstDate = timer.startTime;
|
||||
}
|
||||
@ -56,9 +69,11 @@ class WeekdayAverages extends StatelessWidget {
|
||||
int totalDays = DateTime(lastDate.year, lastDate.month, lastDate.day)
|
||||
.difference(DateTime(firstDate.year, firstDate.month, firstDate.day))
|
||||
.inDays;
|
||||
double totalWeeks = totalDays.toDouble() / 7.0;
|
||||
if(totalDays > 0) {
|
||||
for(int i = 0; i < 7; i++) {
|
||||
daySums[i] /= totalDays.toDouble() / 7.0;
|
||||
//daySums[i] /= totalDays.toDouble() / 7.0;
|
||||
daySums[i].updateAll((int _project, double sum) => sum / totalWeeks);
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,6 +86,19 @@ class WeekdayAverages extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ProjectsBloc projects = BlocProvider.of<ProjectsBloc>(context);
|
||||
|
||||
double maxY = _daysData
|
||||
.values
|
||||
.fold(0.0, (osum, day) =>
|
||||
max(
|
||||
osum,
|
||||
day
|
||||
.values
|
||||
.fold(0.0, (isum, v) => max(isum, v))
|
||||
)
|
||||
);
|
||||
maxY = ((maxY ~/ 5) + 1) * 5.0;
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(16, 16, 16, 40),
|
||||
@ -81,6 +109,7 @@ class WeekdayAverages extends StatelessWidget {
|
||||
child: BarChart(
|
||||
BarChartData(
|
||||
alignment: BarChartAlignment.spaceAround,
|
||||
maxY: maxY,
|
||||
barTouchData: BarTouchData(
|
||||
enabled: true,
|
||||
touchTooltipData: BarTouchTooltipData(
|
||||
@ -139,9 +168,10 @@ class WeekdayAverages extends StatelessWidget {
|
||||
x: day,
|
||||
barRods: <BarChartRodData>[
|
||||
BarChartRodData(
|
||||
color: Theme.of(context).accentColor,
|
||||
color: Theme.of(context).disabledColor,
|
||||
width: 22,
|
||||
y: _daysData[day],
|
||||
y: _daysData[day].entries.fold(0.0, (double sum, MapEntry<int, double> entry) => sum + entry.value),
|
||||
rodStackItem: _buildDayStack(day, projects),
|
||||
)
|
||||
]
|
||||
))
|
||||
@ -149,10 +179,55 @@ class WeekdayAverages extends StatelessWidget {
|
||||
)
|
||||
),
|
||||
),
|
||||
Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
children: projects.state.projects
|
||||
.map(
|
||||
(project) {
|
||||
return Chip(
|
||||
avatar: ProjectColour(project: project,),
|
||||
label: Text(project.name),
|
||||
);
|
||||
}
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
Container(height: 16,),
|
||||
Text(L10N.of(context).tr.averageDailyHours, style: Theme.of(context).textTheme.title, textAlign: TextAlign.center,),
|
||||
],
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
List<BarChartRodStackItem> _buildDayStack(int day, ProjectsBloc projects) {
|
||||
double runningY = 0;
|
||||
|
||||
// sort the stack from largest to smallest
|
||||
List<List<dynamic>> derp =
|
||||
_daysData[day]
|
||||
.entries
|
||||
.map((entry) {
|
||||
Project project = projects.state.projects.firstWhere((project) => project.id == entry.key);
|
||||
return <dynamic>[project, entry.value];
|
||||
})
|
||||
.toList();
|
||||
derp.sort((a, b) {
|
||||
double va = a[1] as double;
|
||||
double vb = b[1] as double;
|
||||
return vb.compareTo(va);
|
||||
});
|
||||
|
||||
List<BarChartRodStackItem> stack = derp
|
||||
.map((entry) {
|
||||
Project project = entry[0] as Project;
|
||||
double value = entry[1] as double;
|
||||
return BarChartRodStackItem(
|
||||
runningY,
|
||||
runningY += value,
|
||||
project.colour
|
||||
);
|
||||
})
|
||||
.toList();
|
||||
return stack;
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
name: timecop
|
||||
description: A time tracking app that respects your privacy and gets the job done without getting too fancy.
|
||||
version: 1.3.1+18
|
||||
version: 1.3.2+19
|
||||
|
||||
environment:
|
||||
sdk: ">=2.6.0 <3.0.0"
|
||||
|
Reference in New Issue
Block a user