From 0fe3dc78e4d78c1fb2bbb434b92b5744adf4667b Mon Sep 17 00:00:00 2001 From: Samuel Clay Date: Mon, 30 Nov 2015 09:31:10 -0800 Subject: [PATCH 1/2] Patching charts to fix broken charts branch. --- apps/rss_feeds/models.py | 34 +- apps/rss_feeds/views.py | 8 +- apps/social/models.py | 28 +- apps/social/views.py | 2 + media/css/reader.css | 33 +- .../newsblur/reader/reader_feed_exception.js | 15 + media/js/newsblur/reader/reader_statistics.js | 75 +- media/js/vendor/chart.js | 656 +++++++++++++++++- 8 files changed, 780 insertions(+), 71 deletions(-) diff --git a/apps/rss_feeds/models.py b/apps/rss_feeds/models.py index 07c275acf..67fad9172 100644 --- a/apps/rss_feeds/models.py +++ b/apps/rss_feeds/models.py @@ -866,28 +866,28 @@ class Feed(models.Model): map_f = """ function() { var date = (this.story_date.getFullYear()) + "-" + (this.story_date.getMonth()+1); - emit(date, 1); + var hour = this.story_date.getHours(); + var day = this.story_date.getDay(); + emit(this.story_hash, {'month': date, 'hour': hour, 'day': day}); } """ reduce_f = """ function(key, values) { - var total = 0; - for (var i=0; i < values.length; i++) { - total += values[i]; - } - return total; + return values; } """ - dates = {} - res = MStory.objects(story_feed_id=self.pk).map_reduce(map_f, reduce_f, output='inline') - for r in res: - dates[r.key] = r.value - year_found = re.findall(r"(\d{4})-\d{1,2}", r.key) - if year_found and len(year_found): - year = int(year_found[0]) - if year < min_year and year > 2000: - min_year = year - + dates = defaultdict(int) + hours = defaultdict(int) + days = defaultdict(int) + results = MStory.objects(story_feed_id=self.pk).map_reduce(map_f, reduce_f, output='inline') + for result in results: + dates[result.value['month']] += 1 + hours[int(result.value['hour'])] += 1 + days[int(result.value['day'])] += 1 + year = int(re.findall(r"(\d{4})-\d{1,2}", result.value['month'])[0]) + if year < min_year and year > 2000: + min_year = year + # Add on to existing months, always amending up, never down. (Current month # is guaranteed to be accurate, since trim_feeds won't delete it until after # a month. Hacker News can have 1,000+ and still be counted.) @@ -912,7 +912,7 @@ class Feed(models.Model): total += dates.get(key, 0) month_count += 1 original_story_count_history = self.data.story_count_history - self.data.story_count_history = json.encode(months) + self.data.story_count_history = json.encode({'months': months, 'hours': hours, 'days': days}) if self.data.story_count_history != original_story_count_history: self.data.save(update_fields=['story_count_history']) diff --git a/apps/rss_feeds/views.py b/apps/rss_feeds/views.py index e1fa6ea6d..50b8141bf 100644 --- a/apps/rss_feeds/views.py +++ b/apps/rss_feeds/views.py @@ -193,7 +193,13 @@ def load_feed_statistics(request, feed_id): # Stories per month - average and month-by-month breakout average_stories_per_month, story_count_history = feed.average_stories_per_month, feed.data.story_count_history stats['average_stories_per_month'] = average_stories_per_month - stats['story_count_history'] = story_count_history and json.decode(story_count_history) + story_count_history = story_count_history and json.decode(story_count_history) + if story_count_history and isinstance(story_count_history, dict): + stats['story_count_history'] = story_count_history['months'] + stats['story_days_history'] = story_count_history['days'] + stats['story_hours_history'] = story_count_history['hours'] + else: + stats['story_count_history'] = story_count_history # Subscribers stats['subscriber_count'] = feed.num_subscribers diff --git a/apps/social/models.py b/apps/social/models.py index 1acce1bb6..4fab1a291 100644 --- a/apps/social/models.py +++ b/apps/social/models.py @@ -133,6 +133,8 @@ class MSocialProfile(mongo.Document): stories_last_month = mongo.IntField(default=0) average_stories_per_month = mongo.IntField(default=0) story_count_history = mongo.ListField() + story_days_history = mongo.DictField() + story_hours_history = mongo.DictField() feed_classifier_counts = mongo.DictField() favicon_color = mongo.StringField(max_length=6) protected = mongo.BooleanField() @@ -690,23 +692,25 @@ class MSocialProfile(mongo.Document): map_f = """ function() { var date = (this.shared_date.getFullYear()) + "-" + (this.shared_date.getMonth()+1); - emit(date, 1); + var hour = this.shared_date.getHours(); + var day = this.shared_date.getDay(); + emit(this.story_hash, {'month': date, 'hour': hour, 'day': day}); } """ reduce_f = """ function(key, values) { - var total = 0; - for (var i=0; i < values.length; i++) { - total += values[i]; - } - return total; + return values; } """ - dates = {} - res = MSharedStory.objects(user_id=self.user_id).map_reduce(map_f, reduce_f, output='inline') - for r in res: - dates[r.key] = r.value - year = int(re.findall(r"(\d{4})-\d{1,2}", r.key)[0]) + dates = defaultdict(int) + hours = defaultdict(int) + days = defaultdict(int) + results = MSharedStory.objects(user_id=self.user_id).map_reduce(map_f, reduce_f, output='inline') + for result in results: + dates[result.value['month']] += 1 + hours[str(int(result.value['hour']))] += 1 + days[str(int(result.value['day']))] += 1 + year = int(re.findall(r"(\d{4})-\d{1,2}", result.value['month'])[0]) if year < min_year: min_year = year @@ -725,6 +729,8 @@ class MSocialProfile(mongo.Document): month_count += 1 self.story_count_history = months + self.story_days_history = days + self.story_hours_history = hours self.average_stories_per_month = total / max(1, month_count) self.save() diff --git a/apps/social/views.py b/apps/social/views.py index 24eb7e595..f89dd57ca 100644 --- a/apps/social/views.py +++ b/apps/social/views.py @@ -1358,6 +1358,8 @@ def load_social_statistics(request, social_user_id, username=None): # Stories per month - average and month-by-month breakout stats['average_stories_per_month'] = social_profile.average_stories_per_month stats['story_count_history'] = social_profile.story_count_history + stats['story_hours_history'] = social_profile.story_hours_history + stats['story_days_history'] = social_profile.story_days_history # Subscribers stats['subscriber_count'] = social_profile.follower_count diff --git a/media/css/reader.css b/media/css/reader.css index bc9a229f2..928c7eff0 100644 --- a/media/css/reader.css +++ b/media/css/reader.css @@ -7512,8 +7512,8 @@ form.opml_import_form input { overflow: hidden; } -.NB-modal-statistics .NB-statistics-stat .NB-statistics-history-chart { - margin: 0px 24px; +.NB-modal-statistics .NB-statistics-stat .NB-statistics-history-count-chart { + margin: 12px 24px 18px; width: 524px; height: 180px; -webkit-box-sizing: border-box; @@ -7521,6 +7521,35 @@ form.opml_import_form input { box-sizing: border-box; } +.NB-modal-statistics .NB-statistics-stat .NB-statistics-history-hours-chart { + margin: 12px 24px 18px; + width: 524px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.NB-modal-statistics .NB-statistics-stat .NB-statistics-history-days-chart { + margin: -32px 24px; + width: 524px; + height: 400px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.NB-modal-statistics .NB-statistics-stat .NB-statistics-history-hours-chart { + text-align: left; + color: #808080; + font-size: 10px; +} +.NB-modal-statistics .NB-statistics-history-chart-hours-row { + background-color: #97BBCD; + height: 50px; +} +.NB-modal-statistics .NB-statistics-history-chart-hours-row td { + width: 18px; +} .NB-modal-statistics .NB-modal-loading { margin: 6px 8px 0; } diff --git a/media/js/newsblur/reader/reader_feed_exception.js b/media/js/newsblur/reader/reader_feed_exception.js index 5339cd6b2..26013674d 100644 --- a/media/js/newsblur/reader/reader_feed_exception.js +++ b/media/js/newsblur/reader/reader_feed_exception.js @@ -531,6 +531,21 @@ _.extend(NEWSBLUR.ReaderFeedException.prototype, { }); }, + animate_saved: function() { + var $status = $('.NB-exception-option-view .NB-exception-option-status', this.$modal); + $status.text('Saved').animate({ + 'opacity': 1 + }, { + 'queue': false, + 'duration': 600, + 'complete': function() { + _.delay(function() { + $status.animate({'opacity': 0}, {'queue': false, 'duration': 1000}); + }, 300); + } + }); + }, + handle_change: function(elem, e) { var self = this; diff --git a/media/js/newsblur/reader/reader_statistics.js b/media/js/newsblur/reader/reader_statistics.js index 96832ae77..ae5df0fe2 100644 --- a/media/js/newsblur/reader/reader_statistics.js +++ b/media/js/newsblur/reader/reader_statistics.js @@ -85,7 +85,9 @@ _.extend(NEWSBLUR.ReaderStatistics.prototype, { $expires.html(""); } setTimeout(function() { - self.make_charts(data); + self.make_chart_count(data); + self.make_chart_hours(data); + self.make_chart_days(data); }, this.first_load ? 200 : 50); setTimeout(function() { @@ -156,7 +158,19 @@ _.extend(NEWSBLUR.ReaderStatistics.prototype, { $.make('div', { className: 'NB-statistics-history-stat' }, [ $.make('div', { className: 'NB-statistics-label' }, 'Stories per month') ]), - $.make('canvas', { id: 'NB-statistics-history-chart', className: 'NB-statistics-history-chart' }) + $.make('canvas', { id: 'NB-statistics-history-count-chart', className: 'NB-statistics-history-count-chart' }) + ]), + $.make('div', { className: 'NB-statistics-stat NB-statistics-history'}, [ + $.make('div', { className: 'NB-statistics-history-stat' }, [ + $.make('div', { className: 'NB-statistics-label' }, 'Stories per day') + ]), + $.make('canvas', { id: 'NB-statistics-history-days-chart', className: 'NB-statistics-history-days-chart' }) + ]), + $.make('div', { className: 'NB-statistics-stat NB-statistics-history'}, [ + $.make('div', { className: 'NB-statistics-history-stat' }, [ + $.make('div', { className: 'NB-statistics-label' }, 'Daily distribution of stories') + ]), + $.make('div', { className: 'NB-statistics-history-hours-chart' }) ]), (data.classifier_counts && $.make('div', { className: 'NB-statistics-state NB-statistics-classifiers' }, [ this.make_classifier_count('tag', data.classifier_counts['tag']), @@ -263,7 +277,7 @@ _.extend(NEWSBLUR.ReaderStatistics.prototype, { return $history; }, - make_charts: function(data) { + make_chart_count: function(data) { var labels = _.map(data['story_count_history'], function(date) { var date_matched = date[0].match(/(\d{4})-(\d{1,2})/); var date = (new Date(parseInt(date_matched[1], 10), parseInt(date_matched[2],10)-1)); @@ -291,7 +305,7 @@ _.extend(NEWSBLUR.ReaderStatistics.prototype, { } ] }; - var $plot = $(".NB-statistics-history-chart"); + var $plot = $(".NB-statistics-history-count-chart"); var width = $plot.width(); var height = $plot.height(); $plot.attr('width', width); @@ -301,6 +315,59 @@ _.extend(NEWSBLUR.ReaderStatistics.prototype, { }); }, + make_chart_hours: function(data) { + var max_count = _.max(data.story_hours_history); + var $chart = $.make('table', [ + $.make('tr', { className: 'NB-statistics-history-chart-hours-row' }, [ + _.map(_.range(24), function(hour) { + var count = data.story_hours_history[hour] || 0; + var opacity = 1 - (count * 1.0 / max_count); + return $.make('td', { style: "background-color: rgba(255, 255, 255, " + opacity + ");" }); + }) + ]), + $.make('tr', { className: 'NB-statistics-history-chart-hours-text-row' }, [ + _.compact(_.map(_.range(24), function(hour, count) { + var am = hour < 12; + if (hour == 0) hour = 12; + var hour_name = am ? (hour + "am") : ((hour > 12 ? hour - 12 : hour) + "pm"); + if (hour % 3 == 0) { + return $.make('td', { colSpan: 3 }, hour_name); + } + })) + ]) + ]); + + $(".NB-statistics-history-hours-chart", this.$modal).html($chart); + }, + + make_chart_days: function(data) { + var labels = NEWSBLUR.utils.dayNames; + var values = _.map(_.range(7), function(day) { + return data['story_days_history'][day] || 0; + }); + var points = { + labels: labels, + datasets: [ + { + fillColor : "rgba(151,187,205,0.5)", + strokeColor : "rgba(151,187,205,1)", + pointColor : "rgba(151,187,205,1)", + pointStrokeColor : "#fff", + data : values + } + ] + }; + var $plot = $(".NB-statistics-history-days-chart"); + var width = $plot.width(); + var height = $plot.height(); + $plot.attr('width', width); + $plot.attr('height', height); + + var myLine = new Chart($plot.get(0).getContext("2d")).Radar(points, { + scaleShowLabelBackdrop: false + }); + }, + close_and_load_premium: function() { this.close(function() { NEWSBLUR.reader.open_feedchooser_modal(); diff --git a/media/js/vendor/chart.js b/media/js/vendor/chart.js index c3d9b09d8..f1100c365 100755 --- a/media/js/vendor/chart.js +++ b/media/js/vendor/chart.js @@ -1,5 +1,14 @@ +/*! + * Chart.js + * http://chartjs.org/ + * + * Copyright 2013 Nick Downie + * Released under the MIT license + * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md + */ + //Define the global Chart Variable as a class. -var Chart = function(context){ +window.Chart = function(context){ var chart = this; @@ -152,6 +161,127 @@ var Chart = function(context){ context.scale(window.devicePixelRatio, window.devicePixelRatio); } + this.PolarArea = function(data,options){ + + chart.PolarArea.defaults = { + scaleOverlay : true, + scaleOverride : false, + scaleSteps : null, + scaleStepWidth : null, + scaleStartValue : null, + scaleShowLine : true, + scaleLineColor : "rgba(0,0,0,.1)", + scaleLineWidth : 1, + scaleShowLabels : true, + scaleLabel : "<%=value%>", + scaleFontFamily : "'Arial'", + scaleFontSize : 12, + scaleFontStyle : "normal", + scaleFontColor : "#666", + scaleShowLabelBackdrop : true, + scaleBackdropColor : "rgba(255,255,255,0.75)", + scaleBackdropPaddingY : 2, + scaleBackdropPaddingX : 2, + segmentShowStroke : true, + segmentStrokeColor : "#fff", + segmentStrokeWidth : 2, + animation : true, + animationSteps : 100, + animationEasing : "easeOutBounce", + animateRotate : true, + animateScale : false, + onAnimationComplete : null + }; + + var config = (options)? mergeChartConfig(chart.PolarArea.defaults,options) : chart.PolarArea.defaults; + + return new PolarArea(data,config,context); + }; + + this.Radar = function(data,options){ + + chart.Radar.defaults = { + scaleOverlay : false, + scaleOverride : false, + scaleSteps : null, + scaleStepWidth : null, + scaleStartValue : null, + scaleShowLine : true, + scaleLineColor : "rgba(0,0,0,.1)", + scaleLineWidth : 1, + scaleShowLabels : false, + scaleLabel : "<%=value%>", + scaleFontFamily : "'Arial'", + scaleFontSize : 12, + scaleFontStyle : "normal", + scaleFontColor : "#666", + scaleShowLabelBackdrop : true, + scaleBackdropColor : "rgba(255,255,255,0.75)", + scaleBackdropPaddingY : 2, + scaleBackdropPaddingX : 2, + angleShowLineOut : true, + angleLineColor : "rgba(0,0,0,.1)", + angleLineWidth : 1, + pointLabelFontFamily : "'Arial'", + pointLabelFontStyle : "normal", + pointLabelFontSize : 12, + pointLabelFontColor : "#666", + pointDot : true, + pointDotRadius : 3, + pointDotStrokeWidth : 1, + datasetStroke : true, + datasetStrokeWidth : 2, + datasetFill : true, + animation : true, + animationSteps : 60, + animationEasing : "easeOutQuart", + onAnimationComplete : null + }; + + var config = (options)? mergeChartConfig(chart.Radar.defaults,options) : chart.Radar.defaults; + + return new Radar(data,config,context); + }; + + this.Pie = function(data,options){ + chart.Pie.defaults = { + segmentShowStroke : true, + segmentStrokeColor : "#fff", + segmentStrokeWidth : 2, + animation : true, + animationSteps : 100, + animationEasing : "easeOutBounce", + animateRotate : true, + animateScale : false, + onAnimationComplete : null + }; + + var config = (options)? mergeChartConfig(chart.Pie.defaults,options) : chart.Pie.defaults; + + return new Pie(data,config,context); + }; + + this.Doughnut = function(data,options){ + + chart.Doughnut.defaults = { + segmentShowStroke : true, + segmentStrokeColor : "#fff", + segmentStrokeWidth : 2, + percentageInnerCutout : 50, + animation : true, + animationSteps : 100, + animationEasing : "easeOutBounce", + animateRotate : true, + animateScale : false, + onAnimationComplete : null + }; + + var config = (options)? mergeChartConfig(chart.Doughnut.defaults,options) : chart.Doughnut.defaults; + + return new Doughnut(data,config,context); + + }; + this.Line = function(data,options){ chart.Line.defaults = { @@ -186,12 +316,476 @@ var Chart = function(context){ var config = (options) ? mergeChartConfig(chart.Line.defaults,options) : chart.Line.defaults; return new Line(data,config,context); - }; + } + + this.Bar = function(data,options){ + chart.Bar.defaults = { + scaleOverlay : false, + scaleOverride : false, + scaleSteps : null, + scaleStepWidth : null, + scaleStartValue : null, + scaleLineColor : "rgba(0,0,0,.1)", + scaleLineWidth : 1, + scaleShowLabels : true, + scaleLabel : "<%=value%>", + scaleFontFamily : "'Arial'", + scaleFontSize : 12, + scaleFontStyle : "normal", + scaleFontColor : "#666", + scaleShowGridLines : true, + scaleGridLineColor : "rgba(0,0,0,.05)", + scaleGridLineWidth : 1, + barShowStroke : true, + barStrokeWidth : 2, + barValueSpacing : 5, + barDatasetSpacing : 1, + animation : true, + animationSteps : 60, + animationEasing : "easeOutQuart", + onAnimationComplete : null + }; + var config = (options) ? mergeChartConfig(chart.Bar.defaults,options) : chart.Bar.defaults; + + return new Bar(data,config,context); + } var clear = function(c){ c.clearRect(0, 0, width, height); }; + var PolarArea = function(data,config,ctx){ + var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString; + + + calculateDrawingSizes(); + + valueBounds = getValueBounds(); + + labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : null; + + //Check and set the scale + if (!config.scaleOverride){ + + calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString); + } + else { + calculatedScale = { + steps : config.scaleSteps, + stepValue : config.scaleStepWidth, + graphMin : config.scaleStartValue, + labels : [] + } + populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth); + } + + scaleHop = maxSize/(calculatedScale.steps); + + //Wrap in an animation loop wrapper + animationLoop(config,drawScale,drawAllSegments,ctx); + + function calculateDrawingSizes(){ + maxSize = (Min([width,height])/2); + //Remove whatever is larger - the font size or line width. + + maxSize -= Max([config.scaleFontSize*0.5,config.scaleLineWidth*0.5]); + + labelHeight = config.scaleFontSize*2; + //If we're drawing the backdrop - add the Y padding to the label height and remove from drawing region. + if (config.scaleShowLabelBackdrop){ + labelHeight += (2 * config.scaleBackdropPaddingY); + maxSize -= config.scaleBackdropPaddingY*1.5; + } + + scaleHeight = maxSize; + //If the label height is less than 5, set it to 5 so we don't have lines on top of each other. + labelHeight = Default(labelHeight,5); + } + function drawScale(){ + for (var i=0; i upperValue) {upperValue = data[i].value;} + if (data[i].value < lowerValue) {lowerValue = data[i].value;} + }; + + var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); + var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); + + return { + maxValue : upperValue, + minValue : lowerValue, + maxSteps : maxSteps, + minSteps : minSteps + }; + + + } + } + + var Radar = function (data,config,ctx) { + var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString; + + //If no labels are defined set to an empty array, so referencing length for looping doesn't blow up. + if (!data.labels) data.labels = []; + + calculateDrawingSizes(); + + var valueBounds = getValueBounds(); + + labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : null; + + //Check and set the scale + if (!config.scaleOverride){ + + calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString); + } + else { + calculatedScale = { + steps : config.scaleSteps, + stepValue : config.scaleStepWidth, + graphMin : config.scaleStartValue, + labels : [] + } + populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth); + } + + scaleHop = maxSize/(calculatedScale.steps); + + animationLoop(config,drawScale,drawAllDataPoints,ctx); + + //Radar specific functions. + function drawAllDataPoints(animationDecimal){ + var rotationDegree = (2*Math.PI)/data.datasets[0].data.length; + + ctx.save(); + //translate to the centre of the canvas. + ctx.translate(width/2,height/2); + + //We accept multiple data sets for radar charts, so show loop through each set + for (var i=0; i Math.PI){ + ctx.textAlign = "right"; + } + else{ + ctx.textAlign = "left"; + } + + ctx.textBaseline = "middle"; + + ctx.fillText(data.labels[k],opposite,-adjacent); + + } + ctx.restore(); + }; + function calculateDrawingSizes(){ + maxSize = (Min([width,height])/2); + + labelHeight = config.scaleFontSize*2; + + var labelLength = 0; + for (var i=0; ilabelLength) labelLength = textMeasurement; + } + + //Figure out whats the largest - the height of the text or the width of what's there, and minus it from the maximum usable size. + maxSize -= Max([labelLength,((config.pointLabelFontSize/2)*1.5)]); + + maxSize -= config.pointLabelFontSize; + maxSize = CapValue(maxSize, null, 0); + scaleHeight = maxSize; + //If the label height is less than 5, set it to 5 so we don't have lines on top of each other. + labelHeight = Default(labelHeight,5); + }; + function getValueBounds() { + var upperValue = Number.MIN_VALUE; + var lowerValue = Number.MAX_VALUE; + + for (var i=0; i upperValue){upperValue = data.datasets[i].data[j]} + if (data.datasets[i].data[j] < lowerValue){lowerValue = data.datasets[i].data[j]} + } + } + + var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); + var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); + + return { + maxValue : upperValue, + minValue : lowerValue, + maxSteps : maxSteps, + minSteps : minSteps + }; + + + } + } + + var Pie = function(data,config,ctx){ + var segmentTotal = 0; + + //In case we have a canvas that is not a square. Minus 5 pixels as padding round the edge. + var pieRadius = Min([height/2,width/2]) - 5; + + for (var i=0; i upperValue) { upperValue = data.datasets[i].data[j]; }; - if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j]; }; + if ( data.datasets[i].data[j] > upperValue) { upperValue = data.datasets[i].data[j] }; + if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] }; } }; @@ -426,7 +1016,7 @@ var Chart = function(context){ } - }; + } var Bar = function(data,config,ctx){ var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString, valueHop,widestXLabel, xAxisLength,yAxisPosX,xAxisPosY,barWidth, rotateLabels = 0; @@ -446,12 +1036,8 @@ var Chart = function(context){ stepValue : config.scaleStepWidth, graphMin : config.scaleStartValue, labels : [] - }; - for (var i=0; i upperValue) { upperValue = data.datasets[i].data[j]; }; - if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j]; }; + if ( data.datasets[i].data[j] > upperValue) { upperValue = data.datasets[i].data[j] }; + if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] }; } }; @@ -632,7 +1218,7 @@ var Chart = function(context){ } - }; + } function calculateOffset(val,calculatedScale,scaleHop){ var outerValue = calculatedScale.steps * calculatedScale.stepValue; @@ -722,19 +1308,9 @@ var Chart = function(context){ numberOfSteps = Math.round(graphRange/stepValue); } }; - - - //Create an array of all the labels by interpolating the string. - var labels = []; - - if(labelTemplateString){ - //Fix floating point errors by setting to fixed the on the same decimal as the stepValue. - for (var i=1; i Date: Tue, 5 Jan 2016 11:03:17 -0800 Subject: [PATCH 2/2] Loosening stats rate limiter. Ready to launch. --- .gitignore | 1 - apps/rss_feeds/views.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 1e3908ea6..0be9a6049 100644 --- a/.gitignore +++ b/.gitignore @@ -63,4 +63,3 @@ media/safari/NewsBlur.safariextz # IDE files clients/android/NewsBlur/.idea -.tm_properties diff --git a/apps/rss_feeds/views.py b/apps/rss_feeds/views.py index 50b8141bf..e1e36926d 100644 --- a/apps/rss_feeds/views.py +++ b/apps/rss_feeds/views.py @@ -150,7 +150,7 @@ def feed_autocomplete(request): else: return feeds -@ratelimit(minutes=1, requests=10) +@ratelimit(minutes=1, requests=30) @json.json_view def load_feed_statistics(request, feed_id): user = get_user(request)