mirror of
https://github.com/viq/NewsBlur.git
synced 2025-08-31 22:20:12 +00:00
Merge branch 'charts2'
* charts2: Loosening stats rate limiter. Ready to launch. Patching charts to fix broken charts branch.
This commit is contained in:
commit
02909962d4
9 changed files with 781 additions and 73 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -63,4 +63,3 @@ media/safari/NewsBlur.safariextz
|
|||
|
||||
# IDE files
|
||||
clients/android/NewsBlur/.idea
|
||||
.tm_properties
|
||||
|
|
|
@ -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'])
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
656
media/js/vendor/chart.js
vendored
656
media/js/vendor/chart.js
vendored
|
@ -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<calculatedScale.steps; i++){
|
||||
//If the line object is there
|
||||
if (config.scaleShowLine){
|
||||
ctx.beginPath();
|
||||
ctx.arc(width/2, height/2, scaleHop * (i + 1), 0, (Math.PI * 2), true);
|
||||
ctx.strokeStyle = config.scaleLineColor;
|
||||
ctx.lineWidth = config.scaleLineWidth;
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
if (config.scaleShowLabels){
|
||||
ctx.textAlign = "center";
|
||||
ctx.font = config.scaleFontStyle + " " + config.scaleFontSize + "px " + config.scaleFontFamily;
|
||||
var label = calculatedScale.labels[i];
|
||||
//If the backdrop object is within the font object
|
||||
if (config.scaleShowLabelBackdrop){
|
||||
var textWidth = ctx.measureText(label).width;
|
||||
ctx.fillStyle = config.scaleBackdropColor;
|
||||
ctx.beginPath();
|
||||
ctx.rect(
|
||||
Math.round(width/2 - textWidth/2 - config.scaleBackdropPaddingX), //X
|
||||
Math.round(height/2 - (scaleHop * (i + 1)) - config.scaleFontSize*0.5 - config.scaleBackdropPaddingY),//Y
|
||||
Math.round(textWidth + (config.scaleBackdropPaddingX*2)), //Width
|
||||
Math.round(config.scaleFontSize + (config.scaleBackdropPaddingY*2)) //Height
|
||||
);
|
||||
ctx.fill();
|
||||
}
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillStyle = config.scaleFontColor;
|
||||
ctx.fillText(label,width/2,height/2 - (scaleHop * (i + 1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
function drawAllSegments(animationDecimal){
|
||||
var startAngle = -Math.PI/2,
|
||||
angleStep = (Math.PI*2)/data.length,
|
||||
scaleAnimation = 1,
|
||||
rotateAnimation = 1;
|
||||
if (config.animation) {
|
||||
if (config.animateScale) {
|
||||
scaleAnimation = animationDecimal;
|
||||
}
|
||||
if (config.animateRotate){
|
||||
rotateAnimation = animationDecimal;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i=0; i<data.length; i++){
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(width/2,height/2,scaleAnimation * calculateOffset(data[i].value,calculatedScale,scaleHop),startAngle, startAngle + rotateAnimation*angleStep, false);
|
||||
ctx.lineTo(width/2,height/2);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = data[i].color;
|
||||
ctx.fill();
|
||||
|
||||
if(config.segmentShowStroke){
|
||||
ctx.strokeStyle = config.segmentStrokeColor;
|
||||
ctx.lineWidth = config.segmentStrokeWidth;
|
||||
ctx.stroke();
|
||||
}
|
||||
startAngle += rotateAnimation*angleStep;
|
||||
}
|
||||
}
|
||||
function getValueBounds() {
|
||||
var upperValue = Number.MIN_VALUE;
|
||||
var lowerValue = Number.MAX_VALUE;
|
||||
for (var i=0; i<data.length; i++){
|
||||
if (data[i].value > 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<data.datasets.length; i++){
|
||||
ctx.beginPath();
|
||||
|
||||
ctx.moveTo(0,animationDecimal*(-1*calculateOffset(data.datasets[i].data[0],calculatedScale,scaleHop)));
|
||||
for (var j=1; j<data.datasets[i].data.length; j++){
|
||||
ctx.rotate(rotationDegree);
|
||||
ctx.lineTo(0,animationDecimal*(-1*calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop)));
|
||||
|
||||
}
|
||||
ctx.closePath();
|
||||
|
||||
|
||||
ctx.fillStyle = data.datasets[i].fillColor;
|
||||
ctx.strokeStyle = data.datasets[i].strokeColor;
|
||||
ctx.lineWidth = config.datasetStrokeWidth;
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
|
||||
if (config.pointDot){
|
||||
ctx.fillStyle = data.datasets[i].pointColor;
|
||||
ctx.strokeStyle = data.datasets[i].pointStrokeColor;
|
||||
ctx.lineWidth = config.pointDotStrokeWidth;
|
||||
for (var k=0; k<data.datasets[i].data.length; k++){
|
||||
ctx.rotate(rotationDegree);
|
||||
ctx.beginPath();
|
||||
ctx.arc(0,animationDecimal*(-1*calculateOffset(data.datasets[i].data[k],calculatedScale,scaleHop)),config.pointDotRadius,2*Math.PI,false);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
}
|
||||
ctx.rotate(rotationDegree);
|
||||
|
||||
}
|
||||
ctx.restore();
|
||||
|
||||
|
||||
}
|
||||
function drawScale(){
|
||||
var rotationDegree = (2*Math.PI)/data.datasets[0].data.length;
|
||||
ctx.save();
|
||||
ctx.translate(width / 2, height / 2);
|
||||
|
||||
if (config.angleShowLineOut){
|
||||
ctx.strokeStyle = config.angleLineColor;
|
||||
ctx.lineWidth = config.angleLineWidth;
|
||||
for (var h=0; h<data.datasets[0].data.length; h++){
|
||||
|
||||
ctx.rotate(rotationDegree);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0,0);
|
||||
ctx.lineTo(0,-maxSize);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
for (var i=0; i<calculatedScale.steps; i++){
|
||||
ctx.beginPath();
|
||||
|
||||
if(config.scaleShowLine){
|
||||
ctx.strokeStyle = config.scaleLineColor;
|
||||
ctx.lineWidth = config.scaleLineWidth;
|
||||
ctx.moveTo(0,-scaleHop * (i+1));
|
||||
for (var j=0; j<data.datasets[0].data.length; j++){
|
||||
ctx.rotate(rotationDegree);
|
||||
ctx.lineTo(0,-scaleHop * (i+1));
|
||||
}
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
|
||||
}
|
||||
|
||||
if (config.scaleShowLabels){
|
||||
ctx.textAlign = 'center';
|
||||
ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
|
||||
ctx.textBaseline = "middle";
|
||||
|
||||
if (config.scaleShowLabelBackdrop){
|
||||
var textWidth = ctx.measureText(calculatedScale.labels[i]).width;
|
||||
ctx.fillStyle = config.scaleBackdropColor;
|
||||
ctx.beginPath();
|
||||
ctx.rect(
|
||||
Math.round(- textWidth/2 - config.scaleBackdropPaddingX), //X
|
||||
Math.round((-scaleHop * (i + 1)) - config.scaleFontSize*0.5 - config.scaleBackdropPaddingY),//Y
|
||||
Math.round(textWidth + (config.scaleBackdropPaddingX*2)), //Width
|
||||
Math.round(config.scaleFontSize + (config.scaleBackdropPaddingY*2)) //Height
|
||||
);
|
||||
ctx.fill();
|
||||
}
|
||||
ctx.fillStyle = config.scaleFontColor;
|
||||
ctx.fillText(calculatedScale.labels[i],0,-scaleHop*(i+1));
|
||||
}
|
||||
|
||||
}
|
||||
for (var k=0; k<data.labels.length; k++){
|
||||
ctx.font = config.pointLabelFontStyle + " " + config.pointLabelFontSize+"px " + config.pointLabelFontFamily;
|
||||
ctx.fillStyle = config.pointLabelFontColor;
|
||||
var opposite = Math.sin(rotationDegree*k) * (maxSize + config.pointLabelFontSize);
|
||||
var adjacent = Math.cos(rotationDegree*k) * (maxSize + config.pointLabelFontSize);
|
||||
|
||||
if(rotationDegree*k == Math.PI || rotationDegree*k == 0){
|
||||
ctx.textAlign = "center";
|
||||
}
|
||||
else if(rotationDegree*k > 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; i<data.labels.length; i++){
|
||||
ctx.font = config.pointLabelFontStyle + " " + config.pointLabelFontSize+"px " + config.pointLabelFontFamily;
|
||||
var textMeasurement = ctx.measureText(data.labels[i]).width;
|
||||
if(textMeasurement>labelLength) 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<data.datasets.length; i++){
|
||||
for (var j=0; j<data.datasets[i].data.length; 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]}
|
||||
}
|
||||
}
|
||||
|
||||
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<data.length; i++){
|
||||
segmentTotal += data[i].value;
|
||||
}
|
||||
|
||||
|
||||
animationLoop(config,null,drawPieSegments,ctx);
|
||||
|
||||
function drawPieSegments (animationDecimal){
|
||||
var cumulativeAngle = -Math.PI/2,
|
||||
scaleAnimation = 1,
|
||||
rotateAnimation = 1;
|
||||
if (config.animation) {
|
||||
if (config.animateScale) {
|
||||
scaleAnimation = animationDecimal;
|
||||
}
|
||||
if (config.animateRotate){
|
||||
rotateAnimation = animationDecimal;
|
||||
}
|
||||
}
|
||||
for (var i=0; i<data.length; i++){
|
||||
var segmentAngle = rotateAnimation * ((data[i].value/segmentTotal) * (Math.PI*2));
|
||||
ctx.beginPath();
|
||||
ctx.arc(width/2,height/2,scaleAnimation * pieRadius,cumulativeAngle,cumulativeAngle + segmentAngle);
|
||||
ctx.lineTo(width/2,height/2);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = data[i].color;
|
||||
ctx.fill();
|
||||
|
||||
if(config.segmentShowStroke){
|
||||
ctx.lineWidth = config.segmentStrokeWidth;
|
||||
ctx.strokeStyle = config.segmentStrokeColor;
|
||||
ctx.stroke();
|
||||
}
|
||||
cumulativeAngle += segmentAngle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var Doughnut = 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 doughnutRadius = Min([height/2,width/2]) - 5;
|
||||
|
||||
var cutoutRadius = doughnutRadius * (config.percentageInnerCutout/100);
|
||||
|
||||
for (var i=0; i<data.length; i++){
|
||||
segmentTotal += data[i].value;
|
||||
}
|
||||
|
||||
|
||||
animationLoop(config,null,drawPieSegments,ctx);
|
||||
|
||||
|
||||
function drawPieSegments (animationDecimal){
|
||||
var cumulativeAngle = -Math.PI/2,
|
||||
scaleAnimation = 1,
|
||||
rotateAnimation = 1;
|
||||
if (config.animation) {
|
||||
if (config.animateScale) {
|
||||
scaleAnimation = animationDecimal;
|
||||
}
|
||||
if (config.animateRotate){
|
||||
rotateAnimation = animationDecimal;
|
||||
}
|
||||
}
|
||||
for (var i=0; i<data.length; i++){
|
||||
var segmentAngle = rotateAnimation * ((data[i].value/segmentTotal) * (Math.PI*2));
|
||||
ctx.beginPath();
|
||||
ctx.arc(width/2,height/2,scaleAnimation * doughnutRadius,cumulativeAngle,cumulativeAngle + segmentAngle,false);
|
||||
ctx.arc(width/2,height/2,scaleAnimation * cutoutRadius,cumulativeAngle + segmentAngle,cumulativeAngle,true);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = data[i].color;
|
||||
ctx.fill();
|
||||
|
||||
if(config.segmentShowStroke){
|
||||
ctx.lineWidth = config.segmentStrokeWidth;
|
||||
ctx.strokeStyle = config.segmentStrokeColor;
|
||||
ctx.stroke();
|
||||
}
|
||||
cumulativeAngle += segmentAngle;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
var Line = function(data,config,ctx){
|
||||
var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString, valueHop,widestXLabel, xAxisLength,yAxisPosX,xAxisPosY, rotateLabels = 0;
|
||||
|
||||
|
@ -210,12 +804,8 @@ var Chart = function(context){
|
|||
stepValue : config.scaleStepWidth,
|
||||
graphMin : config.scaleStartValue,
|
||||
labels : []
|
||||
};
|
||||
for (var i=0; i<calculatedScale.steps; i++){
|
||||
if(labelTemplateString){
|
||||
calculatedScale.labels.push(tmpl(labelTemplateString,{value:(config.scaleStartValue + (config.scaleStepWidth * i)).toFixed(getDecimalPlaces (config.scaleStepWidth))}));
|
||||
}
|
||||
}
|
||||
populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth);
|
||||
}
|
||||
|
||||
scaleHop = Math.floor(scaleHeight/calculatedScale.steps);
|
||||
|
@ -227,7 +817,7 @@ var Chart = function(context){
|
|||
ctx.strokeStyle = data.datasets[i].strokeColor;
|
||||
ctx.lineWidth = config.datasetStrokeWidth;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(yAxisPosX, xAxisPosY - animPc*(calculateOffset(data.datasets[i].data[0],calculatedScale,scaleHop)));
|
||||
ctx.moveTo(yAxisPosX, xAxisPosY - animPc*(calculateOffset(data.datasets[i].data[0],calculatedScale,scaleHop)))
|
||||
|
||||
for (var j=1; j<data.datasets[i].data.length; j++){
|
||||
if (config.bezierCurve){
|
||||
|
@ -407,8 +997,8 @@ var Chart = function(context){
|
|||
var lowerValue = Number.MAX_VALUE;
|
||||
for (var i=0; i<data.datasets.length; i++){
|
||||
for (var j=0; j<data.datasets[i].data.length; 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]; };
|
||||
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<calculatedScale.steps; i++){
|
||||
if(labelTemplateString){
|
||||
calculatedScale.labels.push(tmpl(labelTemplateString,{value:(config.scaleStartValue + (config.scaleStepWidth * i)).toFixed(getDecimalPlaces (config.scaleStepWidth))}));
|
||||
}
|
||||
}
|
||||
populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth);
|
||||
}
|
||||
|
||||
scaleHop = Math.floor(scaleHeight/calculatedScale.steps);
|
||||
|
@ -615,8 +1201,8 @@ var Chart = function(context){
|
|||
var lowerValue = Number.MAX_VALUE;
|
||||
for (var i=0; i<data.datasets.length; i++){
|
||||
for (var j=0; j<data.datasets[i].data.length; 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]; };
|
||||
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<numberOfSteps+1; i++){
|
||||
labels.push(tmpl(labelTemplateString,{value:(graphMin + (stepValue*i)).toFixed(getDecimalPlaces (stepValue))}));
|
||||
}
|
||||
}
|
||||
populateLabels(labelTemplateString, labels, numberOfSteps, graphMin, stepValue);
|
||||
|
||||
return {
|
||||
steps : numberOfSteps,
|
||||
|
@ -742,7 +1318,7 @@ var Chart = function(context){
|
|||
graphMin : graphMin,
|
||||
labels : labels
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
function calculateOrderOfMagnitude(val){
|
||||
return Math.floor(Math.log(val) / Math.LN10);
|
||||
|
@ -750,6 +1326,16 @@ var Chart = function(context){
|
|||
|
||||
|
||||
}
|
||||
|
||||
//Populate an array of all the labels by interpolating the string.
|
||||
function populateLabels(labelTemplateString, labels, numberOfSteps, graphMin, stepValue) {
|
||||
if (labelTemplateString) {
|
||||
//Fix floating point errors by setting to fixed the on the same decimal as the stepValue.
|
||||
for (var i = 1; i < numberOfSteps + 1; i++) {
|
||||
labels.push(tmpl(labelTemplateString, {value: (graphMin + (stepValue * i)).toFixed(getDecimalPlaces(stepValue))}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Max value from array
|
||||
function Max( array ){
|
||||
|
@ -788,7 +1374,7 @@ var Chart = function(context){
|
|||
function getDecimalPlaces (num){
|
||||
var numberOfDecimalPlaces;
|
||||
if (num%1!=0){
|
||||
return num.toString().split(".")[1].length;
|
||||
return num.toString().split(".")[1].length
|
||||
}
|
||||
else{
|
||||
return 0;
|
||||
|
@ -809,7 +1395,7 @@ var Chart = function(context){
|
|||
function tmpl(str, data){
|
||||
// Figure out if we're getting a template, or if we need to
|
||||
// load the template - and be sure to cache the result.
|
||||
var fn = !(/\W/.test(str)) ?
|
||||
var fn = !/\W/.test(str) ?
|
||||
cache[str] = cache[str] ||
|
||||
tmpl(document.getElementById(str).innerHTML) :
|
||||
|
||||
|
@ -835,7 +1421,5 @@ var Chart = function(context){
|
|||
// Provide some basic currying to the user
|
||||
return data ? fn( data ) : fn;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue