-
Notifications
You must be signed in to change notification settings - Fork 160
Visual update of scores on analysis tab. #1419
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file | ||
// for details. All rights reserved. Use of this source code is governed by a | ||
// BSD-style license that can be found in the LICENSE file. | ||
|
||
/// Represents an RGBA color code. | ||
class Color { | ||
static const black = const Color(0, 0, 0); | ||
static const blue = const Color(0, 0, 255); | ||
static const green = const Color(0, 255, 0); | ||
static const peach = const Color(255, 229, 180); | ||
static const red = const Color(255, 0, 0); | ||
static const silver = const Color(192, 192, 192); | ||
static const slateGray = const Color(112, 128, 144); | ||
static const white = const Color(255, 255, 255); | ||
|
||
final int r; | ||
final int g; | ||
final int b; | ||
final double a; | ||
|
||
const Color(this.r, this.g, this.b, [this.a = 1.0]); | ||
|
||
Color change({int r, int g, int b, double a}) => | ||
new Color(r ?? this.r, g ?? this.g, b ?? this.b, a ?? this.a); | ||
|
||
Color interpolateTo(Color to, double value) { | ||
final red = ((to.r - r) * value).round() + r; | ||
final green = ((to.g - g) * value).round() + g; | ||
final blue = ((to.b - b) * value).round() + b; | ||
final alpha = ((to.a - a) * value) + a; | ||
return new Color(red, green, blue, alpha); | ||
} | ||
|
||
Color multipleValues(double value) { | ||
return change( | ||
r: (r * value).round(), | ||
g: (g * value).round(), | ||
b: (b * value).round(), | ||
); | ||
} | ||
|
||
@override | ||
String toString() => a == 1.0 | ||
? 'rgb($r, $g, $b)' | ||
: 'rgba($r, $g, $b, ${a.toStringAsFixed(4)})'; | ||
} | ||
|
||
/// Represents the selection of colors that will be used to draw a specific shape. | ||
class Brush { | ||
final Color background; | ||
final Color color; | ||
final Color shadow; | ||
|
||
const Brush({this.background, this.color, this.shadow}); | ||
|
||
Brush interpolateTo(Brush to, double value) { | ||
return new Brush( | ||
background: background.interpolateTo(to.background, value), | ||
color: color.interpolateTo(to.color, value), | ||
shadow: shadow.interpolateTo(to.shadow, value), | ||
); | ||
} | ||
|
||
Brush change({Color background, Color color, Color shadow}) { | ||
return new Brush( | ||
background: background ?? this.background, | ||
color: color ?? this.color, | ||
shadow: shadow ?? this.shadow); | ||
} | ||
} | ||
|
||
/// A semi-transparent black for using as a non-intrusive shadow. | ||
final _blackShadow = Color.black.change(a: 0.5); | ||
|
||
/// Color to use when the analysis/score is missing (skipped or not done yet). | ||
final _scoreBoxMissing = new Color(204, 204, 204); | ||
|
||
/// Color to use when the analysis result was top of the range (70+). | ||
final _scoreBoxSolid = new Color(1, 117, 194); | ||
|
||
/// Color to use when the analysis result was in the middle of the range (40-70) | ||
final _scoreBoxGood = new Color(0, 196, 179); | ||
|
||
/// Color to use when the analysis result was in the lower range (0-40) | ||
final _scoreBoxRotten = new Color(187, 36, 0); | ||
|
||
/// The default set of colors to use. | ||
final _defaultBrush = new Brush( | ||
background: _scoreBoxMissing, color: Color.white, shadow: _blackShadow); | ||
|
||
/// Get the [Brush] that will be used to render the overall score progress bar. | ||
Brush overallScoreBrush(double score) { | ||
if (score == null) { | ||
return _defaultBrush; | ||
} | ||
if (score <= 0.4) { | ||
return _defaultBrush.change(background: _scoreBoxRotten); | ||
} | ||
if (score <= 0.7) { | ||
return _defaultBrush.change(background: _scoreBoxGood); | ||
} | ||
return _defaultBrush.change(background: _scoreBoxSolid); | ||
} | ||
|
||
/// Get the [Brush] that will be used to render the generic score progress bars | ||
/// (e.g. popularity or health score). | ||
Brush genericScoreBrush(double score) { | ||
if (score == null) { | ||
return _defaultBrush; | ||
} | ||
final bg = Color.slateGray.interpolateTo(Color.silver, score); | ||
return _defaultBrush.change(background: bg); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,7 @@ import '../shared/search_service.dart' show SearchQuery, serializeSearchOrder; | |
import '../shared/urls.dart' as urls; | ||
import '../shared/utils.dart'; | ||
|
||
import 'color.dart'; | ||
import 'model_properties.dart' show Author; | ||
import 'models.dart'; | ||
import 'static_files.dart'; | ||
|
@@ -276,10 +277,7 @@ class TemplateService { | |
'has_dev': devDeps.isNotEmpty, | ||
'dev': devDeps, | ||
}, | ||
'health': _formatScore(extract?.health), | ||
'maintenance': _formatScore(extract?.maintenance), | ||
'popularity': _formatScore(extract?.popularity), | ||
'score_box_html': _renderScoreBox(analysisStatus, extract?.overallScore), | ||
'score_bars': _renderScoreBars(extract), | ||
}; | ||
|
||
return _renderTemplate('pkg/analysis_tab', data); | ||
|
@@ -420,6 +418,34 @@ class TemplateService { | |
return values; | ||
} | ||
|
||
Map<String, dynamic> _renderScoreBars(AnalysisExtract extract) { | ||
String renderScoreBar(double score, Brush brush) { | ||
return _renderTemplate('pkg/score_bar', { | ||
'percent': _formatScore(score ?? 0.0), | ||
'score': _formatScore(score), | ||
'background': brush.background.toString(), | ||
'color': brush.color.toString(), | ||
'shadow': brush.shadow.toString(), | ||
}); | ||
} | ||
|
||
final analysisSkipped = _isAnalysisSkipped(extract?.analysisStatus); | ||
final healthScore = extract?.health; | ||
final maintenanceScore = extract?.maintenance; | ||
final popularityScore = extract?.popularity; | ||
final overallScore = analysisSkipped ? null : extract?.overallScore ?? 0.0; | ||
return { | ||
'health_html': | ||
renderScoreBar(healthScore, genericScoreBrush(healthScore)), | ||
'maintenance_html': | ||
renderScoreBar(maintenanceScore, genericScoreBrush(maintenanceScore)), | ||
'popularity_html': | ||
renderScoreBar(popularityScore, genericScoreBrush(popularityScore)), | ||
'overall_html': | ||
renderScoreBar(overallScore, overallScoreBrush(overallScore)), | ||
}; | ||
} | ||
|
||
String _renderLicenses(String baseUrl, List<LicenseFile> licenses) { | ||
if (licenses == null || licenses.isEmpty) return null; | ||
return licenses.map((license) { | ||
|
@@ -835,10 +861,12 @@ String _getAuthorsHtml(List<String> authors) { | |
}).join('<br/>'); | ||
} | ||
|
||
bool _isAnalysisSkipped(AnalysisStatus status) => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe skipped is the wrong word here? I was thinking of packages that are brand new (no data yet), or there's a minor problem why the data isn't there right now; not so much that it's packages that are obsolete. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't have an We 'skip' two kinds of packages in the analysis: one that is older than two years, and one that is flagged as discontinued. We shall analyse everything else. Ideally #1352 could also track the scheduled but not done yet status too. |
||
status == AnalysisStatus.outdated || status == AnalysisStatus.discontinued; | ||
|
||
String _renderScoreBox(AnalysisStatus status, double overallScore, | ||
{bool isNewPackage, String package}) { | ||
final skippedAnalysis = status == AnalysisStatus.outdated || | ||
status == AnalysisStatus.discontinued; | ||
final skippedAnalysis = _isAnalysisSkipped(status); | ||
final score = skippedAnalysis ? null : overallScore; | ||
final String formattedScore = _formatScore(score); | ||
final String scoreClass = _classifyScore(score); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A skipped package will have a 0 score rather than a N/A score?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
percent
will be used only to render the width of the progress bar, while thescore
one line below will be used to render the actual text label. Btw. great catch, because I haven't used thescore
to do so.