Skip to content

Commit

Permalink
Creating a per citizen view of spending. Related to Issue codeforokc#41
Browse files Browse the repository at this point in the history
…. Adding thumbnail image and updating contributors page.
  • Loading branch information
Brent Lightsey authored and Brent Lightsey committed Mar 15, 2017
1 parent 281f71e commit ca5eb60
Show file tree
Hide file tree
Showing 9 changed files with 274 additions and 1 deletion.
4 changes: 4 additions & 0 deletions _src/_data.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,9 @@
"budget-radar": {
"title": "Budget Radar | Open Budget: OKC",
"slug": "budget-radar"
},
"budget-per-capita": {
"title": "OKC 2017 FY Budget Per Citizen | Open Budget: OKC",
"slug": "budget-per-capita"
}
}
2 changes: 2 additions & 0 deletions _src/_who-we-are.jade
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ div
li Alex Ayon (#[a(href='https://github.com/alex-code4okc') github])
li James England (#[a(href='https://twitter.com/JEinOKC') twitter]/#[a(href='https://github.com/JEinOKC') github])
li Daniel Ashcraft (#[a(href='https://github.com/dashcraft') github])
li Brent Lightsey (#[a(href='https://github.com/brentlightsey') github])

13 changes: 13 additions & 0 deletions _src/budget-per-capita.jade
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.container
h1 2017 FY OKC Budget Per Citizen
#list-container
// divs to be added here
// style elements
link(href='https://fonts.googleapis.com/css?family=Lato', rel='stylesheet')
script(src='https://code.jquery.com/jquery-3.1.1.min.js', integrity='sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=', crossorigin='anonymous')
script(src='http://benalman.com/code/projects/jquery-throttle-debounce/jquery.ba-throttle-debounce.js', type='application/javascript')
script(src='/js/okc-per-capita.js', type='application/javascript')
//if IE
script(src='http://html5shiv.googlecode.com/svn/trunk/html5.js')
3 changes: 3 additions & 0 deletions _src/budget-visuals.jade
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,6 @@
h3 FY2016 Budget Overview
img.img-responsive(src="/images/fy2016-tree-thumb.png")
.col-md-4
a(href="/budget-per-capita.html")
h3 FY2017 Budget Per Citizen
img.img-responsive(src="/images/budget-per-capita-thumb.png")
64 changes: 64 additions & 0 deletions _src/css/_okc-per-capita.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//colors
$color_alto_approx: #ddd;
$color_smalt_blue_approx: #5c828a;
$color_mountain_mist_approx: #999;
$color_pink_swan_approx: #bbb;

//fonts
$font_0: Lato;
$font_1: sans-serif;

body {
font-family: $font_0, $font_1;
}
div {
display: block;
}
p {
margin: 0 0 1em 0;
&.o-l1 {
text-transform: uppercase;
font-size: 12px;
color: $color_smalt_blue_approx;
margin-bottom: 0;
font-weight: 700;
}
&.o-l2 {
font-size: 22px;
margin-bottom: 3px;
text-transform: capitalize;
}
&.o-total {
font-size: 18px;
margin-bottom: 0;
color: $color_mountain_mist_approx;
}
}
.o-row {
display: block;
span {
display: inline-block;
}
.o-measure {
width: 70%;
text-align: right;
margin-bottom: 10px;
font-weight: 300;
position: relative;
}
.o-detail {
width: 28%;
margin-left: 1%;
}
}
.o-value {
border-bottom: 1px solid $color_alto_approx;
}
.o-cash {
color: $color_pink_swan_approx;
font-size: 50%;
vertical-align: top;
top: 0.4em;
position: relative;
margin-right: 2px;
}
4 changes: 3 additions & 1 deletion _src/css/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
@import "treemap"; // D3 Treemap
@import "flow"; // D3 Sankey diagram
@import "okc-budget-tree";
@import "okc-per-capita";

html {
height: 100%;
overflow-y: scroll;
Expand Down Expand Up @@ -102,7 +104,7 @@ footer {
.row.visualizations {
h3 {
text-align: center;
}
}
img {
height: 214px;
margin: 0 auto 15px;
Expand Down
18 changes: 18 additions & 0 deletions _src/data/population.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[
{
"year": 2015,
"city-population": "631346",
"metro-population": "1358452"
},
{
"year": 2016,
"city-population": "631346",
"metro-population": "1358452"
},
{
"year": 2017,
"city-population": "631346",
"metro-population": "1358452"
}

]
Binary file added _src/images/budget-per-capita-thumb.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
167 changes: 167 additions & 0 deletions _src/js/okc-per-capita.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/* Takes in a 2-level hierarchical set of data, and renders a series of divs
showing all of the measures sized proportinally to each other based on their
value compared to the grand total. This ensures that the largest spending item
will have the largest font.
*/
function getRootElement() {
return $("#list-container");
}

function getResizeElements(rootElement) {
return rootElement.find(".o-measure");
}

function readData(callback) {
$.when($.getJSON("./data/fy2017/c4okc_fy2017.json"),
$.getJSON("./data/population.json"))
.done(function(budgetData, populationData) {
callback(budgetData[0], populationData[0]);
})
.fail(function (jqxhr, textStatus, error) {
output("Had a problem getting the data: " + error);
});
}

// master function to begin after data retrieval
function processData(budgetData, populationData) {
var aggregated = aggregateData(budgetData);
var perCapita = calculatePerCapita(aggregated, populationData);
var sorted = sortData(perCapita);
var rootElement = getRootElement();
rootElement = renderList(rootElement, sorted);
resizeList();
}

// Create a new list that reduces the data into totals based on given keys
function aggregateData(data){
aggregated = data.reduce(function(acc,val){
var key = val.agency+"-"+val.program;
// break here, what's happening?
if (!acc.hasOwnProperty(key)){
acc[key] =
{
"agency": val.agency,
"program": val.program,
"program_total": 0
};
}
acc[key]["program_total"] += Number(val.value);
return acc;
}, {});
// convert single object to array
aggArray = [];
for(var key in aggregated) {
aggArray.push(aggregated[key]);
}

return aggArray;
}

// Calculates the per capita value of each program total
function calculatePerCapita(budgetData, populationData) {
// expects aggregated budget data with "program_total" attribute
// find the metro population, assume 2017
var metroPopObject = $.grep(populationData, function(e){return e.year == 2017});
var metroPop = Number(metroPopObject[0]["metro-population"]);

var perCapitaData = budgetData.map(function (e) {
var programTotal = Number(e["program_total"]);
var programPerCapita = programTotal / metroPop;

return {
"agency": e["agency"],
"program": e["program"],
"program_total": e["program_total"].toLocaleString(),
"program_per_capita": programPerCapita.toLocaleString(undefined,
{ maximumFractionDigits: 2, minimumFractionDigits: 2})
};
});

return perCapitaData;
}

// assumes the data is already structured with L1, L2 & measure
function sortData(data) {
var sortedData = data.sort(function(a, b) {
// sort only by program total
return b.program_per_capita - a.program_per_capita; // descending order
});

return sortedData;
}

//Render the elements in the data as a series of divs with the 'o-row' class applied
function renderList(rootElement, data) {
data.forEach(function (element) {
rowDiv = $("<div class='o-row'></div>");
rowDiv.className = "o-row";
rowDiv.id = element.L2;

spanMeasure = $("<span class='o-measure'></span>");
spanMeasure.append("<span class='o-cash'>$</span");
spanMeasure.append("<span class='o-value'>" + element.program_per_capita + "</span>");

spanDetail = $("<span class='o-detail'></span>");
spanDetail.append("<p class='o-l1'>" + element.agency + "</p>");
spanDetail.append("<p class='o-l2'>" + element.program + "</p>");
spanDetail.append("<p class='o-total'>Total: $" + element.program_total + "</p>");

rowDiv.append(spanMeasure);
rowDiv.append(spanDetail);
rootElement.append(rowDiv);
});

return rootElement;
}

// Resize the list based on window size
function resizeList() {
rootElement = getRootElement();
elementsToResize = getResizeElements(rootElement);

var maxWidth = 1800;
var defaultScaler = 22.5; //scaler - multiplication factor for fonts
var minScaler = 4.2;


var newWidth = Math.min($(window).width(), maxWidth); // sets max for width calc
var scaler = Math.max(defaultScaler * newWidth / maxWidth, minScaler); // min scale

elementsToResize.each(function(k,v) {
var valSpan = $(v).children('.o-value')[0];
var value = $(valSpan).text();
var fontSize = getFontSize(value, scaler);
$(v).css('font-size', fontSize + 'px');
});

}

function getFontSize(val, scaler) {
var minSize = 18; // minimum font size
var pc = String(val);
var str = pc.replace(',', ''); // "100.01"
var val = Number(str); // 100.01
var roundNum = Math.round(val);
var periodCount = (str.match(/\./g) || []).length;
var numeralCount = (str.match(/[0-9]/g) || []).length;
var nonNumerals = Math.floor((String(roundNum).length-1) / 3) + periodCount; // count of periods and commas

var size = Math.sqrt((val) / (.7*( (.56 * numeralCount) + (.27*nonNumerals) ))); // font size function
var fontSize = scaler * size;

return Math.max(fontSize, minSize);

}

// helpers
function output(message) {
alert(message);
}
// run on load
$(function () {
// find root element
// TODO: make root element dynamic
readData(processData);
// resize fonts when window resizes
$(window).resize($.debounce(250, resizeList));
});

0 comments on commit ca5eb60

Please sign in to comment.