`web/github_dataviz`: Migrate to null safety (#919)

pull/925/head
Brett Morgan 4 years ago committed by GitHub
parent 8932e60976
commit 83d3ea99f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,6 @@
include: package:flutter_lints/flutter.yaml
linter:
rules:
avoid_print: false
prefer_single_quotes: true

@ -1,8 +1,8 @@
import 'package:github_dataviz/mathutils.dart'; import 'package:github_dataviz/mathutils.dart';
class ControlPointAndValue { class ControlPointAndValue {
int point; late int point;
double value; double? value;
ControlPointAndValue() { ControlPointAndValue() {
value = 0; value = 0;
@ -37,9 +37,9 @@ class CatmullInterpolator implements Interpolator {
} }
ControlPointAndValue progressiveGet(ControlPointAndValue cpv) { ControlPointAndValue progressiveGet(ControlPointAndValue cpv) {
double v = cpv.value; double? v = cpv.value;
for (int i = cpv.point; i < controlPoints.length - 1; i++) { for (int i = cpv.point; i < controlPoints.length - 1; i++) {
if (controlPoints[i].x >= v) { if (controlPoints[i].x >= v!) {
double t = (v - controlPoints[i - 1].x) / double t = (v - controlPoints[i - 1].x) /
(controlPoints[i].x - controlPoints[i - 1].x); (controlPoints[i].x - controlPoints[i - 1].x);
double p0 = controlPoints[i - 2].y; double p0 = controlPoints[i - 2].y;

@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
import 'dart:ui'; import 'dart:ui';
class Constants { class Constants {
static final Color backgroundColor = const Color(0xFF000020); static const Color backgroundColor = Color(0xFF000020);
static final Color timelineLineColor = Color(0x60FFFFFF); static const Color timelineLineColor = Color(0x60FFFFFF);
static final Color milestoneColor = Color(0x40FFFFFF); static const Color milestoneColor = Color(0x40FFFFFF);
static final Color milestoneTimelineColor = Colors.white; static const Color milestoneTimelineColor = Colors.white;
} }

@ -8,7 +8,7 @@ class ContributionData {
static ContributionData fromJson(Map<String, dynamic> jsonMap) { static ContributionData fromJson(Map<String, dynamic> jsonMap) {
ContributionData data = ContributionData( ContributionData data = ContributionData(
jsonMap["w"], jsonMap["a"], jsonMap["d"], jsonMap["c"]); jsonMap['w'], jsonMap['a'], jsonMap['d'], jsonMap['c']);
return data; return data;
} }
} }

@ -6,7 +6,7 @@ class User {
User(this.id, this.username, this.avatarUrl); User(this.id, this.username, this.avatarUrl);
static User fromJson(Map<String, dynamic> jsonMap) { static User fromJson(Map<String, dynamic> jsonMap) {
User user = User(jsonMap["id"], jsonMap["login"], jsonMap["avatar_url"]); User user = User(jsonMap['id'], jsonMap['login'], jsonMap['avatar_url']);
return user; return user;
} }
} }

@ -8,11 +8,11 @@ class UserContribution {
UserContribution(this.user, this.contributions); UserContribution(this.user, this.contributions);
static UserContribution fromJson(Map<String, dynamic> jsonMap) { static UserContribution fromJson(Map<String, dynamic> jsonMap) {
List<ContributionData> contributionList = (jsonMap["weeks"] as List) List<ContributionData> contributionList = (jsonMap['weeks'] as List)
.map((e) => ContributionData.fromJson(e)) .map((e) => ContributionData.fromJson(e))
.toList(); .toList();
var userContribution = var userContribution =
UserContribution(User.fromJson(jsonMap["author"]), contributionList); UserContribution(User.fromJson(jsonMap['author']), contributionList);
return userContribution; return userContribution;
} }
} }

@ -1,24 +1,23 @@
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
class WeekLabel { class WeekLabel {
int weekNum; int? weekNum;
String label; String label;
WeekLabel(this.weekNum, this.label); WeekLabel(this.weekNum, this.label);
WeekLabel.forDate(DateTime date, String label) { WeekLabel.forDate(DateTime date, this.label) {
this.label = label;
int year = getYear(date); int year = getYear(date);
int weekOfYearNum = getWeekNumber(date); int weekOfYearNum = getWeekNumber(date);
this.weekNum = 9 + ((year - 2015) * 52) + weekOfYearNum; weekNum = 9 + ((year - 2015) * 52) + weekOfYearNum;
} }
int getYear(DateTime date) { int getYear(DateTime date) {
return int.parse(DateFormat("y").format(date)); return int.parse(DateFormat('y').format(date));
} }
int getWeekNumber(DateTime date) { int getWeekNumber(DateTime date) {
int dayOfYear = int.parse(DateFormat("D").format(date)); int dayOfYear = int.parse(DateFormat('D').format(date));
return ((dayOfYear - date.weekday + 10) / 7).floor(); return ((dayOfYear - date.weekday + 10) / 7).floor();
} }
} }

@ -14,7 +14,9 @@ class LayeredChart extends StatefulWidget {
final List<WeekLabel> milestones; final List<WeekLabel> milestones;
final double animationValue; final double animationValue;
LayeredChart(this.dataToPlot, this.milestones, this.animationValue); const LayeredChart(this.dataToPlot, this.milestones, this.animationValue,
{Key? key})
: super(key: key);
@override @override
State<StatefulWidget> createState() { State<StatefulWidget> createState() {
@ -23,14 +25,14 @@ class LayeredChart extends StatefulWidget {
} }
class LayeredChartState extends State<LayeredChart> { class LayeredChartState extends State<LayeredChart> {
List<Path> paths; late List<Path> paths;
List<Path> capPaths; late List<Path> capPaths;
List<double> maxValues; late List<double> maxValues;
double theta; late double theta;
double graphHeight; late double graphHeight;
List<TextPainter> labelPainter; late List<TextPainter> labelPainter;
List<TextPainter> milestonePainter; late List<TextPainter> milestonePainter;
Size lastSize; Size? lastSize;
void buildPaths( void buildPaths(
Size size, Size size,
@ -89,7 +91,7 @@ class LayeredChartState extends State<LayeredChart> {
j.toDouble(), 0, (numPoints - 1).toDouble(), 0, (n - 1).toDouble()); j.toDouble(), 0, (numPoints - 1).toDouble(), 0, (n - 1).toDouble());
curve.progressiveGet(cpv); curve.progressiveGet(cpv);
curvePoints.add(MathUtils.map( curvePoints.add(MathUtils.map(
max(0, cpv.value), 0, maxValues[i].toDouble(), 0, graphHeight)); max(0, cpv.value!), 0, maxValues[i].toDouble(), 0, graphHeight));
} }
paths.add(Path()); paths.add(Path());
capPaths.add(Path()); capPaths.add(Path());
@ -136,7 +138,7 @@ class LayeredChartState extends State<LayeredChart> {
labelPainter = <TextPainter>[]; labelPainter = <TextPainter>[];
for (int i = 0; i < dataToPlot.length; i++) { for (int i = 0; i < dataToPlot.length; i++) {
TextSpan span = TextSpan( TextSpan span = TextSpan(
style: TextStyle( style: const TextStyle(
color: Color.fromARGB(255, 255, 255, 255), fontSize: 12), color: Color.fromARGB(255, 255, 255, 255), fontSize: 12),
text: dataToPlot[i].label.toUpperCase()); text: dataToPlot[i].label.toUpperCase());
TextPainter tp = TextPainter( TextPainter tp = TextPainter(
@ -149,7 +151,7 @@ class LayeredChartState extends State<LayeredChart> {
milestonePainter = <TextPainter>[]; milestonePainter = <TextPainter>[];
for (int i = 0; i < milestones.length; i++) { for (int i = 0; i < milestones.length; i++) {
TextSpan span = TextSpan( TextSpan span = TextSpan(
style: TextStyle( style: const TextStyle(
color: Color.fromARGB(255, 255, 255, 255), fontSize: 10), color: Color.fromARGB(255, 255, 255, 255), fontSize: 10),
text: milestones[i].label.toUpperCase()); text: milestones[i].label.toUpperCase());
TextPainter tp = TextPainter( TextPainter tp = TextPainter(
@ -174,15 +176,15 @@ class LayeredChartState extends State<LayeredChart> {
} }
class ChartPainter extends CustomPainter { class ChartPainter extends CustomPainter {
static List<Color> colors = [ static List<Color?> colors = [
Colors.red[900], Colors.red[900],
Color(0xffc4721a), const Color(0xffc4721a),
Colors.lime[900], Colors.lime[900],
Colors.green[900], Colors.green[900],
Colors.blue[900], Colors.blue[900],
Colors.purple[900], Colors.purple[900],
]; ];
static List<Color> capColors = [ static List<Color?> capColors = [
Colors.red[500], Colors.red[500],
Colors.amber[500], Colors.amber[500],
Colors.lime[500], Colors.lime[500],
@ -196,17 +198,17 @@ class ChartPainter extends CustomPainter {
double margin; double margin;
double graphGap; double graphGap;
double capTheta; late double capTheta;
double capSize; double capSize;
int numPoints; int numPoints;
double amount = 1.0; double amount = 1.0;
Paint pathPaint; late Paint pathPaint;
Paint capPaint; late Paint capPaint;
Paint textPaint; late Paint textPaint;
Paint milestonePaint; late Paint milestonePaint;
Paint linePaint; late Paint linePaint;
Paint fillPaint; late Paint fillPaint;
LayeredChartState state; LayeredChartState state;
@ -220,13 +222,13 @@ class ChartPainter extends CustomPainter {
this.capSize, this.capSize,
this.numPoints, this.numPoints,
this.amount) { this.amount) {
this.capTheta = pi * capDegrees / 180; capTheta = pi * capDegrees / 180;
pathPaint = Paint(); pathPaint = Paint();
pathPaint.style = PaintingStyle.fill; pathPaint.style = PaintingStyle.fill;
capPaint = Paint(); capPaint = Paint();
capPaint.style = PaintingStyle.fill; capPaint.style = PaintingStyle.fill;
textPaint = Paint(); textPaint = Paint();
textPaint.color = Color(0xFFFFFFFF); textPaint.color = const Color(0xFFFFFFFF);
milestonePaint = Paint(); milestonePaint = Paint();
milestonePaint.color = Constants.milestoneColor; milestonePaint.color = Constants.milestoneColor;
milestonePaint.style = PaintingStyle.stroke; milestonePaint.style = PaintingStyle.stroke;
@ -236,19 +238,19 @@ class ChartPainter extends CustomPainter {
linePaint.strokeWidth = 0.5; linePaint.strokeWidth = 0.5;
fillPaint = Paint(); fillPaint = Paint();
fillPaint.style = PaintingStyle.fill; fillPaint.style = PaintingStyle.fill;
fillPaint.color = Color(0xFF000000); fillPaint.color = const Color(0xFF000000);
} }
@override @override
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
if (dataToPlot.length == 0) { if (dataToPlot.isEmpty) {
return; return;
} }
if (state.lastSize == null || if (state.lastSize == null ||
size.width != state.lastSize.width || size.width != state.lastSize!.width ||
size.height != state.lastSize.height) { size.height != state.lastSize!.height) {
print("Building paths, lastsize = ${state.lastSize}"); print('Building paths, lastsize = ${state.lastSize}');
state.buildPaths(size, dataToPlot, milestones, numPoints, graphGap, state.buildPaths(size, dataToPlot, milestones, numPoints, graphGap,
margin, capTheta, capSize); margin, capTheta, capSize);
} }
@ -266,7 +268,7 @@ class ChartPainter extends CustomPainter {
{ {
for (int i = 0; i < milestones.length; i++) { for (int i = 0; i < milestones.length; i++) {
WeekLabel milestone = milestones[i]; WeekLabel milestone = milestones[i];
double p = (milestone.weekNum.toDouble() / numWeeks) + (1 - amount); double p = (milestone.weekNum!.toDouble() / numWeeks) + (1 - amount);
if (p < 1) { if (p < 1) {
double x1 = MathUtils.map(p, 0, 1, startX, endX); double x1 = MathUtils.map(p, 0, 1, startX, endX);
double y1 = MathUtils.map(p, 0, 1, startY, endY); double y1 = MathUtils.map(p, 0, 1, startY, endY);
@ -282,7 +284,7 @@ class ChartPainter extends CustomPainter {
canvas.translate(textX, textY); canvas.translate(textX, textY);
canvas.skew(tan(capTheta * 1.0), -tan(state.theta)); canvas.skew(tan(capTheta * 1.0), -tan(state.theta));
canvas.translate(-tp.width / 2, 0); canvas.translate(-tp.width / 2, 0);
tp.paint(canvas, Offset(0, 0)); tp.paint(canvas, const Offset(0, 0));
canvas.restore(); canvas.restore();
} }
} }
@ -302,11 +304,11 @@ class ChartPainter extends CustomPainter {
canvas.skew(0, -tan(state.theta)); canvas.skew(0, -tan(state.theta));
canvas.drawRect( canvas.drawRect(
Rect.fromLTWH(-1, -1, tp.width + 2, tp.height + 2), fillPaint); Rect.fromLTWH(-1, -1, tp.width + 2, tp.height + 2), fillPaint);
tp.paint(canvas, Offset(0, 0)); tp.paint(canvas, const Offset(0, 0));
canvas.restore(); canvas.restore();
} }
linePaint.color = capColors[i]; linePaint.color = capColors[i]!;
canvas.drawLine(Offset(startX, startY), Offset(endX, endY), linePaint); canvas.drawLine(Offset(startX, startY), Offset(endX, endY), linePaint);
Path clipPath = Path(); Path clipPath = Path();
@ -317,8 +319,8 @@ class ChartPainter extends CustomPainter {
clipPath.close(); clipPath.close();
canvas.clipPath(clipPath); canvas.clipPath(clipPath);
pathPaint.color = colors[i]; pathPaint.color = colors[i]!;
capPaint.color = capColors[i]; capPaint.color = capColors[i]!;
double offsetX = MathUtils.map(1 - amount, 0, 1, startX, endX); double offsetX = MathUtils.map(1 - amount, 0, 1, startX, endX);
double offsetY = MathUtils.map(1 - amount, 0, 1, startY, endY); double offsetY = MathUtils.map(1 - amount, 0, 1, startY, endY);
canvas.translate(offsetX - startX, offsetY - startY); canvas.translate(offsetX - startX, offsetY - startY);

@ -19,21 +19,23 @@ import 'package:github_dataviz/mathutils.dart';
import 'package:github_dataviz/timeline.dart'; import 'package:github_dataviz/timeline.dart';
class MainLayout extends StatefulWidget { class MainLayout extends StatefulWidget {
const MainLayout({Key? key}) : super(key: key);
@override @override
_MainLayoutState createState() => _MainLayoutState(); _MainLayoutState createState() => _MainLayoutState();
} }
class _MainLayoutState extends State<MainLayout> with TickerProviderStateMixin { class _MainLayoutState extends State<MainLayout> with TickerProviderStateMixin {
AnimationController _animation; AnimationController? _animation;
List<UserContribution> contributions; List<UserContribution>? contributions;
List<StatForWeek> starsByWeek; List<StatForWeek>? starsByWeek;
List<StatForWeek> forksByWeek; List<StatForWeek>? forksByWeek;
List<StatForWeek> pushesByWeek; List<StatForWeek>? pushesByWeek;
List<StatForWeek> issueCommentsByWeek; List<StatForWeek>? issueCommentsByWeek;
List<StatForWeek> pullRequestActivityByWeek; List<StatForWeek>? pullRequestActivityByWeek;
List<WeekLabel> weekLabels; late List<WeekLabel> weekLabels;
static final double earlyInterpolatorFraction = 0.8; static const double earlyInterpolatorFraction = 0.8;
static final EarlyInterpolator interpolator = static final EarlyInterpolator interpolator =
EarlyInterpolator(earlyInterpolatorFraction); EarlyInterpolator(earlyInterpolatorFraction);
double animationValue = 1.0; double animationValue = 1.0;
@ -47,14 +49,14 @@ class _MainLayoutState extends State<MainLayout> with TickerProviderStateMixin {
createAnimation(0); createAnimation(0);
weekLabels = <WeekLabel>[]; weekLabels = <WeekLabel>[];
weekLabels.add(WeekLabel.forDate(DateTime(2019, 2, 26), "v1.2")); weekLabels.add(WeekLabel.forDate(DateTime(2019, 2, 26), 'v1.2'));
weekLabels.add(WeekLabel.forDate(DateTime(2018, 12, 4), "v1.0")); 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(new DateTime(2018, 9, 19), "Preview 2"));
weekLabels.add(WeekLabel.forDate(DateTime(2018, 6, 21), "Preview 1")); 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(new DateTime(2018, 5, 7), "Beta 3"));
weekLabels.add(WeekLabel.forDate(DateTime(2018, 2, 27), "Beta 1")); weekLabels.add(WeekLabel.forDate(DateTime(2018, 2, 27), 'Beta 1'));
weekLabels.add(WeekLabel.forDate(DateTime(2017, 5, 1), "Alpha")); weekLabels.add(WeekLabel.forDate(DateTime(2017, 5, 1), 'Alpha'));
weekLabels.add(WeekLabel(48, "Repo Made Public")); weekLabels.add(WeekLabel(48, 'Repo Made Public'));
loadGitHubData(); loadGitHubData();
} }
@ -66,10 +68,10 @@ class _MainLayoutState extends State<MainLayout> with TickerProviderStateMixin {
duration: const Duration(milliseconds: 14400), duration: const Duration(milliseconds: 14400),
vsync: this, vsync: this,
)..repeat(); )..repeat();
_animation.addListener(() { _animation!.addListener(() {
setState(() { setState(() {
if (!timelineOverride) { if (!timelineOverride) {
animationValue = _animation.value; animationValue = _animation!.value;
interpolatedAnimationValue = interpolator.get(animationValue); interpolatedAnimationValue = interpolator.get(animationValue);
} }
}); });
@ -82,7 +84,7 @@ class _MainLayoutState extends State<MainLayout> with TickerProviderStateMixin {
List<DataSeries> dataToPlot = []; List<DataSeries> dataToPlot = [];
if (contributions != null) { if (contributions != null) {
List<int> series = []; List<int> series = [];
for (UserContribution userContrib in contributions) { for (UserContribution userContrib in contributions!) {
for (int i = 0; i < userContrib.contributions.length; i++) { for (int i = 0; i < userContrib.contributions.length; i++) {
ContributionData data = userContrib.contributions[i]; ContributionData data = userContrib.contributions[i];
if (series.length > i) { if (series.length > i) {
@ -92,32 +94,32 @@ class _MainLayoutState extends State<MainLayout> with TickerProviderStateMixin {
} }
} }
} }
dataToPlot.add(DataSeries("Added Lines", series)); dataToPlot.add(DataSeries('Added Lines', series));
} }
if (starsByWeek != null) { if (starsByWeek != null) {
dataToPlot dataToPlot
.add(DataSeries("Stars", starsByWeek.map((e) => e.stat).toList())); .add(DataSeries('Stars', starsByWeek!.map((e) => e.stat).toList()));
} }
if (forksByWeek != null) { if (forksByWeek != null) {
dataToPlot dataToPlot
.add(DataSeries("Forks", forksByWeek.map((e) => e.stat).toList())); .add(DataSeries('Forks', forksByWeek!.map((e) => e.stat).toList()));
} }
if (pushesByWeek != null) { if (pushesByWeek != null) {
dataToPlot dataToPlot
.add(DataSeries("Pushes", pushesByWeek.map((e) => e.stat).toList())); .add(DataSeries('Pushes', pushesByWeek!.map((e) => e.stat).toList()));
} }
if (issueCommentsByWeek != null) { if (issueCommentsByWeek != null) {
dataToPlot.add(DataSeries( dataToPlot.add(DataSeries(
"Issue Comments", issueCommentsByWeek.map((e) => e.stat).toList())); 'Issue Comments', issueCommentsByWeek!.map((e) => e.stat).toList()));
} }
if (pullRequestActivityByWeek != null) { if (pullRequestActivityByWeek != null) {
dataToPlot.add(DataSeries("Pull Request Activity", dataToPlot.add(DataSeries('Pull Request Activity',
pullRequestActivityByWeek.map((e) => e.stat).toList())); pullRequestActivityByWeek!.map((e) => e.stat).toList()));
} }
LayeredChart layeredChart = LayeredChart layeredChart =
@ -126,15 +128,13 @@ class _MainLayoutState extends State<MainLayout> with TickerProviderStateMixin {
const double timelinePadding = 60.0; const double timelinePadding = 60.0;
var timeline = Timeline( var timeline = Timeline(
numWeeks: dataToPlot != null && dataToPlot.length > 0 numWeeks: dataToPlot.isNotEmpty ? dataToPlot.last.series.length : 0,
? dataToPlot.last.series.length
: 0,
animationValue: interpolatedAnimationValue, animationValue: interpolatedAnimationValue,
weekLabels: weekLabels, weekLabels: weekLabels,
mouseDownCallback: (double xFraction) { mouseDownCallback: (double xFraction) {
setState(() { setState(() {
timelineOverride = true; timelineOverride = true;
_animation.stop(); _animation?.stop();
interpolatedAnimationValue = xFraction; interpolatedAnimationValue = xFraction;
}); });
}, },
@ -176,53 +176,55 @@ class _MainLayoutState extends State<MainLayout> with TickerProviderStateMixin {
@override @override
void dispose() { void dispose() {
_animation.dispose(); _animation?.dispose();
super.dispose(); super.dispose();
} }
Future loadGitHubData() async { Future loadGitHubData() async {
String contributorsJsonStr = String contributorsJsonStr =
(await http.get("assets/github_data/contributors.json")).body; (await http.get(Uri.parse('assets/github_data/contributors.json')))
.body;
List jsonObjs = jsonDecode(contributorsJsonStr) as List; List jsonObjs = jsonDecode(contributorsJsonStr) as List;
List<UserContribution> contributionList = List<UserContribution> contributionList =
jsonObjs.map((e) => UserContribution.fromJson(e)).toList(); jsonObjs.map((e) => UserContribution.fromJson(e)).toList();
print( print(
"Loaded ${contributionList.length} code contributions to /flutter/flutter repo."); 'Loaded ${contributionList.length} code contributions to /flutter/flutter repo.');
int numWeeksTotal = contributionList[0].contributions.length; int numWeeksTotal = contributionList[0].contributions.length;
String starsByWeekStr = String starsByWeekStr =
(await http.get("assets/github_data/stars.tsv")).body; (await http.get(Uri.parse('assets/github_data/stars.tsv'))).body;
List<StatForWeek> starsByWeekLoaded = List<StatForWeek> starsByWeekLoaded =
summarizeWeeksFromTSV(starsByWeekStr, numWeeksTotal); summarizeWeeksFromTSV(starsByWeekStr, numWeeksTotal);
String forksByWeekStr = String forksByWeekStr =
(await http.get("assets/github_data/forks.tsv")).body; (await http.get(Uri.parse('assets/github_data/forks.tsv'))).body;
List<StatForWeek> forksByWeekLoaded = List<StatForWeek> forksByWeekLoaded =
summarizeWeeksFromTSV(forksByWeekStr, numWeeksTotal); summarizeWeeksFromTSV(forksByWeekStr, numWeeksTotal);
String commitsByWeekStr = String commitsByWeekStr =
(await http.get("assets/github_data/commits.tsv")).body; (await http.get(Uri.parse('assets/github_data/commits.tsv'))).body;
List<StatForWeek> commitsByWeekLoaded = List<StatForWeek> commitsByWeekLoaded =
summarizeWeeksFromTSV(commitsByWeekStr, numWeeksTotal); summarizeWeeksFromTSV(commitsByWeekStr, numWeeksTotal);
String commentsByWeekStr = String commentsByWeekStr =
(await http.get("assets/github_data/comments.tsv")).body; (await http.get(Uri.parse('assets/github_data/comments.tsv'))).body;
List<StatForWeek> commentsByWeekLoaded = List<StatForWeek> commentsByWeekLoaded =
summarizeWeeksFromTSV(commentsByWeekStr, numWeeksTotal); summarizeWeeksFromTSV(commentsByWeekStr, numWeeksTotal);
String pullRequestActivityByWeekStr = String pullRequestActivityByWeekStr =
(await http.get("assets/github_data/pull_requests.tsv")).body; (await http.get(Uri.parse('assets/github_data/pull_requests.tsv')))
.body;
List<StatForWeek> pullRequestActivityByWeekLoaded = List<StatForWeek> pullRequestActivityByWeekLoaded =
summarizeWeeksFromTSV(pullRequestActivityByWeekStr, numWeeksTotal); summarizeWeeksFromTSV(pullRequestActivityByWeekStr, numWeeksTotal);
setState(() { setState(() {
this.contributions = contributionList; contributions = contributionList;
this.starsByWeek = starsByWeekLoaded; starsByWeek = starsByWeekLoaded;
this.forksByWeek = forksByWeekLoaded; forksByWeek = forksByWeekLoaded;
this.pushesByWeek = commitsByWeekLoaded; pushesByWeek = commitsByWeekLoaded;
this.issueCommentsByWeek = commentsByWeekLoaded; issueCommentsByWeek = commentsByWeekLoaded;
this.pullRequestActivityByWeek = pullRequestActivityByWeekLoaded; pullRequestActivityByWeek = pullRequestActivityByWeekLoaded;
}); });
} }
@ -230,18 +232,18 @@ class _MainLayoutState extends State<MainLayout> with TickerProviderStateMixin {
String statByWeekStr, int numWeeksTotal) { String statByWeekStr, int numWeeksTotal) {
List<StatForWeek> loadedStats = []; List<StatForWeek> loadedStats = [];
HashMap<int, StatForWeek> statMap = HashMap(); HashMap<int, StatForWeek> statMap = HashMap();
statByWeekStr.split("\n").forEach((s) { statByWeekStr.split('\n').forEach((s) {
List<String> split = s.split("\t"); List<String> split = s.split('\t');
if (split.length == 2) { if (split.length == 2) {
int weekNum = int.parse(split[0]); int weekNum = int.parse(split[0]);
statMap[weekNum] = StatForWeek(weekNum, int.parse(split[1])); statMap[weekNum] = StatForWeek(weekNum, int.parse(split[1]));
} }
}); });
print("Loaded ${statMap.length} weeks."); print('Loaded ${statMap.length} weeks.');
// Convert into a list by week, but fill in empty weeks with 0 // Convert into a list by week, but fill in empty weeks with 0
for (int i = 0; i < numWeeksTotal; i++) { for (int i = 0; i < numWeeksTotal; i++) {
StatForWeek starsForWeek = statMap[i]; StatForWeek? starsForWeek = statMap[i];
if (starsForWeek == null) { if (starsForWeek == null) {
loadedStats.add(StatForWeek(i, 0)); loadedStats.add(StatForWeek(i, 0));
} else { } else {
@ -253,5 +255,5 @@ class _MainLayoutState extends State<MainLayout> with TickerProviderStateMixin {
} }
void main() { void main() {
runApp(Center(child: MainLayout())); runApp(const Center(child: MainLayout()));
} }

@ -14,17 +14,19 @@ class Timeline extends StatefulWidget {
final double animationValue; final double animationValue;
final List<WeekLabel> weekLabels; final List<WeekLabel> weekLabels;
final MouseDownCallback mouseDownCallback; final MouseDownCallback? mouseDownCallback;
final MouseMoveCallback mouseMoveCallback; final MouseMoveCallback? mouseMoveCallback;
final MouseUpCallback mouseUpCallback; final MouseUpCallback? mouseUpCallback;
Timeline( const Timeline(
{@required this.numWeeks, {required this.numWeeks,
@required this.animationValue, required this.animationValue,
@required this.weekLabels, required this.weekLabels,
this.mouseDownCallback, this.mouseDownCallback,
this.mouseMoveCallback, this.mouseMoveCallback,
this.mouseUpCallback}); this.mouseUpCallback,
Key? key})
: super(key: key);
@override @override
State<StatefulWidget> createState() { State<StatefulWidget> createState() {
@ -39,17 +41,17 @@ class TimelineState extends State<Timeline> {
void initState() { void initState() {
super.initState(); super.initState();
for (int year = 2015; year < 2020; year++) { for (int year = 2015; year < 2020; year++) {
String yearLabel = "$year"; String yearLabel = '$year';
labelPainters[yearLabel] = labelPainters[yearLabel] =
_makeTextPainter(Constants.timelineLineColor, yearLabel); _makeTextPainter(Constants.timelineLineColor, yearLabel);
} }
widget.weekLabels.forEach((WeekLabel weekLabel) { for (var weekLabel in widget.weekLabels) {
labelPainters[weekLabel.label] = labelPainters[weekLabel.label] =
_makeTextPainter(Constants.milestoneTimelineColor, weekLabel.label); _makeTextPainter(Constants.milestoneTimelineColor, weekLabel.label);
labelPainters[weekLabel.label + "_red"] = labelPainters[weekLabel.label + '_red'] =
_makeTextPainter(Colors.redAccent, weekLabel.label); _makeTextPainter(Colors.redAccent, weekLabel.label);
}); }
} }
@override @override
@ -57,19 +59,22 @@ class TimelineState extends State<Timeline> {
return GestureDetector( return GestureDetector(
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
onHorizontalDragDown: (DragDownDetails details) { onHorizontalDragDown: (DragDownDetails details) {
if (widget.mouseDownCallback != null) { final mouseDownCallback = widget.mouseDownCallback;
widget.mouseDownCallback( if (mouseDownCallback != null) {
mouseDownCallback(
_getClampedXFractionLocalCoords(context, details.globalPosition)); _getClampedXFractionLocalCoords(context, details.globalPosition));
} }
}, },
onHorizontalDragEnd: (DragEndDetails details) { onHorizontalDragEnd: (DragEndDetails details) {
if (widget.mouseUpCallback != null) { final mouseUpCallback = widget.mouseUpCallback;
widget.mouseUpCallback(); if (mouseUpCallback != null) {
mouseUpCallback();
} }
}, },
onHorizontalDragUpdate: (DragUpdateDetails details) { onHorizontalDragUpdate: (DragUpdateDetails details) {
if (widget.mouseMoveCallback != null) { final mouseMoveCallback = widget.mouseMoveCallback;
widget.mouseMoveCallback( if (mouseMoveCallback != null) {
mouseMoveCallback(
_getClampedXFractionLocalCoords(context, details.globalPosition)); _getClampedXFractionLocalCoords(context, details.globalPosition));
} }
}, },
@ -95,17 +100,17 @@ class TimelineState extends State<Timeline> {
double _getClampedXFractionLocalCoords( double _getClampedXFractionLocalCoords(
BuildContext context, Offset globalOffset) { BuildContext context, Offset globalOffset) {
final RenderBox box = context.findRenderObject(); final RenderBox box = context.findRenderObject() as RenderBox;
final Offset localOffset = box.globalToLocal(globalOffset); final Offset localOffset = box.globalToLocal(globalOffset);
return MathUtils.clamp(localOffset.dx / context.size.width, 0, 1); return MathUtils.clamp(localOffset.dx / context.size!.width, 0, 1);
} }
} }
class TimelinePainter extends CustomPainter { class TimelinePainter extends CustomPainter {
TimelineState state; TimelineState state;
Paint mainLinePaint; late Paint mainLinePaint;
Paint milestoneLinePaint; late Paint milestoneLinePaint;
Color lineColor = Colors.white; Color lineColor = Colors.white;
@ -164,7 +169,7 @@ class TimelinePainter extends CustomPainter {
var mappedValue = var mappedValue =
MathUtils.clampedMap(currTimeXDiff, 0, 0.025, 0, 1); MathUtils.clampedMap(currTimeXDiff, 0, 0.025, 0, 1);
var lerpedColor = Color.lerp(Constants.milestoneTimelineColor, var lerpedColor = Color.lerp(Constants.milestoneTimelineColor,
Constants.timelineLineColor, mappedValue); Constants.timelineLineColor, mappedValue)!;
mainLinePaint.color = lerpedColor; mainLinePaint.color = lerpedColor;
} else { } else {
mainLinePaint.color = Constants.timelineLineColor; mainLinePaint.color = Constants.timelineLineColor;
@ -174,8 +179,8 @@ class TimelinePainter extends CustomPainter {
} }
if (isYear) { if (isYear) {
var yearLabel = "$yearNumber"; var yearLabel = '$yearNumber';
state.labelPainters[yearLabel] state.labelPainters[yearLabel]!
.paint(canvas, Offset(currX, size.height - labelHeight)); .paint(canvas, Offset(currX, size.height - labelHeight));
yearNumber++; yearNumber++;
} }
@ -185,17 +190,17 @@ class TimelinePainter extends CustomPainter {
{ {
for (int i = 0; i < weekLabels.length; i++) { for (int i = 0; i < weekLabels.length; i++) {
WeekLabel weekLabel = weekLabels[i]; WeekLabel weekLabel = weekLabels[i];
double currX = (weekLabel.weekNum / numWeeks.toDouble()) * size.width; double currX = (weekLabel.weekNum! / numWeeks.toDouble()) * size.width;
var timelineXDiff = (currTimeX - currX) / size.width; var timelineXDiff = (currTimeX - currX) / size.width;
double maxTimelineDiff = 0.08; double maxTimelineDiff = 0.08;
TextPainter textPainter = state.labelPainters[weekLabel.label]; TextPainter textPainter = state.labelPainters[weekLabel.label]!;
if (timelineXDiff > 0 && if (timelineXDiff > 0 &&
timelineXDiff < maxTimelineDiff && timelineXDiff < maxTimelineDiff &&
animationValue < 1) { animationValue < 1) {
var mappedValue = var mappedValue =
MathUtils.clampedMap(timelineXDiff, 0, maxTimelineDiff, 0, 1); MathUtils.clampedMap(timelineXDiff, 0, maxTimelineDiff, 0, 1);
var lerpedColor = Color.lerp( var lerpedColor = Color.lerp(
Colors.redAccent, Constants.milestoneTimelineColor, mappedValue); Colors.redAccent, Constants.milestoneTimelineColor, mappedValue)!;
milestoneLinePaint.strokeWidth = milestoneLinePaint.strokeWidth =
MathUtils.clampedMap(timelineXDiff, 0, maxTimelineDiff, 6, 1); MathUtils.clampedMap(timelineXDiff, 0, maxTimelineDiff, 6, 1);
milestoneLinePaint.color = lerpedColor; milestoneLinePaint.color = lerpedColor;

@ -1,13 +1,20 @@
# Generated by pub # Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile # See https://dart.dev/tools/pub/glossary#lockfile
packages: packages:
async:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.8.2"
characters: characters:
dependency: transitive dependency: transitive
description: description:
name: characters name: characters
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0" version: "1.2.0"
charcode: charcode:
dependency: transitive dependency: transitive
description: description:
@ -15,6 +22,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.1" version: "1.3.1"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
collection: collection:
dependency: transitive dependency: transitive
description: description:
@ -27,27 +41,41 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.4"
http: http:
dependency: "direct main" dependency: "direct main"
description: description:
name: http name: http
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.12.2" version: "0.13.4"
http_parser: http_parser:
dependency: transitive dependency: transitive
description: description:
name: http_parser name: http_parser
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.1.4" version: "4.0.0"
intl: intl:
dependency: "direct main" dependency: "direct main"
description: description:
name: intl name: intl
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.16.1" version: "0.17.0"
lints:
dependency: transitive
description:
name: lints
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
meta: meta:
dependency: transitive dependency: transitive
description: description:
@ -62,13 +90,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.0" version: "1.8.0"
pedantic:
dependency: transitive
description:
name: pedantic
url: "https://pub.dartlang.org"
source: hosted
version: "1.11.1"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -110,4 +131,4 @@ packages:
source: hosted source: hosted
version: "2.1.0" version: "2.1.0"
sdks: sdks:
dart: ">=2.12.0 <3.0.0" dart: ">=2.14.0 <3.0.0"

@ -1,13 +1,17 @@
name: github_dataviz name: github_dataviz
environment: environment:
sdk: ">=2.2.0 <3.0.0" sdk: '>=2.12.0 <3.0.0'
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
intl: ^0.16.0 intl: ^0.17.0
http: ^0.12.0 http: ^0.13.4
dev_dependencies:
flutter_lints: ^1.0.4
flutter: flutter:
assets: assets:
- preview.png - preview.png

Loading…
Cancel
Save