Finishing up statistics graphs. Using Flot instead of Raphael. Added a migration to change how stories are counted.

This commit is contained in:
Samuel Clay 2010-08-13 10:43:48 -04:00
parent 56c4fd005c
commit d4f30d8236
9 changed files with 2474 additions and 122 deletions

View file

@ -104,10 +104,13 @@ def load_feeds(request):
return data
user_subs = UserSubscription.objects.select_related('feed').filter(user=user)
updated_count = 0
for sub in user_subs:
if sub.needs_unread_recalc:
if updated_count < 200 and sub.needs_unread_recalc:
# > 200 means that we counted enough, just move to refresh during live.
sub.calculate_feed_scores()
updated_count += 1
feeds[sub.feed.pk] = {
'id': sub.feed.pk,
'feed_title': sub.feed.feed_title,

View file

@ -0,0 +1,124 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Deleting model 'StoriesPerMonth'
db.delete_table('rss_feeds_storiespermonth')
def backwards(self, orm):
# Adding model 'StoriesPerMonth'
db.create_table('rss_feeds_storiespermonth', (
('feed', self.gf('django.db.models.fields.related.ForeignKey')(related_name='stories_per_month', to=orm['rss_feeds.Feed'])),
('month', self.gf('django.db.models.fields.IntegerField')()),
('story_count', self.gf('django.db.models.fields.IntegerField')()),
('beginning_of_month', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)),
('year', self.gf('django.db.models.fields.IntegerField')()),
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
))
db.send_create_signal('rss_feeds', ['StoriesPerMonth'])
models = {
'rss_feeds.feed': {
'Meta': {'object_name': 'Feed', 'db_table': "'feeds'"},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
'average_stories_per_month': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'creation': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'days_to_trim': ('django.db.models.fields.IntegerField', [], {'default': '90'}),
'etag': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
'feed_address': ('django.db.models.fields.URLField', [], {'unique': 'True', 'max_length': '255'}),
'feed_link': ('django.db.models.fields.URLField', [], {'default': "''", 'max_length': '1000', 'null': 'True', 'blank': 'True'}),
'feed_tagline': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'null': 'True', 'blank': 'True'}),
'feed_title': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'null': 'True', 'blank': 'True'}),
'fetched_once': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_load_time': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'last_modified': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'last_update': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'min_to_decay': ('django.db.models.fields.IntegerField', [], {'default': '15'}),
'next_scheduled_update': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'num_subscribers': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'popular_authors': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'null': 'True', 'blank': 'True'}),
'popular_tags': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
'stories_last_month': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'stories_last_year': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'})
},
'rss_feeds.feedfetchhistory': {
'Meta': {'object_name': 'FeedFetchHistory'},
'exception': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'feed': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'feed_fetch_history'", 'to': "orm['rss_feeds.Feed']"}),
'fetch_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'message': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'status_code': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'})
},
'rss_feeds.feedpage': {
'Meta': {'object_name': 'FeedPage'},
'feed': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'feed_page'", 'unique': 'True', 'to': "orm['rss_feeds.Feed']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'page_data': ('utils.compressed_textfield.StoryField', [], {'null': 'True', 'blank': 'True'})
},
'rss_feeds.feedupdatehistory': {
'Meta': {'object_name': 'FeedUpdateHistory'},
'average_per_feed': ('django.db.models.fields.DecimalField', [], {'max_digits': '4', 'decimal_places': '1'}),
'fetch_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'number_of_feeds': ('django.db.models.fields.IntegerField', [], {}),
'seconds_taken': ('django.db.models.fields.IntegerField', [], {})
},
'rss_feeds.feedxml': {
'Meta': {'object_name': 'FeedXML'},
'feed': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'feed_xml'", 'unique': 'True', 'to': "orm['rss_feeds.Feed']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'rss_xml': ('utils.compressed_textfield.StoryField', [], {'null': 'True', 'blank': 'True'})
},
'rss_feeds.pagefetchhistory': {
'Meta': {'object_name': 'PageFetchHistory'},
'exception': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'feed': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'page_fetch_history'", 'to': "orm['rss_feeds.Feed']"}),
'fetch_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'message': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'status_code': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'})
},
'rss_feeds.story': {
'Meta': {'unique_together': "(('story_feed', 'story_guid_hash'),)", 'object_name': 'Story', 'db_table': "'stories'"},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'story_author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['rss_feeds.StoryAuthor']"}),
'story_author_name': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True', 'blank': 'True'}),
'story_content': ('utils.compressed_textfield.StoryField', [], {'null': 'True', 'blank': 'True'}),
'story_content_type': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'story_date': ('django.db.models.fields.DateTimeField', [], {}),
'story_feed': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'stories'", 'to': "orm['rss_feeds.Feed']"}),
'story_guid': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
'story_guid_hash': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
'story_original_content': ('utils.compressed_textfield.StoryField', [], {'null': 'True', 'blank': 'True'}),
'story_past_trim_date': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
'story_permalink': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
'story_tags': ('django.db.models.fields.CharField', [], {'max_length': '2000', 'null': 'True', 'blank': 'True'}),
'story_title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['rss_feeds.Tag']", 'symmetrical': 'False'})
},
'rss_feeds.storyauthor': {
'Meta': {'object_name': 'StoryAuthor'},
'author_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'feed': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['rss_feeds.Feed']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'rss_feeds.tag': {
'Meta': {'object_name': 'Tag'},
'feed': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['rss_feeds.Feed']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
}
}
complete_apps = ['rss_feeds']

View file

@ -0,0 +1,116 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import DataMigration
from django.db import models
class Migration(DataMigration):
def forwards(self, orm):
from apps.rss_feeds.models import Feed
for feed in Feed.objects.all():
feed.stories_last_year = None
feed.save()
feed.count_stories(verbose=True)
def backwards(self, orm):
"Write your backwards methods here."
models = {
'rss_feeds.feed': {
'Meta': {'object_name': 'Feed', 'db_table': "'feeds'"},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
'average_stories_per_month': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'creation': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'days_to_trim': ('django.db.models.fields.IntegerField', [], {'default': '90'}),
'etag': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
'feed_address': ('django.db.models.fields.URLField', [], {'unique': 'True', 'max_length': '255'}),
'feed_link': ('django.db.models.fields.URLField', [], {'default': "''", 'max_length': '1000', 'null': 'True', 'blank': 'True'}),
'feed_tagline': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'null': 'True', 'blank': 'True'}),
'feed_title': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'null': 'True', 'blank': 'True'}),
'fetched_once': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_load_time': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'last_modified': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'last_update': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'min_to_decay': ('django.db.models.fields.IntegerField', [], {'default': '15'}),
'next_scheduled_update': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'num_subscribers': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'popular_authors': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'null': 'True', 'blank': 'True'}),
'popular_tags': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
'stories_last_month': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'stories_last_year': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'})
},
'rss_feeds.feedfetchhistory': {
'Meta': {'object_name': 'FeedFetchHistory'},
'exception': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'feed': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'feed_fetch_history'", 'to': "orm['rss_feeds.Feed']"}),
'fetch_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'message': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'status_code': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'})
},
'rss_feeds.feedpage': {
'Meta': {'object_name': 'FeedPage'},
'feed': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'feed_page'", 'unique': 'True', 'to': "orm['rss_feeds.Feed']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'page_data': ('utils.compressed_textfield.StoryField', [], {'null': 'True', 'blank': 'True'})
},
'rss_feeds.feedupdatehistory': {
'Meta': {'object_name': 'FeedUpdateHistory'},
'average_per_feed': ('django.db.models.fields.DecimalField', [], {'max_digits': '4', 'decimal_places': '1'}),
'fetch_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'number_of_feeds': ('django.db.models.fields.IntegerField', [], {}),
'seconds_taken': ('django.db.models.fields.IntegerField', [], {})
},
'rss_feeds.feedxml': {
'Meta': {'object_name': 'FeedXML'},
'feed': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'feed_xml'", 'unique': 'True', 'to': "orm['rss_feeds.Feed']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'rss_xml': ('utils.compressed_textfield.StoryField', [], {'null': 'True', 'blank': 'True'})
},
'rss_feeds.pagefetchhistory': {
'Meta': {'object_name': 'PageFetchHistory'},
'exception': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'feed': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'page_fetch_history'", 'to': "orm['rss_feeds.Feed']"}),
'fetch_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'message': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'status_code': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'})
},
'rss_feeds.story': {
'Meta': {'unique_together': "(('story_feed', 'story_guid_hash'),)", 'object_name': 'Story', 'db_table': "'stories'"},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'story_author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['rss_feeds.StoryAuthor']"}),
'story_author_name': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True', 'blank': 'True'}),
'story_content': ('utils.compressed_textfield.StoryField', [], {'null': 'True', 'blank': 'True'}),
'story_content_type': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'story_date': ('django.db.models.fields.DateTimeField', [], {}),
'story_feed': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'stories'", 'to': "orm['rss_feeds.Feed']"}),
'story_guid': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
'story_guid_hash': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
'story_original_content': ('utils.compressed_textfield.StoryField', [], {'null': 'True', 'blank': 'True'}),
'story_past_trim_date': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
'story_permalink': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
'story_tags': ('django.db.models.fields.CharField', [], {'max_length': '2000', 'null': 'True', 'blank': 'True'}),
'story_title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['rss_feeds.Tag']", 'symmetrical': 'False'})
},
'rss_feeds.storyauthor': {
'Meta': {'object_name': 'StoryAuthor'},
'author_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'feed': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['rss_feeds.Feed']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'rss_feeds.tag': {
'Meta': {'object_name': 'Tag'},
'feed': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['rss_feeds.Feed']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
}
}
complete_apps = ['rss_feeds']

View file

@ -97,38 +97,79 @@ class Feed(models.Model):
'' if self.num_subscribers == 1 else 's',
self.feed_title,
),
def count_stories(self, verbose=False, lock=None):
month_ago = datetime.datetime.now() - datetime.timedelta(days=30)
stories_last_month = Story.objects.filter(story_feed=self, story_date__gte=month_ago).count()
self.stories_last_month = stories_last_month
# Save stories for this month count in granular StoriesPerMonth model
today = datetime.datetime.now()
beginning_of_month = datetime.datetime(today.year, today.month, 1)
stories_this_month = Story.objects.filter(story_feed=self,
story_date__gte=beginning_of_month).count()
stories_per_month, created = StoriesPerMonth.objects.get_or_create(
feed=self,
year=today.year,
month=today.month,
defaults={
'story_count': stories_this_month,
'beginning_of_month': beginning_of_month,
})
if not created:
stories_per_month.story_count = stories_this_month
stories_per_month.save()
stories_last_year, average_stories_per_month = StoriesPerMonth.past_year(self)
self.stories_last_year = json.encode(stories_last_year)
self.average_stories_per_month = average_stories_per_month
self.recount_feed(lock)
self.save(lock=lock)
if verbose:
print " ---> %s [%s]: %s stories" % (self.feed_title, self.pk, self.stories_last_month)
def recount_feed(self, lock=None):
"""
Fills in missing months between earlier occurances and now.
Save format: [('YYYY-MM, #), ...]
Example output: [(2010-12, 123), (2011-01, 146)]
"""
d = defaultdict(int)
now = datetime.datetime.now()
min_year = now.year
total = 0
month_count = 0
current_counts = self.stories_last_year and json.decode(self.stories_last_year)
if not current_counts:
current_counts = []
# Count stories, aggregate by year and month
stories = Story.objects.filter(story_feed=self).extra(select={
'year': "EXTRACT(year FROM story_date)",
'month': "EXTRACT(month from story_date)"
}).values('year', 'month')
for story in stories:
year = int(story['year'])
d['%s-%s' % (year, int(story['month']))] += 1
if year < min_year:
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.)
for current_month, current_count in current_counts:
if current_month not in d or d[current_month] < current_count:
d[current_month] = current_count
year = re.findall(r"(\d{4})-\d{1,2}", current_month)[0]
if year < min_year:
min_year = year
# Assemble a list with 0's filled in for missing months,
# trimming left and right 0's.
months = []
start = False
for year in range(min_year, now.year+1):
for month in range(1, 12+1):
if datetime.datetime(year, month, 1) < now:
key = '%s-%s' % (year, month)
if d.get(key) or start:
start = True
months.append((key, d.get(key, 0)))
total += d.get(key, 0)
month_count += 1
self.stories_last_year = json.encode(months)
if not total:
self.average_stories_per_month = 0
else:
self.average_stories_per_month = total / month_count
self.save(lock)
def last_updated(self):
return time.time() - time.mktime(self.last_update.timetuple())
@ -638,65 +679,3 @@ class PageFetchHistory(models.Model):
self.message,
self.exception[:50]
)
class StoriesPerMonth(models.Model):
feed = models.ForeignKey(Feed, related_name='stories_per_month')
year = models.IntegerField()
month = models.IntegerField()
story_count = models.IntegerField()
beginning_of_month = models.DateTimeField(default=datetime.datetime.now)
@classmethod
def past_year(cls, feed):
year_ago = datetime.datetime.now() - datetime.timedelta(days=365)
story_counts = StoriesPerMonth.objects.filter(
feed=feed,
beginning_of_month__gte=year_ago
).order_by('beginning_of_month')
month_counts = [m.story_count for m in story_counts]
average_per_month = sum(month_counts) / max(len(month_counts), 1)
return month_counts, average_per_month
@classmethod
def recount_feed(cls, feed, current_counts=None):
d = defaultdict(int)
now = datetime.datetime.now()
min_year = now.year
if not current_counts:
current_counts = []
# Count stories, aggregate by year and month
stories = Story.objects.filter(story_feed=feed).extra(select={
'year': "EXTRACT(year FROM story_date)",
'month': "EXTRACT(month from story_date)"
}).values('year', 'month')
for story in stories:
year = int(story['year'])
d['%s-%s' % (year, int(story['month']))] += 1
if year < min_year:
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.)
for current_month, current_count in current_counts:
if current_month not in d or d[current_month] < current_count:
d[current_month] = current_count
year = re.findall(r"(\d{4})-\d{1,2}", current_month)[0]
if year < min_year:
min_year = year
# Assemble a list with 0's filled in for missing months,
# trimming left and right 0's.
months = []
start = False
for year in range(min_year, now.year+1):
for month in range(1, 12+1):
if datetime.datetime(year, month, 1) < now:
key = '%s-%s' % (year, month)
if d.get(key) or start:
start = True
months.append((key, d.get(key, 0)))
from pprint import pprint
pprint(months)

View file

@ -2591,16 +2591,15 @@ background: transparent;
}
.NB-modal-statistics .NB-statistics-stat .NB-statistics-history-stat {
float: left;
width: 150px;
text-align: center;
margin: 0 0 12px 0;
margin: 0 24px;
overflow: hidden;
}
.NB-modal-statistics .NB-statistics-stat .NB-statistics-history-chart {
margin: -14px 0 0 0;
float: right;
width: 325px;
margin: 0px 24px;
width: 524px;
height: 180px;
}
.NB-modal-statistics .NB-modal-feed-chooser-container {
margin: 3px 0 12px;
@ -2612,3 +2611,17 @@ background: transparent;
.NB-modal-statistics .NB-modal-loading {
margin: 6px 8px 0;
}
.NB-modal-statistics .NB-statistics-subscribers {
float: right;
clear: both;
font-size: 11px;
text-transform: uppercase;
color: #d0d0d0;
text-shadow: 0 1px 0 #FFF;
}
.NB-modal-statistics .NB-statistics-subscribers-count {
color: #A8A8A8;
padding-right: 4px;
}

2119
media/js/jquery.flot.js Normal file

File diff suppressed because it is too large Load diff

View file

@ -3021,11 +3021,11 @@
});
$document.bind('keydown', 'j', function(e) {
e.preventDefault();
self.show_next_story(-1);
self.show_next_story(1);
});
$document.bind('keydown', 'k', function(e) {
e.preventDefault();
self.show_next_story(1);
self.show_next_story(-1);
});
$document.bind('keydown', 'left', function(e) {
e.preventDefault();

View file

@ -155,41 +155,41 @@ NEWSBLUR.ReaderStatistics.prototype = {
]),
$.make('div', { className: 'NB-statistics-stat NB-statistics-history'}, [
$.make('div', { className: 'NB-statistics-history-stat' }, [
$.make('div', { className: 'NB-statistics-count' }, ''+data['subscriber_count']),
$.make('div', { className: 'NB-statistics-label' }, 'subscribers'),
$.make('div', { className: 'NB-statistics-count' }, ''+data['average_stories_per_month']),
$.make('div', { className: 'NB-statistics-label' }, ' stories per month')
$.make('div', { className: 'NB-statistics-label' }, 'Stories per month')
]),
$.make('div', { id: 'NB-statistics-history-chart', className: 'NB-statistics-history-chart' })
])
]);
var $subscribers = $.make('div', { className: 'NB-statistics-subscribers' }, [
$.make('span', { className: 'NB-statistics-subscribers-count' }, ''+data['subscriber_count']),
$.make('span', { className: 'NB-statistics-subscribers-label' }, 'subscriber' + data['subscriber_count']==1?'':'s')
]);
$('.NB-statistics-subscribers', this.$modal).remove();
$('.NB-modal-subtitle', this.$modal).prepend($subscribers);
return $stats;
},
make_charts: function(data) {
var r = Raphael("NB-statistics-history-chart", 325, 170);
var lines = r.g.linechart(20, 20, 290, 200,
[[0, 2, 4, 6, 8, 10, 12],
[0, 2, 4, 6, 8, 10, 12]],
[[12, 12, 23, 15, 17, 27, 22],
[10, 20, 30, 25, 15, 28, 2]], {
nostroke: false,
axis: false,
symbol: "o",
smooth: true
}).hoverColumn(function () {
this.tags = r.set();
for (var i = 0, ii = this.y.length; i < ii; i++) {
this.tags.push(r.g.tag(this.x, this.y[i], this.values[i], 160, 10).insertBefore(this).attr([{fill: "#fff"}, {fill: this.symbols[i].attr("fill")}]));
}
}, function () {
this.tags && this.tags.remove();
data['stories_last_year'] = _.map(data['stories_last_year'], function(date) {
var date_matched = date[0].match(/(\d{4})-(\d{1,2})/);
return [(new Date(parseInt(date_matched[1], 10), parseInt(date_matched[2],10)-1)).getTime(),
date[1]];
});
lines.symbols.attr({r: 3});
// lines.lines[0].animate({"stroke-width": 6}, 1000);
// lines.symbols[0].attr({stroke: "#fff"});
// lines.symbols[0][1].animate({fill: "#f00"}, 1000);
var $plot = $(".NB-statistics-history-chart");
var plot = $.plot($plot,
[ { data: data['stories_last_year'], label: "Stories"} ], {
series: {
lines: { show: true },
points: { show: true }
},
average: data['average_stories_per_month'],
legend: { show: false },
grid: { hoverable: true, clickable: true },
yaxis: { tickDecimals: 0, min: 0 },
xaxis: { mode: 'time' }
});
},
handle_change: function(elem, e) {

View file

@ -112,9 +112,7 @@ COMPRESS_JS = {
'js/jquery.layout.js',
'js/jquery.tinysort.js',
'js/jquery.fieldselection.js',
'js/raphael-1.4.7.js',
'js/g.raphael.js',
'js/g.line.js',
'js/jquery.flot.js',
'js/underscore.js',
'js/newsblur/assetmodel.js',
'js/newsblur/reader.js',