more clear rendering
This commit is contained in:
parent
dc0f473188
commit
e4b91ee96b
|
@ -3,3 +3,4 @@ node_modules/
|
||||||
*.log
|
*.log
|
||||||
src/components/
|
src/components/
|
||||||
build/
|
build/
|
||||||
|
config.json
|
|
@ -1,58 +1,14 @@
|
||||||
#!/usr/bin/env coffee
|
#!/usr/bin/env coffee
|
||||||
{ _ } = require 'lodash'
|
{ Repos } = require './repos'
|
||||||
async = require 'async'
|
|
||||||
Rickshaw = require 'rickshaw'
|
|
||||||
|
|
||||||
# Modules.
|
|
||||||
milestones = require './milestones'
|
|
||||||
issues = require './issues'
|
|
||||||
graph = require './graph'
|
|
||||||
reg = require './regex'
|
|
||||||
|
|
||||||
# Eco templates as functions.
|
|
||||||
templates = {}
|
|
||||||
( templates[t] = require("./#{t}") for t in [ 'body', 'label' ] )
|
|
||||||
|
|
||||||
user = 'radekstepan'
|
|
||||||
repo = 'disposable'
|
|
||||||
|
|
||||||
module.exports = ->
|
module.exports = ->
|
||||||
milestones.get_current { user, repo }, (err, warn, m) ->
|
# A new repo collection.
|
||||||
issues.get_all { user, repo, milestone: m.number }, (err, [ open, closed ]) ->
|
collection = new Repos()
|
||||||
issues.filter closed, reg.size_label, (err, warn, closed) ->
|
# Get the coll/config.
|
||||||
async.parallel [
|
collection.fetch (err) ->
|
||||||
_.partial(graph.actual, closed, m.created_at, 10)
|
throw err if err
|
||||||
_.partial(graph.ideal, m.created_at, m.due_on, 10)
|
# Use the head.
|
||||||
], (err, [ actual, ideal ]) ->
|
repo = collection.at(0)
|
||||||
document.querySelector('body').innerHTML = templates.body({})
|
# Render the repo.
|
||||||
|
repo.render (err) ->
|
||||||
graph = new Rickshaw.Graph
|
throw err if err
|
||||||
'element': document.querySelector('#graph')
|
|
||||||
'renderer': 'line'
|
|
||||||
'series': [
|
|
||||||
{ 'data': actual, 'color': '#73C03A', 'name': 'actual' }
|
|
||||||
{ 'data': ideal, 'color': 'rgba(0,0,0,0.2)', 'name': 'ideal' }
|
|
||||||
]
|
|
||||||
|
|
||||||
hoverDetail = new Rickshaw.Graph.HoverDetail
|
|
||||||
'graph': graph
|
|
||||||
'xFormatter': (timestamp) ->
|
|
||||||
new Date(timestamp * 1e3).toUTCString().substring(0, 11)
|
|
||||||
|
|
||||||
'formatter': (series, timestamp, points) ->
|
|
||||||
templates.label { 'class': series.name, points }
|
|
||||||
|
|
||||||
xAxis = new Rickshaw.Graph.Axis.Time 'graph': graph
|
|
||||||
|
|
||||||
yAxis = new Rickshaw.Graph.Axis.Y
|
|
||||||
'graph': graph
|
|
||||||
'orientation': 'left'
|
|
||||||
'tickFormat': Rickshaw.Fixtures.Number.formatKMBT
|
|
||||||
|
|
||||||
annotator = new Rickshaw.Graph.Annotate
|
|
||||||
'graph': graph
|
|
||||||
'element': document.querySelector('#timeline')
|
|
||||||
|
|
||||||
annotator.add +new Date / 1e3, 'Now'
|
|
||||||
|
|
||||||
graph.render()
|
|
|
@ -5,8 +5,9 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bestiejs/lodash": "*",
|
"bestiejs/lodash": "*",
|
||||||
"caolan/async": "*",
|
"caolan/async": "*",
|
||||||
"cristiandouce/rickshaw": "*",
|
"mbostock/d3": "*",
|
||||||
"visionmedia/superagent": "*"
|
"visionmedia/superagent": "*",
|
||||||
|
"necolas/normalize.css": "*"
|
||||||
},
|
},
|
||||||
"scripts": [
|
"scripts": [
|
||||||
"app.coffee",
|
"app.coffee",
|
||||||
|
@ -15,6 +16,7 @@
|
||||||
"modules/milestones.coffee",
|
"modules/milestones.coffee",
|
||||||
"modules/regex.coffee",
|
"modules/regex.coffee",
|
||||||
"modules/request.coffee",
|
"modules/request.coffee",
|
||||||
|
"modules/repos.coffee",
|
||||||
"templates/body.eco",
|
"templates/body.eco",
|
||||||
"templates/label.eco"
|
"templates/label.eco"
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#!/usr/bin/env coffee
|
#!/usr/bin/env coffee
|
||||||
{ _ } = require 'lodash'
|
{ _ } = require 'lodash'
|
||||||
|
d3 = require 'd3'
|
||||||
|
|
||||||
reg = require './regex'
|
reg = require './regex'
|
||||||
|
|
||||||
|
@ -7,9 +8,9 @@ module.exports =
|
||||||
# Map closed issues ready to be visualized by Rickshaw.
|
# Map closed issues ready to be visualized by Rickshaw.
|
||||||
# Assumes collection has been `filter`ed and is ordered.
|
# Assumes collection has been `filter`ed and is ordered.
|
||||||
'actual': (collection, created_at, total, cb) ->
|
'actual': (collection, created_at, total, cb) ->
|
||||||
head = [ { x: +new Date(created_at) / 1e3, y: total } ]
|
head = [ { date: new Date(created_at), points: total } ]
|
||||||
rest = _.map collection, ({ closed_at, size }) ->
|
rest = _.map collection, ({ closed_at, size }) ->
|
||||||
{ x: +new Date(closed_at) / 1e3, y: total -= size }
|
{ date: new Date(closed_at), points: total -= size }
|
||||||
cb null, head.concat rest
|
cb null, head.concat rest
|
||||||
|
|
||||||
# Map ideal velocity for each day ready to be visualized by Rickshaw.
|
# Map ideal velocity for each day ready to be visualized by Rickshaw.
|
||||||
|
@ -17,21 +18,23 @@ module.exports =
|
||||||
# Swap?
|
# Swap?
|
||||||
[ b, a ] = [ a, b ] if b < a
|
[ b, a ] = [ a, b ] if b < a
|
||||||
|
|
||||||
|
return cb null, [
|
||||||
|
{ date: new Date(a), points: total }
|
||||||
|
{ date: new Date(b), points: 0 }
|
||||||
|
]
|
||||||
|
|
||||||
# When do we start & end?
|
# When do we start & end?
|
||||||
[ year, month, day ] = _.map(a.match(reg.datetime)[1].split('-'), (d) -> parseInt(d) )
|
[ year, month, day ] = _.map(a.match(reg.datetime)[1].split('-'), (d) -> parseInt(d) )
|
||||||
b = b.match(reg.datetime)[1]
|
|
||||||
|
|
||||||
# The head/tail are quite specific.
|
# The head/tail are quite specific.
|
||||||
head = { x: +new Date(a) / 1e3, y: total }
|
head = { date: new Date(a), points: total }
|
||||||
tail = { x: b = +new Date(b) / 1e3, y: 0 }
|
tail = { date: b = new Date(b.match(reg.datetime)[1]), points: 0 }
|
||||||
|
|
||||||
# The fillers...
|
# The fillers...
|
||||||
days = []
|
days = []
|
||||||
do add = (i = 1) ->
|
do add = (i = 1) ->
|
||||||
# Lunchtime to "handle" daylight saving.
|
# Add the time point at lunchtime.
|
||||||
c = +new Date year, month - 1, day + i, 12
|
days.push { date: c = new Date(year, month - 1, day + i, 12) }
|
||||||
# Add the time point.
|
|
||||||
days.push { x: c / 1e3 }
|
|
||||||
# Moar?
|
# Moar?
|
||||||
add(i + 1) if c < b
|
add(i + 1) if c < b
|
||||||
|
|
||||||
|
@ -39,7 +42,81 @@ module.exports =
|
||||||
daily = total / (days.length + 1)
|
daily = total / (days.length + 1)
|
||||||
# Map points to days.
|
# Map points to days.
|
||||||
days = _.map days, (day) ->
|
days = _.map days, (day) ->
|
||||||
day.y = total -= daily
|
day.points = total -= daily
|
||||||
day
|
day
|
||||||
|
|
||||||
cb null, [ head ].concat(days).concat([ tail ])
|
cb null, [ head ].concat(days).concat([ tail ])
|
||||||
|
|
||||||
|
'render': ([ actual, ideal ], cb) ->
|
||||||
|
# Get available space.
|
||||||
|
{ height, width } = document.querySelector('#graph').getBoundingClientRect()
|
||||||
|
|
||||||
|
margin = { top: 20, right: 20, bottom: 20, left: 20 }
|
||||||
|
width -= margin.left + margin.right
|
||||||
|
height -= margin.top + margin.bottom
|
||||||
|
|
||||||
|
# Scales and axis.
|
||||||
|
x = d3.time.scale().range([ 0, width ])
|
||||||
|
y = d3.scale.linear().range([ height, 0 ])
|
||||||
|
|
||||||
|
xAxis = d3.svg.axis().scale(x)
|
||||||
|
# Show vertical lines...
|
||||||
|
.tickSize(-height)
|
||||||
|
# ...with day of the month...
|
||||||
|
.tickFormat( (d) -> d.getDate() )
|
||||||
|
# ...once per day.
|
||||||
|
.ticks(d3.time.hours, 24)
|
||||||
|
|
||||||
|
# Area generator.
|
||||||
|
area = d3.svg.area()
|
||||||
|
.interpolate("monotone")
|
||||||
|
.x( (d) -> x(d.date) )
|
||||||
|
.y0(height)
|
||||||
|
.y1( (d) -> y(d.points) )
|
||||||
|
|
||||||
|
# Line generator.
|
||||||
|
line = d3.svg.line()
|
||||||
|
.interpolate("basis")
|
||||||
|
.x( (d) -> x(d.date) )
|
||||||
|
.y( (d) -> y(d.points) )
|
||||||
|
|
||||||
|
# Get the minimum and maximum date, and initial points.
|
||||||
|
x.domain([ ideal[0].date, ideal[ideal.length - 1].date ])
|
||||||
|
y.domain([ 0, ideal[0].points ]).nice()
|
||||||
|
|
||||||
|
# Add an SVG element with the desired dimensions and margin.
|
||||||
|
svg = d3.select("#graph").append("svg")
|
||||||
|
.attr("width", width + margin.left + margin.right)
|
||||||
|
.attr("height", height + margin.top + margin.bottom)
|
||||||
|
.append("g")
|
||||||
|
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
|
||||||
|
|
||||||
|
# Add the clip path.
|
||||||
|
svg.append("clipPath")
|
||||||
|
.attr("id", "clip")
|
||||||
|
.append("rect")
|
||||||
|
.attr("width", width)
|
||||||
|
.attr("height", height)
|
||||||
|
|
||||||
|
# Add the area path.
|
||||||
|
svg.append("path")
|
||||||
|
.attr("class", "area")
|
||||||
|
.attr("d", area(ideal))
|
||||||
|
|
||||||
|
# Add the x-axis.
|
||||||
|
svg.append("g")
|
||||||
|
.attr("class", "x axis")
|
||||||
|
.attr("transform", "translate(0,#{height})")
|
||||||
|
.call(xAxis)
|
||||||
|
|
||||||
|
# Add the ideal line path.
|
||||||
|
svg.append("path")
|
||||||
|
.attr("class", "ideal line")
|
||||||
|
.attr("d", line(ideal))
|
||||||
|
|
||||||
|
# Add the actual line path.
|
||||||
|
svg.append("path")
|
||||||
|
.attr("class", "actual line")
|
||||||
|
.attr("d", line(actual))
|
||||||
|
|
||||||
|
cb null
|
|
@ -7,19 +7,17 @@ reg = require './regex'
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
# Used on an initial fetch of issues for a repo.
|
# Used on an initial fetch of issues for a repo.
|
||||||
'get_all': ({ user, repo, milestone }, cb) ->
|
'get_all': (repo, cb) ->
|
||||||
# For each state...
|
# For each state...
|
||||||
one_status = (state, cb) ->
|
one_status = (state, cb) ->
|
||||||
# Concat them here.
|
# Concat them here.
|
||||||
results = []
|
results = []
|
||||||
# One pageful fetch (next pages in series).
|
# One pageful fetch (next pages in series).
|
||||||
do fetch_page = (page = 1) ->
|
do fetch_page = (page = 1) ->
|
||||||
req.all_issues {
|
req.all_issues repo, {
|
||||||
user
|
milestone: repo.milestone.number
|
||||||
repo
|
state
|
||||||
milestone
|
page
|
||||||
state: state
|
|
||||||
page: page
|
|
||||||
}, (err, data) ->
|
}, (err, data) ->
|
||||||
# Request errors.
|
# Request errors.
|
||||||
return cb err if err
|
return cb err if err
|
||||||
|
@ -27,8 +25,8 @@ module.exports =
|
||||||
return cb data.message if data.message
|
return cb data.message if data.message
|
||||||
# Empty?
|
# Empty?
|
||||||
return cb null, results unless data.length
|
return cb null, results unless data.length
|
||||||
# Concat.
|
# Concat sorted (API does not sort on closed_at!).
|
||||||
results = results.concat data
|
results = results.concat _.sortBy data, 'closed_at'
|
||||||
# < 100 results?
|
# < 100 results?
|
||||||
return cb null, results if data.length < 100
|
return cb null, results if data.length < 100
|
||||||
# Fetch the next page then.
|
# Fetch the next page then.
|
||||||
|
@ -42,7 +40,7 @@ module.exports =
|
||||||
|
|
||||||
# Filter an array of incoming issues based on a regex & save size on them.
|
# Filter an array of incoming issues based on a regex & save size on them.
|
||||||
'filter': (collection, regex, cb) ->
|
'filter': (collection, regex, cb) ->
|
||||||
warnings = null
|
warnings = null ; total = 0
|
||||||
try
|
try
|
||||||
filtered = _.filter collection, (issue) ->
|
filtered = _.filter collection, (issue) ->
|
||||||
{ labels, number } = issue
|
{ labels, number } = issue
|
||||||
|
@ -52,14 +50,14 @@ module.exports =
|
||||||
when 0 then false
|
when 0 then false
|
||||||
when 1
|
when 1
|
||||||
# Provide the size attribute on the issue.
|
# Provide the size attribute on the issue.
|
||||||
issue.size = parseInt name.match(regex)[1]
|
total += issue.size = parseInt name.match(regex)[1]
|
||||||
true
|
true
|
||||||
else
|
else
|
||||||
warnings ?= []
|
warnings ?= []
|
||||||
warnings.push "Issue ##{number} has multiple matching size labels"
|
warnings.push "Issue ##{number} has multiple matching size labels"
|
||||||
true
|
true
|
||||||
|
|
||||||
cb null, warnings, filtered
|
cb null, warnings, filtered, total
|
||||||
|
|
||||||
catch err
|
catch err
|
||||||
return cb err, warnings
|
return cb err, warnings
|
||||||
|
|
|
@ -3,8 +3,8 @@ req = require './request'
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
# Used at initialization stage.
|
# Used at initialization stage.
|
||||||
'get_current': (opts, cb) ->
|
'get_current': (repo, cb) ->
|
||||||
req.all_milestones opts, (err, data) ->
|
req.all_milestones repo, (err, data) ->
|
||||||
# Request errors.
|
# Request errors.
|
||||||
return cb err if err
|
return cb err if err
|
||||||
# GitHub errors.
|
# GitHub errors.
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
#!/usr/bin/env coffee
|
||||||
|
{ _ } = require 'lodash'
|
||||||
|
async = require 'async'
|
||||||
|
|
||||||
|
milestones = require './milestones'
|
||||||
|
issues = require './issues'
|
||||||
|
graph = require './graph'
|
||||||
|
reg = require './regex'
|
||||||
|
req = require './request'
|
||||||
|
|
||||||
|
# Eco templates as functions.
|
||||||
|
templates = {} ; ( templates[t] = require("./#{t}") for t in [ 'body', 'label' ] )
|
||||||
|
|
||||||
|
class exports.Repos
|
||||||
|
|
||||||
|
constructor: ->
|
||||||
|
@models = []
|
||||||
|
|
||||||
|
fetch: (cb) ->
|
||||||
|
self = @
|
||||||
|
req.config (err, config) ->
|
||||||
|
return cb err if err
|
||||||
|
self.models = ( new Repo(entry) for entry in config )
|
||||||
|
cb null
|
||||||
|
|
||||||
|
at: (index) ->
|
||||||
|
@models[index]
|
||||||
|
|
||||||
|
|
||||||
|
class Repo
|
||||||
|
|
||||||
|
constructor: (opts) ->
|
||||||
|
( @[k] = v for k, v of opts )
|
||||||
|
|
||||||
|
render: (cb) ->
|
||||||
|
self = @
|
||||||
|
|
||||||
|
async.waterfall [ (cb) ->
|
||||||
|
# Get the current milestone.
|
||||||
|
milestones.get_current self, (err, warn, milestone) ->
|
||||||
|
self.milestone = milestone
|
||||||
|
cb err
|
||||||
|
|
||||||
|
# Get all issues.
|
||||||
|
(cb) ->
|
||||||
|
issues.get_all self, cb
|
||||||
|
|
||||||
|
# Filter them to labeled ones.
|
||||||
|
(all, cb) ->
|
||||||
|
async.map all, (array, cb) ->
|
||||||
|
issues.filter array, reg.size_label, (err, warn, filtered, total) ->
|
||||||
|
cb err, [ filtered, total ]
|
||||||
|
, (err, [ open, closed ]) ->
|
||||||
|
return cb err if err
|
||||||
|
# Save the open/closed on us first.
|
||||||
|
self.issues =
|
||||||
|
closed: { points: closed[1], data: closed[0] }
|
||||||
|
open: { points: open[1], data: open[0] }
|
||||||
|
cb null
|
||||||
|
|
||||||
|
# Create actual and ideal lines & render.
|
||||||
|
(cb) ->
|
||||||
|
progress = 100 * self.issues.closed.points /
|
||||||
|
(total = self.issues.open.points + self.issues.closed.points)
|
||||||
|
|
||||||
|
async.parallel [
|
||||||
|
_.partial(graph.actual, self.issues.closed.data, self.milestone.created_at, total)
|
||||||
|
_.partial(graph.ideal, self.milestone.created_at, self.milestone.due_on, total)
|
||||||
|
], (err, values) ->
|
||||||
|
document.querySelector('body').innerHTML = templates.body { progress }
|
||||||
|
|
||||||
|
graph.render values, cb
|
||||||
|
|
||||||
|
], cb
|
|
@ -1,27 +1,39 @@
|
||||||
#!/usr/bin/env coffee
|
#!/usr/bin/env coffee
|
||||||
|
sa = require 'superagent'
|
||||||
{ _ } = require 'lodash'
|
{ _ } = require 'lodash'
|
||||||
|
|
||||||
protocol = 'https'
|
|
||||||
domain = 'api.github.com'
|
|
||||||
token = ''
|
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
'all_milestones': ({ user, repo }, cb) ->
|
# Get all milestones.
|
||||||
opts = { state: 'open', sort: 'due_date', direction: 'asc' }
|
'all_milestones': (repo, cb) ->
|
||||||
request { user, repo, opts, path: 'milestones' }, cb
|
query = { state: 'open', sort: 'due_date', direction: 'asc' }
|
||||||
|
request repo, query, 'milestones', cb
|
||||||
|
|
||||||
'all_issues': ({ user, repo }, cb) ->
|
# Get all issues for a state.
|
||||||
opts = _.extend {}, arguments[0], { per_page: '100', direction: 'asc' }
|
'all_issues': (repo, query, cb) ->
|
||||||
request { user, repo, opts, path: 'issues' }, cb
|
_.extend query, { per_page: '100' }
|
||||||
|
request repo, query, 'issues', cb
|
||||||
|
|
||||||
# Make a request using SuperAgent.
|
# Get config from our domain always.
|
||||||
request = ({ user, repo, path, opts }, cb) ->
|
'config': (cb) ->
|
||||||
opts = ( "#{k}=#{v}" for k, v of opts when k not in [ 'user', 'repo' ] ).join('&')
|
sa
|
||||||
|
.get("http://#{window.location.host}/config.json")
|
||||||
(require 'superagent')
|
|
||||||
.get("#{protocol}://#{domain}/repos/#{user}/#{repo}/#{path}?#{opts}")
|
|
||||||
.set('Content-Type', 'application/json')
|
.set('Content-Type', 'application/json')
|
||||||
.set('Accept', 'application/vnd.github.raw')
|
|
||||||
.set('Authorization', "token #{token}")
|
|
||||||
.end (err, data) ->
|
.end (err, data) ->
|
||||||
cb err, data?.body
|
cb err, data?.body
|
||||||
|
|
||||||
|
# Make a request using SuperAgent.
|
||||||
|
request = ({ domain, token, user, repo }, query, path, cb) ->
|
||||||
|
# Make the query params.
|
||||||
|
q = ( "#{k}=#{v}" for k, v of query ).join('&')
|
||||||
|
|
||||||
|
req = sa
|
||||||
|
.get("https://#{domain}/repos/#{user}/#{repo}/#{path}?#{q}")
|
||||||
|
.set('Content-Type', 'application/json')
|
||||||
|
.set('Accept', 'application/vnd.github.raw')
|
||||||
|
|
||||||
|
# Auth token?
|
||||||
|
req = req.set('Authorization', "token #{token}") if token
|
||||||
|
|
||||||
|
# Send.
|
||||||
|
req.end (err, data) ->
|
||||||
|
cb err, data?.body
|
|
@ -1,5 +1,93 @@
|
||||||
|
$closed = #4ACAB4
|
||||||
|
$opened = #FE5D55
|
||||||
|
|
||||||
body
|
body
|
||||||
|
background: #31323A
|
||||||
padding: 100px
|
padding: 100px
|
||||||
|
|
||||||
|
#box
|
||||||
|
background: #43444f
|
||||||
|
border-radius: 6px
|
||||||
|
box-shadow: 2px 4px 6px rgba(0,0,0,0.2)
|
||||||
|
|
||||||
|
#progress
|
||||||
|
padding: 20px
|
||||||
|
border-radius: 0 0 6px 6px
|
||||||
|
|
||||||
|
&:after
|
||||||
|
clear: both
|
||||||
|
display: block
|
||||||
|
content: ""
|
||||||
|
|
||||||
|
.bars
|
||||||
|
position: relative
|
||||||
|
|
||||||
|
div
|
||||||
|
border-radius: 6px
|
||||||
|
height: 12px
|
||||||
|
|
||||||
|
&.closed
|
||||||
|
position: absolute
|
||||||
|
top: 0
|
||||||
|
left: 0
|
||||||
|
background: $closed
|
||||||
|
|
||||||
|
&:not(.done)
|
||||||
|
border-radius: 6px 0 0 6px
|
||||||
|
|
||||||
|
&.opened
|
||||||
|
width: 100%
|
||||||
|
background: $opened
|
||||||
|
|
||||||
|
h2
|
||||||
|
font-size: 14px
|
||||||
|
text-transform: uppercase
|
||||||
|
margin: 10px 0 0 0
|
||||||
|
|
||||||
|
&.closed
|
||||||
|
float: left
|
||||||
|
color: $closed
|
||||||
|
|
||||||
|
&.opened
|
||||||
|
float: right
|
||||||
|
color: $opened
|
||||||
|
|
||||||
#graph
|
#graph
|
||||||
|
background: #FFF
|
||||||
|
border-radius: 6px 6px 0 0
|
||||||
height: 200px
|
height: 200px
|
||||||
|
|
||||||
|
svg
|
||||||
|
path
|
||||||
|
&.line
|
||||||
|
fill: none
|
||||||
|
stroke: #CBC8C3
|
||||||
|
stroke-width: 1px
|
||||||
|
clip-path: url(#clip)
|
||||||
|
|
||||||
|
&.actual
|
||||||
|
stroke-width: 2px
|
||||||
|
stroke: $closed
|
||||||
|
|
||||||
|
&.area
|
||||||
|
clip-path: url(#clip)
|
||||||
|
fill: #FAFAF8
|
||||||
|
|
||||||
|
.axis
|
||||||
|
shape-rendering: crispEdges
|
||||||
|
|
||||||
|
&.x
|
||||||
|
line
|
||||||
|
stroke: #EBEBE9
|
||||||
|
|
||||||
|
text
|
||||||
|
font-weight: bold
|
||||||
|
fill: #CBC8C3
|
||||||
|
|
||||||
|
path
|
||||||
|
display: none
|
||||||
|
|
||||||
|
&.y
|
||||||
|
line, path
|
||||||
|
fill: none
|
||||||
|
stroke: #000
|
|
@ -1,2 +1,15 @@
|
||||||
<div id="graph"></div>
|
<div id="box">
|
||||||
<div id="timeline"></div>
|
<div id="graph"></div>
|
||||||
|
<div id="progress">
|
||||||
|
<div class="bars">
|
||||||
|
<% if @progress is 100: %>
|
||||||
|
<div class="closed done" style="width:100%"></div>
|
||||||
|
<% else: %>
|
||||||
|
<div class="closed" style="width:<%= @progress %>%"></div>
|
||||||
|
<% end %>
|
||||||
|
<div class="opened"></div>
|
||||||
|
</div>
|
||||||
|
<h2 class="closed">Closed / <%= Math.floor @progress %>%</h2>
|
||||||
|
<h2 class="opened">Open / <%= 100 - Math.floor @progress %>%</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
Loading…
Reference in New Issue