Skip to content

Instantly share code, notes, and snippets.

@JhumanJ
Last active March 12, 2023 22:04
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save JhumanJ/5f98aac8a6b5c2f0b44324ff2b0d7282 to your computer and use it in GitHub Desktop.
Save JhumanJ/5f98aac8a6b5c2f0b44324ff2b0d7282 to your computer and use it in GitHub Desktop.
class LineChart {
// LineChart by https://kevinkub.de/
constructor(width, height, values) {
this.ctx = new DrawContext();
this.ctx.size = new Size(width, height);
this.values = values;
}
_calculatePath() {
let maxValue = Math.max(...this.values);
let minValue = Math.min(...this.values);
let difference = maxValue - minValue;
let count = this.values.length;
let step = this.ctx.size.width / (count - 1);
let points = this.values.map((current, index, all) => {
let x = step*index;
let y = this.ctx.size.height - (current - minValue) / difference * this.ctx.size.height;
return new Point(x, y);
});
return this._getSmoothPath(points);
}
_getSmoothPath(points) {
let path = new Path();
path.move(new Point(0, this.ctx.size.height));
path.addLine(points[0]);
for(let i = 0; i < points.length-1; i++) {
let xAvg = (points[i].x + points[i+1].x) / 2;
let yAvg = (points[i].y + points[i+1].y) / 2;
let avg = new Point(xAvg, yAvg);
let cp1 = new Point((xAvg + points[i].x) / 2, points[i].y);
let next = new Point(points[i+1].x, points[i+1].y);
let cp2 = new Point((xAvg + points[i+1].x) / 2, points[i+1].y);
path.addQuadCurve(avg, cp1);
path.addQuadCurve(next, cp2);
}
path.addLine(new Point(this.ctx.size.width, this.ctx.size.height));
path.closeSubpath();
return path;
}
configure(fn) {
let path = this._calculatePath();
if(fn) {
fn(this.ctx, path);
} else {
this.ctx.addPath(path);
this.ctx.fillPath(path);
}
return this.ctx;
}
}
// Load MRR
// https://gist.github.com/daolf/ae104b1ab7cabf564b47770c88d4214b
const API_TOKEN = "PROFITWELL_API_TOKEN"
// Recreating a basic auth with Scriptable lib
const currentYear = new Date().getFullYear();
const tempMonth = new Date().getMonth() + 1
const currentDate = new Date().getDate()
const currentMonth = (tempMonth < 10 ? '0' + tempMonth : tempMonth);
const date = `${currentYear}-${currentMonth}`
const endpoint = `https://api.profitwell.com/v2/metrics/daily/?metrics=recurring_revenue,active_customers&month=${date}`
async function loadItems() {
let at = endpoint
let req = new Request(at)
req.headers = {"Authorization" : API_TOKEN}
let response = await req.loadJSON()
return response
}
function kFormatter(num) {
return Math.abs(num) > 999 ? Math.sign(num)*((Math.abs(num)/1000).toFixed(1)) + 'k' : Math.sign(num)*Math.abs(num)
}
let json = await loadItems()
let MRR = json["data"]["recurring_revenue"][json["data"]["recurring_revenue"].length - 1]['value']
const ARR = kFormatter(MRR*12)
let absoluteChange = Math.floor(MRR - json["data"]["recurring_revenue"][0]['value']).toString()
let percentChange = (absoluteChange*100/MRR).toFixed(1).toString()
let dailyAbsoluteChange = currentDate >= 2 ? Math.floor(MRR - json["data"]["recurring_revenue"][currentDate - 2]['value']).toString() : null
MRR = Math.floor(MRR)
let nbActiveCustomers = json["data"]["active_customers"][currentDate - 1]['value']
const today = new Date();
today.setHours(23, 59, 0, 0);
const data = []
for (const val of json["data"]["recurring_revenue"]){
const date = new Date(val.date)
if (date <= today ) {
data.push(val.value)
}
}
let widget = new ListWidget();
let chart = new LineChart(400, 400, data).configure((ctx, path) => {
ctx.opaque = false;
ctx.setFillColor(new Color("8cc5ff", .5));
ctx.addPath(path);
ctx.fillPath(path);
}).getImage();
widget.addText(`MRR $${MRR.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}`);
widget.addText(`ARR $${ARR.toString()}`);
// display (% change)
widget.addSpacer(10)
if (absoluteChange>=0) {
let changeText = widget.addText(`+$${absoluteChange.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")} (${percentChange}%) this month`);
changeText.textColor = Color.green()
changeText.font = Font.semiboldSystemFont(10)
} else {
let changeText = widget.addText(`$${Math.abs(absoluteChange).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")} (-${percentChange}%) this month`);
changeText.textColor = Color.red()
changeText.font = Font.semiboldSystemFont(10)
}
// Daily change
widget.addSpacer(4)
if (dailyAbsoluteChange!== null) {
if (dailyAbsoluteChange>=0) {
let dailyChangeText = widget.addText(`+$${dailyAbsoluteChange.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")} today`);
if (dailyAbsoluteChange>0){
dailyChangeText.textColor = Color.green()
}
dailyChangeText.font = Font.semiboldSystemFont(10)
} else {
let dailyChangeText = widget.addText(`-$${Math.abs(dailyAbsoluteChange).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")} today`);
dailyChangeText.textColor = Color.red()
dailyChangeText.font = Font.semiboldSystemFont(10)
}
}
widget.addSpacer(4)
let activeCustomerText = widget.addText(`${nbActiveCustomers} customers`);
activeCustomerText.font = Font.semiboldSystemFont(10)
activeCustomerText.textColor = Color.white()
widget.addSpacer();
widget.backgroundImage = chart;
Script.setWidget(widget);
if (!config.runsInWidget) {
await widget.presentSmall();
}
Script.complete();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment