Compare commits

...

2 Commits

Author SHA1 Message Date
Sam 3f3f27d4c8 Major refactor of charts 2024-08-14 19:48:58 +01:00
Sam a0796dacc5 Change api endpoints to use apiURL 2024-08-14 17:59:38 +01:00
17 changed files with 277 additions and 77 deletions

View File

@ -1,11 +1,42 @@
from flask import Flask, jsonify, request, json, Response, send_from_directory, abort
from flask import Flask, g, jsonify, request, json, Response, send_from_directory, abort
from flask_cors import CORS
import orjson, os
import datetime
import time
app = Flask(__name__)
CORS(app)
FILES_DIRECTORY = '../data/'
today = datetime.datetime.today()
@app.before_request
def start_timer():
g.start = time.time()
@app.after_request
def log(response):
now = time.time()
duration = round(now - g.start, 4)
dt = datetime.datetime.fromtimestamp(now).strftime('%Y-%m-%d %H:%M:%S')
log_entry = {
'timestamp': dt,
'duration': duration,
'method': request.method,
'url': request.url,
'status': response.status_code,
'remote_addr': request.remote_addr,
'user_agent': request.user_agent.string,
}
log_line = ','.join(f'{key}={value}' for key, value in log_entry.items())
with open('api_logs.txt', 'a') as f:
f.write(log_line + '\n')
return response
@app.route('/bitcoin_business_growth_by_country', methods=['GET'])
def business_growth():
@ -29,8 +60,18 @@ def business_growth():
countries = [name.strip() for name in country_names.split(",")]
filtered_data = [item for item in filtered_data if item['country_name'] in countries]
if cumulative_period_type:
filtered_data = [item for item in filtered_data if item['cumulative_period_type'] == cumulative_period_type]
if cumulative_period_type == "1 day":
delta = today - datetime.timedelta(days=2)
filtered_data = [item for item in filtered_data if item['cumulative_period_type'] == cumulative_period_type and delta <= datetime.datetime.strptime(item['date'], '%Y-%m-%d')]
elif cumulative_period_type == "7 day":
delta = today - datetime.timedelta(days=7)
filtered_data = [item for item in filtered_data if item['cumulative_period_type'] == cumulative_period_type and delta <= datetime.datetime.strptime(item['date'], '%Y-%m-%d')]
elif cumulative_period_type == "28 day":
delta = today - datetime.timedelta(days=28)
filtered_data = [item for item in filtered_data if item['cumulative_period_type'] == cumulative_period_type and delta <= datetime.datetime.strptime(item['date'], '%Y-%m-%d')]
elif cumulative_period_type == "365 day":
delta = today - datetime.timedelta(days=365)
filtered_data = [item for item in filtered_data if item['cumulative_period_type'] == cumulative_period_type and delta <= datetime.datetime.strptime(item['date'], '%Y-%m-%d')]
# Sort by date
sorted_data = sorted(filtered_data, key=lambda x: x['date'], reverse=True)
@ -40,14 +81,36 @@ def business_growth():
@app.route('/get_json/<filename>', methods=['GET'])
def get_json(filename):
period = request.args.get('period')
file_path = os.path.join(FILES_DIRECTORY, filename)
if not os.path.isfile(file_path):
abort(404)
with open(file_path, 'r') as file:
data = json.load(file)
with open(file_path, 'r') as f:
data = orjson.loads(f.read())
return jsonify(data)
if period == "last 7 days":
delta = today - datetime.timedelta(days=7)
filtered_data = [item for item in data if delta <= datetime.datetime.strptime(item['date'], '%Y-%m-%d') <= today]
sorted_data = sorted(filtered_data, key=lambda x: x['date'])
elif period == "last 28 days":
delta = today - datetime.timedelta(days=28)
filtered_data = [item for item in data if delta <= datetime.datetime.strptime(item['date'], '%Y-%m-%d') <= today]
sorted_data = sorted(filtered_data, key=lambda x: x['date'])
elif period == "last 365 days":
delta = today - datetime.timedelta(days=365)
filtered_data = [item for item in data if delta <= datetime.datetime.strptime(item['date'], '%Y-%m-%d') <= today]
sorted_data = sorted(filtered_data, key=lambda x: x['date'])
elif period == "last 2 years":
delta = today - datetime.timedelta(days=730)
filtered_data = [item for item in data if delta <= datetime.datetime.strptime(item['date'], '%Y-%m-%d') <= today]
sorted_data = sorted(filtered_data, key=lambda x: x['date'])
else:
sorted_data = sorted(data, key=lambda x: x['date'])
return jsonify(sorted_data)
@app.route('/download/<filename>', methods=['GET'])
def download_file(filename):

View File

@ -11,7 +11,7 @@ tags: ["QGIS", "SRTM", "DEM", "Raster", "download"]
{{< figure src="/pics/blog/batch-import-postgis-rasters/singapore-final.webp" width="300">}}
Download the Digital Elevation Model featured in [this](/blog/batch-import-postgis-rasters/) blog.
{{< download-data src="https://api.baseddata.io/download/singapore-srtm-dem.tif" name="singapore-srtm-dem.tif" >}}
{{< download-data src="/singapore-srtm-dem.tif" name="singapore-srtm-dem.tif" >}}
#### Citations
NASA Shuttle Radar Topography Mission (SRTM)(2013). Shuttle Radar Topography Mission (SRTM) Global. Distributed by OpenTopography. https://doi.org/10.5069/G9445JDF. Accessed: 2024-08-06

View File

@ -8,13 +8,12 @@ summary: "This analysis uses OpenStreetMaps data to chart the yearly growth of b
tags: ["Bitcoin", "Stats", "OpenStreetMaps"]
---
The table below illustrates growth of businesses worldwide that accept bitcoin as payment for products or services. The accompanying chart displays the yearly cumulative number of bitcoin-accepting businesses for the countries selected in the table. The data is sourced from OpenStreetMaps and is updated approximately every 2 hours.
The table below illustrates growth of businesses worldwide that accept bitcoin as payment for products or services. The accompanying chart displays the cumulative number of bitcoin-accepting businesses for the countries selected in the table. The data is sourced from OpenStreetMaps and is updated approximately every 2 hours.
You can select the growth period of interest from the drop-down, which updates the values in the table. The table shows the ***Previous*** value, which was the number of businesses *n* days ago specified in the drop-down. The ***Current*** value is the number of businesses as of the latest update. The table also shows the ***Absolute Difference***, and the ***Percent Difference*** between this period.
The chart always reflects the countries selected in the table. There is a zoom feature on the chart to focus in on a period of interest.
The chart always reflects the countries selected in the table.
<br/>
Select growth period: {{< dropdown-filter id=cumulative_period_type select="365 day,28 day,7 day,1 day" >}}
{{< chart src="/js/bitcoin-business-growth-chart.js" >}}
{{< table src="/js/bitcoin-business-growth-table.js" >}}

View File

@ -1,7 +1,10 @@
baseURL = 'https://baseddata.io/'
baseURL = 'baseddata.io'
languageCode = 'en-gb'
title = 'Based Data'
[params]
apiURL = 'http://localhost:5000'
[markup.highlight]
pygmentsUseClasses = false
codeFences = true

View File

@ -1,7 +1,7 @@
<script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.min.js"></script>
<section class="chart-container">
<div id="chart-modifiers"></div>
<div id="chart">
<canvas id="{{ .id }}"></canvas>
<script src="{{ .src }}"></script>
</div>
</section>

View File

@ -4,7 +4,7 @@
<p id="price"></p>
<script>
fetch("https://api.baseddata.io/get_json/final__price.json")
fetch(`${"{{ .Site.Params.apiURL }}"}/get_json/final__price.json`)
.then((response) => response.json())
.then((data) => {
var lastElement = data[data.length - 1];

View File

@ -1,2 +1,5 @@
{{ $id := .Get "src" | md5 }}
{{ partial "chart.html" (dict "src" (.Get "src") "id" $id) }}
<script>
const apiURL = "{{ .Site.Params.apiURL }}";
</script>
{{ $id := .Get "src" | md5 }} {{ partial "chart.html" (dict "src" (.Get "src")
"id" $id) }}

View File

@ -5,7 +5,7 @@
<script>
async function downloadFile(url, name) {
try {
const response = await fetch(url);
const response = await fetch(`${"{{ .Site.Params.apiURL }}"}/download${url}`);
if (!response.ok) {
throw new Error('Network response was not ok');
}

View File

@ -1,7 +0,0 @@
<select id={{ .Get "id" }} class="dropdownFilter">
{{ with .Get "select" }}
{{ range $index, $element := split . "," }}
<option value="{{ $element }}">{{ $element }}</option>
{{ end }}
</select>
{{ end }}

View File

@ -16,4 +16,9 @@
margin-top: 20px;
display: flex;
justify-content: center;
flex-direction: column;
}
#chart-modifiers {
display: flex;
}

View File

@ -1,9 +1,14 @@
let chartData = [];
let myChart;
async function fetchDataForChart(str) {
let periodIndex = 3;
const periods = ["1 day", "7 day", "28 day", "365 day"];
async function fetchDataForChart(str, period) {
try {
const apiEndpoint = `https://api.baseddata.io/bitcoin_business_growth_by_country?cumulative_period_type=365 day&countries=${str}`;
const apiEndpoint = `${apiURL}/bitcoin_business_growth_by_country?cumulative_period_type=${period}&countries=${str}`;
console.log("Fetching from " + apiEndpoint);
const response = await fetch(apiEndpoint);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
@ -69,30 +74,35 @@ function handleCheckboxChange(event) {
if (event.target.type === "checkbox") {
const boxChecked = event.target.checked;
const boxId = event.target.id;
let selectedPeriod = getPeriodFromDropdown();
// Remove unchecked boxes
if (boxChecked === false) {
delete chartData[boxId];
updateChart();
// Add checked boxes
} else {
fetchDataForChart(boxId);
fetchDataForChart(boxId, selectedPeriod);
}
}
}
document.addEventListener("reloadTable", () => {
myChart = echarts.init(document.getElementById("chart"));
jsonTableCheckedBoxes = document.querySelectorAll(
'#jsonTableContainer input[type="checkbox"]:checked',
);
const checkedBoxes = Array.from(jsonTableCheckedBoxes).map(
(checkbox) => checkbox.id,
);
const str = checkedBoxes.join(",");
chartData = [];
fetchDataForChart(str);
document.addEventListener("DOMContentLoaded", function () {
periodDropdown(periods, periodIndex);
document.addEventListener("reloadTable", () => {
myChart = echarts.init(document.getElementById("chart"));
jsonTableCheckedBoxes = document.querySelectorAll(
'#jsonTableContainer input[type="checkbox"]:checked',
);
const checkedBoxes = Array.from(jsonTableCheckedBoxes).map(
(checkbox) => checkbox.id,
);
const str = checkedBoxes.join(",");
let selectedPeriod = getPeriodFromDropdown();
chartData = [];
fetchDataForChart(str, selectedPeriod);
jsonTable = document.getElementById("jsonTableContainer");
jsonTable.removeEventListener("change", handleCheckboxChange);
jsonTable.addEventListener("change", handleCheckboxChange);
jsonTable = document.getElementById("jsonTableContainer");
jsonTable.removeEventListener("change", handleCheckboxChange);
jsonTable.addEventListener("change", handleCheckboxChange);
});
});

View File

@ -1,12 +1,9 @@
async function fetchDataForTable() {
try {
const dropdown = document.querySelector(".dropdownFilter");
let selectedIndex = dropdown.selectedIndex;
let selectedValue = dropdown.options[selectedIndex].value;
const apiEndpoint =
"https://api.baseddata.io/bitcoin_business_growth_by_country?latest_date=true";
selectedPeriod = getPeriodFromDropdown();
const apiEndpoint = `${apiURL}/bitcoin_business_growth_by_country?latest_date=true`;
const response = await fetch(
apiEndpoint + `&cumulative_period_type=${selectedValue}`,
apiEndpoint + `&cumulative_period_type=${selectedPeriod}`,
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
@ -89,10 +86,10 @@ function createTable(data) {
);
}
window.onload = () => {
const dropdown = document.querySelector(".dropdownFilter");
document.addEventListener("DOMContentLoaded", function () {
const dropdown = document.getElementById("select-period-dropdown");
fetchDataForTable();
dropdown.addEventListener("change", () => {
fetchDataForTable();
});
};
});

View File

@ -1,9 +1,18 @@
let dataArr = [];
const myChart = echarts.init(document.getElementById("chart"));
const filename = "final__price.json";
async function fetchDataForChart() {
const periods = [
"all time",
"last 7 days",
"last 28 days",
"last 365 days",
"last 2 years",
];
async function fetchDataForChart(selectedValue) {
try {
const apiEndpoint = "https://api.baseddata.io/get_json/final__price.json";
const apiEndpoint = `${apiURL}/get_json/${filename}?period=${selectedValue}`;
const response = await fetch(apiEndpoint);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
@ -27,7 +36,7 @@ function initEchart(dataArr) {
axisLine: axisLine,
},
grid: grid,
dataZoom: dataZoom(),
dataZoom: dataZoom(0),
yAxis: [
{
type: "value",
@ -62,4 +71,14 @@ function initEchart(dataArr) {
myChart.setOption(option);
}
fetchDataForChart();
document.addEventListener("DOMContentLoaded", function () {
let periodIndex = 2;
periodDropdown(periods, periodIndex);
fetchDataForChart(periods[periodIndex]);
const selectElement = document.getElementById("select-period-dropdown");
selectElement.addEventListener("change", function (event) {
const selectedValue = event.target.value;
fetchDataForChart(selectedValue);
});
});

View File

@ -106,6 +106,35 @@ function nFormatter(value, digits) {
: "0";
}
function periodDropdown(periods, defaultIndex = 0) {
const modifiers = document.getElementById("chart-modifiers");
const dropdownContainer = document.createElement("div");
const dropdown = document.createElement("select");
const textNode = document.createTextNode("Select period: ");
dropdownContainer.id = "dropdown-container";
dropdown.id = "select-period-dropdown";
dropdownContainer.appendChild(textNode);
dropdownContainer.appendChild(dropdown);
modifiers.appendChild(dropdownContainer);
periods.forEach((period, index) => {
const option = document.createElement("option");
if (index === defaultIndex) {
option.selected = true;
}
option.value = period;
option.textContent = period;
dropdown.appendChild(option);
});
}
function getPeriodFromDropdown() {
let dropdown = document.getElementById("select-period-dropdown");
let selectedIndex = dropdown.selectedIndex;
return dropdown.options[selectedIndex].value;
}
window.addEventListener("resize", function () {
if (myChart != null && myChart != undefined) {
myChart.resize();

View File

@ -1,10 +1,18 @@
let dataArr = [];
const myChart = echarts.init(document.getElementById("chart"));
const filename = "final__feerate_percentiles.json";
async function fetchDataForChart() {
const periods = [
"all time",
"last 7 days",
"last 28 days",
"last 365 days",
"last 2 years",
];
async function fetchDataForChart(selectedValue) {
try {
const apiEndpoint =
"https://api.baseddata.io/get_json/final__feerate_percentiles.json";
const apiEndpoint = `${apiURL}/get_json/${filename}?period=${selectedValue}`;
const response = await fetch(apiEndpoint);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
@ -39,7 +47,7 @@ function initEchart(dataArr) {
axisLine: axisLine,
},
grid: grid,
dataZoom: dataZoom((start = 98)),
dataZoom: dataZoom(0),
series: [
{
type: "line",
@ -105,4 +113,15 @@ function initEchart(dataArr) {
myChart.setOption(option);
}
fetchDataForChart();
document.addEventListener("DOMContentLoaded", function () {
let periodIndex = 2;
periodDropdown(periods, periodIndex);
fetchDataForChart(periods[periodIndex]);
const selectElement = document.getElementById("select-period-dropdown");
selectElement.addEventListener("change", function (event) {
const selectedValue = event.target.value;
fetchDataForChart(selectedValue);
});
});

View File

@ -1,10 +1,17 @@
let dataArr = [];
const myChart = echarts.init(document.getElementById("chart"));
const filename = "final__hashrate.json";
const periods = [
"all time",
"last 7 days",
"last 28 days",
"last 365 days",
"last 2 years",
];
async function fetchDataForChart() {
async function fetchDataForChart(selectedValue) {
try {
const apiEndpoint =
"https://api.baseddata.io/get_json/final__hashrate.json";
const apiEndpoint = `${apiURL}/get_json/${filename}?period=${selectedValue}`;
const response = await fetch(apiEndpoint);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
@ -33,7 +40,7 @@ function initEchart(dataArr) {
axisLine: axisLine,
},
grid: grid,
dataZoom: dataZoom(),
dataZoom: dataZoom(0),
yAxis: [
{
type: "value",
@ -106,4 +113,15 @@ function initEchart(dataArr) {
myChart.setOption(option);
}
fetchDataForChart();
document.addEventListener("DOMContentLoaded", function () {
let periodIndex = 2;
periodDropdown(periods, periodIndex);
fetchDataForChart(periods[periodIndex]);
const selectElement = document.getElementById("select-period-dropdown");
selectElement.addEventListener("change", function (event) {
const selectedValue = event.target.value;
fetchDataForChart(selectedValue);
});
});

View File

@ -1,10 +1,17 @@
let dataArr = [];
const myChart = echarts.init(document.getElementById("chart"));
const filename = "final__miner_rewards.json";
const periods = [
"all time",
"last 7 days",
"last 28 days",
"last 365 days",
"last 2 years",
];
async function fetchDataForChart() {
async function fetchDataForChart(selectedValue) {
try {
const apiEndpoint =
"https://api.baseddata.io/get_json/final__miner_rewards.json";
const apiEndpoint = `${apiURL}/get_json/${filename}?period=${selectedValue}`;
const response = await fetch(apiEndpoint);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
@ -17,9 +24,37 @@ async function fetchDataForChart() {
}
function initEchart(dataArr) {
console.log(dataArr);
const option = {
backgroundColor: backgroundColor,
tooltip: tooltip,
tooltip: {
...tooltip,
formatter: function (params) {
let colors = ["#ee6666", "#000000", "#b0da9d", "#8699d5"];
let colorSpan = (color) =>
'<span style="display:inline-block;margin-right:1px;border-radius:5px;width:9px;height:9px;background-color:' +
color +
'"></span>';
let tooltip = "<p>" + params[0].axisValue + "</p>";
params.reverse().forEach((item, index) => {
let color = colors[index % colors.length];
let labels =
"<p>" +
colorSpan(color) +
" " +
item.seriesName +
": " +
nFormatter(item.data, 3);
("</p>");
tooltip += labels;
});
return tooltip;
},
valueFormatter(value, index) {
return nFormatter(value, 3);
},
},
toolbox: toolboxParams,
xAxis: {
data: dataArr.map((row) => row.date),
@ -28,13 +63,13 @@ function initEchart(dataArr) {
axisLine: axisLine,
},
grid: grid,
dataZoom: dataZoom(97),
dataZoom: dataZoom(0),
yAxis: [
{
type: "value",
name: "Rewards (USD)",
nameLocation: "middle",
nameGap: 35,
nameGap: 30,
nameTextStyle: textStyleMain,
position: "left",
alignTicks: true,
@ -52,7 +87,7 @@ function initEchart(dataArr) {
},
{
type: "value",
name: "Block Subsidy (BTC)",
name: "Subsidy (BTC)",
nameLocation: "middle",
nameGap: 20,
nameTextStyle: {
@ -75,8 +110,7 @@ function initEchart(dataArr) {
series: [
{
type: "line",
name: "Daily Subsidy (USD)",
smooth: true,
name: "Subsidy (USD)",
stack: "Total",
areaStyle: {},
symbol: "none",
@ -87,8 +121,7 @@ function initEchart(dataArr) {
},
{
type: "line",
name: "Daily Fees (USD)",
smooth: true,
name: "Fees (USD)",
stack: "Total",
areaStyle: {},
symbol: "none",
@ -99,8 +132,7 @@ function initEchart(dataArr) {
},
{
type: "line",
name: "Daily Total Reward (USD)",
smooth: true,
name: "Total (USD)",
symbol: "none",
lineStyle: {
width: 1,
@ -124,4 +156,14 @@ function initEchart(dataArr) {
myChart.setOption(option);
}
fetchDataForChart();
document.addEventListener("DOMContentLoaded", function () {
let periodIndex = 2;
periodDropdown(periods, periodIndex);
fetchDataForChart(periods[periodIndex]);
const selectElement = document.getElementById("select-period-dropdown");
selectElement.addEventListener("change", function (event) {
const selectedValue = event.target.value;
fetchDataForChart(selectedValue);
});
});