mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-02-17 08:50:58 -05:00
Add line charts to admin analytics dashboard
https://app.asana.com/0/54276215890539/64369256136957
This commit is contained in:
parent
5e4fccf775
commit
e33323e7eb
4 changed files with 461 additions and 78 deletions
127
app/core/d3_utils.coffee
Normal file
127
app/core/d3_utils.coffee
Normal file
|
@ -0,0 +1,127 @@
|
|||
# Caller needs require 'vendor/d3'
|
||||
|
||||
module.exports.createContiguousDays = (timeframeDays) ->
|
||||
# Return list of last 'timeframeDays' contiguous days in yyyy-mm-dd format
|
||||
days = []
|
||||
currentDate = new Date()
|
||||
currentDate.setUTCDate(currentDate.getUTCDate() - timeframeDays)
|
||||
for i in [0..timeframeDays]
|
||||
currentDay = currentDate.toISOString().substr(0, 10)
|
||||
days.push(currentDay)
|
||||
currentDate.setUTCDate(currentDate.getUTCDate() + 1)
|
||||
days
|
||||
|
||||
module.exports.createLineChart = (containerSelector, chartLines) ->
|
||||
# Creates a line chart within 'containerSelector' based on chartLines
|
||||
return unless chartLines?.length > 0 and containerSelector
|
||||
|
||||
margin = 20
|
||||
keyHeight = 20
|
||||
xAxisHeight = 20
|
||||
yAxisWidth = 40
|
||||
containerWidth = $(containerSelector).width()
|
||||
containerHeight = $(containerSelector).height()
|
||||
|
||||
yScaleCount = 0
|
||||
yScaleCount++ for line in chartLines when line.showYScale
|
||||
svg = d3.select(containerSelector).append("svg")
|
||||
.attr("width", containerWidth)
|
||||
.attr("height", containerHeight)
|
||||
width = containerWidth - margin * 2 - yAxisWidth * yScaleCount
|
||||
height = containerHeight - margin * 2 - xAxisHeight - keyHeight * chartLines.length
|
||||
currentLine = 0
|
||||
currentYScale = 0
|
||||
|
||||
# Horizontal guidelines
|
||||
marks = (Math.round(i * height / 5) for i in [1..5])
|
||||
yRange = d3.scale.linear().range([height, 0]).domain([0, height])
|
||||
svg.selectAll(".line")
|
||||
.data(marks)
|
||||
.enter()
|
||||
.append("line")
|
||||
.attr("x1", margin + yAxisWidth * yScaleCount)
|
||||
.attr("y1", (d) -> margin + yRange(d))
|
||||
.attr("x2", margin + yAxisWidth * yScaleCount + width)
|
||||
.attr("y2", (d) -> margin + yRange(d))
|
||||
.attr("stroke", 'gray')
|
||||
.style("opacity", "0.3")
|
||||
|
||||
for line in chartLines
|
||||
# continue unless line.enabled
|
||||
xRange = d3.scale.linear().range([0, width]).domain([d3.min(line.points, (d) -> d.x), d3.max(line.points, (d) -> d.x)])
|
||||
yRange = d3.scale.linear().range([height, 0]).domain([line.min, line.max])
|
||||
|
||||
# x-Axis
|
||||
if currentLine is 0
|
||||
startDay = new Date(line.points[0].day)
|
||||
endDay = new Date(line.points[line.points.length - 1].day)
|
||||
xAxisRange = d3.time.scale()
|
||||
.domain([startDay, endDay])
|
||||
.range([0, width])
|
||||
xAxis = d3.svg.axis()
|
||||
.scale(xAxisRange)
|
||||
svg.append("g")
|
||||
.attr("class", "x axis")
|
||||
.call(xAxis)
|
||||
.selectAll("text")
|
||||
.attr("dy", ".35em")
|
||||
.attr("transform", "translate(" + (margin + yAxisWidth) + "," + (height + margin) + ")")
|
||||
.style("text-anchor", "start")
|
||||
|
||||
if line.showYScale
|
||||
# y-Axis
|
||||
yAxisRange = d3.scale.linear().range([height, 0]).domain([line.min, line.max])
|
||||
yAxis = d3.svg.axis()
|
||||
.scale(yRange)
|
||||
.orient("left")
|
||||
svg.append("g")
|
||||
.attr("class", "y axis")
|
||||
.attr("transform", "translate(" + (margin + yAxisWidth * currentYScale) + "," + margin + ")")
|
||||
.style("color", line.lineColor)
|
||||
.call(yAxis)
|
||||
.selectAll("text")
|
||||
.attr("y", 0)
|
||||
.attr("x", 0)
|
||||
.attr("fill", line.lineColor)
|
||||
.style("text-anchor", "start")
|
||||
currentYScale++
|
||||
|
||||
# Key
|
||||
svg.append("line")
|
||||
.attr("x1", margin)
|
||||
.attr("y1", margin + height + xAxisHeight + keyHeight * currentLine + keyHeight / 2)
|
||||
.attr("x2", margin + 40)
|
||||
.attr("y2", margin + height + xAxisHeight + keyHeight * currentLine + keyHeight / 2)
|
||||
.attr("stroke", line.lineColor)
|
||||
.attr("class", "key-line")
|
||||
svg.append("text")
|
||||
.attr("x", margin + 40 + 10)
|
||||
.attr("y", margin + height + xAxisHeight + keyHeight * currentLine + (keyHeight + 10) / 2)
|
||||
.attr("fill", line.lineColor)
|
||||
.attr("class", "key-text")
|
||||
.text(line.description)
|
||||
|
||||
# Path and points
|
||||
svg.selectAll(".circle")
|
||||
.data(line.points)
|
||||
.enter()
|
||||
.append("circle")
|
||||
.attr("transform", "translate(" + (margin + yAxisWidth * yScaleCount) + "," + margin + ")")
|
||||
.attr("cx", (d) -> xRange(d.x))
|
||||
.attr("cy", (d) -> yRange(d.y))
|
||||
.attr("r", 2)
|
||||
.attr("fill", line.lineColor)
|
||||
.attr("stroke-width", 1)
|
||||
.attr("class", "graph-point")
|
||||
.attr("data-pointid", (d) -> "#{line.lineID}#{d.x}")
|
||||
d3line = d3.svg.line()
|
||||
.x((d) -> xRange(d.x))
|
||||
.y((d) -> yRange(d.y))
|
||||
.interpolate("linear")
|
||||
svg.append("path")
|
||||
.attr("d", d3line(line.points))
|
||||
.attr("transform", "translate(" + (margin + yAxisWidth * yScaleCount) + "," + margin + ")")
|
||||
.style("stroke-width", line.strokeWidth)
|
||||
.style("stroke", line.lineColor)
|
||||
.style("fill", "none")
|
||||
currentLine++
|
|
@ -14,3 +14,15 @@
|
|||
font-size: 70pt
|
||||
.description
|
||||
font-size: 8pt
|
||||
|
||||
.line-chart-container
|
||||
height: 500px
|
||||
width: 100%
|
||||
.x.axis
|
||||
font-size: 9pt
|
||||
path
|
||||
display: none
|
||||
.y.axis
|
||||
font-size: 9pt
|
||||
path
|
||||
display: none
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
extends /templates/base
|
||||
|
||||
block content
|
||||
|
||||
|
||||
//- NOTE: do not localize / i18n
|
||||
|
||||
if me.isAdmin()
|
||||
.container-fluid
|
||||
.row
|
||||
|
@ -18,6 +20,21 @@ block content
|
|||
div.description 30-day Active Users
|
||||
div.count= activeUsers[0].monthlyCount
|
||||
|
||||
h3 KPI 60 days
|
||||
.kpi-recent-chart.line-chart-container
|
||||
|
||||
h3 KPI 300 days
|
||||
.kpi-chart.line-chart-container
|
||||
|
||||
h3 Active Classes 90 days
|
||||
.active-classes-chart.line-chart-container
|
||||
|
||||
h3 Recurring Revenue 90 days
|
||||
.recurring-revenue-chart.line-chart-container
|
||||
|
||||
h3 Active Users 90 days
|
||||
.active-users-chart.line-chart-container
|
||||
|
||||
h1 Active Classes
|
||||
table.table.table-striped.table-condensed
|
||||
tr
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
require 'vendor/d3'
|
||||
d3Utils = require 'core/d3_utils'
|
||||
RootView = require 'views/core/RootView'
|
||||
template = require 'templates/admin/analytics'
|
||||
utils = require 'core/utils'
|
||||
|
@ -5,86 +7,11 @@ utils = require 'core/utils'
|
|||
module.exports = class AnalyticsView extends RootView
|
||||
id: 'admin-analytics-view'
|
||||
template: template
|
||||
lineColors: ['red', 'blue', 'green', 'purple', 'goldenrod', 'brown', 'darkcyan']
|
||||
|
||||
constructor: (options) ->
|
||||
super options
|
||||
|
||||
@supermodel.addRequestResource('active_classes', {
|
||||
url: '/db/analytics_perday/-/active_classes'
|
||||
method: 'POST'
|
||||
success: (data) =>
|
||||
@activeClassGroups = {}
|
||||
dayEventsMap = {}
|
||||
for activeClass in data
|
||||
dayEventsMap[activeClass.day] ?= {}
|
||||
dayEventsMap[activeClass.day]['Total'] = 0
|
||||
for event, val of activeClass.classes
|
||||
@activeClassGroups[event] = true
|
||||
dayEventsMap[activeClass.day][event] = val
|
||||
dayEventsMap[activeClass.day]['Total'] += val
|
||||
@activeClassGroups = Object.keys(@activeClassGroups)
|
||||
@activeClassGroups.push 'Total'
|
||||
for day of dayEventsMap
|
||||
for event in @activeClassGroups
|
||||
dayEventsMap[day][event] ?= 0
|
||||
@activeClasses = []
|
||||
for day of dayEventsMap
|
||||
data = day: day, groups: []
|
||||
for group in @activeClassGroups
|
||||
data.groups.push(dayEventsMap[day][group] ? 0)
|
||||
@activeClasses.push data
|
||||
@activeClasses.sort (a, b) -> b.day.localeCompare(a.day)
|
||||
@render?()
|
||||
}, 0).load()
|
||||
|
||||
@supermodel.addRequestResource('active_users', {
|
||||
url: '/db/analytics_perday/-/active_users'
|
||||
method: 'POST'
|
||||
success: (data) =>
|
||||
@activeUsers = data
|
||||
@activeUsers.sort (a, b) -> b.day.localeCompare(a.day)
|
||||
@render?()
|
||||
}, 0).load()
|
||||
|
||||
@supermodel.addRequestResource('recurring_revenue', {
|
||||
url: '/db/analytics_perday/-/recurring_revenue'
|
||||
method: 'POST'
|
||||
success: (data) =>
|
||||
@revenueGroups = {}
|
||||
dayGroupCountMap = {}
|
||||
for dailyRevenue in data
|
||||
dayGroupCountMap[dailyRevenue.day] ?= {}
|
||||
dayGroupCountMap[dailyRevenue.day]['Daily'] = 0
|
||||
for group, val of dailyRevenue.groups
|
||||
@revenueGroups[group] = true
|
||||
dayGroupCountMap[dailyRevenue.day][group] = val
|
||||
dayGroupCountMap[dailyRevenue.day]['Daily'] += val
|
||||
@revenueGroups = Object.keys(@revenueGroups)
|
||||
@revenueGroups.push 'Daily'
|
||||
@revenueGroups.push 'Monthly'
|
||||
for day of dayGroupCountMap
|
||||
for group in @revenueGroups
|
||||
dayGroupCountMap[day][group] ?= 0
|
||||
@revenue = []
|
||||
for day of dayGroupCountMap
|
||||
data = day: day, groups: []
|
||||
for group in @revenueGroups
|
||||
data.groups.push(dayGroupCountMap[day][group] ? 0)
|
||||
@revenue.push data
|
||||
@revenue.sort (a, b) -> b.day.localeCompare(a.day)
|
||||
monthlyValues = []
|
||||
|
||||
return unless @revenue.length > 0
|
||||
|
||||
for i in [@revenue.length-1..0]
|
||||
dailyTotal = @revenue[i].groups[@revenue[i].groups.length - 2]
|
||||
monthlyValues.push(dailyTotal)
|
||||
monthlyValues.shift() if monthlyValues.length > 30
|
||||
if monthlyValues.length is 30
|
||||
monthlyIndex = @revenue[i].groups.length - 1
|
||||
@revenue[i].groups[monthlyIndex] = _.reduce(monthlyValues, (s, num) -> s + num)
|
||||
@render?()
|
||||
}, 0).load()
|
||||
@loadData()
|
||||
|
||||
getRenderData: ->
|
||||
context = super()
|
||||
|
@ -94,3 +21,303 @@ module.exports = class AnalyticsView extends RootView
|
|||
context.revenue = @revenue ? []
|
||||
context.revenueGroups = @revenueGroups ? {}
|
||||
context
|
||||
|
||||
afterRender: ->
|
||||
super()
|
||||
@createLineCharts()
|
||||
|
||||
loadData: ->
|
||||
@supermodel.addRequestResource('active_classes', {
|
||||
url: '/db/analytics_perday/-/active_classes'
|
||||
method: 'POST'
|
||||
success: (data) =>
|
||||
# Organize data by day, then group
|
||||
groupMap = {}
|
||||
dayGroupMap = {}
|
||||
for activeClass in data
|
||||
dayGroupMap[activeClass.day] ?= {}
|
||||
dayGroupMap[activeClass.day]['Total'] = 0
|
||||
for group, val of activeClass.classes
|
||||
groupMap[group] = true
|
||||
dayGroupMap[activeClass.day][group] = val
|
||||
dayGroupMap[activeClass.day]['Total'] += val
|
||||
@activeClassGroups = Object.keys(groupMap)
|
||||
@activeClassGroups.push 'Total'
|
||||
# Build list of active classes, where each entry is a day of individual group values
|
||||
@activeClasses = []
|
||||
for day of dayGroupMap
|
||||
dashedDay = "#{day.substring(0, 4)}-#{day.substring(4, 6)}-#{day.substring(6, 8)}"
|
||||
data = day: dashedDay, groups: []
|
||||
for group in @activeClassGroups
|
||||
data.groups.push(dayGroupMap[day][group] ? 0)
|
||||
@activeClasses.push data
|
||||
@activeClasses.sort (a, b) -> b.day.localeCompare(a.day)
|
||||
|
||||
@updateAllKPIChartData()
|
||||
@updateActiveClassesChartData()
|
||||
@render?()
|
||||
}, 0).load()
|
||||
|
||||
@supermodel.addRequestResource('active_users', {
|
||||
url: '/db/analytics_perday/-/active_users'
|
||||
method: 'POST'
|
||||
success: (data) =>
|
||||
@activeUsers = data.map (a) ->
|
||||
a.day = "#{a.day.substring(0, 4)}-#{a.day.substring(4, 6)}-#{a.day.substring(6, 8)}"
|
||||
a
|
||||
@activeUsers.sort (a, b) -> b.day.localeCompare(a.day)
|
||||
|
||||
@updateAllKPIChartData()
|
||||
@updateActiveUsersChartData()
|
||||
@render?()
|
||||
}, 0).load()
|
||||
|
||||
@supermodel.addRequestResource('recurring_revenue', {
|
||||
url: '/db/analytics_perday/-/recurring_revenue'
|
||||
method: 'POST'
|
||||
success: (data) =>
|
||||
# Organize data by day, then group
|
||||
groupMap = {}
|
||||
dayGroupCountMap = {}
|
||||
for dailyRevenue in data
|
||||
dayGroupCountMap[dailyRevenue.day] ?= {}
|
||||
dayGroupCountMap[dailyRevenue.day]['Daily'] = 0
|
||||
for group, val of dailyRevenue.groups
|
||||
groupMap[group] = true
|
||||
dayGroupCountMap[dailyRevenue.day][group] = val
|
||||
dayGroupCountMap[dailyRevenue.day]['Daily'] += val
|
||||
@revenueGroups = Object.keys(groupMap)
|
||||
@revenueGroups.push 'Daily'
|
||||
# Build list of recurring revenue entries, where each entry is a day of individual group values
|
||||
@revenue = []
|
||||
for day of dayGroupCountMap
|
||||
dashedDay = "#{day.substring(0, 4)}-#{day.substring(4, 6)}-#{day.substring(6, 8)}"
|
||||
data = day: dashedDay, groups: []
|
||||
for group in @revenueGroups
|
||||
data.groups.push(dayGroupCountMap[day][group] ? 0)
|
||||
@revenue.push data
|
||||
@revenue.sort (a, b) -> b.day.localeCompare(a.day)
|
||||
|
||||
return unless @revenue.length > 0
|
||||
|
||||
# Add monthly recurring revenue values
|
||||
@revenueGroups.push 'Monthly'
|
||||
monthlyValues = []
|
||||
for i in [@revenue.length-1..0]
|
||||
dailyTotal = @revenue[i].groups[@revenue[i].groups.length - 1]
|
||||
monthlyValues.push(dailyTotal)
|
||||
monthlyValues.shift() while monthlyValues.length > 30
|
||||
if monthlyValues.length is 30
|
||||
@revenue[i].groups.push(_.reduce(monthlyValues, (s, num) -> s + num))
|
||||
|
||||
@updateAllKPIChartData()
|
||||
@updateRevenueChartData()
|
||||
@render?()
|
||||
}, 0).load()
|
||||
|
||||
createLineChartPoints: (days, data) ->
|
||||
points = []
|
||||
for entry, i in data
|
||||
points.push
|
||||
x: i
|
||||
y: entry.value
|
||||
day: entry.day
|
||||
|
||||
# Ensure points for each day
|
||||
for day, i in days
|
||||
if points.length <= i or points[i].day isnt day
|
||||
prevY = if i > 0 then points[i - 1].y else 0.0
|
||||
points.splice i, 0,
|
||||
y: prevY
|
||||
day: day
|
||||
points[i].x = i
|
||||
|
||||
points.splice(0, points.length - days.length) if points.length > days.length
|
||||
points
|
||||
|
||||
createLineCharts: ->
|
||||
d3Utils.createLineChart('.kpi-recent-chart', @kpiRecentChartLines)
|
||||
d3Utils.createLineChart('.kpi-chart', @kpiChartLines)
|
||||
d3Utils.createLineChart('.active-classes-chart', @activeClassesChartLines)
|
||||
d3Utils.createLineChart('.active-users-chart', @activeUsersChartLines)
|
||||
d3Utils.createLineChart('.recurring-revenue-chart', @revenueChartLines)
|
||||
|
||||
updateAllKPIChartData: ->
|
||||
@kpiRecentChartLines = []
|
||||
@kpiChartLines = []
|
||||
@updateKPIChartData(60, @kpiRecentChartLines)
|
||||
@updateKPIChartData(300, @kpiChartLines)
|
||||
|
||||
updateKPIChartData: (timeframeDays, chartLines) ->
|
||||
days = d3Utils.createContiguousDays(timeframeDays)
|
||||
|
||||
if @activeClasses?.length > 0
|
||||
data = []
|
||||
for entry in @activeClasses
|
||||
data.push
|
||||
day: entry.day
|
||||
value: entry.groups[entry.groups.length - 1]
|
||||
data.reverse()
|
||||
points = @createLineChartPoints(days, data)
|
||||
chartLines.push
|
||||
points: points
|
||||
description: '30-day Active Classes'
|
||||
lineColor: 'blue'
|
||||
strokeWidth: 1
|
||||
min: 0
|
||||
max: _.max(points, 'y').y
|
||||
showYScale: true
|
||||
|
||||
if @revenue?.length > 0
|
||||
data = []
|
||||
for entry in @revenue
|
||||
data.push
|
||||
day: entry.day
|
||||
value: entry.groups[entry.groups.length - 1] / 100000
|
||||
data.reverse()
|
||||
points = @createLineChartPoints(days, data)
|
||||
chartLines.push
|
||||
points: points
|
||||
description: '30-day Recurring Revenue (in thousands)'
|
||||
lineColor: 'green'
|
||||
strokeWidth: 1
|
||||
min: 0
|
||||
max: _.max(points, 'y').y
|
||||
showYScale: true
|
||||
|
||||
if @activeUsers?.length > 0
|
||||
data = []
|
||||
for entry in @activeUsers
|
||||
break unless entry.monthlyCount
|
||||
data.push
|
||||
day: entry.day
|
||||
value: entry.monthlyCount / 1000
|
||||
data.reverse()
|
||||
points = @createLineChartPoints(days, data)
|
||||
chartLines.push
|
||||
points: points
|
||||
description: '30-day Active Users (in thousands)'
|
||||
lineColor: 'red'
|
||||
strokeWidth: 1
|
||||
min: 0
|
||||
max: _.max(points, 'y').y
|
||||
showYScale: true
|
||||
|
||||
updateActiveClassesChartData: ->
|
||||
@activeClassesChartLines = []
|
||||
return unless @activeClasses?.length
|
||||
days = d3Utils.createContiguousDays(90)
|
||||
|
||||
groupDayMap = {}
|
||||
for entry in @activeClasses
|
||||
for count, i in entry.groups
|
||||
groupDayMap[@activeClassGroups[i]] ?= {}
|
||||
groupDayMap[@activeClassGroups[i]][entry.day] ?= 0
|
||||
groupDayMap[@activeClassGroups[i]][entry.day] += count
|
||||
|
||||
lines = []
|
||||
colorIndex = 0
|
||||
totalMax = 0
|
||||
for group, entries of groupDayMap
|
||||
data = []
|
||||
for day, count of entries
|
||||
data.push
|
||||
day: day
|
||||
value: count
|
||||
data.reverse()
|
||||
points = @createLineChartPoints(days, data)
|
||||
@activeClassesChartLines.push
|
||||
points: points
|
||||
description: group.replace('Active classes ', '')
|
||||
lineColor: @lineColors[colorIndex++ % @lineColors.length]
|
||||
strokeWidth: 1
|
||||
min: 0
|
||||
showYScale: group is 'Total'
|
||||
totalMax = _.max(points, 'y').y if group is 'Total'
|
||||
line.max = totalMax for line in @activeClassesChartLines
|
||||
|
||||
updateActiveUsersChartData: ->
|
||||
@activeUsersChartLines = []
|
||||
return unless @activeUsers?.length
|
||||
days = d3Utils.createContiguousDays(90)
|
||||
|
||||
dailyData = []
|
||||
monthlyData = []
|
||||
dausmausData = []
|
||||
colorIndex = 0
|
||||
for entry in @activeUsers
|
||||
dailyData.push
|
||||
day: entry.day
|
||||
value: entry.dailyCount / 1000
|
||||
if entry.monthlyCount
|
||||
monthlyData.push
|
||||
day: entry.day
|
||||
value: entry.monthlyCount / 1000
|
||||
dausmausData.push
|
||||
day: entry.day
|
||||
value: Math.round(entry.dailyCount / entry.monthlyCount * 100)
|
||||
dailyData.reverse()
|
||||
monthlyData.reverse()
|
||||
dausmausData.reverse()
|
||||
dailyPoints = @createLineChartPoints(days, dailyData)
|
||||
monthlyPoints = @createLineChartPoints(days, monthlyData)
|
||||
dausmausPoints = @createLineChartPoints(days, dausmausData)
|
||||
@activeUsersChartLines.push
|
||||
points: dailyPoints
|
||||
description: 'Daily active users (in thousands)'
|
||||
lineColor: @lineColors[colorIndex++ % @lineColors.length]
|
||||
strokeWidth: 1
|
||||
min: 0
|
||||
max: _.max(dailyPoints, 'y').y
|
||||
showYScale: true
|
||||
@activeUsersChartLines.push
|
||||
points: monthlyPoints
|
||||
description: 'Monthly active users (in thousands)'
|
||||
lineColor: @lineColors[colorIndex++ % @lineColors.length]
|
||||
strokeWidth: 1
|
||||
min: 0
|
||||
max: _.max(monthlyPoints, 'y').y
|
||||
showYScale: true
|
||||
@activeUsersChartLines.push
|
||||
points: dausmausPoints
|
||||
description: 'DAUs/MAUs %'
|
||||
lineColor: @lineColors[colorIndex++ % @lineColors.length]
|
||||
strokeWidth: 1
|
||||
min: 0
|
||||
max: _.max(dausmausPoints, 'y').y
|
||||
showYScale: true
|
||||
|
||||
updateRevenueChartData: ->
|
||||
@revenueChartLines = []
|
||||
return unless @revenue?.length
|
||||
days = d3Utils.createContiguousDays(90)
|
||||
|
||||
groupDayMap = {}
|
||||
for entry in @revenue
|
||||
for count, i in entry.groups
|
||||
groupDayMap[@revenueGroups[i]] ?= {}
|
||||
groupDayMap[@revenueGroups[i]][entry.day] ?= 0
|
||||
groupDayMap[@revenueGroups[i]][entry.day] += count
|
||||
|
||||
lines = []
|
||||
colorIndex = 0
|
||||
dailyMax = 0
|
||||
for group, entries of groupDayMap
|
||||
data = []
|
||||
for day, count of entries
|
||||
data.push
|
||||
day: day
|
||||
value: count / 100
|
||||
data.reverse()
|
||||
points = @createLineChartPoints(days, data)
|
||||
@revenueChartLines.push
|
||||
points: points
|
||||
description: group.replace('DRR ', '')
|
||||
lineColor: @lineColors[colorIndex++ % @lineColors.length]
|
||||
strokeWidth: 1
|
||||
min: 0
|
||||
max: _.max(points, 'y').y
|
||||
showYScale: group in ['Daily', 'Monthly']
|
||||
dailyMax = _.max(points, 'y').y if group is 'Daily'
|
||||
for line in @revenueChartLines when line.description isnt 'Monthly'
|
||||
line.max = dailyMax
|
||||
|
|
Loading…
Reference in a new issue