mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-25 08:38:09 -05:00
192 lines
5.7 KiB
CoffeeScript
192 lines
5.7 KiB
CoffeeScript
RootView = require 'views/kinds/RootView'
|
|
template = require 'templates/admin/growth'
|
|
RealTimeCollection = require 'collections/RealTimeCollection'
|
|
|
|
# Growth View ###################
|
|
#
|
|
# Display interesting growth data.
|
|
#
|
|
# Currently shows:
|
|
# Registered user totals and added, per-day and per-month
|
|
# 7-day moving average for registered users added per-day
|
|
#
|
|
# TODO: @padding isn't applied correctly
|
|
# TODO: aggregate recent data if missing?
|
|
#
|
|
|
|
module.exports = class GrowthView extends RootView
|
|
id: 'admin-growth-view'
|
|
template: template
|
|
height: 300
|
|
width: 1000
|
|
xAxisGuideHeight: 80
|
|
yAxisGuideWidth: 60
|
|
padding: 10
|
|
|
|
constructor: (options) ->
|
|
super options
|
|
@usersPerMonth = new RealTimeCollection 'growth/users/registered/per-month'
|
|
@usersPerMonth.on 'add', @refreshData
|
|
@usersPerDay = new RealTimeCollection 'growth/users/registered/per-day'
|
|
@usersPerDay.on 'add', @refreshData
|
|
|
|
destroy: ->
|
|
@usersPerMonth.off 'add', @refreshData
|
|
@usersPerDay.off 'add', @refreshData
|
|
|
|
refreshData: =>
|
|
@render()
|
|
|
|
getRenderData: ->
|
|
c = super()
|
|
c.crunchingData = @usersPerMonth.length is 0 and @usersPerDay.length is 0
|
|
c.usersPerDay = []
|
|
# @usersPerDay.each (item) ->
|
|
# c.usersPerDay.push date: item.get('id'), added: item.get('added'), total: item.get('total')
|
|
c.usersPerMonth = []
|
|
# @usersPerMonth.each (item) ->
|
|
# c.usersPerMonth.push date: item.get('id'), added: item.get('added'), total: item.get('total')
|
|
c
|
|
|
|
afterRender: ->
|
|
super()
|
|
if me.isAdmin()
|
|
@createPerDayChart()
|
|
@createPerMonthChart()
|
|
|
|
createPerDayChart: ->
|
|
addedData = []
|
|
totalData = []
|
|
@usersPerDay.each (item) ->
|
|
addedData.push id: item.get('id'), value: item.get('added')
|
|
totalData.push id: item.get('id'), value: item.get('total')
|
|
@createLineChart ".perDayTotal", totalData, 1000
|
|
@createLineChart ".perDayAdded", addedData, 10, true
|
|
|
|
createPerMonthChart: ->
|
|
addedData = []
|
|
totalData = []
|
|
@usersPerMonth.each (item) ->
|
|
addedData.push id: item.get('id'), value: item.get('added')
|
|
totalData.push id: item.get('id'), value: item.get('total')
|
|
@createLineChart ".perMonthTotal", totalData, 1000
|
|
@createLineChart ".perMonthAdded", addedData, 1000
|
|
|
|
createLineChart: (selector, data, guidelineSpacing, sevenDayAverage=false) ->
|
|
return unless data.length > 1
|
|
|
|
minVal = d3.min(data, (d) -> d.value)
|
|
maxVal = d3.max(data, (d) -> d.value)
|
|
|
|
widthSpacing = (@width - @yAxisGuideWidth - @padding) / (data.length - 1)
|
|
|
|
y = d3.scale.linear()
|
|
.domain([minVal, maxVal])
|
|
.range([@height - @xAxisGuideHeight - 2 * @padding, 0])
|
|
|
|
points = []
|
|
for i in [0...data.length]
|
|
points.push id: data[i].id, x: i * widthSpacing + @yAxisGuideWidth, y: y(data[i].value) + @padding
|
|
|
|
links = []
|
|
for i in [0...points.length - 1]
|
|
if points[i] and points[i + 1]
|
|
links.push start: points[i], end: points[i + 1]
|
|
|
|
guidelines = []
|
|
diff = maxVal - minVal
|
|
interval = Math.floor(diff / 5)
|
|
for i in [0..4]
|
|
yVal = i * interval + minVal
|
|
yVal = Math.floor(yVal / guidelineSpacing) * guidelineSpacing
|
|
guidelines.push start: {id: yVal, x: 0, y: y(yVal)}, end: {id: yVal, x: @width, y: y(yVal)}
|
|
|
|
sevenPoints = []
|
|
sevenLinks = []
|
|
if sevenDayAverage
|
|
sevenTotal = 0
|
|
for i in [0...data.length]
|
|
sevenTotal += data[i].value
|
|
if i > 5
|
|
sevenAvg = sevenTotal / 7
|
|
sevenPoints.push x: i * widthSpacing + @yAxisGuideWidth, y: y(sevenAvg) + @padding
|
|
if i > 6
|
|
sevenTotal -= data[i - 7].value
|
|
for i in [0...sevenPoints.length - 1]
|
|
if sevenPoints[i] and sevenPoints[i + 1]
|
|
sevenLinks.push start: sevenPoints[i], end: sevenPoints[i + 1]
|
|
|
|
chart = d3.select(selector)
|
|
.attr("width", @width)
|
|
.attr("height", @height)
|
|
|
|
chart.selectAll(".circle")
|
|
.data(points)
|
|
.enter()
|
|
.append("circle")
|
|
.attr("cx", (d) -> d.x )
|
|
.attr("cy", (d) -> d.y )
|
|
.attr("r", "2px")
|
|
.attr("fill", "black")
|
|
|
|
chart.selectAll(".text")
|
|
.data(points)
|
|
.enter()
|
|
.append("text")
|
|
.attr("dy", ".35em")
|
|
.attr("transform", (d, i) => "translate(" + d.x + "," + @height + ") rotate(270)")
|
|
.text((d) ->
|
|
if d.id.length is 8
|
|
return "#{parseInt(d.id[4..5])}/#{parseInt(d.id[6..7])}/#{d.id[0..3]}"
|
|
else
|
|
return "#{parseInt(d.id[4..5])}/#{d.id[0..3]}"
|
|
)
|
|
|
|
chart.selectAll('.line')
|
|
.data(links)
|
|
.enter()
|
|
.append("line")
|
|
.attr("x1", (d) -> d.start.x )
|
|
.attr("y1", (d) -> d.start.y )
|
|
.attr("x2", (d) -> d.end.x )
|
|
.attr("y2", (d) -> d.end.y )
|
|
.style("stroke", "rgb(6,120,155)")
|
|
|
|
chart.selectAll(".circle")
|
|
.data(sevenPoints)
|
|
.enter()
|
|
.append("circle")
|
|
.attr("cx", (d) -> d.x )
|
|
.attr("cy", (d) -> d.y )
|
|
.attr("r", "2px")
|
|
.attr("fill", "purple")
|
|
|
|
chart.selectAll('.line')
|
|
.data(sevenLinks)
|
|
.enter()
|
|
.append("line")
|
|
.attr("x1", (d) -> d.start.x )
|
|
.attr("y1", (d) -> d.start.y )
|
|
.attr("x2", (d) -> d.end.x )
|
|
.attr("y2", (d) -> d.end.y )
|
|
.style("stroke", "rgb(200,0,0)")
|
|
|
|
chart.selectAll('.line')
|
|
.data(guidelines)
|
|
.enter()
|
|
.append("line")
|
|
.attr("x1", (d) -> d.start.x )
|
|
.attr("y1", (d) -> d.start.y )
|
|
.attr("x2", (d) -> d.end.x )
|
|
.attr("y2", (d) -> d.end.y )
|
|
.style("stroke", "rgb(140,140,140)")
|
|
|
|
chart.selectAll(".text")
|
|
.data(guidelines)
|
|
.enter()
|
|
.append("text")
|
|
.attr("x", (d) -> d.start.x)
|
|
.attr("y", (d) -> d.start.y - 6)
|
|
.attr("dy", ".35em")
|
|
.text((d) -> d.start.id)
|
|
|