Last active
January 28, 2018 06:21
-
-
Save malatx/b7f33289d37442c918a5 to your computer and use it in GitHub Desktop.
Sample Indie App Dashboard via AppFigures Data
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// This is sample code to draw some of the charts I blogged about here: | |
// https://medium.com/ios-os-x-development/keeping-your-wits-as-an-indie-app-developer-3b5b14428e1f | |
// | |
// A few disclaimers: | |
// 1. This assumes you have at least 30 days of sales data in order to draw the Trailing 7 Days Chart | |
// 2. This assumes you have over 52 consecutive weeks of data in order to draw the Trailing Year Chart | |
// 3. I don't really know PHP. Everything this does, I had to look it up while writing it. If you actually know PHP, sorry. There are surely better ways. | |
// | |
// the results of this PHP script is intended to be drawn using the Flot library. | |
// be sure to place the Flot files in a folder named 'flot', next to this script. (or change the HTML below) | |
// see http://www.flotcharts.org | |
// set these variables to your appfigures credentials | |
// see https://appfigures.com/developers/keys | |
$appFiguresUsername = "[email protected]"; | |
$appFiguresPassword = "YourPassword!"; | |
$appFiguresApiKey = "your_api_key"; | |
// set this to the first date for which AppFigures has valid data in your account | |
$appFiguresFirstDate = new DateTime('2009-07-14'); | |
// be sure the web server user has permission to create, write, and delete files in this cache folder. | |
// if the cache folder does not exist, no caching will take place (live hits to the API every time). | |
$cacheFolder = "./sample-cache"; | |
$context = stream_context_create(array( | |
'http' => array( | |
'header' => "Authorization: Basic " . base64_encode("$appFiguresUsername:$appFiguresPassword") | |
) | |
)); | |
$revenueDates = getAllRevenueDatesData($context); | |
$revenueDates = makeTTMFromWeeklyRevenues($revenueDates); | |
$last30DaysSummary = getLast30DaysSalesData($context); | |
$t7dDates = makeT7DFromDailyRevenues($last30DaysSummary); | |
function makeTTMFromWeeklyRevenues($weeklyRevenues) | |
{ | |
$ttmPoints = array(); | |
$numWeeks = count($weeklyRevenues); | |
if ($numWeeks >= 52) | |
{ | |
$currentWeek = $numWeeks - 1; | |
while (true) | |
{ | |
$startWeek = $currentWeek-52; | |
if ($startWeek < 0) | |
{ | |
break; | |
} | |
$yearSum = 0; | |
for ($i=$startWeek;$i < $currentWeek; $i++) | |
{ | |
if ($i < 0) | |
{ | |
break; | |
} | |
$yearSum += $weeklyRevenues[$i]["revenue"]; | |
} | |
$ttmPoint["date"] = $weeklyRevenues[$currentWeek]["date"]; | |
$ttmPoint["revenue"] = $yearSum; | |
$ttmPoints[] = $ttmPoint; | |
$currentWeek--; | |
} | |
} | |
return $ttmPoints; | |
} | |
function makeT7DFromDailyRevenues($dailyRevenues) | |
{ | |
$t7dPoints = array(); | |
$numDays = count($dailyRevenues); | |
if ($numDays >= 14) | |
{ | |
$currentDay = $numDays - 1; | |
while (true) | |
{ | |
$startDay = $currentDay-7; | |
if ($startDay < 0) | |
{ | |
break; | |
} | |
$weekSum = 0; | |
for ($i=$startDay;$i < $currentDay; $i++) | |
{ | |
if ($i < 0) | |
{ | |
break; | |
} | |
$weekSum += $dailyRevenues[$i]["revenue"]; | |
} | |
$t7dPoint["date"] = $dailyRevenues[$currentDay]["date"]; | |
$t7dPoint["revenue"] = $weekSum; | |
$t7dPoints[] = $t7dPoint; | |
$currentDay--; | |
} | |
} | |
return $t7dPoints; | |
} | |
// $products can be an array of appFigures product ID numbers. null means all products. | |
function getLast30DaysSalesData($context, $products=null) | |
{ | |
$currentDate = new DateTime(); | |
$currentDate->sub(new DateInterval('P1D')); | |
$firstDate = clone $currentDate; | |
$firstDate->sub(new DateInterval('P30D')); | |
$startDate = $firstDate->format('Y-m-d'); | |
$endDate = $currentDate->format('Y-m-d'); | |
global $appFiguresApiKey; | |
$url = 'https://api.appfigures.com/v2/sales/?start_date=' . $startDate . '&end_date=' . $endDate . '&group_by=date&granularity=daily&client_key=' . $appFiguresApiKey; | |
$cacheKeyExtra = "all"; | |
if ($products != null) | |
{ | |
$cacheKeyExtra = implode('_', $products); | |
$url .= "&products=" . implode(';', $products); | |
} | |
$cacheKey = "$cacheKeyExtra.last30days.$startDate.$endDate"; | |
$json = getJsonWithCache($context, $cacheKey, $url); | |
foreach($json as $item) | |
{ | |
$date = $item["date"]; | |
$revenue = $item["revenue"]; | |
if ($revenue == 0) | |
{ | |
bustCache($cacheKey); | |
continue; | |
} | |
$revenuePoint["date"] = $date; | |
$revenuePoint["downloads"] = $item["downloads"]; | |
$revenuePoint["revenue"] = $revenue; | |
$result[] = $revenuePoint; | |
} | |
return $result; | |
} | |
function getAllRevenueDatesData($context, $products=null) | |
{ | |
$currentDate = new DateTime(); | |
$currentDate->sub(new DateInterval('P6D')); | |
global $appFiguresFirstDate; | |
$firstDate = clone $appFiguresFirstDate; | |
$exit = false; | |
$result = array(); | |
global $appFiguresApiKey; | |
while (!$exit) | |
{ | |
$secondDate = clone $firstDate; | |
$secondDate->add(new DateInterval('P365D')); | |
if ($secondDate > $currentDate) | |
{ | |
$secondDate = $currentDate; | |
$exit = true; | |
} | |
$startDate = $firstDate->format('Y-m-d'); | |
$endDate = $secondDate->format('Y-m-d'); | |
$url = 'https://api.appfigures.com/v2/sales/?start_date=' . $startDate . '&end_date=' . $endDate . '&group_by=dates&granularity=weekly&client_key=' . $appFiguresApiKey; | |
$cacheKeyExtra = "all"; | |
if ($products != null) | |
{ | |
$cacheKeyExtra = implode('_', $products); | |
$url .= "&products=" . implode(';', $products); | |
} | |
$json = getJsonWithCache($context, "$cacheKeyExtra.weekly.$startDate.$endDate", $url); | |
foreach($json as $item) | |
{ | |
$date = $item["date"]; | |
$revenue = $item["revenue"]; | |
$revenuePoint["date"] = $date; | |
$revenuePoint["revenue"] = $revenue; | |
$result[] = $revenuePoint; | |
} | |
$firstDate = $secondDate; | |
} | |
return $result; | |
} | |
function getJson($context, $url) | |
{ | |
$response = file_get_contents($url, false, $context); | |
$json = json_decode($response, TRUE); | |
return $json; | |
} | |
function bustCache($cacheKey) | |
{ | |
global $cacheFolder; | |
unlink("$cacheFolder/$cacheKey.json"); | |
} | |
function getJsonWithCache($context, $cacheKey, $url) | |
{ | |
global $cacheFolder; | |
$cacheFile = "$cacheFolder/$cacheKey.json"; | |
$cacheContent = file_get_contents($cacheFile); | |
$response = ""; | |
if ($cacheContent) | |
{ | |
$response = $cacheContent; | |
} | |
else | |
{ | |
$response = file_get_contents($url, false, $context); | |
file_put_contents($cacheFile, $response); | |
} | |
$json = json_decode($response, TRUE); | |
return $json; | |
} | |
?> | |
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> | |
<html> | |
<head> | |
<title>Sample Dashboard</title> | |
<style type="text/css"> | |
* { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; | |
} | |
.large-chart {width:1000px;height:550px;} | |
.half-chart {width:500px;height:350px;} | |
</style> | |
<script language="javascript" type="text/javascript" src="./flot/jquery.min.js"></script> | |
<script language="javascript" type="text/javascript" src="./flot/jquery.flot.min.js"></script> | |
<script language="javascript" type="text/javascript" src="./flot/jquery.flot.time.min.js"></script> | |
<script type="text/javascript"> | |
$(function() { | |
var last_14days = [ | |
<?php | |
$last14DaysSummary = array_slice($last30DaysSummary, -14); | |
foreach ($last14DaysSummary as $point) | |
{ | |
$dateTime = new DateTime($point["date"]); | |
$unixTimestamp = $dateTime->getTimestamp(); | |
$javascriptTimeStamp = $unixTimestamp *1000; | |
print_r('['); | |
print_r($javascriptTimeStamp); | |
print_r(','); | |
print_r($point["revenue"]); | |
print_r('],'); | |
} | |
?> | |
]; | |
var all_t7d = [ | |
<?php | |
foreach ($t7dDates as $point) | |
{ | |
$dateTime = new DateTime($point["date"]); | |
$unixTimestamp = $dateTime->getTimestamp(); | |
$javascriptTimeStamp = $unixTimestamp *1000; | |
print_r('['); | |
print_r($javascriptTimeStamp); | |
print_r(','); | |
print_r($point["revenue"]); | |
print_r('],'); | |
} | |
?> | |
]; | |
var all_ttm = [ | |
<?php | |
foreach ($revenueDates as $point) | |
{ | |
$dateTime = new DateTime($point["date"]); | |
$unixTimestamp = $dateTime->getTimestamp(); | |
$javascriptTimeStamp = $unixTimestamp *1000; | |
print_r('['); | |
print_r($javascriptTimeStamp); | |
print_r(','); | |
print_r($point["revenue"]); | |
print_r('],'); | |
} | |
?> | |
]; | |
var options = { | |
grid: { hoverable: true, clickable: true, color:"#007AFF" }, | |
xaxis: { mode: "time", font: { color: "#099eff" } }, | |
yaxis: { tickDecimals: "2", font: { color: "#099eff" }}, | |
lines: { show:true }, | |
legend: { position: "nw" }, | |
points: { | |
radius:2, | |
show:true | |
}, | |
colors: ["#FF5E3A","#FF9500","#FFDB4C", "#87FC70", "#007AFF", "#1AD6FD", "#C644FC", "#EF4DB6", "#5856D6" ] | |
}; | |
var rankOptions = { | |
grid: { hoverable: true, clickable: true, color:"#007AFF" }, | |
xaxis: { mode: "time", font: { color: "#099eff" } }, | |
yaxis: { tickDecimals: "2", | |
transform: function (v) { return -v; }, | |
inverseTransform: function (v) { return -v; }, | |
font: { color: "#099eff" }, | |
}, | |
lines: { show:true }, | |
legend: { position: "nw", container: $("#flot-top-grossing-legend") }, | |
points: { | |
radius:2, | |
show:true | |
}, | |
colors: ["#FF5E3A","#FF9500","#FFDB4C", "#87FC70", "#007AFF", "#1AD6FD", "#C644FC", "#EF4DB6", "#5856D6", "#FF1300" ] | |
}; | |
function showTooltip(x, y, contents) { | |
$('<div id="tooltip">' + contents + '</div>').css( { | |
position: 'absolute', | |
display: 'none', | |
top: y + 5, | |
left: x + 5, | |
border: '1px solid #fdd', | |
padding: '2px', | |
'background-color': '#fee', | |
opacity: 0.80 | |
}).appendTo("body").fadeIn(200); | |
} | |
function bindPlotHover(chartId) | |
{ | |
var previousPoint = null; | |
$(chartId).bind("plothover", function (event, pos, item) { | |
$("#x").text(pos.x.toFixed(2)); | |
$("#y").text(pos.y.toFixed(2)); | |
if (item) { | |
if (previousPoint != item.dataIndex) { | |
previousPoint = item.dataIndex; | |
$("#tooltip").remove(); | |
var x = item.datapoint[0].toFixed(2), | |
y = item.datapoint[1].toFixed(2); | |
showTooltip(item.pageX, item.pageY, | |
item.series.label + " = " + y); | |
} | |
} | |
else { | |
$("#tooltip").remove(); | |
previousPoint = null; | |
} | |
}); | |
} | |
bindPlotHover("#flot-last-14days-placeholder"); | |
bindPlotHover("#flot-all-ttm-placeholder"); | |
bindPlotHover("#flot-t7d-placeholder"); | |
$.plot("#flot-last-14days-placeholder", [{label:"$", data:last_14days}], options); | |
$.plot("#flot-all-ttm-placeholder", [{label:"$", data:all_ttm}], options); | |
$.plot("#flot-t7d-placeholder",[{label:"All", data:all_t7d}], options); | |
}); | |
</script> | |
</head> | |
<body bgcolor="#FFFFFF"> | |
<div style="width:50%;float:left"> | |
<h2>Sales Daily Net Revenue</h2> | |
<div id="flot-last-14days-placeholder" class="half-chart"></div> | |
</div> | |
<br style="clear:both;"/> | |
<p></p> | |
<h2>Net Revenue Trailing 7 Days</h2> | |
<div id="flot-t7d-placeholder" class="large-chart"></div> | |
<h2>Revenue Trailing Year</h2> | |
<div id="flot-all-ttm-placeholder" class="large-chart"></div> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment