diff --git a/app/templates/editor/campaign/campaign-analytics-modal.jade b/app/templates/editor/campaign/campaign-analytics-modal.jade
new file mode 100644
index 000000000..a19be9ac9
--- /dev/null
+++ b/app/templates/editor/campaign/campaign-analytics-modal.jade
@@ -0,0 +1,37 @@
+extends /templates/core/modal-base
+
+block modal-header-content
+  h3 Campaign Analytics
+  if campaignCompletions.startDay && campaignCompletions.endDay
+    .input-group.input-group-sm
+      input.form-control#input-startday(type='text', style='width:100px;', value=campaignCompletions.startDay)
+      input.form-control#input-endday(type='text', style='width:100px;', value=campaignCompletions.endDay)
+      button.btn.btn-default.btn-sm#reload-button(style='margin-left:10px;') Reload
+
+block modal-body-content
+  if campaignCompletions && campaignCompletions.levels
+    table.table.table-bordered.table-condensed.table-hover(style='font-size:10pt')
+      thead
+        tr
+          td Level
+          td Started
+          td Finished
+          td Playtime (s)
+          td Completion %
+      tbody
+        - for (var i = 0; i < campaignCompletions.levels.length; i++)
+          tr
+            td= campaignCompletions.levels[i].level
+            td= campaignCompletions.levels[i].started
+            td= campaignCompletions.levels[i].finished
+            td= campaignCompletions.levels[i].averagePlaytime
+            if campaignCompletions.top3.indexOf(campaignCompletions.levels[i].level) >= 0
+              td(style='background-color:lightblue;')= campaignCompletions.levels[i].completionRate
+            else if campaignCompletions.bottom3.indexOf(campaignCompletions.levels[i].level) >= 0
+              td(style='background-color:pink;')= campaignCompletions.levels[i].completionRate
+            else
+              td= campaignCompletions.levels[i].completionRate
+  else
+    div Loading...
+
+block modal-footer
\ No newline at end of file
diff --git a/app/templates/editor/campaign/campaign-editor-view.jade b/app/templates/editor/campaign/campaign-editor-view.jade
index 0e873ddf7..b97154825 100644
--- a/app/templates/editor/campaign/campaign-editor-view.jade
+++ b/app/templates/editor/campaign/campaign-editor-view.jade
@@ -16,6 +16,9 @@ block header
             span.glyphicon-home.glyphicon
 
       ul.nav.navbar-nav.navbar-right
+        li#analytics-button
+          a
+            span.glyphicon-stats.glyphicon
         if me.isAdmin()
           li#save-button
             a
@@ -40,43 +43,5 @@ block outer_content
     #right-column
       #campaign-view
       #campaign-level-view.hidden
-      if campaignCompletions
-        button.btn.btn-default#analytics-button(title="Analytics", data-toggle="modal" data-target="#analytics-modal") Analytics
-        .modal.fade#analytics-modal(tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true")
-          .modal-dialog.modal-lg
-            .modal-content
-              .modal-header
-                button.close(type="button", data-dismiss="modal", aria-label="Close")
-                  span(aria-hidden="true") &times;
-                h4.modal-title Analytics
-              .modal-body
-                if campaignCompletions.startDay
-                  if campaignCompletions.endDay
-                    div #{campaignCompletions.startDay} to #{campaignCompletions.endDay}
-                  else
-                    div #{campaignCompletions.startDay} to yesterday
-                table.table.table-bordered.table-condensed.table-hover(style='font-size:10pt')
-                  thead
-                    tr
-                      td Level
-                      td Started
-                      td Finished
-                      td Playtime (s)
-                      td Completion %
-                  tbody
-                    - for (var i = 0; i < campaignCompletions.levels.length; i++)
-                      tr
-                        td= campaignCompletions.levels[i].level
-                        td= campaignCompletions.levels[i].started
-                        td= campaignCompletions.levels[i].finished
-                        td= campaignCompletions.levels[i].averagePlaytime
-                        if campaignCompletions.top3.indexOf(campaignCompletions.levels[i].level) >= 0
-                          td(style='background-color:lightblue;')= campaignCompletions.levels[i].completionRate
-                        else if campaignCompletions.bottom3.indexOf(campaignCompletions.levels[i].level) >= 0
-                          td(style='background-color:pink;')= campaignCompletions.levels[i].completionRate
-                        else
-                          td= campaignCompletions.levels[i].completionRate
-      else
-        button.btn.btn-default.disabled#analytics-button Analytics Loading...
 
 block footer
diff --git a/app/views/editor/campaign/CampaignAnalyticsModal.coffee b/app/views/editor/campaign/CampaignAnalyticsModal.coffee
new file mode 100644
index 000000000..0b47bf6b5
--- /dev/null
+++ b/app/views/editor/campaign/CampaignAnalyticsModal.coffee
@@ -0,0 +1,101 @@
+template = require 'templates/editor/campaign/campaign-analytics-modal'
+utils = require 'core/utils'
+ModalView = require 'views/core/ModalView'
+
+# TODO: jquery-ui datepicker doesn't work well in this view
+# TODO: the date format handling is confusing (yyyy-mm-dd <=> yyyymmdd)
+
+module.exports = class CampaignAnalyticsModal extends ModalView
+  id: 'campaign-analytics-modal'
+  template: template
+  plain: true
+
+  events:
+    'click #reload-button': 'onClickReloadButton'
+
+  constructor: (options, @campaignHandle, @campaignCompletions) ->
+    super options
+    @getCampaignAnalytics() unless @campaignCompletions?.levels?
+
+  getRenderData: ->
+    c = super()
+    c.campaignCompletions = @campaignCompletions
+    c
+
+  afterRender: ->
+    super()
+    $("#input-startday").datepicker dateFormat: "yy-mm-dd"
+    $("#input-endday").datepicker dateFormat: "yy-mm-dd"
+
+  onClickReloadButton: () =>
+    startDay = $('#input-startday').val()
+    endDay = $('#input-endday').val()
+    delete @campaignCompletions.levels
+    @render()
+    @getCampaignAnalytics startDay, endDay
+
+  getCampaignAnalytics: (startDay, endDay) =>
+    # Fetch campaign analytics, unless dates given
+
+    startDay = startDay.replace(/-/g, '') if startDay?
+    endDay = endDay.replace(/-/g, '') if endDay?
+
+    startDay ?= utils.getUTCDay -14
+    endDay ?= utils.getUTCDay -1
+
+    success = (data) =>
+      return if @destroyed
+      mapFn = (item) ->
+        item.completionRate = (item.finished / item.started * 100).toFixed(2)
+        item
+      @campaignCompletions.levels = _.map data, mapFn, @
+      sortedLevels = _.cloneDeep @campaignCompletions.levels
+      sortedLevels = _.filter sortedLevels, ((a) -> a.finished >= 10), @
+      sortedLevels.sort (a, b) -> b.completionRate - a.completionRate
+      @campaignCompletions.top3 = _.pluck sortedLevels[0..2], 'level'
+      sortedLevels.sort (a, b) -> a.completionRate - b.completionRate
+      @campaignCompletions.bottom3 = _.pluck sortedLevels[0..2], 'level'
+      @campaignCompletions.startDay = "#{startDay[0..3]}-#{startDay[4..5]}-#{startDay[6..7]}"
+      @campaignCompletions.endDay = "#{endDay[0..3]}-#{endDay[4..5]}-#{endDay[6..7]}"
+      @getCampaignAveragePlaytimes startDay, endDay
+
+    # TODO: Why do we need this url dash?
+    request = @supermodel.addRequestResource 'campaign_completions', {
+      url: '/db/analytics_perday/-/campaign_completions'
+      data: {startDay: startDay, endDay: endDay, slug: @campaignHandle}
+      method: 'POST'
+      success: success
+    }, 0
+    request.load()
+
+  getCampaignAveragePlaytimes: (startDay, endDay) =>
+    # Fetch level average playtimes
+    success = (data) =>
+      return if @destroyed
+      levelAverages = {}
+      for item in data
+        levelAverages[item.level] ?= []
+        levelAverages[item.level].push item.average
+      for level in @campaignCompletions.levels
+        if levelAverages[level.level]
+          if levelAverages[level.level].length > 0
+            total = _.reduce levelAverages[level.level], ((sum, num) -> sum + num)
+            level.averagePlaytime = (total / levelAverages[level.level].length).toFixed(2)
+          else
+            level.averagePlaytime = 0.0
+      @render()
+
+    startDay ?= utils.getUTCDay -14
+    startDay = "#{startDay[0..3]}-#{startDay[4..5]}-#{startDay[6..7]}"
+    endDay ?= utils.getUTCDay -1
+    endDay = "#{endDay[0..3]}-#{endDay[4..5]}-#{endDay[6..7]}"
+
+    levelSlugs = _.pluck @campaignCompletions.levels, 'level'
+
+    request = @supermodel.addRequestResource 'playtime_averages', {
+      url: '/db/level/-/playtime_averages'
+      data: {startDay: startDay, endDay: endDay, slugs: levelSlugs}
+      method: 'POST'
+      success: success
+    }, 0
+    request.load()
diff --git a/app/views/editor/campaign/CampaignEditorView.coffee b/app/views/editor/campaign/CampaignEditorView.coffee
index 7f9fe1969..db3cb140e 100644
--- a/app/views/editor/campaign/CampaignEditorView.coffee
+++ b/app/views/editor/campaign/CampaignEditorView.coffee
@@ -7,9 +7,10 @@ CampaignView = require 'views/play/CampaignView'
 CocoCollection = require 'collections/CocoCollection'
 treemaExt = require 'core/treema-ext'
 utils = require 'core/utils'
-SaveCampaignModal = require './SaveCampaignModal'
 RelatedAchievementsCollection = require 'collections/RelatedAchievementsCollection'
+CampaignAnalyticsModal = require './CampaignAnalyticsModal'
 CampaignLevelView = require './CampaignLevelView'
+SaveCampaignModal = require './SaveCampaignModal'
 
 achievementProject = ['related', 'rewards', 'name', 'slug']
 thangTypeProject = ['name', 'original']
@@ -20,6 +21,7 @@ module.exports = class CampaignEditorView extends RootView
   className: 'editor'
 
   events:
+    'click #analytics-button': 'onClickAnalyticsButton'
     'click #save-button': 'onClickSaveButton'
 
   constructor: (options, @campaignHandle) ->
@@ -27,6 +29,9 @@ module.exports = class CampaignEditorView extends RootView
     @campaign = new Campaign({_id:@campaignHandle})
     @supermodel.loadModel(@campaign, 'campaign')
 
+    # Save reference to data used by anlytics modal so it persists across modal open/closes.
+    @campaignAnalytics = {}
+
     @levels = new CocoCollection([], {
       model: Level
       url: "/db/campaign/#{@campaignHandle}/levels"
@@ -47,8 +52,6 @@ module.exports = class CampaignEditorView extends RootView
     @listenToOnce @levels, 'sync', @onFundamentalLoaded
     @listenToOnce @achievements, 'sync', @onFundamentalLoaded
 
-    _.delay @getCampaignAnalytics, 500
-
   loadThangTypeNames: ->
     # Load the names of the ThangTypes that this level's Treema nodes might want to display.
     originals = []
@@ -132,9 +135,11 @@ module.exports = class CampaignEditorView extends RootView
   getRenderData: ->
     c = super()
     c.campaign = @campaign
-    c.campaignCompletions = @campaignCompletions
     c
 
+  onClickAnalyticsButton: ->
+    @openModalView new CampaignAnalyticsModal {}, @campaignHandle, @campaignAnalytics
+
   onClickSaveButton: ->
     @toSave.set @toSave.filter (m) -> m.hasLocalChanges()
     @openModalView new SaveCampaignModal({}, @toSave)
@@ -240,65 +245,6 @@ module.exports = class CampaignEditorView extends RootView
       if achievement.hasLocalChanges()
         @toSave.add achievement
 
-  getCampaignAnalytics: =>
-    # Fetch last 14 days of campaign analytics
-
-    startDay = utils.getUTCDay -14
-    endDay = utils.getUTCDay -1
-
-    success = (data) =>
-      return if @destroyed
-      mapFn = (item) ->
-        item.completionRate = (item.finished / item.started * 100).toFixed(2)
-        item
-      @campaignCompletions = levels:  _.map data, mapFn, @
-      sortedLevels = _.cloneDeep @campaignCompletions.levels
-      sortedLevels = _.filter sortedLevels, ((a) -> a.completionRate > 1.0), @
-      sortedLevels.sort (a, b) -> b.completionRate - a.completionRate
-      @campaignCompletions.top3 = _.pluck sortedLevels[0..2], 'level'
-      sortedLevels.sort (a, b) -> a.completionRate - b.completionRate
-      @campaignCompletions.bottom3 = _.pluck sortedLevels[0..2], 'level'
-      @campaignCompletions.startDay = "#{startDay[0..3]}-#{startDay[4..5]}-#{startDay[6..7]}"
-      @campaignCompletions.endDay = "#{endDay[0..3]}-#{endDay[4..5]}-#{endDay[6..7]}"
-      @getCampaignAveragePlaytimes()
-
-    # TODO: Why do we need this url dash?
-    request = @supermodel.addRequestResource 'campaign_completions', {
-      url: '/db/analytics_perday/-/campaign_completions'
-      data: {startDay: startDay, slug: @campaignHandle}
-      method: 'POST'
-      success: success
-    }, 0
-    request.load()
-
-  getCampaignAveragePlaytimes: =>
-    # Fetch last 14 days of level average playtimes
-    success = (data) =>
-      return if @destroyed
-      levelAverages = {}
-      for item in data
-        levelAverages[item.level] ?= []
-        levelAverages[item.level].push item.average
-      for level in @campaignCompletions.levels
-        if levelAverages[level.level]
-          if levelAverages[level.level].length > 0
-            total = _.reduce levelAverages[level.level], ((sum, num) -> sum + num)
-            level.averagePlaytime = (total / levelAverages[level.level].length).toFixed(2)
-          else
-            level.averagePlaytime = 0.0
-      @render()
-
-    levelSlugs = _.pluck @campaignCompletions.levels, 'level'
-    startDay = utils.getUTCDay -14
-    startDay = "#{startDay[0..3]}-#{startDay[4..5]}-#{startDay[6..7]}"
-    request = @supermodel.addRequestResource 'playtime_averages', {
-      url: '/db/level/-/playtime_averages'
-      data: {startDay: startDay, slugs: levelSlugs}
-      method: 'POST'
-      success: success
-    }, 0
-    request.load()
-
 
 class LevelsNode extends TreemaObjectNode
   valueClass: 'treema-levels'