Compare commits
64 Commits
ecdc63857f
...
6f5ca2dbda
Author | SHA1 | Date |
---|---|---|
Sam | 6f5ca2dbda | |
Sam | 67f1bb2c80 | |
Sam | bbd906de4b | |
Sam | 83d458f39d | |
Sam | 0e5119c6fd | |
Sam | 032b1616dc | |
Sam | 198ab2a1f8 | |
Sam | 1ef277edc5 | |
Sam | ed011152b2 | |
Sam | 69158336bf | |
Sam | 11c8c73319 | |
Sam | bbada45e84 | |
Sam | 36beefd0dd | |
Sam | 28ad68cf46 | |
Sam | ddc1de0909 | |
Sam | ad05385853 | |
Sam | 2653beec5c | |
Sam | 26c3aa3d73 | |
Sam | 8d8bd905a6 | |
Sam | 6bc310acbf | |
Sam | 15e8477605 | |
Sam | fd861d9bf4 | |
Sam | 87df76ffdd | |
Sam | 4f6d67a6ba | |
Sam | 6296292179 | |
Sam | 12e2fc2fe1 | |
Sam | 7489fd3f90 | |
Sam | 420c04f916 | |
Sam | 3f45a98600 | |
Sam | e0294ccf06 | |
Sam | 87727c3b3d | |
Sam | 36d489eb8c | |
Sam | 8f50994a67 | |
Sam | b34e05998c | |
Sam | 4ed12e10e6 | |
Sam | 01c75a7ac7 | |
Sam | 407880a2de | |
Sam | 37835808a0 | |
Sam | 04fcadc4bf | |
Sam | 3bb281ec25 | |
Sam | e435760be9 | |
Sam | 4dde8c9842 | |
Sam | 9d5247306d | |
Sam | 607aadcabb | |
Sam | ed6f41e9d8 | |
Sam | ab4b919b16 | |
Sam | 2f037af4d6 | |
Sam | cca70412c6 | |
Sam | 5405fc1b8b | |
Sam | bfc86487ed | |
Sam | 2ac0c2c144 | |
Sam | cf40c32443 | |
Sam | 284b5469ce | |
Sam | 4f6efe42c1 | |
Sam | 8c3133e6da | |
Sam | 5769d7cca0 | |
Sam | 483418af7d | |
Sam | 1447f6056c | |
Sam | 10666a91ad | |
Sam | 57864ee00d | |
Sam | 14cf9f8fce | |
Sam | 4a202ef136 | |
Sam | 5b8d0e08bc | |
Sam | c014001c83 |
|
@ -1,10 +1,12 @@
|
|||
from flask import Flask, request, json, Response
|
||||
from flask import Flask, jsonify, request, json, Response, send_from_directory, abort
|
||||
from flask_cors import CORS
|
||||
import orjson
|
||||
import orjson, os
|
||||
|
||||
app = Flask(__name__)
|
||||
CORS(app)
|
||||
|
||||
FILES_DIRECTORY = '../data/'
|
||||
|
||||
@app.route('/bitcoin_business_growth_by_country', methods=['GET'])
|
||||
def business_growth():
|
||||
# Parse args from request
|
||||
|
@ -36,45 +38,26 @@ def business_growth():
|
|||
# Return json
|
||||
return Response(json.dumps(sorted_data), mimetype='application/json')
|
||||
|
||||
@app.route('/price', methods=['GET'])
|
||||
def price():
|
||||
@app.route('/get_json/<filename>', methods=['GET'])
|
||||
def get_json(filename):
|
||||
file_path = os.path.join(FILES_DIRECTORY, filename)
|
||||
if not os.path.isfile(file_path):
|
||||
abort(404)
|
||||
|
||||
# Open json locally
|
||||
with open('../data/final__price.json', 'rb') as f:
|
||||
data = orjson.loads(f.read())
|
||||
with open(file_path, 'r') as file:
|
||||
data = json.load(file)
|
||||
|
||||
# Return json
|
||||
return Response(json.dumps(data), mimetype='application/json')
|
||||
return jsonify(data)
|
||||
|
||||
@app.route('/miner_rewards', methods=['GET'])
|
||||
def miner_rewards():
|
||||
@app.route('/download/<filename>', methods=['GET'])
|
||||
def download_file(filename):
|
||||
try:
|
||||
return send_from_directory(FILES_DIRECTORY, filename, as_attachment=True)
|
||||
except FileNotFoundError:
|
||||
abort(404)
|
||||
|
||||
# Open json locally
|
||||
with open('../data/final__miner_rewards.json', 'rb') as f:
|
||||
data = orjson.loads(f.read())
|
||||
|
||||
# Return json
|
||||
return Response(json.dumps(data), mimetype='application/json')
|
||||
|
||||
@app.route('/hashrate', methods=['GET'])
|
||||
def hashrate():
|
||||
|
||||
# Open json locally
|
||||
with open('../data/dev/final__hashrate.json', 'rb') as f:
|
||||
data = orjson.loads(f.read())
|
||||
|
||||
# Return json
|
||||
return Response(json.dumps(data), mimetype='application/json')
|
||||
|
||||
@app.route('/feerates', methods=['GET'])
|
||||
def feerates():
|
||||
|
||||
# Open json locally
|
||||
with open('../data/final__feerate_percentiles.json', 'rb') as f:
|
||||
data = orjson.loads(f.read())
|
||||
|
||||
# Return json
|
||||
return Response(json.dumps(data), mimetype='application/json')
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run()
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
[Unit]
|
||||
Description=Gunicorn instance to serve bitlab21.com
|
||||
Description=Gunicorn instance to serve baseddata.io
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=admin
|
||||
Group=www-data
|
||||
WorkingDirectory=/var/www/bitlab21.com/backend
|
||||
Environment="PATH=/var/www/bitlab21.com/.venv/bin"
|
||||
ExecStart=/var/www/bitlab21.com/.venv/bin/gunicorn --workers 4 --bind unix:bitlab21.sock -m 007 app:app
|
||||
WorkingDirectory=/var/www/baseddata.io/backend
|
||||
Environment="PATH=/var/www/baseddata.io/.venv/bin"
|
||||
ExecStart=/var/www/baseddata.io/.venv/bin/gunicorn --workers 4 --bind unix:baseddata.sock -m 007 app:app
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
|
@ -1,43 +1,28 @@
|
|||
# Bitlab21
|
||||
---
|
||||
toc: False
|
||||
---
|
||||
|
||||
Welcome to Bitlab21! My name is Sam Chance, and this is my personal site.
|
||||
# Grounded Insights from Open Data
|
||||
|
||||
I host various content here that interests me.
|
||||
This site is currently under construction. The direction I intend for this site to take is as a place to publish analytical insights derived from open data. I have a page called [Data Lab](/data-lab) where I publish interactive analytical dashboards, a blog to publish tutorials and thoughts on various related topics, and a [data downloads](/data-download) page for data access.
|
||||
|
||||
Feel free to look around. If you have any questions, then you can email me on *contact@sjplab.com*.
|
||||
I'm interested in a broad set of topics such as Linux, analytics and data engineering, GIS and bitcoin. So expect to see content related to these topics published on this site.
|
||||
|
||||
More about my professional life [here](/cv).
|
||||
I strongly believe in the philosophy of [Free Software](https://www.gnu.org/philosophy/free-sw.html) and [Open Data](https://en.wikipedia.org/wiki/Open_data). Therefore, all material on this site is released into the public domain unless otherwise specified. For more information, see [here](/license).
|
||||
|
||||
This site is still very much under construction.
|
||||
### Explore Based Data
|
||||
|
||||
### Shortcuts to some of my work:
|
||||
|
||||
[Bitcoin metrics](/bitcoin) A selection of charts and metrics obtained from public data
|
||||
|
||||
[Semita Maps](https://semitamaps.com) A free map printing service based on Openstreetmaps
|
||||
|
||||
[Code base](https://git.bitlab21.com) My personal self-hosted git repo using Gitea
|
||||
|
||||
[Nixos config](https://git.bitlab21.com/sam/nixos) My Nixos configuration
|
||||
|
||||
[Recipes](/recipes) My personal recipe book
|
||||
|
||||
### Software I use:
|
||||
|
||||
[**Neovim**](https://neovim.io/) for text editing (my neovim config is part of my [nixos](https://git.bitlab21.com/sam/nixos) configuration)
|
||||
|
||||
I use **Linux** on all my machines
|
||||
|
||||
**Nixos** and **Arch Linux** are my Linux distros of choice
|
||||
|
||||
[**dwm**](https://dwm.suckless.org) is my window manager (my dwm [config](https://git.bitlab21.com/sam/dwm))
|
||||
|
||||
[**st**](https://st.suckless.org) is my terminal emulator (my st [config](https://git.bitlab21.com/sam/st))
|
||||
|
||||
I use [**QGIS**](https://www.qgis.org/) and [**Postgis**](https://postgis.net/) for geospatial work
|
||||
|
||||
[**DBT**](https://github.com/dbt-labs/dbt-core) for data modelling
|
||||
|
||||
[**Postgres**](https://www.postgresql.org/) is my relational database of choice to power my backends
|
||||
|
||||
[**Hugo**](https://gohugo.io/) to build this [**website**](https://git.bitlab21.com/sam/bitlab21.com)
|
||||
{{< content-list >}}
|
||||
["Data Lab"]
|
||||
pic = "/pics/home/data-lab.webp"
|
||||
content = "Analysis and Insights derived from Open Data"
|
||||
link = "/data-lab"
|
||||
["Blog"]
|
||||
pic = "/pics/home/blog.webp"
|
||||
content = "Sharing methodologies and discoveries with the community."
|
||||
link = "/blog"
|
||||
["Services"]
|
||||
pic = "/pics/home/services.webp"
|
||||
content = "Please reach out if you wish to work with me."
|
||||
link = "/about-me"
|
||||
{{</ content-list >}}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
title: 'About Me'
|
||||
header_image: '/pics/blog/batch-import-postgis-rasters/batch-import-postgis-rasters.webp'
|
||||
summary: "This article explains how to batch import rasters into a PostGIS table"
|
||||
toc: false
|
||||
---
|
||||
|
||||
{{< figure src="/sam.webp" title=" " width="150">}}
|
||||
|
||||
My name is Sam Chance, and this is my personal site.
|
||||
|
||||
I'm an independent Analytics Engineer with a passion for working with data. I built this website as a place to publish my personal work.
|
||||
|
||||
If you wish to work with me, then please email me on *contact@sjplab.com*.
|
||||
|
||||
More about my professional life [here](/cv).
|
||||
|
||||
|
||||
### Shortcuts to some of my work:
|
||||
|
||||
- [Based Data Lab](/data-lab) A selection of charts and metrics obtained from public data
|
||||
|
||||
- [Map Printing Service](https://semitamaps.com) A free map printing service based on Openstreetmaps
|
||||
|
||||
- [Code base](https://git.bitlab21.com) My personal self-hosted git repo using Gitea
|
||||
|
||||
- [Nixos config](https://git.bitlab21.com/sam/nixos) My Nixos configuration
|
||||
|
||||
### Software I use:
|
||||
|
||||
- [**Neovim**](https://neovim.io/) for text editing (my neovim config is part of my [nixos](https://git.bitlab21.com/sam/nixos) configuration)
|
||||
|
||||
- I use **Linux** on all my machines
|
||||
|
||||
- **Nixos** and **Arch Linux** are my Linux distros of choice
|
||||
|
||||
- [**dwm**](https://dwm.suckless.org) is my window manager (my dwm [config](https://git.bitlab21.com/sam/dwm))
|
||||
|
||||
- [**st**](https://st.suckless.org) is my terminal emulator (my st [config](https://git.bitlab21.com/sam/st))
|
||||
|
||||
- I use [**QGIS**](https://www.qgis.org/) and [**Postgis**](https://postgis.net/) for geospatial work
|
||||
|
||||
- [**DBT**](https://github.com/dbt-labs/dbt-core) for data modelling
|
||||
|
||||
- [**Postgres**](https://www.postgresql.org/) is my relational database of choice to power my backends
|
||||
|
||||
- [**Hugo**](https://gohugo.io/) to build this [**website**](https://git.bitlab21.com/sam/bitlab21.com)
|
|
@ -0,0 +1,226 @@
|
|||
---
|
||||
title: 'Import Global SRTM Elevation Data Into Postgres Database Using PostGIS'
|
||||
date: 2024-08-06T12:15:44+01:00
|
||||
author:
|
||||
name: "Sam Chance"
|
||||
header_image: '/pics/blog/batch-import-postgis-rasters/singapore-final.webp'
|
||||
summary: "This article explains how to batch import rasters into a Postgres database. It also covers the use of PostGIS functions to query these rasters and create new ones. These techniques are particularly useful for applications such as QGIS."
|
||||
toc: true
|
||||
tags: ["QGIS", "PostGIS", "Database", "Raster"]
|
||||
---
|
||||
|
||||
## TLDR
|
||||
In this article, we walk through the process of obtaining and downloading rasters of global digital elevation data from the SRTM satellite.
|
||||
|
||||
We split these rasters into tiles and import them into a local Postgres database using PostGIS.
|
||||
|
||||
We then use tiles from this database to construct a query using PostGIS functions to generate a single DEM raster of Singapore and import this into QGIS.
|
||||
|
||||
The final image looks like this:
|
||||
|
||||
{{< figure src="/pics/blog/batch-import-postgis-rasters/singapore-final.webp" width="600">}}
|
||||
|
||||
## Introduction
|
||||
It's possible to download the entire SRTM (Shuttle Radar Topography Mission) satellite imagery dataset and insert it into a Postgres database for personal use. This can be helpful for if you need global elevation data for your analysis and don't want to be limited by third-parties APIs.
|
||||
|
||||
The SRTM is a near-global dataset of elevation data with a resolution of 1-arc-second (30m). More information is available from [USGS](https://www.usgs.gov/centers/eros/science/usgs-eros-archive-digital-elevation-shuttle-radar-topography-mission-srtm?qt-science_center_objects=0#qt-science_center_objects).
|
||||
In this guide we will go through the process of downloading the data, inserting it into a Postgres database with PostGIS, then querying the final result to create a DEM for any country or region.
|
||||
|
||||
This guide assumes you are using Linux (this may also apply to other Unix like systems such as MacOS) and have a Postgres database with the PostGIS extension installed. More information on how to do this can be found on the [PostGIS](https://postgis.net/documentation/getting_started/) website.
|
||||
|
||||
## Download data
|
||||
|
||||
We'll download the data from the [OpenTopography](https://portal.opentopography.org/raster?opentopoID=OTSRTM.082015.4326.1) AWS S3 bucket. You should create a free account with them before downloading. Also, remember to provide citations where necessary.
|
||||
|
||||
To download the data, first we need to install the [AWS](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) CLI utility:
|
||||
|
||||
{{< highlight shell >}}
|
||||
# install for linux
|
||||
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
|
||||
unzip awscliv2.zip
|
||||
sudo ./aws/install
|
||||
|
||||
# MacOS
|
||||
curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg"
|
||||
sudo installer -pkg AWSCLIV2.pkg -target /
|
||||
{{</ highlight >}}
|
||||
|
||||
Next we can run the following command to download the rasters. This will take some time depending on your internet speed. The final size is about 127GB and took me about 1 hour to download on a 600MB connection.
|
||||
|
||||
{{< highlight shell >}}
|
||||
aws s3 cp s3://raster/SRTM_GL1/ . --recursive --endpoint-url https://opentopography.s3.sdsc.edu --no-sign-request
|
||||
{{</ highlight >}}
|
||||
|
||||
If you'd prefer to download specific rasters or rasters for a region instead, you can checkout this [website](https://dwtkns.com/srtm30m/).
|
||||
|
||||
## Using raster2pgsql to import raster tiles into PostGIS
|
||||
Now we have the data downloaded on our system, we can import it into our database.
|
||||
If we look inside the SRTM_GL1_srtm directory, we can see all of the 14280 raster files:
|
||||
|
||||
{{< figure src="/pics/blog/batch-import-postgis-rasters/raster-tiffs.webp" width="600">}}
|
||||
|
||||
Next we will use the `raster2pgsql` command to import these rasters into our database.
|
||||
|
||||
{{< highlight shell >}}
|
||||
raster2pgsql -d -M -C -I -s 4326 -t 128x128 -F *.tif dem.srtm | psql --dbname=osm --username=postgres -h 127.0.0.1
|
||||
{{</ highlight >}}
|
||||
|
||||
Here's a breakdown of the above command:
|
||||
- `-d` This drops an existing table if it already exists. It's useful to include this in case we need to run the command again.
|
||||
- `-M` This runs `VACUUM ANALYZE` on the table after the raster tiles have been inserted into the database. This optimizes the table by reclaiming storage space and then collecting table statistics.
|
||||
- `-C` This applies raster constraints to the table by using the `AddRasterConstraints` function in PostGIS. This adds several types of constraints to the table which ensures data integrity and enforces rules for consistency.
|
||||
- `-I` Creates a spatial index (GiST) on the table.
|
||||
- `-s` Assign SRID (Spatial Reference System Identifier) to the raster.
|
||||
- `-t` Tile size. This splits the raster into tiles of the specified size. Each tile is a row in the final table.
|
||||
- `-F` Creates a filename column. As the raster is split into many smaller tiles, this is useful to help us identify the original file the tile belonged to.
|
||||
- ` *.tif` Selects all of the .tif files in the directory.
|
||||
- `dem.srtm` Defines the schema and name of the table. Remember to create the `dem` schema in the database before running the command.
|
||||
|
||||
The `raster2pgsql` command will output a sql query which we can then pipe into the `psql` command.
|
||||
|
||||
For more information on the `raster2pgsql` command, please visit the PostGIS [website](https://postgis.net/docs/manual-2.1/using_raster_dataman.html).
|
||||
|
||||
This process will take a couple of hours. It could take longer if running over a network, so it'd be best to run this command on the same machine as the Postgres instance.
|
||||
|
||||
Once this process has completed, we can query the table to see how many tiles we have:
|
||||
|
||||
{{< highlight sql >}}
|
||||
select count(*) from dem.srtm
|
||||
{{</ highlight >}}
|
||||
|
||||
{{< highlight plain-text >}}
|
||||
count
|
||||
---------
|
||||
12009480
|
||||
{{</ highlight >}}
|
||||
|
||||
So we have just over 12 million 128x128 tiles in the database.
|
||||
|
||||
## Raster Clipping
|
||||
|
||||
Now we have global SRTM data loaded in our local database, we can extract any of the tiles for further analysis and access this using any application.
|
||||
|
||||
Here I'll demonstrate using the following PostGIS commands: `st_clip`, `st_intersects` and `st_union` to create a digital elevation table for singapore and access this from from within QGIS.
|
||||
|
||||
I already have countries vector data in my Postgres database that I extracted from OpenStreetMaps. You can download a pg_dump of this [here](/data/countries.sql) to insert into your database. Alternatively, I have a post [here](/blogs/import-osm-countries-data) that explains the process to do this manually.
|
||||
|
||||
To create our table, we'll use the following SQL. I'll break down each section step-by-step.
|
||||
|
||||
First we'll use the `st_intersects` command to select all of the raster tiles that intersect with the Singapore polygon. Note, we'll need to create a table, spatial index then apply constraints for this to work in QGIS.
|
||||
|
||||
{{< highlight sql >}}
|
||||
-- drop existing table and create a new table
|
||||
drop table if exists dem.singapore_srtm;
|
||||
create table dem.singapore_srtm as
|
||||
|
||||
-- cte to create polygon of singapore
|
||||
with country as (select geom as geom from dev.countries where country = 'Singapore')
|
||||
|
||||
-- select tiles that intersect with the Singapore polygon using a left join and st_intersects function
|
||||
select srtm.rast, srtm.rid
|
||||
from dem.srtm srtm
|
||||
join country ply on st_intersects(ply.geom, st_convexhull(srtm.rast));
|
||||
|
||||
-- create index and apply constraints
|
||||
create index on "dem"."singapore_srtm" using gist (st_convexhull("rast"));
|
||||
analyze "dem"."singapore_srtm" ;
|
||||
select addrasterconstraints( 'dem', 'singapore_srtm', 'rast', true, true, true, true, true, true, false, true, true, true, true, true) ;
|
||||
{{</ highlight >}}
|
||||
|
||||
Now we can query the table:
|
||||
{{< highlight sql >}}
|
||||
-- check number of tiles in the table
|
||||
select count(*) from dem.singapore_srtm
|
||||
{{</ highlight >}}
|
||||
|
||||
{{< highlight plain-text >}}
|
||||
count
|
||||
-------
|
||||
153
|
||||
{{</ highlight >}}
|
||||
|
||||
Here we have 153 of our 128x128 tiles.
|
||||
|
||||
We can visualise these tiles in QGIS:
|
||||
{{< figure src="/pics/blog/batch-import-postgis-rasters/singapore-intersects.webp" width="600">}}
|
||||
|
||||
Note, you can use the `st_convexhull` command to generate an outline of the raster tiles as seen in the previous figure:
|
||||
|
||||
{{< highlight sql >}}
|
||||
select row_number() over () as uid, st_convexhull(rast) as geom
|
||||
from dem.singapore_srtm
|
||||
{{</ highlight >}}
|
||||
|
||||
It would be nice to remove the pixels outside of the Singapore polygon. For this we can use the `st_clip` function along with `st_intersect`:
|
||||
|
||||
{{< highlight sql >}}
|
||||
-- drop existing table and create a new table
|
||||
drop table if exists dem.singapore_srtm;
|
||||
create table dem.singapore_srtm as
|
||||
|
||||
-- cte to create polygon of singapore
|
||||
with country as (select geom as geom from dev.countries where country = 'Singapore'),
|
||||
|
||||
-- cte to clip tiles to the singapore polygon
|
||||
clipped_tiles as (
|
||||
select st_clip(srtm.rast, ply.geom, true) as rast, srtm.rid
|
||||
from dem.srtm srtm
|
||||
join country ply on st_intersects(ply.geom, st_convexhull(srtm.rast))
|
||||
)
|
||||
|
||||
select rast as rast
|
||||
from clipped_tiles;
|
||||
|
||||
-- create index and apply constraints
|
||||
create index on "dem"."singapore_srtm" using gist (st_convexhull("rast"));
|
||||
analyze "dem"."singapore_srtm" ;
|
||||
select addrasterconstraints( 'dem', 'singapore_srtm', 'rast', true, true, true, true, true, true, false, true, true, true, true, true) ;
|
||||
vacuum analyze "dem"."singapore_srtm";
|
||||
{{</ highlight >}}
|
||||
|
||||
Give us this:
|
||||
|
||||
{{< figure src="/pics/blog/batch-import-postgis-rasters/weird-clipping.webp" width="600">}}
|
||||
|
||||
Hmm, this doesn't look quite right! As you can see, the clipped tiles only seem to apply within the tile boundaries outside of the clip region. These areas have been set to `nodata` (which is expected). However, outside of that, QGIS is rendering those pixels as something else (it's rendering them as having a value of 0).
|
||||
|
||||
The reason for this is because we are not strictly importing a single raster into QGIS, rather we are still importing all of the indivitual tiles. Since GeoTIFFs are rasters that can only exist as a grid of pixels (i.e. a rectangle), QGIS must therefore render the entire raster extent, which leads to the unusual rendering issue above.
|
||||
|
||||
We can solve this by using the `st_union` function in our query. This takes all of the individual raster tiles and unions them into a single raster. All of the `nodata` values for the extent of the clipped region will also be unioned, thus creating a single rectangular raster that we can import into QGIS.
|
||||
|
||||
Our final query will look like this:
|
||||
|
||||
{{< highlight sql >}}
|
||||
-- drop existing table and create a new table
|
||||
drop table if exists dem.singapore_srtm;
|
||||
create table dem.singapore_srtm as
|
||||
|
||||
-- cte to create polygon of singapore
|
||||
with country as (select geom as geom from dev.countries where country = 'Singapore'),
|
||||
|
||||
-- cte to clip tiles to the singapore polygon
|
||||
clipped_tiles as (
|
||||
select st_clip(srtm.rast, ply.geom, true) as rast, srtm.rid
|
||||
from dem.srtm srtm
|
||||
join country ply on st_intersects(ply.geom, st_convexhull(srtm.rast))
|
||||
)
|
||||
|
||||
-- union tiles into a single raster
|
||||
select st_union(rast) as rast
|
||||
from clipped_tiles;
|
||||
|
||||
-- create index and apply constraints
|
||||
create index on "dem"."singapore_srtm" using gist (st_convexhull("rast"));
|
||||
analyze "dem"."singapore_srtm" ;
|
||||
select addrasterconstraints( 'dem', 'singapore_srtm', 'rast', true, true, true, true, true, true, false, true, true, true, true, true) ;
|
||||
vacuum analyze "dem"."singapore_srtm";
|
||||
{{</ highlight >}}
|
||||
|
||||
{{< figure src="/pics/blog/batch-import-postgis-rasters/singapore-final.webp" width="600">}}
|
||||
|
||||
Looks much better!
|
||||
|
||||
This DEM raster of Singapore is available for download from the downloads page [here](/data-downloads/singapore-srtm)
|
||||
|
||||
#### 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
|
|
@ -3,9 +3,11 @@ title: "Artix Linux Installation Guide"
|
|||
date: 2024-02-24T17:04:21Z
|
||||
author:
|
||||
name: "Sam Chance"
|
||||
header_image: "/pics/blog/artix-logo.webp"
|
||||
header_image: "/pics/blog/install-artix/artix-logo.webp"
|
||||
draft: False
|
||||
summary: "This guide will run through the process of installing Artix Linux with runit as the init system on an encrypted disk partition."
|
||||
toc: true
|
||||
tags: ["Linux", "Arch"]
|
||||
---
|
||||
|
||||
This guide will run through the process of installing Artix Linux, which is a
|
||||
|
@ -33,7 +35,7 @@ guide I'll be using an encrypted partition on an UEFI system. If if you want a
|
|||
different configuration, please consult the [Arch
|
||||
wiki](https://wiki.archlinux.org/title/Partitioning#Example_layouts).
|
||||
|
||||
![artix-keyboard-select](/pics/blog/artix-keyboard-select.webp)
|
||||
{{< figure src="/pics/blog/install-artix/artix-keyboard-select.webp" width="400">}}
|
||||
|
||||
## Partition layout
|
||||
|
||||
|
@ -67,7 +69,7 @@ List all drives attached to system:
|
|||
lsblk
|
||||
{{</ highlight >}}
|
||||
|
||||
![artix-lsblk](/pics/blog/artix-lsblk.webp)
|
||||
{{< figure src="/pics/blog/install-artix/artix-lsblk.webp" width="400">}}
|
||||
|
||||
Locate the target drive (in this case `/dev/sda`) where we will install Artix.
|
||||
|
||||
|
@ -88,7 +90,7 @@ Run through the options to partition the disk:
|
|||
|
||||
You should now have two partitions under `/dev/sda`:
|
||||
|
||||
![artix-lsblk1](/pics/blog/artix-lsblk1.webp)
|
||||
{{< figure src="/pics/blog/install-artix/artix-lsblk1.webp" width="400">}}
|
||||
|
||||
`/dev/sda1` is the unencrypted boot partition, and `/dev/sda2` will be where we store our encrypted volume.
|
||||
|
||||
|
@ -132,7 +134,7 @@ lsblk -f
|
|||
|
||||
It should look something like this:
|
||||
|
||||
![artix-lsblk2](/pics/blog/artix-lsblk2.webp)
|
||||
{{< figure src="/pics/blog/install-artix/artix-lsblk2.webp" width="400">}}
|
||||
|
||||
Note the UUIDs - they will be needed later for setting up decryption during boot.
|
||||
|
||||
|
@ -284,7 +286,7 @@ grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=grub
|
|||
grub-mkconfig -o /boot/grub/grub.cfg
|
||||
{{</ highlight >}}
|
||||
|
||||
![artix-grub-install](/pics/blog/artix-grub-install.webp)
|
||||
{{< figure src="/pics/blog/install-artix/artix-grub-install.webp" width="400">}}
|
||||
|
||||
## Add Users
|
||||
|
||||
|
@ -304,7 +306,7 @@ passwd user
|
|||
Edit the sudoers file to allow sudo root commands for user.
|
||||
|
||||
{{< highlight shell >}}
|
||||
EDITOR=vim visudo`
|
||||
EDITOR=vim visudo
|
||||
{{</ highlight >}}
|
||||
|
||||
Then uncomment the following line:
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
---
|
||||
title: "Global Bitcoin Business Growth"
|
||||
date: 2024-04-04T09:16:33+01:00
|
||||
author:
|
||||
name: "Sam Chance"
|
||||
header_image: "/pics/charts/growth.webp"
|
||||
summary: "Growth of bitcoin businesses based on OSM data"
|
||||
---
|
||||
The following table shows bitcoin business growth around the world for the selected period in the dropdown. The chart displays yearly cumulative number of bitcoin businesses for the countries selected in the table.
|
||||
|
||||
Data is obtained from Openstreetmaps and is updated roughly every 2 hours.
|
||||
|
||||
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" >}}
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: "Data Downloads"
|
||||
---
|
||||
|
||||
Data available for download
|
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
title: 'Singapore SRTM DEM (Data Download)'
|
||||
date: 2024-08-06T12:15:44+01:00
|
||||
author:
|
||||
name: "Sam Chance"
|
||||
header_image: '/pics/blog/batch-import-postgis-rasters/singapore-final.webp'
|
||||
summary: "Download the Shuttle Radar Topography Mission (SRTM) Digital Elevation Model (DEM) of Singapore"
|
||||
toc: false
|
||||
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="http://localhost:5000/download/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
|
|
@ -6,6 +6,7 @@ author:
|
|||
header_image: "/pics/charts/feerate-percentile.webp"
|
||||
summary: "Bar chart showing historical median daily feerate percentiles for the Bitcoin protocol."
|
||||
chart: "/js/feerate_percentile.js"
|
||||
tags: ["Bitcoin", "Stats"]
|
||||
---
|
||||
|
||||
This chart shows historical median daily feerate percentiles for the Bitcoin
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
title: "Global Bitcoin Business Growth"
|
||||
date: 2024-04-04T09:16:33+01:00
|
||||
author:
|
||||
name: "Sam Chance"
|
||||
header_image: "/pics/charts/growth.webp"
|
||||
summary: "This analysis uses OpenStreetMaps data to chart the yearly growth of bitcoin-accepting businesses worldwide."
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
<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" >}}
|
|
@ -5,6 +5,7 @@ author:
|
|||
name: "Sam Chance"
|
||||
header_image: "/pics/charts/hashrate.webp"
|
||||
summary: "Timeseries chart showing the Bitcoin network hashrate and difficulty."
|
||||
tags: ["Bitcoin", "Stats", "Hashrate"]
|
||||
---
|
||||
|
||||
The estimated hashrate and difficulty of the Bitcoin network, accompanied by the 28-day moving average.
|
|
@ -6,7 +6,7 @@ author:
|
|||
summary: "Miner rewards"
|
||||
header_image: "/pics/charts/rewards.webp"
|
||||
draft: false
|
||||
chart: "/js/miner-rewards.js"
|
||||
tags: ["Bitcoin", "Stats"]
|
||||
---
|
||||
|
||||
Total daily aggregated miner income denominated in USD for that day.
|
|
@ -5,6 +5,7 @@ author:
|
|||
name: "Sam Chance"
|
||||
header_image: "/pics/charts/price.webp"
|
||||
summary: "Daily bitcoin price. Data is obtained from CoinGecko using their public API."
|
||||
tags: ["Bitcoin", "Stats"]
|
||||
---
|
||||
|
||||
Daily bitcoin price. Data is obtained from CoinGecko using their
|
|
@ -1,3 +1,3 @@
|
|||
# License
|
||||
|
||||
All work on this site is licensed using [unlicense](https://unlicense.org/). This effectively means all of the content hosted on https://bitlab21.com and in the bitlab21.com repository at https://git.bitlab21.com/sam/bitlab21.com is in the public domain. You can use this material as you wish. Attribution is appreciated, but not required.
|
||||
All work on this site is licensed using [unlicense](https://unlicense.org/). This effectively means all of the content hosted on https://baseddata.io and in the baseddata repository at https://git.bitlab21.com/sam/bitlab21.com is in the public domain. You can use this material as you wish. Attribution is appreciated, but not required.
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
- url: "/recipes"
|
||||
name: Recipes
|
||||
- url: "/data-analysis"
|
||||
name: Data Analysis
|
||||
- url: "https://semitamaps.com"
|
||||
name: Map Printing
|
||||
- url: "/blog"
|
||||
name: Blog
|
||||
- url: "https://git.bitlab21.com"
|
||||
name: Git
|
24
hugo.toml
|
@ -1,5 +1,23 @@
|
|||
baseURL = 'https://bitlab21.com/'
|
||||
baseURL = 'https://baseddate.io/'
|
||||
languageCode = 'en-gb'
|
||||
title = 'Bitlab21'
|
||||
title = 'baseddata.io'
|
||||
|
||||
markup.highlight.noClasses=false
|
||||
[markup.highlight]
|
||||
pygmentsUseClasses = false
|
||||
codeFences = true
|
||||
guessSyntax = true
|
||||
hl_Lines = ""
|
||||
lineNoStart = 1
|
||||
lineNos = false
|
||||
lineNumbersInTable = false
|
||||
tabWidth = 4
|
||||
noClasses = true
|
||||
style = "catppuccin-latte"
|
||||
|
||||
[markup.tableOfContents]
|
||||
endLevel = 2
|
||||
ordered = false
|
||||
startLevel = 2
|
||||
|
||||
[markup.goldmark.renderer]
|
||||
unsafe = true
|
||||
|
|
|
@ -1,26 +1,44 @@
|
|||
{{ define "main" }}
|
||||
<div class="page-content">
|
||||
<h1>{{ .Title }}</h1>
|
||||
{{ .Content }}
|
||||
</div>
|
||||
<div class="list-content-container">
|
||||
<div class="article-card-flex-container">
|
||||
<div class="page-content">
|
||||
<h1>{{ .Title }}</h1>
|
||||
{{ .Content }}
|
||||
</div>
|
||||
<div class="article-card-container">
|
||||
{{ range.Pages }}
|
||||
<a class="article-card" href="{{ .RelPermalink }}">
|
||||
<div class="article-card">
|
||||
<div class="article-card-info">
|
||||
<img class="article-card-thumbnail" src="{{ .Params.header_image }}" />
|
||||
<div class="article-card-thumb">
|
||||
<a href="{{ .RelPermalink }}">
|
||||
<img
|
||||
class="article-card-thumbnail"
|
||||
src="{{ .Params.header_image }}"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div class="article-card-summary">
|
||||
<h3><strong>{{ .Title | safeHTML }}</strong></h3>
|
||||
<p>{{ .Summary | safeHTML }}</p>
|
||||
{{ template "partials/get-tags.html" . }}
|
||||
<a href="{{ .RelPermalink }}">
|
||||
<h3><strong>{{ .Title | safeHTML }}</strong></h3>
|
||||
</a>
|
||||
<p>
|
||||
{{ .Summary | safeHTML }}
|
||||
<i class="reading-time"
|
||||
>({{ .ReadingTime }} minute{{ if (ne .ReadingTime 1) }}s{{ end
|
||||
}})</i
|
||||
>
|
||||
</p>
|
||||
|
||||
<br />
|
||||
<div class="article-card-author-row">
|
||||
{{ with .Params.author }}
|
||||
<strong><p class="author-name">{{ .name }}</p></strong>
|
||||
{{ end }}
|
||||
<time>{{ .Date.Format "January 2, 2006" }}</time>
|
||||
</div>
|
||||
</div>
|
||||
{{ if isset .Params "date" }}
|
||||
<div class="article-card-author-row">
|
||||
<time>{{ .Date.Format "January 2, 2006" }}</time>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
<!doctype html>
|
||||
<head>
|
||||
{{ partial "head.html" . }}
|
||||
</head>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"
|
||||
></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.min.js"></script>
|
||||
{{ template "partials/body.html" . }}
|
|
@ -0,0 +1,5 @@
|
|||
<!doctype html>
|
||||
<head>
|
||||
{{ partial "head.html" . }}
|
||||
</head>
|
||||
{{ template "partials/body.html" . }}
|
|
@ -1,8 +1,7 @@
|
|||
{{ define "main" }}
|
||||
<div class="page-content">
|
||||
<div class="home-page">
|
||||
<div class="article-container">
|
||||
<div class="main-article">
|
||||
<div class="home-page-content">{{ .Content }}</div>
|
||||
<figure class="profile-img"><img src="/sam.webp" /></figure>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
<div id="chart">
|
||||
<canvas id="{{ .id }}"></canvas>
|
||||
<script src="{{ .src }}"></script>
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.min.js"></script>
|
||||
<section class="chart-container">
|
||||
<div id="chart">
|
||||
<canvas id="{{ .id }}"></canvas>
|
||||
<script src="{{ .src }}"></script>
|
||||
</div>
|
||||
</section>
|
||||
<script src="/js/chart-params.js"></script>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<footer>
|
||||
<div id="footer">
|
||||
<p>
|
||||
Bitlab21.com by Sam Chance.
|
||||
baseddata.io by Sam Chance.
|
||||
<a href="/license">Uncopywrited.</a>
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<div class="tags-container">
|
||||
{{ with .Param "tags" }} {{ range $index, $tag := (. | sort) }} {{ with
|
||||
$.Site.GetPage (printf "/%s/%s" "tags" $tag) }}
|
||||
<a a href="{{ .Permalink }}">{{ $tag | urlize }}</a>
|
||||
{{ end }} {{ end }} {{ end }}
|
||||
</div>
|
|
@ -3,5 +3,21 @@
|
|||
<link rel="icon" type="image/png" sizes="48x48" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="/css/style.css" type="text/css" media="all" />
|
||||
<link rel="stylesheet" href="/css/syntax.css" />
|
||||
<link rel="stylesheet" href="/css/navbar.css" type="text/css" media="all" />
|
||||
<link rel="stylesheet" href="/css/tables.css" type="text/css" media="all" />
|
||||
<link rel="stylesheet" href="/css/toc.css" type="text/css" media="all" />
|
||||
<link rel="stylesheet" href="/css/articles.css" type="text/css" media="all" />
|
||||
<link rel="stylesheet" href="/css/charts.css" type="text/css" media="all" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="/css/codeblock.css"
|
||||
type="text/css"
|
||||
media="all"
|
||||
/>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Fira+Mono:wght@400;500;700&family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
</html>
|
||||
|
|
|
@ -1,17 +1,26 @@
|
|||
<header>
|
||||
<nav class="navbar" role="navigation">
|
||||
<div class="navbar__left">
|
||||
<a href="/"><strong>Bitlab21.com</strong></a>
|
||||
<a href="/"><strong>Based Data</strong></a>
|
||||
</div>
|
||||
<div class="navbar__right">
|
||||
<div class="navbar-links">{{ partial "navbarlinks.html" . }}</div>
|
||||
<div class="navbar-dropdown">
|
||||
<button class="hamburger-dropbtn">
|
||||
<img
|
||||
class="hamburger"
|
||||
src="/hamburger-menu.svg"
|
||||
alt="description of image"
|
||||
/>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="3"
|
||||
stroke="#6c757d"
|
||||
class="hamburger-svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M4 6h16M4 12h16M4 18h16"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="navbar-dropdown-content">
|
||||
{{ partial "navbarlinks.html" . }}
|
||||
|
|
|
@ -1,15 +1,21 @@
|
|||
<article class="main-article">
|
||||
<header>
|
||||
<h1>{{ .Title }}</h1>
|
||||
<div class="author-row">
|
||||
{{ with .Params.author }}
|
||||
<strong><p class="author-name">{{ .name }}</p></strong>
|
||||
<p class="author-name">on</p>
|
||||
{{ end }}
|
||||
{{ if isset .Params "date" }}
|
||||
<time>{{ .Date.Format "January 2, 2006" }}</time>
|
||||
{{ end }}
|
||||
<div class="article-container">
|
||||
<article class="main-article">
|
||||
<header>
|
||||
<h1>{{ .Title }}</h1>
|
||||
<div class="author-row">
|
||||
{{ with .Params.author }}
|
||||
<strong><p class="author-name">{{ .name }}</p></strong>
|
||||
<p class="author-name">on</p>
|
||||
{{ end }} {{ if isset .Params "date" }}
|
||||
<time>{{ .Date.Format "January 2, 2006" }}</time>
|
||||
{{ end }}
|
||||
</div>
|
||||
</header>
|
||||
{{ if .Params.toc }}
|
||||
<div id="tocWrapper">
|
||||
<h4>Table of Contents</h4>
|
||||
<aside>{{.TableOfContents}}</aside>
|
||||
</div>
|
||||
</header>
|
||||
{{ .Content }}
|
||||
</article>
|
||||
{{ end }} {{ .Content }}
|
||||
</article>
|
||||
</div>
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
{{ range $.Site.Data.navbarlinks }}
|
||||
<ul><a href="{{ .url }}">{{ .name }}</a></ul>
|
||||
{{ end }}
|
||||
<ul>
|
||||
<li><a href="/blog">Blog</a></li>
|
||||
<li><a href="/about-me">About Me</a></li>
|
||||
<li class="navbar_link_dropdown">
|
||||
<a href="#" class="dropbtn">Projects</a>
|
||||
<div class="navbar-link-dropdown-content">
|
||||
<a href="/data-lab">Data Lab</a>
|
||||
<a href="/data-downloads">Data Downloads</a>
|
||||
<a href="https://semitamaps.com">Map Printing</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<p id="price"></p>
|
||||
|
||||
<script>
|
||||
fetch("https://api.bitlab21.com/price")
|
||||
fetch("https://api.baseddata.io/get_json/final__price.json")
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
var lastElement = data[data.length - 1];
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
<section class="content-list-container">
|
||||
{{ $items := .Inner | unmarshal }} {{ range $k, $v := $items }}
|
||||
<a class="content-list-item" href="{{ $v.link }}">
|
||||
<div class="content-list-info">
|
||||
<img class="content-list-thumbnail" src="{{ $v.pic }}" />
|
||||
<div class="content-list-summary">
|
||||
<div class="content-list-title"><p>{{ $k | safeHTML }}</p></div>
|
||||
<div class="content-list-description">
|
||||
<p>{{ $v.content | safeHTML }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{{ end }}
|
||||
</section>
|
|
@ -0,0 +1,21 @@
|
|||
<button class="download-button" onclick="downloadFile('{{ .Get "src" }}', '{{ .Get "name" }}')">
|
||||
Download
|
||||
</button>
|
||||
|
||||
<script>
|
||||
async function downloadFile(url, name) {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
const blob = await response.blob();
|
||||
const link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = name;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
} catch (error) {
|
||||
console.error('Error downloading the file:', error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
{{ define "main" }}
|
||||
<div class="list-content-container">
|
||||
<div class="page-content">
|
||||
<h1>{{ .Title }}</h1>
|
||||
{{ .Content }}
|
||||
</div>
|
||||
<div class="article-card-container">
|
||||
{{ range.Pages }}
|
||||
<a class="article-card" href="{{ .RelPermalink }}">
|
||||
<div class="article-card-info">
|
||||
<div class="article-card-thumb">
|
||||
<img
|
||||
class="article-card-thumbnail"
|
||||
src="{{ .Params.header_image }}"
|
||||
/>
|
||||
</div>
|
||||
<div class="article-card-summary">
|
||||
<h3><strong>{{ .Title | safeHTML }}</strong></h3>
|
||||
<p>{{ .Summary | safeHTML }}</p>
|
||||
<br />
|
||||
<div class="article-card-author-row">
|
||||
<time>{{ .Date.Format "January 2, 2006" }}</time>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
|
@ -0,0 +1,309 @@
|
|||
.page-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.author-row {
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
align-items: center;
|
||||
color: var(--subtext-color);
|
||||
font-size: var(--author-row-font-size);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.author-name {
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
.author-row time {
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
.article-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
color: var(--text-color);
|
||||
padding: var(--content-padding);
|
||||
line-height: var(--line-height);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.page-content h1 {
|
||||
color: var(--heading1-color);
|
||||
}
|
||||
|
||||
.home-page-content {
|
||||
line-height: 2;
|
||||
margin-bottom: 30px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.home-page-content h1 {
|
||||
text-align: center;
|
||||
font-size: 32px !important;
|
||||
margin-bottom: 120px;
|
||||
}
|
||||
|
||||
.home-page-content h2 {
|
||||
font-weight: normal;
|
||||
font-size: 28px;
|
||||
text-align: center;
|
||||
margin-bottom: 60px !important;
|
||||
}
|
||||
|
||||
.home-page-content h3 {
|
||||
font-weight: normal;
|
||||
font-size: 28px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.home-page-content h4 {
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.home-page-content h5 {
|
||||
font-weight: normal;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
margin-bottom: 60px;
|
||||
margin-top: 60px;
|
||||
}
|
||||
|
||||
.home-page-content p {
|
||||
font-weight: normal;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
margin-bottom: 80px;
|
||||
margin-left: 10vw;
|
||||
margin-right: 10vw;
|
||||
}
|
||||
|
||||
.profile-img {
|
||||
margin-right: 20px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.profile-img img {
|
||||
width: 300px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
figcaption h4 {
|
||||
font-size: 10px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.main-article {
|
||||
width: 70%;
|
||||
padding: var(--content-padding);
|
||||
line-height: 1.5;
|
||||
text-align: left;
|
||||
border-left: var(--border-width) var(--border-style) var(--border-color);
|
||||
border-right: var(--border-width) var(--border-style) var(--border-color);
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.main-article {
|
||||
width: 100%;
|
||||
padding: var(--content-padding);
|
||||
line-height: 1.5;
|
||||
text-align: left;
|
||||
border-left: var(--border-width) var(--border-style) var(--border-color);
|
||||
border-right: var(--border-width) var(--border-style) var(--border-color);
|
||||
}
|
||||
}
|
||||
|
||||
.main-article h1 {
|
||||
color: var(--heading1-color);
|
||||
font-size: var(--heading1-font-size);
|
||||
}
|
||||
|
||||
.main-article h2,
|
||||
h3 {
|
||||
margin-bottom: var(--article-margin);
|
||||
margin-top: var(--heading-margin);
|
||||
color: var(--heading2-color);
|
||||
}
|
||||
|
||||
.main-article ul,
|
||||
ol {
|
||||
margin-bottom: var(--article-margin);
|
||||
margin-left: var(--article-margin);
|
||||
}
|
||||
|
||||
.main-article li {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.main-article img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* article cards */
|
||||
.list-content-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 100%;
|
||||
justify-content: center;
|
||||
padding: var(--content-padding);
|
||||
}
|
||||
|
||||
.article-card {
|
||||
display: inline-block;
|
||||
border-top: var(--border-width) var(--border-style) var(--border-color);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.article-card-summary h3 {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.article-card-summary p {
|
||||
color: var(--text-color);
|
||||
text-align: left;
|
||||
margin: 0px;
|
||||
border: none;
|
||||
}
|
||||
.article-card-container a:hover {
|
||||
text-decoration: underline;
|
||||
color: var(--text-color) !important;
|
||||
}
|
||||
|
||||
.tags-container a {
|
||||
border: var(--border-width) var(--border-style) var(--border-color);
|
||||
color: var(--text-color);
|
||||
border-radius: 20px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.tags-container a:hover {
|
||||
border: var(--border-width) var(--border-style) var(--border-color);
|
||||
border-radius: 20px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
text-decoration: none;
|
||||
background-color: var(--navbar-hover);
|
||||
}
|
||||
|
||||
.article-card-info {
|
||||
display: flex;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.article-card-info img {
|
||||
width: 300px;
|
||||
height: auto;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.reading-time {
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
.article-card {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.article-card-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.article-card-thumb {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.article-card-info img {
|
||||
width: 70vw;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.article-card-summary {
|
||||
margin-bottom: 30px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.article-card-summary h3 {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
/* content list*/
|
||||
.content-list-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 100%;
|
||||
justify-content: left;
|
||||
}
|
||||
|
||||
.content-list-item {
|
||||
width: 100%;
|
||||
border-bottom: var(--border-width) var(--border-style) var(--border-color);
|
||||
}
|
||||
|
||||
.content-list-container a:hover {
|
||||
text-decoration: underline;
|
||||
color: var(--text-color) !important;
|
||||
}
|
||||
|
||||
.content-list-info {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: left;
|
||||
margin: 20px 0px 20px 0px;
|
||||
}
|
||||
|
||||
.content-list-info img {
|
||||
padding: 10px;
|
||||
height: 100%;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.content-list-summary {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: left;
|
||||
}
|
||||
|
||||
.content-list-summary p {
|
||||
color: var(--text-color);
|
||||
text-align: left;
|
||||
margin: 0px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.content-list-description p {
|
||||
margin: 0px;
|
||||
font-size: 16px;
|
||||
line-height: 1.2;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.content-list-title p {
|
||||
margin-bottom: 1rem;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.download-button {
|
||||
padding: 10px;
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
/* Charts */
|
||||
.chart-flex-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.chart-flex-container article {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#chart {
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
pre {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.highlight pre {
|
||||
background: none !important;
|
||||
}
|
||||
|
||||
.codeblock {
|
||||
position: relative;
|
||||
background-color: var(--codeblock-bg-color);
|
||||
width: 100%;
|
||||
padding: var(--element-padding);
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: var(--radius);
|
||||
border: var(--border-width) var(--border-style) var(--border-color);
|
||||
font-size: var(--code-block-font-size);
|
||||
}
|
||||
|
||||
.codeblock::before {
|
||||
font-family: var(--code-font-family);
|
||||
content: attr(data-lang);
|
||||
position: absolute;
|
||||
color: var(--heading1-color);
|
||||
right: 5px;
|
||||
top: 0px;
|
||||
font-size: var(--code-block-font-size);
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: var(--code-font-family);
|
||||
}
|
||||
|
||||
:not(pre) > code {
|
||||
color: var(--inline-code-color);
|
||||
background-color: var(--inline-code-bg-color);
|
||||
padding: 5px;
|
||||
border-radius: 2px;
|
||||
font-size: var(--code-block-font-size);
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
/* Navbar */
|
||||
.navbar {
|
||||
background-color: var(--navbar-background-color);
|
||||
border-bottom: var(--border-width) var(--border-style) var(--border-color);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
height: 50px;
|
||||
width: 100%;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.navbar a {
|
||||
color: var(--navbar-text-color);
|
||||
text-decoration: none;
|
||||
padding: 10px 10px 5px 5px;
|
||||
transition: background-color 0.3s ease;
|
||||
border-bottom: 3px var(--border-style) transparent;
|
||||
}
|
||||
|
||||
.navbar a:hover {
|
||||
color: var(--text-color);
|
||||
border-bottom: 3px var(--border-style) var(--hover-color);
|
||||
}
|
||||
|
||||
.navbar-link-dropdown-content {
|
||||
display: none;
|
||||
position: absolute;
|
||||
flex-direction: column;
|
||||
right: 20px;
|
||||
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
|
||||
background-color: var(--navbar-background-color);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.navbar_link_dropdown:hover .navbar-link-dropdown-content {
|
||||
display: flex;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.navbar-link-dropdown-content a {
|
||||
border-width: 0px;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.navbar-link-dropdown-content a:hover {
|
||||
border-width: 0px;
|
||||
border-color: transparent;
|
||||
background-color: var(--navbar-hover);
|
||||
}
|
||||
|
||||
.navbar__left a {
|
||||
text-decoration: none !important;
|
||||
color: var(--text-color) !important;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.navbar__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.navbar__right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.navbar-links ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.navbar-links ul li {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.navbar-links ul li a {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.navbar-dropdown {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.navbar-dropdown {
|
||||
display: flex;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.navbar-links {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hamburger-dropbtn {
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
border-width: 0;
|
||||
background-color: var(--background-color);
|
||||
}
|
||||
|
||||
.hamburger-dropbtn:hover {
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
border-width: 0;
|
||||
background-color: var(--navbar-hover);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.navbar-dropdown-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Show the dropdown menu on hover */
|
||||
.navbar-dropdown:hover .navbar-dropdown-content {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 80%;
|
||||
right: 1%;
|
||||
flex-direction: column;
|
||||
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
|
||||
background-color: var(--navbar-background-color);
|
||||
border-radius: 5px;
|
||||
}
|
||||
.navbar-dropdown-content li {
|
||||
list-style-type: none;
|
||||
padding: 10px 10px 5px 5px;
|
||||
}
|
||||
|
||||
.navbar-dropdown-content a {
|
||||
border-width: 0px;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.navbar-dropdown-content li:hover {
|
||||
color: var(--navbar-text-color);
|
||||
padding: 10px 10px 5px 5px;
|
||||
text-decoration: none;
|
||||
transition: background-color 0.3s ease;
|
||||
background-color: var(--navbar-hover);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.navbar-dropdown-content a:hover {
|
||||
border-width: 0px;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.navbar-link-dropdown-content {
|
||||
position: absolute;
|
||||
flex-direction: column;
|
||||
width: 150px;
|
||||
right: 95%;
|
||||
top: 50%;
|
||||
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
|
||||
background-color: var(--navbar-background-color);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.navbar-link-dropdown-content a:hover {
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
|
@ -5,175 +5,122 @@
|
|||
}
|
||||
|
||||
:root {
|
||||
--font-family: sans-serif;
|
||||
/* root */
|
||||
--font-family: "Montserrat", sans-serif;
|
||||
--font-size: 10;
|
||||
--author-row-font-size: 12px;
|
||||
--background-color: #f9f9f9;
|
||||
--text-color: #373841;
|
||||
--subtext-color: #6c757d;
|
||||
--link-color: #438bb1;
|
||||
--hover-color: #fc8452;
|
||||
--border-color: #c4c9d9;
|
||||
--border-width: 1px;
|
||||
--border-style: solid;
|
||||
|
||||
/* navbar/footer */
|
||||
--navbar-background-color: #f9f9f9;
|
||||
--navbar-text-color: #373841;
|
||||
--navbar-hover: #e7e7f5;
|
||||
--footer-background-color: #f9f9f9;
|
||||
|
||||
/* headings */
|
||||
--heading1-color: #fc8452;
|
||||
--heading2-color: #373841;
|
||||
--heading3-color: #373841;
|
||||
--heading1-font-size: 25px;
|
||||
--code-block-font-size: 10px;
|
||||
--chart-modifier-height: 60px;
|
||||
--article-max-width: 60vw;
|
||||
|
||||
/* content */
|
||||
--author-row-font-size: 12px;
|
||||
--content-padding: 40px;
|
||||
--content-margin: 40px;
|
||||
--element-padding: 20px;
|
||||
--element-margin: 10px;
|
||||
--element-margin: 20px;
|
||||
--article-margin: 10px;
|
||||
--heading-margin: 30px;
|
||||
--radius: 5px;
|
||||
--line-height: 1.5;
|
||||
--checkbox-width: max-content;
|
||||
--checkbox-font-size: 16px;
|
||||
--avatar-width: 20px;
|
||||
--background-color: #181a1b;
|
||||
--text-color: #e0ddd9;
|
||||
--subtext-color: #6c757d;
|
||||
--link-color: #749571;
|
||||
--hover-color: #0056b3;
|
||||
--heading1-color: #fc8452;
|
||||
--heading2-color: #e0ddd9;
|
||||
--heading3-color: #e0ddd9;
|
||||
--navbar-background-color: #343451;
|
||||
--navbar-text-color: #e0ddd9;
|
||||
--navbar-hover: #50507c;
|
||||
--footer-background-color: #333;
|
||||
--summary-container-hover-bg: #343a40;
|
||||
--codeblock-bg-color: #0d1117;
|
||||
--inline-code-bg-color: #749571;
|
||||
--table-even-row-bg-color: #343a40;
|
||||
--table-odd-row-bg-color: #515c66;
|
||||
--table-header-bg: #212529;
|
||||
--table-font-color: #e0ddd9;
|
||||
|
||||
/* code */
|
||||
--code-block-font-size: 14px;
|
||||
--codeblock-bg-color: #edeeee;
|
||||
--inline-code-color: #373841;
|
||||
--inline-code-bg-color: #edeeee;
|
||||
--code-font-family: "Fira Mono", monospace;
|
||||
|
||||
/* table */
|
||||
--table-even-row-bg-color: #f9f9f9;
|
||||
--table-odd-row-bg-color: #f9f9f9;
|
||||
--table-header-bg: #eff3f7;
|
||||
--table-font-color: #373841;
|
||||
--table-header-font-size: 14px;
|
||||
--table-row-font-size: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
@media (max-width: 600px) {
|
||||
:root {
|
||||
--font-size: 10;
|
||||
--author-row-font-size: 12px;
|
||||
--heading1-font-size: 25px;
|
||||
--code-block-font-size: 10px;
|
||||
--avatar-width: 20px;
|
||||
--article-max-width: 100%;
|
||||
--table-header-font-size: 14px;
|
||||
--table-row-font-size: 12px;
|
||||
--table-header-font-size: 9px;
|
||||
--table-row-font-size: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Style for WebKit browsers (Chrome, Safari, etc.) */
|
||||
*::-webkit-scrollbar {
|
||||
height: 5px;
|
||||
border-radius: 6px;
|
||||
background: #1f1f28;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb {
|
||||
border-radius: 6px;
|
||||
background: #585653;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb:hover {
|
||||
background: #454445; /* Background color on hover */
|
||||
}
|
||||
|
||||
html {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-family);
|
||||
background-color: var(--background-color);
|
||||
font-size: var(--font-size);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.page-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
figure {
|
||||
text-align: center;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.page-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
p {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-top: 30px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--link-color);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--hover-color);
|
||||
}
|
||||
|
||||
.page-content {
|
||||
color: var(--text-color);
|
||||
padding-top: var(--content-padding);
|
||||
padding: var(--content-padding);
|
||||
padding-left: var(--content-padding);
|
||||
padding-right: var(--content-padding);
|
||||
max-width: 1000px;
|
||||
line-height: var(--line-height);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.page-content h1 {
|
||||
color: var(--heading1-color);
|
||||
}
|
||||
|
||||
.home-page {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.profile-img {
|
||||
margin-right: 20px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.profile-img img {
|
||||
width: 300px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
.home-page {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.profile-img {
|
||||
margin-right: 0px;
|
||||
order: 1;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.profile-img img {
|
||||
width: 200px;
|
||||
height: auto;
|
||||
}
|
||||
.home-page-content {
|
||||
order: 2;
|
||||
}
|
||||
}
|
||||
|
||||
.main-article {
|
||||
padding: var(--content-padding);
|
||||
width: var(--article-max-width);
|
||||
line-height: 1.5;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.main-article h1 {
|
||||
color: var(--heading1-color);
|
||||
font-size: var(--heading1-font-size);
|
||||
}
|
||||
|
||||
.main-article h2,
|
||||
h3 {
|
||||
margin-bottom: var(--article-margin);
|
||||
margin-top: var(--heading-margin);
|
||||
color: var(--heading2-color);
|
||||
}
|
||||
|
||||
.main-article ul,
|
||||
ol {
|
||||
margin-bottom: var(--article-margin);
|
||||
margin-left: var(--article-margin);
|
||||
}
|
||||
|
||||
.main-article li {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.main-article img {
|
||||
max-width: 60vw;
|
||||
margin-bottom: var(--article-margin);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
time {
|
||||
|
@ -181,236 +128,6 @@ time {
|
|||
margin-bottom: var(--article-margin);
|
||||
}
|
||||
|
||||
.article-card-flex-container {
|
||||
margin-left: var(--content-margin);
|
||||
margin-right: var(--content-margin);
|
||||
margin-bottom: var(--content-margin);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: left;
|
||||
line-height: var(--line-height);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.article-card {
|
||||
margin: var(--element-margin);
|
||||
background-color: black;
|
||||
width: 200px;
|
||||
min-height: 100%;
|
||||
border-radius: var(--radius);
|
||||
transition: background-color 0.3s ease;
|
||||
text-decoration: none; /* Remove underline */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.article-card-text p,
|
||||
.article-text a {
|
||||
color: var(--subtext-color);
|
||||
margin-bottom: var(--element-margin);
|
||||
}
|
||||
|
||||
.article-card-summary h3 {
|
||||
color: var(--heading3-color);
|
||||
margin: var(--article-margin);
|
||||
font-size: 16px;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
.article-card-summary p {
|
||||
color: var(--subtext-color);
|
||||
margin: var(--article-margin);
|
||||
}
|
||||
|
||||
.article-card-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.article-card-thumbnail {
|
||||
max-width: 100%;
|
||||
border-top-left-radius: var(--radius);
|
||||
border-top-right-radius: var(--radius);
|
||||
}
|
||||
|
||||
.article-card-author-row {
|
||||
font-size: 10px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
margin: var(--element-margin);
|
||||
}
|
||||
|
||||
.article-card:hover {
|
||||
background-color: var(--summary-container-hover-bg);
|
||||
}
|
||||
|
||||
/* Navbar */
|
||||
.navbar {
|
||||
background-color: var(--navbar-background-color);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
height: 50px;
|
||||
width: 100%;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.navbar a {
|
||||
color: var(--navbar-text-color);
|
||||
padding: 10px;
|
||||
margin: 5px;
|
||||
text-decoration: none;
|
||||
transition: background-color 0.3s ease;
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.navbar a:hover {
|
||||
padding: 10px;
|
||||
background-color: var(--navbar-hover);
|
||||
border-radius: var(--radius);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.navbar__left a {
|
||||
text-decoration: none !important;
|
||||
color: var(--text-color) !important;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.navbar__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.navbar__right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.navbar-links {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: var(--element-margin);
|
||||
}
|
||||
|
||||
.navbar-dropdown {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
.navbar-links {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navbar-dropdown {
|
||||
display: block;
|
||||
align-items: center;
|
||||
float: right;
|
||||
max-height: 60px;
|
||||
}
|
||||
|
||||
.hamburger {
|
||||
width: 30px;
|
||||
margin-right: var(--article-margin);
|
||||
}
|
||||
|
||||
.navbar-dropdown .hamburger-dropbtn {
|
||||
font-size: 66px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
max-height: 60px;
|
||||
outline: none;
|
||||
color: white;
|
||||
background-color: inherit;
|
||||
font-family: inherit;
|
||||
margin: 0;
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.navbar-dropdown:hover .hamburger {
|
||||
background-color: var(--navbar-hover);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.navbar-dropdown-content {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
width: 300px;
|
||||
background-color: var(--navbar-background-color);
|
||||
min-width: 160px;
|
||||
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
|
||||
z-index: 1;
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
/* Links inside the dropdown */
|
||||
.navbar-dropdown-content a {
|
||||
float: none;
|
||||
color: var(--text-color);
|
||||
padding: 12px 16px;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* Add a grey background color to dropdown links on hover */
|
||||
.navbar-dropdown-content a:hover {
|
||||
padding: 12px 16px;
|
||||
background-color: var(--navbar-hover);
|
||||
}
|
||||
|
||||
/* Show the dropdown menu on hover */
|
||||
.navbar-dropdown:hover .navbar-dropdown-content {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
/* Charts */
|
||||
.chart-flex-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.chart-flex-container article {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#chart {
|
||||
width: 100%;
|
||||
height: 50vh;
|
||||
}
|
||||
|
||||
.chart-area {
|
||||
width: 60%;
|
||||
margin: var(--article-margin);
|
||||
}
|
||||
|
||||
/* @media only screen and (max-width: 800px) {
|
||||
.chart-flex-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.chart-area {
|
||||
width: 95%;
|
||||
margin: var(--article-margin);
|
||||
}
|
||||
|
||||
#chart {
|
||||
width: 100%;
|
||||
height: 30vh;
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.chart-flex-container article {
|
||||
order: 2;
|
||||
}
|
||||
} */
|
||||
|
||||
/* Footer */
|
||||
footer {
|
||||
background-color: var(--footer-background-color);
|
||||
color: var(--text-color);
|
||||
|
@ -418,155 +135,5 @@ footer {
|
|||
height: 10vh;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
footer a:hover {
|
||||
background-color: #ddd;
|
||||
color: black;
|
||||
}
|
||||
|
||||
/* Chart modifiers */
|
||||
.chart-modifiers {
|
||||
height: var(--chart-modifier-height);
|
||||
}
|
||||
|
||||
.ck-button {
|
||||
width: var(--checkbox-width);
|
||||
color: white;
|
||||
font-size: var(--checkbox-font-size);
|
||||
cursor: pointer;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
margin-left: 50px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.ck-button label span {
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #a19fbc;
|
||||
margin: 5px;
|
||||
display: flex;
|
||||
width: 100px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.ck-button input:checked + span {
|
||||
background-color: #666fbc;
|
||||
}
|
||||
|
||||
.author-row {
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
align-items: center;
|
||||
color: var(--subtext-color);
|
||||
font-size: var(--author-row-font-size);
|
||||
}
|
||||
|
||||
.avatar-container {
|
||||
width: var(--avatar-width);
|
||||
margin-right: var(--element-margin);
|
||||
}
|
||||
|
||||
.avatar {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.author-name {
|
||||
margin-right: var(--element-margin);
|
||||
}
|
||||
|
||||
article p {
|
||||
margin-bottom: var(--article-margin);
|
||||
}
|
||||
|
||||
article em,
|
||||
article strong {
|
||||
color: var(--heading-color);
|
||||
}
|
||||
|
||||
/* Code block */
|
||||
pre {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.codeblock {
|
||||
position: relative;
|
||||
background-color: var(--codeblock-bg-color);
|
||||
max-width: 100vw;
|
||||
width: 60vw;
|
||||
padding: var(--element-padding);
|
||||
margin-top: var(--article-margin);
|
||||
margin-bottom: var(--article-margin);
|
||||
border-radius: var(--radius);
|
||||
font-size: var(--code-block-font-size);
|
||||
}
|
||||
|
||||
/* Code block language label */
|
||||
.codeblock::before {
|
||||
content: attr(data-lang);
|
||||
position: absolute;
|
||||
color: var(--heading1-color);
|
||||
right: 5px;
|
||||
top: 0px;
|
||||
font-size: var(--code-block-font-size);
|
||||
}
|
||||
|
||||
/* Styles for inline <code> */
|
||||
:not(pre) > code {
|
||||
color: #343a40;
|
||||
background-color: var(--inline-code-bg-color);
|
||||
padding: 0.1em 0.3em;
|
||||
border-radius: var(--radius);
|
||||
font-size: var(--code-block-font-size);
|
||||
}
|
||||
|
||||
#jsonTableContainer {
|
||||
max-width: 150%;
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
color: var(--table-font-color);
|
||||
margin-bottom: var(--article-margin);
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: var(--table-header-bg);
|
||||
padding: 5px;
|
||||
text-align: left;
|
||||
font-size: var(--table-header-font-size);
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 5px;
|
||||
text-align: left;
|
||||
max-width: 90px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
#scrollable {
|
||||
max-width: 90px;
|
||||
max-height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background-color: var(--table-even-row-bg-color);
|
||||
font-size: var(--table-row-font-size);
|
||||
}
|
||||
|
||||
tr:nth-child(odd) {
|
||||
background-color: var(--table-odd-row-bg-color);
|
||||
font-size: var(--table-row-font-size);
|
||||
border-top: var(--border-width) var(--border-style) var(--border-color);
|
||||
}
|
||||
|
|
|
@ -1,86 +0,0 @@
|
|||
/* Background */ .bg { color: #e6edf3; background-color: #0d1117; }
|
||||
/* PreWrapper */ .chroma { color: #e6edf3; background-color: #0d1117; }
|
||||
/* Other */ .chroma .x { }
|
||||
/* Error */ .chroma .err { color: #f85149 }
|
||||
/* CodeLine */ .chroma .cl { }
|
||||
/* LineLink */ .chroma .lnlinks { outline: none; text-decoration: none; color: inherit }
|
||||
/* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; }
|
||||
/* LineTable */ .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; }
|
||||
/* LineHighlight */ .chroma .hl { color: #6e7681 }
|
||||
/* LineNumbersTable */ .chroma .lnt { white-space: pre; -webkit-user-select: none; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #737679 }
|
||||
/* LineNumbers */ .chroma .ln { white-space: pre; -webkit-user-select: none; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #6e7681 }
|
||||
/* Line */ .chroma .line { display: flex; }
|
||||
/* Keyword */ .chroma .k { color: #ff7b72 }
|
||||
/* KeywordConstant */ .chroma .kc { color: #79c0ff }
|
||||
/* KeywordDeclaration */ .chroma .kd { color: #ff7b72 }
|
||||
/* KeywordNamespace */ .chroma .kn { color: #ff7b72 }
|
||||
/* KeywordPseudo */ .chroma .kp { color: #79c0ff }
|
||||
/* KeywordReserved */ .chroma .kr { color: #ff7b72 }
|
||||
/* KeywordType */ .chroma .kt { color: #ff7b72 }
|
||||
/* Name */ .chroma .n { }
|
||||
/* NameAttribute */ .chroma .na { }
|
||||
/* NameBuiltin */ .chroma .nb { }
|
||||
/* NameBuiltinPseudo */ .chroma .bp { }
|
||||
/* NameClass */ .chroma .nc { color: #f0883e; font-weight: bold }
|
||||
/* NameConstant */ .chroma .no { color: #79c0ff; font-weight: bold }
|
||||
/* NameDecorator */ .chroma .nd { color: #d2a8ff; font-weight: bold }
|
||||
/* NameEntity */ .chroma .ni { color: #ffa657 }
|
||||
/* NameException */ .chroma .ne { color: #f0883e; font-weight: bold }
|
||||
/* NameFunction */ .chroma .nf { color: #d2a8ff; font-weight: bold }
|
||||
/* NameFunctionMagic */ .chroma .fm { }
|
||||
/* NameLabel */ .chroma .nl { color: #79c0ff; font-weight: bold }
|
||||
/* NameNamespace */ .chroma .nn { color: #ff7b72 }
|
||||
/* NameOther */ .chroma .nx { }
|
||||
/* NameProperty */ .chroma .py { color: #79c0ff }
|
||||
/* NameTag */ .chroma .nt { color: #7ee787 }
|
||||
/* NameVariable */ .chroma .nv { color: #79c0ff }
|
||||
/* NameVariableClass */ .chroma .vc { }
|
||||
/* NameVariableGlobal */ .chroma .vg { }
|
||||
/* NameVariableInstance */ .chroma .vi { }
|
||||
/* NameVariableMagic */ .chroma .vm { }
|
||||
/* Literal */ .chroma .l { color: #a5d6ff }
|
||||
/* LiteralDate */ .chroma .ld { color: #79c0ff }
|
||||
/* LiteralString */ .chroma .s { color: #a5d6ff }
|
||||
/* LiteralStringAffix */ .chroma .sa { color: #79c0ff }
|
||||
/* LiteralStringBacktick */ .chroma .sb { color: #a5d6ff }
|
||||
/* LiteralStringChar */ .chroma .sc { color: #a5d6ff }
|
||||
/* LiteralStringDelimiter */ .chroma .dl { color: #79c0ff }
|
||||
/* LiteralStringDoc */ .chroma .sd { color: #a5d6ff }
|
||||
/* LiteralStringDouble */ .chroma .s2 { color: #a5d6ff }
|
||||
/* LiteralStringEscape */ .chroma .se { color: #79c0ff }
|
||||
/* LiteralStringHeredoc */ .chroma .sh { color: #79c0ff }
|
||||
/* LiteralStringInterpol */ .chroma .si { color: #a5d6ff }
|
||||
/* LiteralStringOther */ .chroma .sx { color: #a5d6ff }
|
||||
/* LiteralStringRegex */ .chroma .sr { color: #79c0ff }
|
||||
/* LiteralStringSingle */ .chroma .s1 { color: #a5d6ff }
|
||||
/* LiteralStringSymbol */ .chroma .ss { color: #a5d6ff }
|
||||
/* LiteralNumber */ .chroma .m { color: #a5d6ff }
|
||||
/* LiteralNumberBin */ .chroma .mb { color: #a5d6ff }
|
||||
/* LiteralNumberFloat */ .chroma .mf { color: #a5d6ff }
|
||||
/* LiteralNumberHex */ .chroma .mh { color: #a5d6ff }
|
||||
/* LiteralNumberInteger */ .chroma .mi { color: #a5d6ff }
|
||||
/* LiteralNumberIntegerLong */ .chroma .il { color: #a5d6ff }
|
||||
/* LiteralNumberOct */ .chroma .mo { color: #a5d6ff }
|
||||
/* Operator */ .chroma .o { color: #ff7b72; font-weight: bold }
|
||||
/* OperatorWord */ .chroma .ow { color: #ff7b72; font-weight: bold }
|
||||
/* Punctuation */ .chroma .p { }
|
||||
/* Comment */ .chroma .c { color: #8b949e; font-style: italic }
|
||||
/* CommentHashbang */ .chroma .ch { color: #8b949e; font-style: italic }
|
||||
/* CommentMultiline */ .chroma .cm { color: #8b949e; font-style: italic }
|
||||
/* CommentSingle */ .chroma .c1 { color: #8b949e; font-style: italic }
|
||||
/* CommentSpecial */ .chroma .cs { color: #8b949e; font-weight: bold; font-style: italic }
|
||||
/* CommentPreproc */ .chroma .cp { color: #8b949e; font-weight: bold; font-style: italic }
|
||||
/* CommentPreprocFile */ .chroma .cpf { color: #8b949e; font-weight: bold; font-style: italic }
|
||||
/* Generic */ .chroma .g { }
|
||||
/* GenericDeleted */ .chroma .gd { color: #ffa198; background-color: #490202 }
|
||||
/* GenericEmph */ .chroma .ge { font-style: italic }
|
||||
/* GenericError */ .chroma .gr { color: #ffa198 }
|
||||
/* GenericHeading */ .chroma .gh { color: #79c0ff; font-weight: bold }
|
||||
/* GenericInserted */ .chroma .gi { color: #56d364; background-color: #0f5323 }
|
||||
/* GenericOutput */ .chroma .go { color: #8b949e }
|
||||
/* GenericPrompt */ .chroma .gp { color: #8b949e }
|
||||
/* GenericStrong */ .chroma .gs { font-weight: bold }
|
||||
/* GenericSubheading */ .chroma .gu { color: #79c0ff }
|
||||
/* GenericTraceback */ .chroma .gt { color: #ff7b72 }
|
||||
/* GenericUnderline */ .chroma .gl { text-decoration: underline }
|
||||
/* TextWhitespace */ .chroma .w { color: #6e7681 }
|
|
@ -0,0 +1,46 @@
|
|||
/* Tables */
|
||||
#jsonTableContainer {
|
||||
max-width: 150%;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
color: var(--table-font-color);
|
||||
margin-bottom: var(--article-margin);
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: var(--table-header-bg);
|
||||
padding: 5px;
|
||||
text-align: left;
|
||||
font-size: var(--table-header-font-size);
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 5px;
|
||||
text-align: left;
|
||||
max-width: 90px;
|
||||
font-size: var(--table-row-font-size);
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
#scrollable {
|
||||
max-width: 90px;
|
||||
max-height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background-color: var(--table-even-row-bg-color);
|
||||
font-size: var(--table-row-font-size);
|
||||
}
|
||||
|
||||
tr:nth-child(odd) {
|
||||
background-color: var(--table-odd-row-bg-color);
|
||||
font-size: var(--table-row-font-size);
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/* table of contents */
|
||||
#tocWrapper {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#TableOfContents li::marker {
|
||||
content: none;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#TableOfContents li {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#TableOfContents a {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#TableOfContents ul {
|
||||
margin: 0px;
|
||||
}
|
|
@ -3,7 +3,7 @@ let myChart;
|
|||
|
||||
async function fetchDataForChart(str) {
|
||||
try {
|
||||
const apiEndpoint = `https://api.bitlab21.com/bitcoin_business_growth_by_country?cumulative_period_type=365 day&countries=${str}`;
|
||||
const apiEndpoint = `https://api.baseddata.io/bitcoin_business_growth_by_country?cumulative_period_type=365 day&countries=${str}`;
|
||||
const response = await fetch(apiEndpoint);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
|
@ -32,26 +32,26 @@ function updateChart() {
|
|||
option = {
|
||||
backgroundColor: backgroundColor,
|
||||
grid: grid,
|
||||
tooltip: {
|
||||
backgroundColor: tooltipBgColor,
|
||||
order: "seriesDesc",
|
||||
textStyle: textStyleMain,
|
||||
trigger: "axis",
|
||||
},
|
||||
tooltip: tooltip,
|
||||
toolbox: toolboxParams,
|
||||
xAxis: {
|
||||
axisTick: axisTick,
|
||||
axisLabel: axisLabel,
|
||||
axisLine: axisLine,
|
||||
type: "time",
|
||||
name: "Date",
|
||||
},
|
||||
yAxis: {
|
||||
axisTick: axisTick,
|
||||
splitLine: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: axisLabel,
|
||||
axisLabel: {
|
||||
fontSize: 12 * fontScale,
|
||||
color: textColor,
|
||||
formatter(value, index) {
|
||||
return nFormatter(value, 2);
|
||||
},
|
||||
},
|
||||
axisLine: axisLine,
|
||||
},
|
||||
|
||||
|
@ -96,134 +96,3 @@ document.addEventListener("reloadTable", () => {
|
|||
jsonTable.removeEventListener("change", handleCheckboxChange);
|
||||
jsonTable.addEventListener("change", handleCheckboxChange);
|
||||
});
|
||||
// let checkedBoxes = [];
|
||||
// let chartData = {};
|
||||
// const apiEndpoint = {{ .Get "endpoint" }};
|
||||
// const apiCategoryVariable = {{ .Get "apiCategoryVariable" }};
|
||||
// let myChart;
|
||||
// let chartDataMap = new Map();
|
||||
//
|
||||
// // Define base chart parameters
|
||||
// const chartBaseConfig = {
|
||||
// backgroundColor: backgroundColor,
|
||||
// grid: grid,
|
||||
// tooltip: {
|
||||
// backgroundColor: tooltipBgColor,
|
||||
// order: "seriesDesc",
|
||||
// textStyle: textStyleMain,
|
||||
// trigger: "axis",
|
||||
// },
|
||||
// toolbox: toolboxParams,
|
||||
// xAxis: {
|
||||
// axisTick: axisTick,
|
||||
// axisLabel: axisLabel,
|
||||
// axisLine: axisLine,
|
||||
// type: "time",
|
||||
// name: "Date",
|
||||
// },
|
||||
// yAxis: {
|
||||
// axisTick: axisTick,
|
||||
// splitLine: {
|
||||
// show: false,
|
||||
// },
|
||||
// axisLabel: axisLabel,
|
||||
// axisLine: axisLine,
|
||||
// },
|
||||
// series: [],
|
||||
// };
|
||||
//
|
||||
// function fetchDataForChart(params) {
|
||||
// return fetch(
|
||||
// apiEndpoint+"&"+apiCategoryVariable+"="+params,
|
||||
// )
|
||||
// .then((response) => response.json())
|
||||
// .then((data) => {
|
||||
// // Transform data using reduce
|
||||
// // so we end with an object where each element is
|
||||
// // a separate country containing an array of
|
||||
// // data
|
||||
// const chartData = data.reduce((acc, item) => {
|
||||
// const objectId = item.{{ .Get "objectCategoryId" | safeJS }};
|
||||
// if (!acc[objectId]) {
|
||||
// acc[objectId] = [];
|
||||
// }
|
||||
// acc[objectId].push([item.{{ .Get "plotXVariable" | safeJS }}, item.{{ .Get "plotYVariable" | safeJS }}]);
|
||||
// return acc;
|
||||
// }, {});
|
||||
// return chartData;
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// // Draw chart with data from chartData
|
||||
// function updateChart() {
|
||||
// for (let objectId in chartData) {
|
||||
// chartDataMap.set(objectId, chartData[objectId]);
|
||||
// }
|
||||
//
|
||||
// // To remove keys in chartDataMap
|
||||
// chartDataMap.forEach((value, key) => {
|
||||
// if (!chartData.hasOwnProperty(key)) {
|
||||
// chartDataMap.delete(key);
|
||||
// }
|
||||
// });
|
||||
// option = {
|
||||
// ...chartBaseConfig,
|
||||
//
|
||||
// series: Array.from(chartDataMap.entries()).map(
|
||||
// ([name, data]) => ({
|
||||
// name,
|
||||
// type: "line",
|
||||
// data,
|
||||
// showSymbol: false,
|
||||
// }),
|
||||
// ),
|
||||
// };
|
||||
// myChart.setOption(option, true);
|
||||
// }
|
||||
//
|
||||
// // Listen for reloadTable event
|
||||
// document
|
||||
// .getElementById("jsonTableContainer")
|
||||
// .addEventListener("reloadTable", (event) => {
|
||||
// myChart = echarts.init(document.getElementById("chart"));
|
||||
// console.log("table reloaded")
|
||||
//
|
||||
// // Get initial checkedBoxes when table loaded
|
||||
// // and cast to string for passing to API
|
||||
// let checkedBoxes = Array.from(
|
||||
// document.querySelectorAll(
|
||||
// '#jsonTableContainer input[type="checkbox"]:checked',
|
||||
// ),
|
||||
// ).map((checkbox) => checkbox.id);
|
||||
//
|
||||
// let str = checkedBoxes.join(",");
|
||||
// fetchDataForChart(str).then((initialData) => {
|
||||
// chartData = initialData;
|
||||
// updateChart();
|
||||
// });
|
||||
// console.log(str)
|
||||
//
|
||||
// // Listen for checkbox events in the table
|
||||
// document
|
||||
// .getElementById("jsonTableContainer")
|
||||
// .addEventListener("change", (event) => {
|
||||
// if (event.target.type === "checkbox") {
|
||||
// boxChecked = event.target.checked
|
||||
// boxId = event.target.id
|
||||
// // Remove unchecked boxes
|
||||
//
|
||||
// if (boxChecked === false) {
|
||||
// delete chartData[boxId];
|
||||
// updateChart();
|
||||
// console.log("Removed "+boxId+" from chartData")
|
||||
// // Add checked boxes
|
||||
// } else {
|
||||
// fetchDataForChart(boxId).then((newData) => {
|
||||
// chartData = { ...chartData, ...newData };
|
||||
// updateChart();
|
||||
// console.log("added "+boxId+" to chartData")
|
||||
// });
|
||||
// }
|
||||
// }});
|
||||
// });
|
||||
//
|
||||
|
|
|
@ -4,7 +4,7 @@ async function fetchDataForTable() {
|
|||
let selectedIndex = dropdown.selectedIndex;
|
||||
let selectedValue = dropdown.options[selectedIndex].value;
|
||||
const apiEndpoint =
|
||||
"https://api.bitlab21.com/bitcoin_business_growth_by_country?latest_date=true";
|
||||
"https://api.baseddata.io/bitcoin_business_growth_by_country?latest_date=true";
|
||||
const response = await fetch(
|
||||
apiEndpoint + `&cumulative_period_type=${selectedValue}`,
|
||||
);
|
||||
|
@ -51,7 +51,7 @@ function createTable(data) {
|
|||
const tr = document.createElement("tr");
|
||||
const checkbox = document.createElement("input");
|
||||
checkbox.type = "checkbox";
|
||||
checkbox.checked = index < 5; // Check the top 5 rows by default
|
||||
checkbox.checked = index < 1;
|
||||
checkbox.addEventListener("change", function () {
|
||||
if (this.checked) {
|
||||
checkedBoxes.push(row["country_name"]);
|
||||
|
|
|
@ -1,21 +1,24 @@
|
|||
let dataArr = [];
|
||||
const myChart = echarts.init(document.getElementById("chart"));
|
||||
|
||||
$.get("https://api.bitlab21.com/price", {}, (response) => {
|
||||
dataArr = response;
|
||||
console.log(dataArr);
|
||||
initEchart();
|
||||
});
|
||||
async function fetchDataForChart() {
|
||||
try {
|
||||
const apiEndpoint = "https://api.baseddata.io/get_json/final__price.json";
|
||||
const response = await fetch(apiEndpoint);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const dataArr = await response.json();
|
||||
initEchart(dataArr);
|
||||
} catch (error) {
|
||||
console.error("Fetching data failed:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function initEchart() {
|
||||
function initEchart(dataArr) {
|
||||
const option = {
|
||||
backgroundColor: backgroundColor,
|
||||
tooltip: {
|
||||
backgroundColor: tooltipBgColor,
|
||||
order: "seriesDesc",
|
||||
textStyle: textStyleMain,
|
||||
trigger: "axis",
|
||||
},
|
||||
tooltip: tooltip,
|
||||
toolbox: toolboxParams,
|
||||
xAxis: {
|
||||
data: dataArr.map((row) => row.date),
|
||||
|
@ -29,12 +32,9 @@ function initEchart() {
|
|||
{
|
||||
type: "value",
|
||||
name: "Price (USD)",
|
||||
nameGap: 30,
|
||||
nameLocation: "middle",
|
||||
nameTextStyle: {
|
||||
fontSize: 12 * fontScale,
|
||||
padding: [0, 0, 40, 0],
|
||||
color: "#eff1d6",
|
||||
},
|
||||
nameTextStyle: textStyleMain,
|
||||
position: "left",
|
||||
alignTicks: true,
|
||||
axisTick: axisTick,
|
||||
|
@ -42,7 +42,12 @@ function initEchart() {
|
|||
splitLine: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: axisLabel,
|
||||
axisLabel: {
|
||||
...axisLabel,
|
||||
formatter(value, index) {
|
||||
return nFormatter(value, 0);
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
series: [
|
||||
|
@ -56,3 +61,5 @@ function initEchart() {
|
|||
|
||||
myChart.setOption(option);
|
||||
}
|
||||
|
||||
fetchDataForChart();
|
||||
|
|
|
@ -1,15 +1,30 @@
|
|||
const fontScale = 1;
|
||||
const fontScale = 0.8;
|
||||
|
||||
const backgroundColor = "#181a1b";
|
||||
const tooltipBgColor = "#00557f";
|
||||
const backgroundColor = "#f9f9f9";
|
||||
const tooltipBgColor = "#e7e7f5";
|
||||
const textColor = "#373841";
|
||||
|
||||
const textStyleMain = {
|
||||
fontFamily: "sans-serif",
|
||||
fontSize: 12 * fontScale,
|
||||
color: textColor,
|
||||
};
|
||||
|
||||
const tooltip = {
|
||||
borderColor: textColor,
|
||||
backgroundColor: tooltipBgColor,
|
||||
order: "seriesDesc",
|
||||
textStyle: textStyleMain,
|
||||
trigger: "axis",
|
||||
};
|
||||
|
||||
const toolboxParams = {
|
||||
itemSize: 8 * fontScale,
|
||||
itemSize: 16 * fontScale,
|
||||
showTitle: true,
|
||||
top: "-1%",
|
||||
right: "20%",
|
||||
iconStyle: {
|
||||
borderColor: "#eff1d6",
|
||||
borderColor: textColor,
|
||||
borderWidth: 2,
|
||||
},
|
||||
feature: {
|
||||
|
@ -23,22 +38,16 @@ const toolboxParams = {
|
|||
},
|
||||
};
|
||||
|
||||
const textStyleMain = {
|
||||
fontFamily: "sans-serif",
|
||||
fontSize: 12 * fontScale,
|
||||
color: "#eff1d6",
|
||||
};
|
||||
|
||||
const axisLabel = {
|
||||
fontSize: 12 * fontScale,
|
||||
color: "#eff1d6",
|
||||
color: textColor,
|
||||
margin: 10,
|
||||
};
|
||||
|
||||
const axisLine = {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: "#eff1d6",
|
||||
color: textColor,
|
||||
width: 2,
|
||||
},
|
||||
};
|
||||
|
@ -55,13 +64,13 @@ const grid = {
|
|||
const yaxisTextStyle = {
|
||||
fontSize: 12 * fontScale,
|
||||
padding: [0, 0, 10, 0],
|
||||
color: "#eff1d6",
|
||||
color: textColor,
|
||||
};
|
||||
|
||||
const yaxisTextStyle2 = {
|
||||
fontSize: 12 * fontScale,
|
||||
padding: [50, 0, 0, 0],
|
||||
color: "#eff1d6",
|
||||
color: textColor,
|
||||
};
|
||||
|
||||
function dataZoom(start = 90, end = 100, bottom = 15, height = 15) {
|
||||
|
@ -97,7 +106,7 @@ function nFormatter(value, digits) {
|
|||
: "0";
|
||||
}
|
||||
|
||||
$(window).on("resize", function () {
|
||||
window.addEventListener("resize", function () {
|
||||
if (myChart != null && myChart != undefined) {
|
||||
myChart.resize();
|
||||
}
|
||||
|
|
|
@ -1,21 +1,27 @@
|
|||
let dataArr = [];
|
||||
const myChart = echarts.init(document.getElementById("chart"));
|
||||
|
||||
$.get("https://api.bitlab21.com/feerates", {}, (response) => {
|
||||
dataArr = response;
|
||||
console.log(dataArr);
|
||||
initEchart();
|
||||
});
|
||||
async function fetchDataForChart() {
|
||||
try {
|
||||
const apiEndpoint =
|
||||
"https://api.baseddata.io/get_json/final__feerate_percentiles.json";
|
||||
const response = await fetch(apiEndpoint);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const dataArr = await response.json();
|
||||
initEchart(dataArr);
|
||||
} catch (error) {
|
||||
console.error("Fetching data failed:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function initEchart() {
|
||||
function initEchart(dataArr) {
|
||||
const option = {
|
||||
backgroundColor: backgroundColor,
|
||||
tooltip: {
|
||||
...tooltip,
|
||||
valueFormatter: (value) => `${value.toFixed(0)} sats/vByte`,
|
||||
backgroundColor: tooltipBgColor,
|
||||
order: "seriesDesc",
|
||||
textStyle: textStyleMain,
|
||||
trigger: "axis",
|
||||
},
|
||||
toolbox: toolboxParams,
|
||||
xAxis: {
|
||||
|
@ -99,3 +105,4 @@ function initEchart() {
|
|||
|
||||
myChart.setOption(option);
|
||||
}
|
||||
fetchDataForChart();
|
||||
|
|
|
@ -1,23 +1,29 @@
|
|||
let dataArr = [];
|
||||
const myChart = echarts.init(document.getElementById("chart"));
|
||||
|
||||
$.get("https://api.bitlab21.com/hashrate", {}, (response) => {
|
||||
dataArr = response;
|
||||
console.log(dataArr);
|
||||
initEchart();
|
||||
});
|
||||
async function fetchDataForChart() {
|
||||
try {
|
||||
const apiEndpoint =
|
||||
"https://api.baseddata.io/get_json/final__hashrate.json";
|
||||
const response = await fetch(apiEndpoint);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const dataArr = await response.json();
|
||||
initEchart(dataArr);
|
||||
} catch (error) {
|
||||
console.error("Fetching data failed:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function initEchart() {
|
||||
function initEchart(dataArr) {
|
||||
const option = {
|
||||
backgroundColor: backgroundColor,
|
||||
tooltip: {
|
||||
backgroundColor: tooltipBgColor,
|
||||
order: "seriesDesc",
|
||||
...tooltip,
|
||||
valueFormatter(value, index) {
|
||||
return nFormatter(value, 0);
|
||||
},
|
||||
textStyle: textStyleMain,
|
||||
trigger: "axis",
|
||||
},
|
||||
toolbox: toolboxParams,
|
||||
xAxis: {
|
||||
|
@ -33,11 +39,8 @@ function initEchart() {
|
|||
type: "value",
|
||||
name: "Hashrate (H/s)",
|
||||
nameLocation: "middle",
|
||||
nameTextStyle: {
|
||||
fontSize: 12 * fontScale,
|
||||
padding: [0, 0, 20, 0],
|
||||
color: "#eff1d6",
|
||||
},
|
||||
nameGap: 30,
|
||||
nameTextStyle: textStyleMain,
|
||||
position: "left",
|
||||
alignTicks: true,
|
||||
axisTick: axisTick,
|
||||
|
@ -47,7 +50,7 @@ function initEchart() {
|
|||
axisLine: axisLine,
|
||||
axisLabel: {
|
||||
fontSize: 12 * fontScale,
|
||||
color: "#eff1d6",
|
||||
color: textColor,
|
||||
formatter(value, index) {
|
||||
return nFormatter(value, 0);
|
||||
},
|
||||
|
@ -57,11 +60,8 @@ function initEchart() {
|
|||
type: "value",
|
||||
name: "Difficulty",
|
||||
nameLocation: "middle",
|
||||
nameTextStyle: {
|
||||
fontSize: 12 * fontScale,
|
||||
padding: [20, 0, 0, 0],
|
||||
color: "#eff1d6",
|
||||
},
|
||||
nameGap: 30,
|
||||
nameTextStyle: textStyleMain,
|
||||
axisTick: axisTick,
|
||||
position: "right",
|
||||
alignTicks: true,
|
||||
|
@ -71,7 +71,7 @@ function initEchart() {
|
|||
},
|
||||
axisLabel: {
|
||||
fontSize: 12 * fontScale,
|
||||
color: "#eff1d6",
|
||||
color: textColor,
|
||||
formatter(value, index) {
|
||||
return nFormatter(value, 0);
|
||||
},
|
||||
|
@ -106,3 +106,4 @@ function initEchart() {
|
|||
|
||||
myChart.setOption(option);
|
||||
}
|
||||
fetchDataForChart();
|
||||
|
|
|
@ -1,21 +1,25 @@
|
|||
let dataArr = [];
|
||||
const myChart = echarts.init(document.getElementById("chart"));
|
||||
|
||||
$.get("https://api.bitlab21.com/miner_rewards", {}, (response) => {
|
||||
dataArr = response;
|
||||
console.log(dataArr);
|
||||
initEchart();
|
||||
});
|
||||
async function fetchDataForChart() {
|
||||
try {
|
||||
const apiEndpoint =
|
||||
"https://api.baseddata.io/get_json/final__miner_rewards.json";
|
||||
const response = await fetch(apiEndpoint);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const dataArr = await response.json();
|
||||
initEchart(dataArr);
|
||||
} catch (error) {
|
||||
console.error("Fetching data failed:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function initEchart() {
|
||||
function initEchart(dataArr) {
|
||||
const option = {
|
||||
backgroundColor: backgroundColor,
|
||||
tooltip: {
|
||||
backgroundColor: tooltipBgColor,
|
||||
order: "seriesDesc",
|
||||
textStyle: textStyleMain,
|
||||
trigger: "axis",
|
||||
},
|
||||
tooltip: tooltip,
|
||||
toolbox: toolboxParams,
|
||||
xAxis: {
|
||||
data: dataArr.map((row) => row.date),
|
||||
|
@ -28,14 +32,10 @@ function initEchart() {
|
|||
yAxis: [
|
||||
{
|
||||
type: "value",
|
||||
nameGap: 50,
|
||||
name: "Total Daily Rewards (USD)",
|
||||
name: "Rewards (USD)",
|
||||
nameLocation: "middle",
|
||||
nameTextStyle: {
|
||||
fontSize: 12 * fontScale,
|
||||
padding: [0, 0, 15, 0],
|
||||
color: "#eff1d6",
|
||||
},
|
||||
nameGap: 35,
|
||||
nameTextStyle: textStyleMain,
|
||||
position: "left",
|
||||
alignTicks: true,
|
||||
axisTick: axisTick,
|
||||
|
@ -43,14 +43,22 @@ function initEchart() {
|
|||
splitLine: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: axisLabel,
|
||||
axisLabel: {
|
||||
...axisLabel,
|
||||
formatter(value, index) {
|
||||
return nFormatter(value, 0);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "value",
|
||||
name: "Block Subsidy (BTC)",
|
||||
nameGap: -30,
|
||||
nameLocation: "middle",
|
||||
nameTextStyle: yaxisTextStyle2,
|
||||
nameGap: 20,
|
||||
nameTextStyle: {
|
||||
fontSize: 12 * fontScale,
|
||||
color: textColor,
|
||||
},
|
||||
axisTick: axisTick,
|
||||
position: "right",
|
||||
alignTicks: true,
|
||||
|
@ -60,7 +68,7 @@ function initEchart() {
|
|||
},
|
||||
axisLabel: {
|
||||
fontSize: 12 * fontScale,
|
||||
color: "#eff1d6",
|
||||
color: textColor,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -96,7 +104,7 @@ function initEchart() {
|
|||
symbol: "none",
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: "#eff1d6",
|
||||
color: textColor,
|
||||
},
|
||||
data: dataArr.map((row) => row.total_reward_usd),
|
||||
},
|
||||
|
@ -116,25 +124,4 @@ function initEchart() {
|
|||
myChart.setOption(option);
|
||||
}
|
||||
|
||||
const checkboxLog = document.body.querySelector("#checkbox-log");
|
||||
|
||||
checkboxLog.addEventListener("change", (e) => {
|
||||
const isChecked = e.target.checked;
|
||||
myChart.setOption({
|
||||
yAxis: [
|
||||
{
|
||||
type: "value",
|
||||
type: isChecked ? "log" : "value",
|
||||
name: "Price (USD)",
|
||||
nameLocation: "middle",
|
||||
splitNumber: isChecked ? 5 : 5,
|
||||
nameTextStyle: yaxisTextStyle,
|
||||
position: "left",
|
||||
alignTicks: true,
|
||||
axisLine: axisLine,
|
||||
axisLabel: xaxisLabel,
|
||||
},
|
||||
],
|
||||
dataZoom: dataZoom((start = isChecked ? 0 : 90)),
|
||||
});
|
||||
});
|
||||
fetchDataForChart();
|
||||
|
|
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 63 KiB |
After Width: | Height: | Size: 1.4 MiB |
After Width: | Height: | Size: 68 KiB |
After Width: | Height: | Size: 56 KiB |
After Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 54 KiB |