Skip to content

Instantly share code, notes, and snippets.

@malatx
Last active January 28, 2018 06:21
Show Gist options
  • Save malatx/b7f33289d37442c918a5 to your computer and use it in GitHub Desktop.
Save malatx/b7f33289d37442c918a5 to your computer and use it in GitHub Desktop.
Sample Indie App Dashboard via AppFigures Data
<?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