Skip to content

Instantly share code, notes, and snippets.

@karlin
Created February 15, 2013 22:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save karlin/4963981 to your computer and use it in GitHub Desktop.
Save karlin/4963981 to your computer and use it in GitHub Desktop.
A CodePen by Karlin Fox. kpi - bullet graph of some key performance metrics
<div style="float:right">
<input id="metric" value="week_billable"/>
<input id="h" type="range" min="0" max="120"/>
</div>
<div class="header"></div>
Kpi = (->
label = (node, attrs) ->
node.append('text')
.attr('class', attrs.class)
.attr('y', attrs.y)
.attr('x', attrs.x)
.text(if typeof attrs.text is 'function' then attrs.text else ((d) -> d[attrs.text]))
rect = (node, attrs) ->
anode = node.append('rect')
.attr('class', attrs.class)
if attrs.bounds?
anode.attr('x', attrs.bounds[0])
.attr('y', attrs.bounds[1])
.attr('width', attrs.bounds[2])
.attr('height', attrs.bounds[3])
else
if attrs.x? then anode.attr('x', attrs.x)
if attrs.y? then anode.attr('y', attrs.y)
if attrs.width? then anode.attr('width', attrs.width)
anode.attr('height', attrs.height) if attrs.height?
if attrs.title?
anode.append('title').text(attrs.title)
anode
kpi_graphs = (home) ->
draw_graphs = (kpi_inputs) ->
kpi_labels = [
label: 'Hours'
label_line2: 'this week'
id: 'week'
week_billable_label: 'billable'
week_total_label: 'total'
,
id: 'quarter'
label_line2: 'this quarter'
label: 'Utilization'
]
WEEK = 0
QUARTER = 1
graph_inputs = []
$.each(kpi_inputs, (i, k) ->
graph_inputs.push($.extend({}, k, kpi_labels[i]))
)
dims =
graph_padding: 10
label_padding: 4
numeric_padding: 12
graph_height: 24
intergraph_padding: 60
hours_max: 40
utilization_max: 100
graph_width: 280
dims.graph_unit_height = dims.graph_height / 3
# Create our scales
hrs_scale = d3.scale.linear()
.domain([0,dims.hours_max])
.range([0,dims.graph_width])
ulz_scale = d3.scale.linear()
.domain([0,dims.utilization_max])
.range([0,dims.graph_width])
# Add axes with ticks and labels
week_axis = d3.svg.axis()
.scale(hrs_scale)
.orient('bottom')
.tickValues([0,8,16,24,32,40])
.tickSubdivide(1)
quarter_axis = d3.svg.axis()
.scale(ulz_scale)
.orient('bottom')
.tickValues([0,25,50,75,100])
# create the SVG element
vis = d3.select(home).append('svg')
.attr('width', 620)
.attr('height', 115)
.attr('id', 'kpi')
.attr('shape-rendering', 'crisp-edges')
$('svg#kpi').append(
"<defs>
<style type='text/css'>
<![CDATA[
@font-face {
font-family: DINOT-Light;
src: url('css/DINOT.woff');
}
]]>
</style>
</defs>")
# group all the graphs
graphs = vis.append('g').selectAll('g.bullet-graph')
.data(graph_inputs).enter()
.append('g')
.attr('class', 'bullet-graph')
# add a group for each graph with a named class
graphs.append('g').attr('class', (d) -> d.id)
#
# Week graph
#
week = graphs.select('g.week')
label week,
class: 'label'
y: dims.graph_height / 2
x: -dims.label_padding
text: 'label'
label week,
class: 'small-label'
y: dims.graph_height
x: -dims.label_padding
text: 'label_line2'
rect week,
class: 'week-total-avg'
bounds: [0, 0, ((d) -> hrs_scale(d.week_total_avg)), dims.graph_height]
title: '12-week average of total hours per week'
rect week,
class: 'week-billable-avg'
height: dims.graph_height
title: '12-week average of billable hours per week'
rect week,
class: 'week-total'
bounds: [0, dims.graph_unit_height, ((d) -> hrs_scale(d.week_total)), dims.graph_unit_height]
title: 'Total hours punched this week'
rect week,
class: 'week-billable'
bounds: [0, dims.graph_unit_height, ((d) -> hrs_scale(d.week_billable)), dims.graph_unit_height]
title: 'Billable hours punched this week'
rect week,
class: 'week-target'
bounds: [
((d) -> hrs_scale(d.week_billable_possible)), dims.graph_unit_height / 2,
2, dims.graph_height-dims.graph_unit_height
]
title: 'Billable hours possible this week'
label(week,
class: 'numeric-label billable'
y: dims.graph_height / 2+3
).attr('dy', '0.20em')
label(week,
class: 'numeric-label total'
y: dims.graph_height / 2+3
).attr('dy', '0.20em')
label week,
class: 'small-label legend billable'
y: dims.graph_height + 18
text: 'week_billable_label'
label week,
class: 'small-label legend total'
y: dims.graph_height+18
text: 'week_total_label'
adjust_week_graph_for = (t) ->
t.select('.week-billable').attr('width', (d) -> hrs_scale(d.week_billable))
t.select('.week-total').attr('width', (d) -> hrs_scale(d.week_total))
t.select('.week-billable-avg').attr 'width', (d) -> hrs_scale(d.week_billable_avg)
t.select('.week-total-avg').attr 'width', (d) -> hrs_scale(d.week_total_avg)
t.select('.week-target').attr('x', (d) -> hrs_scale(d.week_billable_possible))
scaled_max_x = (d) ->
x = 0
(x = n if n > x) for n in [d.week_total_avg, d.week_billable, d.week_total, d.week_billable_avg, d.week_billable_possible]
hrs_scale(x)
after_billable = (d) ->
dims.numeric_padding*2 +
scaled_max_x(d) +
Math.max($('text.legend.billable')[0].getComputedTextLength?(), $('text.billable.numeric-label')[0].getComputedTextLength?())
t.select('text.billable.numeric-label').text((d) -> d.week_billable)
t.select('text.total.numeric-label').text((d) -> d.week_total)
# t.select('rect.label-container')
# .attr('x', (d) -> hrs_scale(d.week_total_avg) + dims.numeric_padding/2)
t.selectAll('text.billable').attr('x', (d) -> scaled_max_x(d) + dims.numeric_padding)
t.selectAll('text.total').attr('x', after_billable)
adjust_week_graph_for week
week.append('g').attr('class', 'axis')
.attr('transform', "translate(0,#{dims.graph_height})")
.call(week_axis)
# create the update functions and return their container
kpi_inputs[WEEK].recalc_graph = ->
graph_inputs[WEEK][metric] = kpi_inputs[WEEK][metric] for metric in [
'week_billable',
'week_total',
'week_billable_avg',
'week_total_avg',
'week_billable_possible'
]
adjust_week_graph_for week.transition()
#
# Quarter graph
#
quarter = graphs.selectAll('g.quarter')
.attr('transform', "translate(0, #{dims.intergraph_padding})")
label quarter,
class: 'label utilization'
y: dims.graph_height / 2
x: -dims.label_padding
text: 'label'
label quarter,
class: 'small-label'
y: dims.graph_height
x: -dims.label_padding
text: 'label_line2'
rect quarter,
class: 'quarter-utilization-goal'
height: dims.graph_height
width: (d) -> ulz_scale(d.quarter_utilization_goal)
rect quarter,
class: 'quarter-co-utilization'
height: dims.graph_height
title: 'Company-wide utilization this fiscal quarter'
rect quarter,
class: 'quarter-utilization'
height: dims.graph_unit_height
y: dims.graph_unit_height
title: 'Utilization this fiscal quarter'
label(quarter,
class: 'numeric-label utilization'
y: dims.graph_height / 2+3
).attr('dy', '0.20em')
adjust_quarter_graph_for = (t) ->
max_x = (d) -> Math.max(d.quarter_utilization, Math.max(d.quarter_utilization_goal, d.quarter_co_utilization))
t.select('text.utilization.numeric-label').attr('x', (d) -> ulz_scale(max_x(d)) + dims.numeric_padding)
t.select('.quarter-utilization').attr('width', (d) -> ulz_scale(d.quarter_utilization))
t.select('.quarter-co-utilization').attr('width', (d) -> ulz_scale(d.quarter_co_utilization))
t.selectAll('text.numeric-label.utilization').text((d) -> "#{d.quarter_utilization}%")
adjust_quarter_graph_for quarter
quarter.append('g')
.attr('class', 'axis')
.attr('transform', "translate(0,#{dims.graph_height})")
.call(quarter_axis)
# bump the whole thing right to accomodate the labels, pad the top
right_padding = dims.graph_padding + parseInt($('text.label.utilization')[0].getComputedTextLength?())
graphs.attr('transform',
"translate(#{right_padding},#{dims.graph_padding})")
q_inputs = kpi_inputs[QUARTER]
q_inputs.recalc_graph = ->
graph_inputs[QUARTER].quarter_utilization = q_inputs.quarter_utilization
graph_inputs[QUARTER].quarter_co_utilization = q_inputs.quarter_co_utilization
adjust_quarter_graph_for(quarter.transition())
kpi_inputs
graphs_in_header = kpi_graphs 'div.header'
KPI = graphs_in_header [
week_billable: 24.25
week_total: 25
week_billable_avg: 34.3
week_total_avg: 45.875
week_billable_possible: 40
,
quarter_utilization: 99
quarter_co_utilization: 62
quarter_utilization_goal: 100
]
update_for = (x) ->
(new_vals) ->
for k, v of new_vals
for p in x when p?[k]
p[k] = v
p.recalc_graph()
update_kpi_with = update_for KPI
update_kpi_with
)()
$('input#h').on 'change', ->
new_data = {}
new_data[$('#metric').val()] = $(this).val()
Kpi new_data
/* Week */
rect.week-billable {
fill: #1e3332;
}
rect.week-total {
fill: #e5f4f3;
}
rect.week-total-avg {
fill: #b0dfde;
}
rect.week-billable-avg {
fill: #79ccc7;
}
rect.week-target {
fill: #1e3332;
}
/* Quarter */
rect.quarter-co-utilization {
fill: #f9aa99;
}
rect.quarter-utilization-goal {
fill: #fbddd6;
}
rect.quarter-utilization {
fill: #3e2a26;
}
/* Labels */
text.label {
fill: #596665;
font-size: 16px;
font-family: DINOT;
text-anchor: end;
dominant-baseline: top;
}
text.numeric-label {
font-family: DINOT;
text-anchor: start;
font-size: 23px;
}
text.numeric-label.total {
font-size: 14px;
fill: #aaa;
}
text.small-label {
font-size: 12px;
font-family: DINOT-Light;
text-anchor: end;
fill: #ccc;
}
text.legend {
text-anchor: start;
}
/* Axes */
.axis {
fill: #ccc;
stroke: none;
stroke-width: 0;
fill:none;
}
.axis line {
shape-rendering: crispEdges;
stroke-width: 1px;
stroke: #ccc;
}
.axis text {
text-rendering: geometricPrecision;
font-family: DINOT-Light;
fill: #ccc;
font-size: 12px;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment