// Copyright 2018 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:collection'; import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:flutter/material.dart'; import 'package:github_dataviz/constants.dart'; import 'package:github_dataviz/data/contribution_data.dart'; import 'package:github_dataviz/data/data_series.dart'; import 'package:github_dataviz/data/stat_for_week.dart'; import 'package:github_dataviz/data/user_contribution.dart'; import 'package:github_dataviz/data/week_label.dart'; import 'package:github_dataviz/layered_chart.dart'; import 'package:github_dataviz/mathutils.dart'; import 'package:github_dataviz/timeline.dart'; class MainLayout extends StatefulWidget { const MainLayout({Key? key}) : super(key: key); @override _MainLayoutState createState() => _MainLayoutState(); } class _MainLayoutState extends State with TickerProviderStateMixin { AnimationController? _animation; List? contributions; List? starsByWeek; List? forksByWeek; List? pushesByWeek; List? issueCommentsByWeek; List? pullRequestActivityByWeek; late List weekLabels; static const double earlyInterpolatorFraction = 0.8; static final EarlyInterpolator interpolator = EarlyInterpolator(earlyInterpolatorFraction); double animationValue = 1.0; double interpolatedAnimationValue = 1.0; bool timelineOverride = false; @override void initState() { super.initState(); createAnimation(0); weekLabels = []; weekLabels.add(WeekLabel.forDate(DateTime(2019, 2, 26), 'v1.2')); weekLabels.add(WeekLabel.forDate(DateTime(2018, 12, 4), 'v1.0')); // weekLabels.add(WeekLabel.forDate(new DateTime(2018, 9, 19), "Preview 2")); weekLabels.add(WeekLabel.forDate(DateTime(2018, 6, 21), 'Preview 1')); // weekLabels.add(WeekLabel.forDate(new DateTime(2018, 5, 7), "Beta 3")); weekLabels.add(WeekLabel.forDate(DateTime(2018, 2, 27), 'Beta 1')); weekLabels.add(WeekLabel.forDate(DateTime(2017, 5, 1), 'Alpha')); weekLabels.add(WeekLabel(48, 'Repo Made Public')); loadGitHubData(); } void createAnimation(double startValue) { _animation?.dispose(); _animation = AnimationController( value: startValue, duration: const Duration(milliseconds: 14400), vsync: this, )..repeat(); _animation!.addListener(() { setState(() { if (!timelineOverride) { animationValue = _animation!.value; interpolatedAnimationValue = interpolator.get(animationValue); } }); }); } @override Widget build(BuildContext context) { // Combined contributions data List dataToPlot = []; if (contributions != null) { List series = []; for (UserContribution userContrib in contributions!) { for (int i = 0; i < userContrib.contributions.length; i++) { ContributionData data = userContrib.contributions[i]; if (series.length > i) { series[i] = series[i] + data.add; } else { series.add(data.add); } } } dataToPlot.add(DataSeries('Added Lines', series)); } if (starsByWeek != null) { dataToPlot .add(DataSeries('Stars', starsByWeek!.map((e) => e.stat).toList())); } if (forksByWeek != null) { dataToPlot .add(DataSeries('Forks', forksByWeek!.map((e) => e.stat).toList())); } if (pushesByWeek != null) { dataToPlot .add(DataSeries('Pushes', pushesByWeek!.map((e) => e.stat).toList())); } if (issueCommentsByWeek != null) { dataToPlot.add(DataSeries( 'Issue Comments', issueCommentsByWeek!.map((e) => e.stat).toList())); } if (pullRequestActivityByWeek != null) { dataToPlot.add(DataSeries('Pull Request Activity', pullRequestActivityByWeek!.map((e) => e.stat).toList())); } LayeredChart layeredChart = LayeredChart(dataToPlot, weekLabels, interpolatedAnimationValue); const double timelinePadding = 60.0; var timeline = Timeline( numWeeks: dataToPlot.isNotEmpty ? dataToPlot.last.series.length : 0, animationValue: interpolatedAnimationValue, weekLabels: weekLabels, mouseDownCallback: (double xFraction) { setState(() { timelineOverride = true; _animation?.stop(); interpolatedAnimationValue = xFraction; }); }, mouseMoveCallback: (double xFraction) { setState(() { interpolatedAnimationValue = xFraction; }); }, mouseUpCallback: () { setState(() { timelineOverride = false; createAnimation( interpolatedAnimationValue * earlyInterpolatorFraction); }); }, ); Column mainColumn = Column( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.max, children: [ Expanded(child: layeredChart), Padding( padding: const EdgeInsets.only( left: timelinePadding, right: timelinePadding, bottom: timelinePadding), child: timeline, ), ], ); return Container( color: Constants.backgroundColor, child: Directionality(textDirection: TextDirection.ltr, child: mainColumn), ); } @override void dispose() { _animation?.dispose(); super.dispose(); } Future loadGitHubData() async { String contributorsJsonStr = (await http.get(Uri.parse('assets/github_data/contributors.json'))) .body; List jsonObjs = jsonDecode(contributorsJsonStr) as List; List contributionList = jsonObjs.map((e) => UserContribution.fromJson(e)).toList(); print( 'Loaded ${contributionList.length} code contributions to /flutter/flutter repo.'); int numWeeksTotal = contributionList[0].contributions.length; String starsByWeekStr = (await http.get(Uri.parse('assets/github_data/stars.tsv'))).body; List starsByWeekLoaded = summarizeWeeksFromTSV(starsByWeekStr, numWeeksTotal); String forksByWeekStr = (await http.get(Uri.parse('assets/github_data/forks.tsv'))).body; List forksByWeekLoaded = summarizeWeeksFromTSV(forksByWeekStr, numWeeksTotal); String commitsByWeekStr = (await http.get(Uri.parse('assets/github_data/commits.tsv'))).body; List commitsByWeekLoaded = summarizeWeeksFromTSV(commitsByWeekStr, numWeeksTotal); String commentsByWeekStr = (await http.get(Uri.parse('assets/github_data/comments.tsv'))).body; List commentsByWeekLoaded = summarizeWeeksFromTSV(commentsByWeekStr, numWeeksTotal); String pullRequestActivityByWeekStr = (await http.get(Uri.parse('assets/github_data/pull_requests.tsv'))) .body; List pullRequestActivityByWeekLoaded = summarizeWeeksFromTSV(pullRequestActivityByWeekStr, numWeeksTotal); setState(() { contributions = contributionList; starsByWeek = starsByWeekLoaded; forksByWeek = forksByWeekLoaded; pushesByWeek = commitsByWeekLoaded; issueCommentsByWeek = commentsByWeekLoaded; pullRequestActivityByWeek = pullRequestActivityByWeekLoaded; }); } List summarizeWeeksFromTSV( String statByWeekStr, int numWeeksTotal) { List loadedStats = []; HashMap statMap = HashMap(); statByWeekStr.split('\n').forEach((s) { List split = s.split('\t'); if (split.length == 2) { int weekNum = int.parse(split[0]); statMap[weekNum] = StatForWeek(weekNum, int.parse(split[1])); } }); print('Loaded ${statMap.length} weeks.'); // Convert into a list by week, but fill in empty weeks with 0 for (int i = 0; i < numWeeksTotal; i++) { StatForWeek? starsForWeek = statMap[i]; if (starsForWeek == null) { loadedStats.add(StatForWeek(i, 0)); } else { loadedStats.add(starsForWeek); } } return loadedStats; } } void main() { runApp(const Center(child: MainLayout())); }