Santhosh has uploaded a new change for review.
https://gerrit.wikimedia.org/r/236764
Change subject: Stats: Weekly trends
......................................................................
Stats: Weekly trends
While doing this, as per Pau's suggestion, I changed
the colors(again).
Translations: Blue, Draft: Gray, Deleted: Red
Bug: T105192
Change-Id: I8301bcd7e595e8144a43622a27503b8ed9494bbf
---
M extension.json
M i18n/en.json
M i18n/qqq.json
M lib/chart.js/Chart.Core.js
M lib/chart.js/Chart.Line.js
M modules/stats/ext.cx.stats.js
M modules/stats/styles/ext.cx.stats.less
7 files changed, 406 insertions(+), 75 deletions(-)
git pull
ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/ContentTranslation
refs/changes/64/236764/1
diff --git a/extension.json b/extension.json
index c6d051f..76fc319 100644
--- a/extension.json
+++ b/extension.json
@@ -849,6 +849,8 @@
"cx-stats-local-published-number",
"cx-stats-local-published",
"cx-stats-grouping-title",
+ "cx-trend-published-translations-title",
+ "cx-trend-translations-to-title",
"percent"
]
},
@@ -857,7 +859,8 @@
"remoteExtPath": "ContentTranslation/lib",
"scripts": [
"chart.js/Chart.Core.js",
- "chart.js/Chart.Line.js"
+ "chart.js/Chart.Line.js",
+ "chart.js/Chart.Bar.js"
]
},
"ext.cx.beta.notification": {
diff --git a/i18n/en.json b/i18n/en.json
index e355a1f..567159b 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -164,6 +164,8 @@
"cx-stats-local-published-number": "$1 in $2",
"cx-stats-local-published": "$1 ($3) in $2",
"cx-stats-grouping-title": "{{PLURAL:$1|$1 translation|$1
translations}}",
+ "cx-trend-published-translations-title": "Translation trend",
+ "cx-trend-translations-to-title": "Translation trend to $1",
"cx-tools-missing-link-text": "{{GENDER:|Mark}} the page as missing to
encourage its creation.",
"cx-tools-missing-link-tooltip": "Translate (in new window)",
"cx-tools-missing-link-title": "Missing link",
diff --git a/i18n/qqq.json b/i18n/qqq.json
index 44cb907..03d15c3 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -169,6 +169,8 @@
"cx-stats-local-published-number": "A message shown in
[[Special:CXStats]] as highlights of CX analytics. $1 is a number, $2 is
language name (autonym).",
"cx-stats-local-published": "A message shown in [[Special:CXStats]] as
highlights of CX analytics. $1 is a number, $2 is language name (autonym), $3
is percentage.",
"cx-stats-grouping-title": "Title text for language grouping based on
number of translations. $1 is number of
translations\n{{Identical|Translation}}",
+ "cx-trend-published-translations-title": "A message shown in
[[Special:CXStats]]",
+ "cx-trend-translations-to-title": "Label shown in the legend section
Content translation trends graph visualization.\n* $1 - language name",
"cx-tools-missing-link-text": "Message with instructions for marking
links as missing",
"cx-tools-missing-link-tooltip": "Tooltip that shows when hovering over
target link card link when working with missing links.\nClicking on link opens
a new translation view for the missing link.",
"cx-tools-missing-link-title": "Title for target link card when card is
used for working with missing links",
diff --git a/lib/chart.js/Chart.Core.js b/lib/chart.js/Chart.Core.js
old mode 100755
new mode 100644
index 5dccd2e..aa6c58d
--- a/lib/chart.js/Chart.Core.js
+++ b/lib/chart.js/Chart.Core.js
@@ -35,17 +35,17 @@
{
return
document.defaultView.getComputedStyle(element).getPropertyValue(dimension);
}
- }
+ };
- var width = this.width =
computeDimension(context.canvas,'Width');
- var height = this.height =
computeDimension(context.canvas,'Height');
+ var width = this.width =
computeDimension(context.canvas,'Width') || context.canvas.width;
+ var height = this.height =
computeDimension(context.canvas,'Height') || context.canvas.height;
// Firefox requires this to work correctly
context.canvas.width = width;
context.canvas.height = height;
- var width = this.width = context.canvas.width;
- var height = this.height = context.canvas.height;
+ width = this.width = context.canvas.width;
+ height = this.height = context.canvas.height;
this.aspectRatio = this.width / this.height;
//High pixel density displays - multiply the size of the canvas
height/width by the device pixel ratio, then scale.
helpers.retinaScale(this);
@@ -150,6 +150,9 @@
// String - Tooltip title font colour
tooltipTitleFontColor: "#fff",
+ // String - Tooltip title template
+ tooltipTitleTemplate: "<%= label%>",
+
// Number - pixel width of padding around tooltip text
tooltipYPadding: 6,
@@ -210,14 +213,18 @@
clone = helpers.clone = function(obj){
var objClone = {};
each(obj,function(value,key){
- if (obj.hasOwnProperty(key)) objClone[key] =
value;
+ if (obj.hasOwnProperty(key)){
+ objClone[key] = value;
+ }
});
return objClone;
},
extend = helpers.extend = function(base){
each(Array.prototype.slice.call(arguments,1),
function(extensionObject) {
each(extensionObject,function(value,key){
- if
(extensionObject.hasOwnProperty(key)) base[key] = value;
+ if
(extensionObject.hasOwnProperty(key)){
+ base[key] = value;
+ }
});
});
return base;
@@ -300,9 +307,9 @@
})(),
warn = helpers.warn = function(str){
//Method for warning of errors
- if (window.console && typeof window.console.warn ==
"function") console.warn(str);
+ if (window.console && typeof window.console.warn ===
"function") console.warn(str);
},
- amd = helpers.amd = (typeof define == 'function' && define.amd),
+ amd = helpers.amd = (typeof define === 'function' &&
define.amd),
//-- Math methods
isNumber = helpers.isNumber = function(n){
return !isNaN(parseFloat(n)) && isFinite(n);
@@ -328,7 +335,20 @@
},
getDecimalPlaces = helpers.getDecimalPlaces = function(num){
if (num%1!==0 && isNumber(num)){
- return num.toString().split(".")[1].length;
+ var s = num.toString();
+ if(s.indexOf("e-") < 0){
+ // no exponent, e.g. 0.01
+ return s.split(".")[1].length;
+ }
+ else if(s.indexOf(".") < 0) {
+ // no decimal point, e.g. 1e-9
+ return parseInt(s.split("e-")[1]);
+ }
+ else {
+ // exponent and decimal point, e.g.
1.23e-9
+ var parts = s.split(".")[1].split("e-");
+ return parts[0].length +
parseInt(parts[1]);
+ }
}
else {
return 0;
@@ -390,7 +410,7 @@
var maxValue = max(valuesArray),
minValue = min(valuesArray);
- // We need some degree of seperation here to calculate
the scales if all the values are the same
+ // We need some degree of separation here to calculate
the scales if all the values are the same
// Adding/minusing 0.5 will give us a range of 1.
if (maxValue === minValue){
maxValue += 0.5;
@@ -505,7 +525,7 @@
/* jshint ignore:end */
generateLabels = helpers.generateLabels =
function(templateString,numberOfSteps,graphMin,stepValue){
var labelsArray = new Array(numberOfSteps);
- if (labelTemplateString){
+ if (templateString){
each(labelsArray,function(val,index){
labelsArray[index] =
template(templateString,{value: (graphMin + (stepValue*(index+1)))});
});
@@ -526,7 +546,9 @@
return -1 * t * (t - 2);
},
easeInOutQuad: function (t) {
- if ((t /= 1 / 2) < 1) return 1 / 2 * t * t;
+ if ((t /= 1 / 2) < 1){
+ return 1 / 2 * t * t;
+ }
return -1 / 2 * ((--t) * (t - 2) - 1);
},
easeInCubic: function (t) {
@@ -536,7 +558,9 @@
return 1 * ((t = t / 1 - 1) * t * t + 1);
},
easeInOutCubic: function (t) {
- if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t;
+ if ((t /= 1 / 2) < 1){
+ return 1 / 2 * t * t * t;
+ }
return 1 / 2 * ((t -= 2) * t * t + 2);
},
easeInQuart: function (t) {
@@ -546,7 +570,9 @@
return -1 * ((t = t / 1 - 1) * t * t * t - 1);
},
easeInOutQuart: function (t) {
- if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t
* t;
+ if ((t /= 1 / 2) < 1){
+ return 1 / 2 * t * t * t * t;
+ }
return -1 / 2 * ((t -= 2) * t * t * t - 2);
},
easeInQuint: function (t) {
@@ -556,7 +582,9 @@
return 1 * ((t = t / 1 - 1) * t * t * t * t +
1);
},
easeInOutQuint: function (t) {
- if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t
* t * t;
+ if ((t /= 1 / 2) < 1){
+ return 1 / 2 * t * t * t * t * t;
+ }
return 1 / 2 * ((t -= 2) * t * t * t * t + 2);
},
easeInSine: function (t) {
@@ -575,60 +603,95 @@
return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 *
t / 1) + 1);
},
easeInOutExpo: function (t) {
- if (t === 0) return 0;
- if (t === 1) return 1;
- if ((t /= 1 / 2) < 1) return 1 / 2 *
Math.pow(2, 10 * (t - 1));
+ if (t === 0){
+ return 0;
+ }
+ if (t === 1){
+ return 1;
+ }
+ if ((t /= 1 / 2) < 1){
+ return 1 / 2 * Math.pow(2, 10 * (t -
1));
+ }
return 1 / 2 * (-Math.pow(2, -10 * --t) + 2);
},
easeInCirc: function (t) {
- if (t >= 1) return t;
+ if (t >= 1){
+ return t;
+ }
return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1);
},
easeOutCirc: function (t) {
return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t);
},
easeInOutCirc: function (t) {
- if ((t /= 1 / 2) < 1) return -1 / 2 *
(Math.sqrt(1 - t * t) - 1);
+ if ((t /= 1 / 2) < 1){
+ return -1 / 2 * (Math.sqrt(1 - t * t) -
1);
+ }
return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) +
1);
},
easeInElastic: function (t) {
var s = 1.70158;
var p = 0;
var a = 1;
- if (t === 0) return 0;
- if ((t /= 1) == 1) return 1;
- if (!p) p = 1 * 0.3;
+ if (t === 0){
+ return 0;
+ }
+ if ((t /= 1) == 1){
+ return 1;
+ }
+ if (!p){
+ p = 1 * 0.3;
+ }
if (a < Math.abs(1)) {
a = 1;
s = p / 4;
- } else s = p / (2 * Math.PI) * Math.asin(1 / a);
+ } else{
+ s = p / (2 * Math.PI) * Math.asin(1 /
a);
+ }
return -(a * Math.pow(2, 10 * (t -= 1)) *
Math.sin((t * 1 - s) * (2 * Math.PI) / p));
},
easeOutElastic: function (t) {
var s = 1.70158;
var p = 0;
var a = 1;
- if (t === 0) return 0;
- if ((t /= 1) == 1) return 1;
- if (!p) p = 1 * 0.3;
+ if (t === 0){
+ return 0;
+ }
+ if ((t /= 1) == 1){
+ return 1;
+ }
+ if (!p){
+ p = 1 * 0.3;
+ }
if (a < Math.abs(1)) {
a = 1;
s = p / 4;
- } else s = p / (2 * Math.PI) * Math.asin(1 / a);
+ } else{
+ s = p / (2 * Math.PI) * Math.asin(1 /
a);
+ }
return a * Math.pow(2, -10 * t) * Math.sin((t *
1 - s) * (2 * Math.PI) / p) + 1;
},
easeInOutElastic: function (t) {
var s = 1.70158;
var p = 0;
var a = 1;
- if (t === 0) return 0;
- if ((t /= 1 / 2) == 2) return 1;
- if (!p) p = 1 * (0.3 * 1.5);
+ if (t === 0){
+ return 0;
+ }
+ if ((t /= 1 / 2) == 2){
+ return 1;
+ }
+ if (!p){
+ p = 1 * (0.3 * 1.5);
+ }
if (a < Math.abs(1)) {
a = 1;
s = p / 4;
- } else s = p / (2 * Math.PI) * Math.asin(1 / a);
- if (t < 1) return -0.5 * (a * Math.pow(2, 10 *
(t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
+ } else {
+ s = p / (2 * Math.PI) * Math.asin(1 /
a);
+ }
+ if (t < 1){
+ return -0.5 * (a * Math.pow(2, 10 * (t
-= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));}
return a * Math.pow(2, -10 * (t -= 1)) *
Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1;
},
easeInBack: function (t) {
@@ -641,7 +704,9 @@
},
easeInOutBack: function (t) {
var s = 1.70158;
- if ((t /= 1 / 2) < 1) return 1 / 2 * (t * t *
(((s *= (1.525)) + 1) * t - s));
+ if ((t /= 1 / 2) < 1){
+ return 1 / 2 * (t * t * (((s *=
(1.525)) + 1) * t - s));
+ }
return 1 / 2 * ((t -= 2) * t * (((s *= (1.525))
+ 1) * t + s) + 2);
},
easeInBounce: function (t) {
@@ -659,7 +724,9 @@
}
},
easeInOutBounce: function (t) {
- if (t < 1 / 2) return
easingEffects.easeInBounce(t * 2) * 0.5;
+ if (t < 1 / 2){
+ return easingEffects.easeInBounce(t *
2) * 0.5;
+ }
return easingEffects.easeOutBounce(t * 2 - 1) *
0.5 + 1 * 0.5;
}
},
@@ -762,14 +829,21 @@
});
},
getMaximumWidth = helpers.getMaximumWidth = function(domNode){
- var container = domNode.parentNode;
+ var container = domNode.parentNode,
+ padding = parseInt(getStyle(container,
'padding-left')) + parseInt(getStyle(container, 'padding-right'));
// TODO = check cross browser stuff with this.
- return container.clientWidth;
+ return container.clientWidth - padding;
},
getMaximumHeight = helpers.getMaximumHeight = function(domNode){
- var container = domNode.parentNode;
+ var container = domNode.parentNode,
+ padding = parseInt(getStyle(container,
'padding-bottom')) + parseInt(getStyle(container, 'padding-top'));
// TODO = check cross browser stuff with this.
- return container.clientHeight;
+ return container.clientHeight - padding;
+ },
+ getStyle = helpers.getStyle = function (el, property) {
+ return el.currentStyle ?
+ el.currentStyle[property] :
+ document.defaultView.getComputedStyle(el,
null).getPropertyValue(property);
},
getMaximumSize = helpers.getMaximumSize =
helpers.getMaximumWidth, // legacy support
retinaScale = helpers.retinaScale = function(chart){
@@ -844,7 +918,7 @@
},
stop : function(){
// Stops any current animation loop occuring
- cancelAnimFrame(this.animationFrame);
+ Chart.animationService.cancelAnimation(this);
return this;
},
resize : function(callback){
@@ -868,15 +942,26 @@
if (reflow){
this.reflow();
}
+
if (this.options.animation && !reflow){
- helpers.animationLoop(
- this.draw,
- this.options.animationSteps,
- this.options.animationEasing,
- this.options.onAnimationProgress,
- this.options.onAnimationComplete,
- this
- );
+ var animation = new Chart.Animation();
+ animation.numSteps =
this.options.animationSteps;
+ animation.easing = this.options.animationEasing;
+
+ // render function
+ animation.render = function(chartInstance,
animationObject) {
+ var easingFunction =
helpers.easingEffects[animationObject.easing];
+ var stepDecimal =
animationObject.currentStep / animationObject.numSteps;
+ var easeDecimal =
easingFunction(stepDecimal);
+
+ chartInstance.draw(easeDecimal,
stepDecimal, animationObject.currentStep);
+ };
+
+ // user events
+ animation.onAnimationProgress =
this.options.onAnimationProgress;
+ animation.onAnimationComplete =
this.options.onAnimationComplete;
+
+ Chart.animationService.addAnimation(this,
animation);
}
else{
this.draw();
@@ -1015,7 +1100,7 @@
labels: tooltipLabels,
legendColors: tooltipColors,
legendColorBackground :
this.options.multiTooltipKeyBackground,
- title: ChartElements[0].label,
+ title:
template(this.options.tooltipTitleTemplate,ChartElements[0]),
chart: this.chart,
ctx: this.chart.ctx,
custom:
this.options.customTooltips
@@ -1283,6 +1368,16 @@
}
});
+ Chart.Animation = Chart.Element.extend({
+ currentStep: null, // the current animation step
+ numSteps: 60, // default number of steps
+ easing: "", // the easing to use for this animation
+ render: null, // render function used by the animation service
+
+ onAnimationProgress: null, // user specified callback to fire
on each step of the animation
+ onAnimationComplete: null, // user specified callback to fire
when the animation finishes
+ });
+
Chart.Tooltip = Chart.Element.extend({
draw : function(){
@@ -1466,7 +1561,7 @@
for (var i=0; i<=this.steps; i++){
this.yLabels.push(template(this.templateString,{value:(this.min + (i *
this.stepValue)).toFixed(stepDecimalPlaces)}));
}
- this.yLabelWidth = (this.display && this.showLabels) ?
longestText(this.ctx,this.font,this.yLabels) : 0;
+ this.yLabelWidth = (this.display && this.showLabels) ?
longestText(this.ctx,this.font,this.yLabels) + 10 : 0;
},
addXLabel : function(label){
this.xLabels.push(label);
@@ -1489,6 +1584,9 @@
// Apply padding settings to the start and end point.
this.startPoint += this.padding;
this.endPoint -= this.padding;
+
+ // Cache the starting endpoint, excluding the space for
x labels
+ var cachedEndPoint = this.endPoint;
// Cache the starting height, so can determine if we
need to recalculate the scale yAxis
var cachedHeight = this.endPoint - this.startPoint,
@@ -1521,6 +1619,7 @@
// Only go through the xLabel loop again if the
yLabel width has changed
if (cachedYLabelWidth < this.yLabelWidth){
+ this.endPoint = cachedEndPoint;
this.calculateXLabelRotation();
}
}
@@ -1539,7 +1638,7 @@
this.xScalePaddingRight = lastWidth/2 + 3;
- this.xScalePaddingLeft = (firstWidth/2 >
this.yLabelWidth + 10) ? firstWidth/2 : this.yLabelWidth + 10;
+ this.xScalePaddingLeft = (firstWidth/2 >
this.yLabelWidth) ? firstWidth/2 : this.yLabelWidth;
this.xLabelRotation = 0;
if (this.display){
@@ -1558,7 +1657,7 @@
lastRotated = cosRotation * lastWidth;
// We're right aligning the text now.
- if (firstRotated + this.fontSize / 2 >
this.yLabelWidth + 8){
+ if (firstRotated + this.fontSize / 2 >
this.yLabelWidth){
this.xScalePaddingLeft =
firstRotated + this.fontSize / 2;
}
this.xScalePaddingRight =
this.fontSize/2;
@@ -1984,6 +2083,90 @@
}
});
+ Chart.animationService = {
+ frameDuration: 17,
+ animations: [],
+ dropFrames: 0,
+ addAnimation: function(chartInstance, animationObject) {
+ for (var index = 0; index < this.animations.length; ++
index){
+ if (this.animations[index].chartInstance ===
chartInstance){
+ // replacing an in progress animation
+ this.animations[index].animationObject
= animationObject;
+ return;
+ }
+ }
+
+ this.animations.push({
+ chartInstance: chartInstance,
+ animationObject: animationObject
+ });
+
+ // If there are no animations queued, manually
kickstart a digest, for lack of a better word
+ if (this.animations.length == 1) {
+ helpers.requestAnimFrame.call(window,
this.digestWrapper);
+ }
+ },
+ // Cancel the animation for a given chart instance
+ cancelAnimation: function(chartInstance) {
+ var index = helpers.findNextWhere(this.animations,
function(animationWrapper) {
+ return animationWrapper.chartInstance ===
chartInstance;
+ });
+
+ if (index)
+ {
+ this.animations.splice(index, 1);
+ }
+ },
+ // calls startDigest with the proper context
+ digestWrapper: function() {
+
Chart.animationService.startDigest.call(Chart.animationService);
+ },
+ startDigest: function() {
+
+ var startTime = Date.now();
+ var framesToDrop = 0;
+
+ if(this.dropFrames > 1){
+ framesToDrop = Math.floor(this.dropFrames);
+ this.dropFrames -= framesToDrop;
+ }
+
+ for (var i = 0; i < this.animations.length; i++) {
+
+ if
(this.animations[i].animationObject.currentStep === null){
+
this.animations[i].animationObject.currentStep = 0;
+ }
+
+ this.animations[i].animationObject.currentStep
+= 1 + framesToDrop;
+
if(this.animations[i].animationObject.currentStep >
this.animations[i].animationObject.numSteps){
+
this.animations[i].animationObject.currentStep =
this.animations[i].animationObject.numSteps;
+ }
+
+
this.animations[i].animationObject.render(this.animations[i].chartInstance,
this.animations[i].animationObject);
+
+ if
(this.animations[i].animationObject.currentStep ==
this.animations[i].animationObject.numSteps){
+ // executed the last frame. Remove the
animation.
+ this.animations.splice(i, 1);
+ // Keep the index in place to offset
the splice
+ i--;
+ }
+ }
+
+ var endTime = Date.now();
+ var delay = endTime - startTime - this.frameDuration;
+ var frameDelay = delay / this.frameDuration;
+
+ if(frameDelay > 1){
+ this.dropFrames += frameDelay;
+ }
+
+ // Do we have more stuff to animate?
+ if (this.animations.length > 0){
+ helpers.requestAnimFrame.call(window,
this.digestWrapper);
+ }
+ }
+ };
+
// Attach global event to resize each chart instance when the browser
resizes
helpers.addEvent(window, "resize", (function(){
// Basic debounce of resize function so it doesn't hurt
performance when resizing browser.
diff --git a/lib/chart.js/Chart.Line.js b/lib/chart.js/Chart.Line.js
index 34ad85b..fdf1b01 100644
--- a/lib/chart.js/Chart.Line.js
+++ b/lib/chart.js/Chart.Line.js
@@ -50,7 +50,10 @@
datasetFill : true,
//String - A legend template
- legendTemplate : "<ul
class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length;
i++){%><li><span
style=\"background-color:<%=datasets[i].strokeColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"
+ legendTemplate : "<ul
class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length;
i++){%><li><span
style=\"background-color:<%=datasets[i].strokeColor%>\"><%if(datasets[i].label){%><%=datasets[i].label%><%}%></span></li><%}%></ul>",
+
+ //Boolean - Whether to horizontally center the label and point
dot inside the grid
+ offsetGridLines : false
};
@@ -61,6 +64,7 @@
initialize: function(data){
//Declare the extension of the default point, to cater
for the options passed in to the constructor
this.PointClass = Chart.Point.extend({
+ offsetGridLines : this.options.offsetGridLines,
strokeWidth : this.options.pointDotStrokeWidth,
radius : this.options.pointDotRadius,
display: this.options.pointDot,
@@ -176,6 +180,7 @@
width : this.chart.width,
ctx : this.chart.ctx,
textColor : this.options.scaleFontColor,
+ offsetGridLines : this.options.offsetGridLines,
fontSize : this.options.scaleFontSize,
fontStyle : this.options.scaleFontStyle,
fontFamily : this.options.scaleFontFamily,
@@ -226,6 +231,7 @@
this.datasets[datasetIndex].points.push(new
this.PointClass({
value : value,
label : label,
+ datasetLabel:
this.datasets[datasetIndex].label,
x:
this.scale.calculateX(this.scale.valuesCount+1),
y: this.scale.endPoint,
strokeColor :
this.datasets[datasetIndex].pointStrokeColor,
@@ -269,6 +275,7 @@
return helpers.findPreviousWhere(collection,
hasValue, index) || point;
};
+ if (!this.scale) return;
this.scale.draw(easingDecimal);
@@ -288,7 +295,7 @@
},this);
- // Control points need to be calculated in a
seperate loop, because we need to know the current x/y of the point
+ // Control points need to be calculated in a
separate loop, because we need to know the current x/y of the point
// This would cause issues when there is no
animation, because the y of the next point would be 0, so beziers would be
skewed
if (this.options.bezierCurve){
helpers.each(pointsWithValues,
function(point, index){
@@ -349,7 +356,9 @@
}
}, this);
- ctx.stroke();
+ if (this.options.datasetStroke) {
+ ctx.stroke();
+ }
if (this.options.datasetFill &&
pointsWithValues.length > 0){
//Round off the line by going to the
base of the chart, back to the start, then fill.
diff --git a/modules/stats/ext.cx.stats.js b/modules/stats/ext.cx.stats.js
index ef86b07..620e10c 100644
--- a/modules/stats/ext.cx.stats.js
+++ b/modules/stats/ext.cx.stats.js
@@ -44,6 +44,18 @@
height: 400
} );
+ this.$translatonTrendBarChart = $( '<canvas>' ).attr( {
+ id: 'cxtrendchart',
+ width: this.$container.width() - 200, // Leave a 200px
margin buffer to avoid overflow
+ height: 400
+ } );
+
+ this.$langTranslatonTrendBarChart = $( '<canvas>' ).attr( {
+ id: 'cxlangtrendchart',
+ width: this.$container.width() - 200, // Leave a 200px
margin buffer to avoid overflow
+ height: 400
+ } );
+
this.$container.append(
$spinner,
this.$highlights,
@@ -53,7 +65,14 @@
'cx-trend-translations-to',
$.uls.data.getAutonym( mw.config.get(
'wgContentLanguage' ) )
).escaped() ),
- $( '<div>' ).addClass( 'cx-stats-cumulative__lang'
).append( this.$languageCumulativeGraph )
+ $( '<div>' ).addClass( 'cx-stats-cumulative__lang'
).append( this.$languageCumulativeGraph ),
+ $( '<h2>' ).text( mw.msg(
'cx-trend-published-translations-title' ) ),
+ $( '<div>' ).addClass( 'cx-stats-trend__total'
).append( this.$translatonTrendBarChart ),
+ $( '<h2>' ).text( mw.message(
+ 'cx-trend-translations-to-title',
+ $.uls.data.getAutonym( mw.config.get(
'wgContentLanguage' ) )
+ ).escaped() ),
+ $( '<div>' ).addClass( 'cx-stats-trend__lang' ).append(
this.$langTranslatonTrendBarChart )
);
$.when(
@@ -69,6 +88,8 @@
self.renderHighlights();
self.drawCumulativeGraph( 'count' );
self.drawLanguageCumulativeGraph( 'count' );
+ self.drawTranslationTrend();
+ self.drawLangTranslationTrend();
} );
this.getCXStats().then( function ( data ) {
if ( !data || !data.query ) {
@@ -463,11 +484,11 @@
},
{
label: mw.msg(
'cx-stats-draft-translations-title' ),
- strokeColor: 'rgba(255, 153, 0, 1)',
- pointColor: 'rgba(255, 153, 0, 1)',
+ strokeColor: '#777',
+ pointColor: '#777',
pointStrokeColor: '#fff',
pointHighlightFill: '#fff',
- pointHighlightStroke: 'rgba(255, 153,
0, 1)',
+ pointHighlightStroke: '#777',
data: $.map( this.totalDraftTrend,
function ( data ) {
return data[ type ];
} )
@@ -496,23 +517,23 @@
} ),
datasets: [
{
- label: mw.msg(
'cx-stats-draft-translations-title' ),
- strokeColor: 'rgba(255, 153, 0, 1)',
- pointColor: 'rgba(255, 153, 0, 1 )',
+ label: mw.msg(
'cx-stats-draft-translations-title' ),
+ strokeColor: '#777',
+ pointColor: '#777',
pointStrokeColor: '#fff',
pointHighlightFill: '#fff',
- pointHighlightStroke:
'rgba(255,153,0,1)',
+ pointHighlightStroke: '#777',
data: $.map( this.languageDraftTrend,
function ( data ) {
return data[ type ];
} )
},
{
- label: mw.msg( 'cx-trend-deletions' ),
- strokeColor: 'rgba(255, 0, 0, 1)',
- pointColor: 'rgba(255, 0, 0, 1 )',
+ label: mw.msg( 'cx-trend-deletions' ),
+ strokeColor: '#FF0000',
+ pointColor: 'FF0000',
pointStrokeColor: '#fff',
pointHighlightFill: '#fff',
- pointHighlightStroke: 'rgba(255, 0,
0,1)',
+ pointHighlightStroke: 'FF0000',
data: $.map(
this.languageDeletionTrend, function ( data ) {
return data[ type ];
} )
@@ -522,11 +543,11 @@
'cx-trend-translations-to',
$.uls.data.getAutonym(
mw.config.get( 'wgContentLanguage' ) )
).escaped(),
- strokeColor: 'rgba(52, 123, 255, 1)',
- pointColor: 'rgba(52, 123, 255, 1)',
+ strokeColor: '#347BFF',
+ pointColor: '#347BFF',
pointStrokeColor: '#fff',
pointHighlightFill: '#fff',
- pointHighlightStroke: 'rgba(52, 123,
255, 1)',
+ pointHighlightStroke: '#347BFF',
data: $.map(
this.languageTranslationTrend, function ( data ) {
return data[ type ];
} )
@@ -542,6 +563,113 @@
} );
this.$container.find( '.cx-stats-cumulative__lang' ).append(
cxCumulativeGraph.generateLegend() );
+ };
+
+ CXStats.prototype.drawTranslationTrend = function () {
+ var data, cxTrendChart, ctx, type = 'delta';
+
+ ctx = this.$translatonTrendBarChart[ 0 ].getContext( '2d' );
+ data = {
+ labels: $.map( this.totalTranslationTrend, function (
data ) {
+ return data.date;
+ } ),
+ datasets: [
+ {
+ label: mw.msg(
'cx-trend-all-translations' ),
+ strokeColor: '#347BFF',
+ fillColor: '#347BFF',
+ pointColor: '#347BFF',
+ pointStrokeColor: '#fff',
+ pointHighlightFill: '#fff',
+ pointHighlightStroke: '#347BFF',
+ data: $.map(
this.totalTranslationTrend, function ( data ) {
+ return data[ type ];
+ } )
+ },
+ {
+ label: mw.msg(
'cx-stats-draft-translations-title' ),
+ strokeColor: '#777',
+ fillColor: '#777',
+ pointColor: '#777',
+ pointStrokeColor: '#fff',
+ pointHighlightFill: '#fff',
+ pointHighlightStroke: '#777',
+ data: $.map( this.totalDraftTrend,
function ( data ) {
+ return data[ type ];
+ } )
+ }
+ ]
+ };
+
+ /*global Chart:false */
+ cxTrendChart = new Chart( ctx ).Bar( data, {
+ responsive: true,
+ barDatasetSpacing: 0,
+ legendTemplate: '<ul><% for (var i=0;
i<datasets.length; i++){%><li
style=\"color:<%=datasets[i].strokeColor%>\"><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>'
+ } );
+
+ this.$container.find( '.cx-stats-trend__total' ).append(
cxTrendChart.generateLegend() );
+ };
+
+ CXStats.prototype.drawLangTranslationTrend = function () {
+ var data, cxTrendChart, ctx, type = 'delta';
+
+ ctx = this.$langTranslatonTrendBarChart[ 0 ].getContext( '2d' );
+ data = {
+ labels: $.map( this.languageTranslationTrend, function
( data ) {
+ return data.date;
+ } ),
+ datasets: [
+ {
+ label: mw.message(
+ 'cx-trend-translations-to',
+ $.uls.data.getAutonym(
mw.config.get( 'wgContentLanguage' ) )
+ ).escaped(),
+ strokeColor: '#347BFF',
+ fillColor: '#347BFF',
+ pointColor: '#347BFF',
+ pointStrokeColor: '#fff',
+ pointHighlightFill: '#fff',
+ pointHighlightStroke: '#347BFF',
+ data: $.map(
this.languageTranslationTrend, function ( data ) {
+ return data[ type ];
+ } )
+ },
+ {
+ label: mw.msg(
'cx-stats-draft-translations-title' ),
+ strokeColor: '#777',
+ fillColor: '#777',
+ pointColor: '#777',
+ pointStrokeColor: '#fff',
+ pointHighlightFill: '#fff',
+ pointHighlightStroke: '#777',
+ data: $.map( this.languageDraftTrend,
function ( data ) {
+ return data[ type ];
+ } )
+ },
+ {
+ label: mw.msg( 'cx-trend-deletions' ),
+ strokeColor: '#FF0000',
+ fillColor: '#FF0000',
+ pointColor: '#FF0000',
+ pointStrokeColor: '#fff',
+ pointHighlightFill: '#fff',
+ pointHighlightStroke: '#FF0000',
+ data: $.map(
this.languageDeletionTrend, function ( data ) {
+ return data[ type ];
+ } )
+ }
+ ]
+ };
+
+ /*global Chart:false */
+ cxTrendChart = new Chart( ctx ).Bar( data, {
+ responsive: true,
+ barDatasetSpacing: 0,
+ legendTemplate: '<ul><% for (var i=0;
i<datasets.length; i++){%><li
style=\"color:<%=datasets[i].strokeColor%>\"><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>'
+ } );
+
+ this.$container.find( '.cx-stats-trend__lang' ).append(
cxTrendChart.generateLegend() );
};
CXStats.prototype.transformJsonToModel = function ( records ) {
@@ -651,13 +779,15 @@
if ( languageData[ i ] && new Date( totalData[ i ].date
) > new Date( languageData[ i ].date ) ) {
totalData.splice( i, 0, {
date: languageData[ i ].date,
- count: totalData[ i - 1 ] ? totalData[
i - 1 ].count : 0
+ count: totalData[ i - 1 ] ? totalData[
i - 1 ].count : 0,
+ delta: totalData[ i - 1 ] ? totalData[
i - 1 ].delta : 0
} );
}
if ( !languageData[ i ] || new Date( totalData[ i
].date ) < new Date( languageData[ i ].date ) ) {
languageData.splice( i, 0, {
date: totalData[ i ].date,
- count: languageData[ i - 1 ] ?
languageData[ i - 1 ].count : 0
+ count: languageData[ i - 1 ] ?
languageData[ i - 1 ].count : 0,
+ delta: languageData[ i - 1 ] ?
languageData[ i - 1 ].delta : 0
} );
}
}
diff --git a/modules/stats/styles/ext.cx.stats.less
b/modules/stats/styles/ext.cx.stats.less
index 126b0fc..1ec6667 100644
--- a/modules/stats/styles/ext.cx.stats.less
+++ b/modules/stats/styles/ext.cx.stats.less
@@ -151,7 +151,9 @@
}
.cx-stats-cumulative__lang,
-.cx-stats-cumulative__total {
+.cx-stats-cumulative__total,
+.cx-stats-trend__lang,
+.cx-stats-trend__total {
background-color: #fff;
}
--
To view, visit https://gerrit.wikimedia.org/r/236764
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I8301bcd7e595e8144a43622a27503b8ed9494bbf
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/ContentTranslation
Gerrit-Branch: master
Gerrit-Owner: Santhosh <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits