Starting blog post on stackoverflow
And loads of supporting graph code
This commit is contained in:
parent
8fdc28e7c5
commit
4233c43102
16 changed files with 1352 additions and 50 deletions
262
GRAPHS.md
Normal file
262
GRAPHS.md
Normal file
|
|
@ -0,0 +1,262 @@
|
||||||
|
# Graph Shortcode Documentation
|
||||||
|
|
||||||
|
This Hugo site includes a custom shortcode for creating terminal-styled graphs that match the hacker green aesthetic.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Server-side processing**: Data is processed when Hugo builds the site
|
||||||
|
- **Terminal styling**: Automatic green color scheme matching the site theme
|
||||||
|
- **Multiple chart types**: Line, bar, pie, doughnut, radar, and more
|
||||||
|
- **Responsive**: Adapts to different screen sizes
|
||||||
|
- **Customizable**: Override colors and styling as needed
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
The shortcode supports two modes:
|
||||||
|
1. **Inline JSON data** - Embed data directly in your markdown
|
||||||
|
2. **CSV file data** - Load data from a CSV file in your page bundle
|
||||||
|
|
||||||
|
### CSV File Mode (Recommended)
|
||||||
|
|
||||||
|
Place your CSV file in the same directory as your blog post's `index.md`:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
{{< graph id="my-chart" type="line" title="My Data" height="500" csv="data.csv" labelColumn="Date" dataColumn="Value" dateFormat="2006-01" >}}
|
||||||
|
{{< /graph >}}
|
||||||
|
```
|
||||||
|
|
||||||
|
**CSV Parameters:**
|
||||||
|
- `csv` - Filename of the CSV in the page bundle (required for CSV mode)
|
||||||
|
- `labelColumn` - Column name for x-axis labels (default: "Month")
|
||||||
|
- `dataColumn` - Column name for y-axis data (default: "Questions")
|
||||||
|
- `dateFormat` - Go date format for parsing timestamps (default: "2006-01")
|
||||||
|
- `skipRows` - Number of rows to skip from the start (default: 0)
|
||||||
|
- `maxRows` - Maximum number of rows to display (default: 0 = all)
|
||||||
|
|
||||||
|
**Example CSV file** (`data.csv`):
|
||||||
|
```csv
|
||||||
|
Date,Value
|
||||||
|
2024-01,100
|
||||||
|
2024-02,150
|
||||||
|
2024-03,175
|
||||||
|
```
|
||||||
|
|
||||||
|
### Inline JSON Mode
|
||||||
|
|
||||||
|
### Line Chart
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
{{< graph id="my-chart" type="line" title="My Data" height="400" >}}
|
||||||
|
{
|
||||||
|
"labels": ["Jan", "Feb", "Mar", "Apr", "May"],
|
||||||
|
"datasets": [{
|
||||||
|
"label": "Sales",
|
||||||
|
"data": [12, 19, 3, 5, 2]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
{{< /graph >}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Bar Chart
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
{{< graph id="bar-example" type="bar" title="Monthly Revenue" height="350" >}}
|
||||||
|
{
|
||||||
|
"labels": ["Q1", "Q2", "Q3", "Q4"],
|
||||||
|
"datasets": [{
|
||||||
|
"label": "Revenue ($k)",
|
||||||
|
"data": [65, 59, 80, 81]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
{{< /graph >}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multiple Datasets
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
{{< graph id="multi-line" type="line" title="Comparison" height="400" >}}
|
||||||
|
{
|
||||||
|
"labels": ["2020", "2021", "2022", "2023", "2024"],
|
||||||
|
"datasets": [
|
||||||
|
{
|
||||||
|
"label": "Product A",
|
||||||
|
"data": [30, 45, 60, 70, 85]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Product B",
|
||||||
|
"data": [20, 35, 55, 65, 75]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
{{< /graph >}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pie Chart
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
{{< graph id="pie-chart" type="pie" title="Market Share" height="400" >}}
|
||||||
|
{
|
||||||
|
"labels": ["Chrome", "Firefox", "Safari", "Edge"],
|
||||||
|
"datasets": [{
|
||||||
|
"data": [65, 15, 12, 8]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
{{< /graph >}}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
### Common Parameters
|
||||||
|
|
||||||
|
| Parameter | Required | Default | Description |
|
||||||
|
|-----------|----------|---------|-------------|
|
||||||
|
| `id` | No | Auto-generated | Unique identifier for the chart |
|
||||||
|
| `type` | No | `line` | Chart type: `line`, `bar`, `pie`, `doughnut`, `radar`, `polarArea` |
|
||||||
|
| `title` | No | Empty | Chart title displayed above the graph |
|
||||||
|
| `height` | No | `400` | Height of the chart container in pixels |
|
||||||
|
|
||||||
|
### CSV Mode Parameters
|
||||||
|
|
||||||
|
| Parameter | Required | Default | Description |
|
||||||
|
|-----------|----------|---------|-------------|
|
||||||
|
| `csv` | Yes (for CSV mode) | - | Filename of CSV in page bundle |
|
||||||
|
| `labelColumn` | No | `"Month"` | CSV column name for labels (x-axis) |
|
||||||
|
| `dataColumn` | No | `"Questions"` | CSV column name for data (y-axis) |
|
||||||
|
| `dateFormat` | No | `"2006-01"` | Go date format string for parsing dates |
|
||||||
|
| `skipRows` | No | `0` | Number of data rows to skip |
|
||||||
|
| `maxRows` | No | `0` | Max rows to display (0 = all) |
|
||||||
|
|
||||||
|
## Data Format
|
||||||
|
|
||||||
|
The shortcode expects JSON data in Chart.js format. The basic structure is:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"labels": ["Label 1", "Label 2", "Label 3"],
|
||||||
|
"datasets": [{
|
||||||
|
"label": "Dataset Name",
|
||||||
|
"data": [value1, value2, value3]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Advanced Data Options
|
||||||
|
|
||||||
|
You can override the automatic styling by providing Chart.js dataset properties:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
{{< graph id="custom-style" type="line" title="Custom Colors" >}}
|
||||||
|
{
|
||||||
|
"labels": ["A", "B", "C"],
|
||||||
|
"datasets": [{
|
||||||
|
"label": "Custom",
|
||||||
|
"data": [10, 20, 30],
|
||||||
|
"borderColor": "rgb(255, 99, 132)",
|
||||||
|
"backgroundColor": "rgba(255, 99, 132, 0.2)",
|
||||||
|
"borderWidth": 3
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
{{< /graph >}}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Color Scheme
|
||||||
|
|
||||||
|
The shortcode automatically applies terminal-style colors:
|
||||||
|
|
||||||
|
- **Primary**: `rgb(173, 255, 47)` (greenyellow)
|
||||||
|
- **Secondary**: `rgb(0, 255, 0)` (green)
|
||||||
|
- **Tertiary**: `rgb(0, 200, 0)` (darker green)
|
||||||
|
- **Grid**: `rgba(0, 255, 0, 0.2)`
|
||||||
|
- **Background**: `rgba(0, 255, 0, 0.1)`
|
||||||
|
|
||||||
|
Multiple datasets automatically cycle through these colors.
|
||||||
|
|
||||||
|
## CSS Classes
|
||||||
|
|
||||||
|
The graph container has the class `.graph-container` with the following variants:
|
||||||
|
|
||||||
|
- `.graph-container.full-width` - Full width graph (extends to edges)
|
||||||
|
- `.graph-container.compact` - Smaller padding and title
|
||||||
|
|
||||||
|
To use variants, wrap the shortcode in a div:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="graph-container compact">
|
||||||
|
{{< graph ... >}}
|
||||||
|
...
|
||||||
|
{{< /graph >}}
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### StackOverflow Decline
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
{{< graph id="stackoverflow" type="line" title="Stack Overflow Activity" height="400" >}}
|
||||||
|
{
|
||||||
|
"labels": ["2018", "2019", "2020", "2021", "2022", "2023", "2024"],
|
||||||
|
"datasets": [{
|
||||||
|
"label": "Questions (thousands/month)",
|
||||||
|
"data": [170, 180, 195, 175, 150, 120, 85],
|
||||||
|
"fill": true
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
{{< /graph >}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Technology Adoption
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
{{< graph id="tech-adoption" type="bar" title="Framework Adoption (%)" height="350" >}}
|
||||||
|
{
|
||||||
|
"labels": ["React", "Vue", "Angular", "Svelte"],
|
||||||
|
"datasets": [{
|
||||||
|
"label": "2023",
|
||||||
|
"data": [67, 45, 42, 18]
|
||||||
|
}, {
|
||||||
|
"label": "2024",
|
||||||
|
"data": [71, 48, 38, 25]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
{{< /graph >}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Response Time Distribution
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
{{< graph id="response-times" type="doughnut" title="API Response Times" height="400" >}}
|
||||||
|
{
|
||||||
|
"labels": ["< 100ms", "100-500ms", "500ms-1s", "> 1s"],
|
||||||
|
"datasets": [{
|
||||||
|
"data": [45, 35, 15, 5]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
{{< /graph >}}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tips
|
||||||
|
|
||||||
|
1. **Keep IDs unique**: Each chart on a page needs a unique ID
|
||||||
|
2. **Data validation**: The shortcode validates JSON at build time - invalid JSON will cause build errors
|
||||||
|
3. **Responsive**: Charts are responsive by default, but fixed heights work better for consistency
|
||||||
|
4. **Performance**: Charts are rendered client-side but data is embedded at build time
|
||||||
|
5. **Testing**: Set `draft: false` and run `hugo server -D` to preview graphs
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
**Graph not showing?**
|
||||||
|
- Check browser console for JavaScript errors
|
||||||
|
- Verify Chart.js is loaded (should be in page `<head>`)
|
||||||
|
- Ensure JSON data is valid
|
||||||
|
- Check that the ID is unique on the page
|
||||||
|
|
||||||
|
**Styling issues?**
|
||||||
|
- Graphs inherit the terminal theme automatically
|
||||||
|
- Custom colors can be added per dataset
|
||||||
|
- Container styling can be modified in `assets/sass/partials/_graphs.scss`
|
||||||
|
|
||||||
|
**Build errors?**
|
||||||
|
- Invalid JSON will cause Hugo build failures
|
||||||
|
- Check for unclosed braces or missing commas
|
||||||
|
- Ensure the shortcode tags are properly formatted
|
||||||
|
|
@ -271,14 +271,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.tag-filter-link {
|
.tag-filter-link {
|
||||||
background: rgba(255, 153, 0, 0.2);
|
background: rgba(255, 153, 0, 0.2);
|
||||||
border-color: rgba(255, 153, 0, 0.5);
|
border-color: rgba(255, 153, 0, 0.5);
|
||||||
color: #ff9900;
|
color: #ff9900;
|
||||||
text-shadow: 0 0 5px rgba(255, 153, 0, 0.5);
|
text-shadow: 0 0 5px rgba(255, 153, 0, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post title
|
// Post title
|
||||||
|
|
@ -555,7 +553,8 @@
|
||||||
background:
|
background:
|
||||||
linear-gradient(#000, #000) padding-box,
|
linear-gradient(#000, #000) padding-box,
|
||||||
linear-gradient(180deg, #0f0, #000) border-box;
|
linear-gradient(180deg, #0f0, #000) border-box;
|
||||||
filter: grayscale(100%) contrast(1.2) brightness(0.9) sepia(100%) hue-rotate(60deg) saturate(300%);
|
filter: grayscale(100%) contrast(1.2) brightness(0.9) sepia(100%)
|
||||||
|
hue-rotate(60deg) saturate(300%);
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|
|
||||||
|
|
@ -52,8 +52,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.whiteboard-description {
|
.whiteboard-description {
|
||||||
color: #546e7a;
|
font-family: "Caveat", cursive;
|
||||||
font-size: 1.1rem;
|
font-size: 28px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2c3e50;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
174
assets/sass/partials/_graphs.scss
Normal file
174
assets/sass/partials/_graphs.scss
Normal file
|
|
@ -0,0 +1,174 @@
|
||||||
|
// Graph container styling to match terminal aesthetic
|
||||||
|
.graph-container {
|
||||||
|
margin: 2rem 0;
|
||||||
|
padding: 20px 20px 100px 20px; // Extra bottom padding for rotated x-axis labels
|
||||||
|
background: rgba(0, 255, 0, 0.05);
|
||||||
|
border: 2px solid rgba(0, 255, 0, 0.3);
|
||||||
|
border-radius: 8px;
|
||||||
|
position: relative;
|
||||||
|
box-shadow:
|
||||||
|
0 0 20px rgba(0, 255, 0, 0.1),
|
||||||
|
inset 0 0 40px rgba(0, 255, 0, 0.05);
|
||||||
|
overflow: visible; // Prevent clipping of labels
|
||||||
|
|
||||||
|
@include media-down(lg) {
|
||||||
|
padding: 15px 15px 120px 15px;
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Terminal-style border glow on hover
|
||||||
|
&:hover {
|
||||||
|
border-color: rgba(0, 255, 0, 0.5);
|
||||||
|
box-shadow:
|
||||||
|
0 0 30px rgba(0, 255, 0, 0.2),
|
||||||
|
inset 0 0 40px rgba(0, 255, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scanline effect overlay
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
0deg,
|
||||||
|
rgba(0, 0, 0, 0.1) 0px,
|
||||||
|
rgba(0, 0, 0, 0.1) 1px,
|
||||||
|
transparent 1px,
|
||||||
|
transparent 3px
|
||||||
|
);
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 1;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Graph title
|
||||||
|
.graph-title {
|
||||||
|
color: greenyellow;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
margin: 0 0 15px 0;
|
||||||
|
text-align: center;
|
||||||
|
text-shadow: 0 0 10px rgba(173, 255, 47, 0.5);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
@include media-down(lg) {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
width: 60%;
|
||||||
|
height: 2px;
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
transparent,
|
||||||
|
rgba(0, 255, 0, 0.6),
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
margin: 8px auto 0;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 255, 0, 0.4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Canvas element
|
||||||
|
canvas {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
filter: drop-shadow(0 0 8px rgba(0, 255, 0, 0.3));
|
||||||
|
}
|
||||||
|
|
||||||
|
// CRT flicker effect (subtle)
|
||||||
|
@keyframes graph-flicker {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.98;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
animation: graph-flicker 3s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special styling for graphs in blog posts
|
||||||
|
.blog-summary,
|
||||||
|
.blogs-content {
|
||||||
|
.graph-container {
|
||||||
|
// Ensure proper spacing in blog context
|
||||||
|
margin: 2.5rem 0;
|
||||||
|
|
||||||
|
@include media-down(lg) {
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Full-width graph variant
|
||||||
|
.graph-container.full-width {
|
||||||
|
margin-left: -20px;
|
||||||
|
margin-right: -20px;
|
||||||
|
border-radius: 0;
|
||||||
|
|
||||||
|
@include media-down(lg) {
|
||||||
|
margin-left: -15px;
|
||||||
|
margin-right: -15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compact graph variant
|
||||||
|
.graph-container.compact {
|
||||||
|
padding: 15px;
|
||||||
|
|
||||||
|
.graph-title {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-down(lg) {
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
.graph-title {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loading state
|
||||||
|
.graph-container.loading {
|
||||||
|
&::after {
|
||||||
|
content: "LOADING DATA...";
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
color: #0f0;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
text-shadow: 0 0 10px rgba(0, 255, 0, 0.8);
|
||||||
|
animation: pulse 1.5s ease-in-out infinite;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
@import "partials/lcd-display";
|
@import "partials/lcd-display";
|
||||||
@import "partials/window";
|
@import "partials/window";
|
||||||
@import "partials/crt-tv";
|
@import "partials/crt-tv";
|
||||||
|
@import "partials/graphs";
|
||||||
|
|
||||||
@import "partials/content-screens";
|
@import "partials/content-screens";
|
||||||
|
|
||||||
|
|
|
||||||
23
config.yml
23
config.yml
|
|
@ -42,29 +42,6 @@ params:
|
||||||
limit: 10
|
limit: 10
|
||||||
keys: ["title", "permalink", "summary", "content"]
|
keys: ["title", "permalink", "summary", "content"]
|
||||||
|
|
||||||
menu:
|
|
||||||
main:
|
|
||||||
- identifier: about
|
|
||||||
name: about
|
|
||||||
url: /about/
|
|
||||||
weight: 5
|
|
||||||
- identifier: gear
|
|
||||||
name: gear & edc
|
|
||||||
url: /gear/
|
|
||||||
weight: 10
|
|
||||||
- identifier: resources
|
|
||||||
name: resources
|
|
||||||
url: /resources/
|
|
||||||
weight: 12
|
|
||||||
- identifier: archives
|
|
||||||
name: archives
|
|
||||||
url: /archives/
|
|
||||||
weight: 15
|
|
||||||
- identifier: tags
|
|
||||||
name: tags
|
|
||||||
url: /tags/
|
|
||||||
weight: 20
|
|
||||||
|
|
||||||
markup:
|
markup:
|
||||||
highlight:
|
highlight:
|
||||||
noClasses: false
|
noClasses: false
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
Month,ai coding: (Worldwide)
|
||||||
|
2019-01,1
|
||||||
|
2019-02,1
|
||||||
|
2019-03,1
|
||||||
|
2019-04,1
|
||||||
|
2019-05,1
|
||||||
|
2019-06,1
|
||||||
|
2019-07,1
|
||||||
|
2019-08,1
|
||||||
|
2019-09,1
|
||||||
|
2019-10,1
|
||||||
|
2019-11,1
|
||||||
|
2019-12,1
|
||||||
|
2020-01,1
|
||||||
|
2020-02,1
|
||||||
|
2020-03,1
|
||||||
|
2020-04,1
|
||||||
|
2020-05,1
|
||||||
|
2020-06,1
|
||||||
|
2020-07,1
|
||||||
|
2020-08,1
|
||||||
|
2020-09,1
|
||||||
|
2020-10,1
|
||||||
|
2020-11,1
|
||||||
|
2020-12,1
|
||||||
|
2021-01,1
|
||||||
|
2021-02,1
|
||||||
|
2021-03,1
|
||||||
|
2021-04,1
|
||||||
|
2021-05,1
|
||||||
|
2021-06,1
|
||||||
|
2021-07,1
|
||||||
|
2021-08,1
|
||||||
|
2021-09,1
|
||||||
|
2021-10,1
|
||||||
|
2021-11,1
|
||||||
|
2021-12,1
|
||||||
|
2022-01,1
|
||||||
|
2022-02,1
|
||||||
|
2022-03,1
|
||||||
|
2022-04,1
|
||||||
|
2022-05,1
|
||||||
|
2022-06,1
|
||||||
|
2022-07,1
|
||||||
|
2022-08,1
|
||||||
|
2022-09,1
|
||||||
|
2022-10,1
|
||||||
|
2022-11,1
|
||||||
|
2022-12,3
|
||||||
|
2023-01,4
|
||||||
|
2023-02,5
|
||||||
|
2023-03,5
|
||||||
|
2023-04,6
|
||||||
|
2023-05,7
|
||||||
|
2023-06,7
|
||||||
|
2023-07,7
|
||||||
|
2023-08,7
|
||||||
|
2023-09,7
|
||||||
|
2023-10,7
|
||||||
|
2023-11,8
|
||||||
|
2023-12,8
|
||||||
|
2024-01,9
|
||||||
|
2024-02,10
|
||||||
|
2024-03,14
|
||||||
|
2024-04,12
|
||||||
|
2024-05,11
|
||||||
|
2024-06,11
|
||||||
|
2024-07,10
|
||||||
|
2024-08,11
|
||||||
|
2024-09,14
|
||||||
|
2024-10,16
|
||||||
|
2024-11,18
|
||||||
|
2024-12,19
|
||||||
|
2025-01,21
|
||||||
|
2025-02,27
|
||||||
|
2025-03,31
|
||||||
|
2025-04,32
|
||||||
|
2025-05,36
|
||||||
|
2025-06,53
|
||||||
|
2025-07,58
|
||||||
|
2025-08,100
|
||||||
|
2025-09,89
|
||||||
|
2025-10,82
|
||||||
|
2025-11,92
|
||||||
|
2025-12,82
|
||||||
|
2026-01,41
|
||||||
|
86
content/blog/the-downfall-of-stackoverflow/ai-trend.csv
Normal file
86
content/blog/the-downfall-of-stackoverflow/ai-trend.csv
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
Month,ai: (Worldwide)
|
||||||
|
2019-01,5
|
||||||
|
2019-02,5
|
||||||
|
2019-03,5
|
||||||
|
2019-04,5
|
||||||
|
2019-05,5
|
||||||
|
2019-06,5
|
||||||
|
2019-07,5
|
||||||
|
2019-08,5
|
||||||
|
2019-09,5
|
||||||
|
2019-10,5
|
||||||
|
2019-11,5
|
||||||
|
2019-12,5
|
||||||
|
2020-01,5
|
||||||
|
2020-02,5
|
||||||
|
2020-03,5
|
||||||
|
2020-04,6
|
||||||
|
2020-05,6
|
||||||
|
2020-06,5
|
||||||
|
2020-07,5
|
||||||
|
2020-08,5
|
||||||
|
2020-09,5
|
||||||
|
2020-10,5
|
||||||
|
2020-11,5
|
||||||
|
2020-12,5
|
||||||
|
2021-01,5
|
||||||
|
2021-02,5
|
||||||
|
2021-03,6
|
||||||
|
2021-04,6
|
||||||
|
2021-05,6
|
||||||
|
2021-06,6
|
||||||
|
2021-07,5
|
||||||
|
2021-08,6
|
||||||
|
2021-09,6
|
||||||
|
2021-10,6
|
||||||
|
2021-11,6
|
||||||
|
2021-12,6
|
||||||
|
2022-01,7
|
||||||
|
2022-02,7
|
||||||
|
2022-03,7
|
||||||
|
2022-04,7
|
||||||
|
2022-05,7
|
||||||
|
2022-06,7
|
||||||
|
2022-07,7
|
||||||
|
2022-08,7
|
||||||
|
2022-09,8
|
||||||
|
2022-10,8
|
||||||
|
2022-11,8
|
||||||
|
2022-12,12
|
||||||
|
2023-01,12
|
||||||
|
2023-02,15
|
||||||
|
2023-03,16
|
||||||
|
2023-04,21
|
||||||
|
2023-05,28
|
||||||
|
2023-06,27
|
||||||
|
2023-07,26
|
||||||
|
2023-08,26
|
||||||
|
2023-09,25
|
||||||
|
2023-10,29
|
||||||
|
2023-11,30
|
||||||
|
2023-12,30
|
||||||
|
2024-01,32
|
||||||
|
2024-02,33
|
||||||
|
2024-03,36
|
||||||
|
2024-04,34
|
||||||
|
2024-05,36
|
||||||
|
2024-06,34
|
||||||
|
2024-07,34
|
||||||
|
2024-08,39
|
||||||
|
2024-09,39
|
||||||
|
2024-10,42
|
||||||
|
2024-11,43
|
||||||
|
2024-12,43
|
||||||
|
2025-01,47
|
||||||
|
2025-02,51
|
||||||
|
2025-03,54
|
||||||
|
2025-04,55
|
||||||
|
2025-05,52
|
||||||
|
2025-06,57
|
||||||
|
2025-07,62
|
||||||
|
2025-08,73
|
||||||
|
2025-09,100
|
||||||
|
2025-10,92
|
||||||
|
2025-11,90
|
||||||
|
2025-12,80
|
||||||
|
2026-01,66
|
||||||
|
80
content/blog/the-downfall-of-stackoverflow/index.md
Normal file
80
content/blog/the-downfall-of-stackoverflow/index.md
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
---
|
||||||
|
title: "The Downfall of StackOverflow"
|
||||||
|
date: 2026-02-04T13:58:02Z
|
||||||
|
tags:
|
||||||
|
- analysis
|
||||||
|
- ai
|
||||||
|
draft: true
|
||||||
|
---
|
||||||
|
|
||||||
|
This post was inspired by a post on [Hacker News](https://news.ycombinator.com/item?id=46482345) that linked to this [StackOverflow data](https://data.stackexchange.com/stackoverflow/query/1926661#graph).
|
||||||
|
|
||||||
|
My kneejerk reaction was that the rise in AI and its code analysis capabilities have caused the downfall of StackOverflow. We can see a peak after a gradual decline in early 2020 (COVID bedroom coders?) which then returns to a roughly normal level by 2021, before starting a stark decline into obscurity, very much accelerating at the end of 2022.
|
||||||
|
|
||||||
|
{{< graph id="stackoverflow-trend" type="line" title="Stack Overflow Questions Over Time" height="500" csv="stackoverflow_questions_over_time.csv" labelColumn="Month" dataColumn="" dateFormat="2006-01" >}}
|
||||||
|
{{< /graph >}}
|
||||||
|
|
||||||
|
## The rise and fall
|
||||||
|
|
||||||
|
StackOverflow had a hell of a run. From just 4 questions monthly in July 2008 to over 207,000 in March 2014—that's six years of basically uninterrupted growth. It became _the_ place every developer went when they were stuck.
|
||||||
|
|
||||||
|
Then around 2014-2015, it plateaued. About 170,000-190,000 questions per month, which held steady for a few years before starting to slip. By 2019 we're down to around 150,000 per month. Still solid, but the writing was on the wall.
|
||||||
|
|
||||||
|
Then it properly falls off a cliff. January 2023: 97,209 questions. December 2023: 42,601. January 2026: 321. That's a 99.8% drop from the 2020 peak. Three hundred and twenty-one questions. In a month.
|
||||||
|
|
||||||
|
## Is it AI?
|
||||||
|
|
||||||
|
Looking at Google Trends[^1] for AI-related searches, there's a bit of a gap between when StackOverflow started dying and when AI actually took off.
|
||||||
|
|
||||||
|
{{< graph id="ai-trends" type="line" title="AI Search Trends" height="500"
|
||||||
|
csv="ai-coding-trend.csv,ai-trend.csv"
|
||||||
|
labelColumn="Month"
|
||||||
|
dataColumns="ai coding: (Worldwide),ai: (Worldwide)"
|
||||||
|
datasetLabels="AI Coding,AI General"
|
||||||
|
dateFormat="2006-01" >}}
|
||||||
|
{{< /graph >}}
|
||||||
|
|
||||||
|
Both general AI interest and AI coding searches were basically flat from 2019 through most of 2022. Then in December 2022 it spikes—that's ChatGPT launching[^2].
|
||||||
|
|
||||||
|
Zooming in on 2019 onwards makes it clearer:
|
||||||
|
|
||||||
|
{{< graph id="ai-trends-vs-questions" type="line" title="AI Search Trends vs StackOverflow Questions" height="500"
|
||||||
|
csv="stackoverflow_questions_after_2019.csv,ai-coding-trend.csv,ai-trend.csv"
|
||||||
|
labelColumn="Month"
|
||||||
|
dataColumns="Questions,ai coding: (Worldwide),ai: (Worldwide)"
|
||||||
|
datasetLabels="StackOverflow Questions,AI Coding,AI General"
|
||||||
|
yAxisIDs="y,y1,y1"
|
||||||
|
dateFormat="2006-01" >}}
|
||||||
|
{{< /graph >}}
|
||||||
|
|
||||||
|
## The timeline doesn't quite add up
|
||||||
|
|
||||||
|
Here's the thing though: StackOverflow's accelerated decline starts in 2021, well before anyone gave a shit about AI coding. From January 2021 (140,009 questions) to December 2022 (96,767 questions), it lost 31% of its traffic while "AI coding" searches sat at baseline.
|
||||||
|
|
||||||
|
The AI coding surge doesn't really kick off until early 2023, then explodes through 2025, peaking in August. But by then StackOverflow was already down to 5,885 questions per month.
|
||||||
|
|
||||||
|
## So what's actually going on?
|
||||||
|
|
||||||
|
The data suggests AI accelerated something that was already happening. A few things probably contributed:
|
||||||
|
|
||||||
|
**The saturation effect**: By 2021, StackOverflow had 16+ years of answered questions. How many times can you ask "how do I parse JSON in Python" before every variation is covered? The "just Google it" response became the correct answer because everything _had_ been Googled already.
|
||||||
|
|
||||||
|
**The pre-AI decline (2021-2022)**: 31% drop over 18 months while AI coding searches were dead flat. This points to other shifts—better documentation, clearer error messages, frameworks maturing and becoming less footgun-y. Developers were finding answers without needing to ask.
|
||||||
|
|
||||||
|
**The AI acceleration (2023-2025)**: ChatGPT launches November 30, 2022. By March 2023, StackOverflow drops from 123,614 questions to 87,543. AI tools give you instant answers without needing to wade through ten variations of your question that are marked as duplicates and locked.
|
||||||
|
|
||||||
|
**The collapse (2025-2026)**: By mid-2025, AI coding tools are just... everywhere. GitHub Copilot, ChatGPT, Claude, all of them baked into every IDE and workflow. The August 2025 peak in "AI coding" searches lines up with StackOverflow hitting 5,885 questions. That's a 96.8% decline from five years earlier.
|
||||||
|
|
||||||
|
## The bottom line
|
||||||
|
|
||||||
|
AI didn't kill StackOverflow, but it's definitely finishing the job. The platform was already bleeding out when ChatGPT showed up—content saturation, better tooling, the ecosystem maturing. But AI coding tools changed the game completely. Why search through old forum posts when you can just ask?
|
||||||
|
|
||||||
|
The inverse relationship is stark: as AI coding interest hits its peak in 2025, StackOverflow craters. By January 2026 we're at 321 questions. That's about the same as August 2008, when it was brand new.
|
||||||
|
|
||||||
|
Twelve years to build it. Five years to tear it down. And yeah, the last three were almost certainly AI finishing what was already started.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[^1]: Google Trends data provides relative search interest rather than absolute numbers, which may not capture the full picture.
|
||||||
|
|
||||||
|
[^2]: [ChatGPT Launch Announcement](https://openai.com/index/chatgpt/)
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
Month,Questions
|
||||||
|
"2019-01-01 00:00:00","149628"
|
||||||
|
"2019-02-01 00:00:00","146421"
|
||||||
|
"2019-03-01 00:00:00","161159"
|
||||||
|
"2019-04-01 00:00:00","153558"
|
||||||
|
"2019-05-01 00:00:00","151393"
|
||||||
|
"2019-06-01 00:00:00","135935"
|
||||||
|
"2019-07-01 00:00:00","151081"
|
||||||
|
"2019-08-01 00:00:00","137181"
|
||||||
|
"2019-09-01 00:00:00","136935"
|
||||||
|
"2019-10-01 00:00:00","152721"
|
||||||
|
"2019-11-01 00:00:00","148338"
|
||||||
|
"2019-12-01 00:00:00","132623"
|
||||||
|
"2020-01-01 00:00:00","146735"
|
||||||
|
"2020-02-01 00:00:00","145191"
|
||||||
|
"2020-03-01 00:00:00","156037"
|
||||||
|
"2020-04-01 00:00:00","183016"
|
||||||
|
"2020-05-01 00:00:00","186573"
|
||||||
|
"2020-06-01 00:00:00","172002"
|
||||||
|
"2020-07-01 00:00:00","166135"
|
||||||
|
"2020-08-01 00:00:00","148452"
|
||||||
|
"2020-09-01 00:00:00","141887"
|
||||||
|
"2020-10-01 00:00:00","141948"
|
||||||
|
"2020-11-01 00:00:00","135088"
|
||||||
|
"2020-12-01 00:00:00","134035"
|
||||||
|
"2021-01-01 00:00:00","140009"
|
||||||
|
"2021-02-01 00:00:00","131810"
|
||||||
|
"2021-03-01 00:00:00","148900"
|
||||||
|
"2021-04-01 00:00:00","136022"
|
||||||
|
"2021-05-01 00:00:00","133911"
|
||||||
|
"2021-06-01 00:00:00","129106"
|
||||||
|
"2021-07-01 00:00:00","124130"
|
||||||
|
"2021-08-01 00:00:00","122273"
|
||||||
|
"2021-09-01 00:00:00","119882"
|
||||||
|
"2021-10-01 00:00:00","119008"
|
||||||
|
"2021-11-01 00:00:00","119260"
|
||||||
|
"2021-12-01 00:00:00","112278"
|
||||||
|
"2022-01-01 00:00:00","119459"
|
||||||
|
"2022-02-01 00:00:00","114114"
|
||||||
|
"2022-03-01 00:00:00","123614"
|
||||||
|
"2022-04-01 00:00:00","114422"
|
||||||
|
"2022-05-01 00:00:00","116346"
|
||||||
|
"2022-06-01 00:00:00","111741"
|
||||||
|
"2022-07-01 00:00:00","111059"
|
||||||
|
"2022-08-01 00:00:00","113048"
|
||||||
|
"2022-09-01 00:00:00","103965"
|
||||||
|
"2022-10-01 00:00:00","106366"
|
||||||
|
"2022-11-01 00:00:00","109719"
|
||||||
|
"2022-12-01 00:00:00","96767"
|
||||||
|
"2023-01-01 00:00:00","97209"
|
||||||
|
"2023-02-01 00:00:00","85973"
|
||||||
|
"2023-03-01 00:00:00","87543"
|
||||||
|
"2023-04-01 00:00:00","68746"
|
||||||
|
"2023-05-01 00:00:00","66749"
|
||||||
|
"2023-06-01 00:00:00","63858"
|
||||||
|
"2023-07-01 00:00:00","62938"
|
||||||
|
"2023-08-01 00:00:00","60319"
|
||||||
|
"2023-09-01 00:00:00","53046"
|
||||||
|
"2023-10-01 00:00:00","52743"
|
||||||
|
"2023-11-01 00:00:00","50646"
|
||||||
|
"2023-12-01 00:00:00","42601"
|
||||||
|
"2024-01-01 00:00:00","47854"
|
||||||
|
"2024-02-01 00:00:00","46292"
|
||||||
|
"2024-03-01 00:00:00","45070"
|
||||||
|
"2024-04-01 00:00:00","42776"
|
||||||
|
"2024-05-01 00:00:00","40485"
|
||||||
|
"2024-06-01 00:00:00","32243"
|
||||||
|
"2024-07-01 00:00:00","31740"
|
||||||
|
"2024-08-01 00:00:00","28059"
|
||||||
|
"2024-09-01 00:00:00","24947"
|
||||||
|
"2024-10-01 00:00:00","23319"
|
||||||
|
"2024-11-01 00:00:00","20891"
|
||||||
|
"2024-12-01 00:00:00","18029"
|
||||||
|
"2025-01-01 00:00:00","22394"
|
||||||
|
"2025-02-01 00:00:00","19340"
|
||||||
|
"2025-03-01 00:00:00","18965"
|
||||||
|
"2025-04-01 00:00:00","14138"
|
||||||
|
"2025-05-01 00:00:00","11824"
|
||||||
|
"2025-06-01 00:00:00","9392"
|
||||||
|
"2025-07-01 00:00:00","7841"
|
||||||
|
"2025-08-01 00:00:00","5885"
|
||||||
|
"2025-09-01 00:00:00","6132"
|
||||||
|
"2025-10-01 00:00:00","5415"
|
||||||
|
"2025-11-01 00:00:00","4366"
|
||||||
|
"2025-12-01 00:00:00","3862"
|
||||||
|
"2026-01-01 00:00:00","321"
|
||||||
|
|
|
@ -0,0 +1,212 @@
|
||||||
|
Month,Questions
|
||||||
|
"2008-07-01 00:00:00","4"
|
||||||
|
"2008-08-01 00:00:00","3749"
|
||||||
|
"2008-09-01 00:00:00","14040"
|
||||||
|
"2008-10-01 00:00:00","14578"
|
||||||
|
"2008-11-01 00:00:00","12717"
|
||||||
|
"2008-12-01 00:00:00","12081"
|
||||||
|
"2009-01-01 00:00:00","15833"
|
||||||
|
"2009-02-01 00:00:00","17581"
|
||||||
|
"2009-03-01 00:00:00","20482"
|
||||||
|
"2009-04-01 00:00:00","21336"
|
||||||
|
"2009-05-01 00:00:00","25814"
|
||||||
|
"2009-06-01 00:00:00","28326"
|
||||||
|
"2009-07-01 00:00:00","32483"
|
||||||
|
"2009-08-01 00:00:00","32775"
|
||||||
|
"2009-09-01 00:00:00","33054"
|
||||||
|
"2009-10-01 00:00:00","36331"
|
||||||
|
"2009-11-01 00:00:00","38518"
|
||||||
|
"2009-12-01 00:00:00","37795"
|
||||||
|
"2010-01-01 00:00:00","44938"
|
||||||
|
"2010-02-01 00:00:00","44811"
|
||||||
|
"2010-03-01 00:00:00","52236"
|
||||||
|
"2010-04-01 00:00:00","50084"
|
||||||
|
"2010-05-01 00:00:00","51996"
|
||||||
|
"2010-06-01 00:00:00","55750"
|
||||||
|
"2010-07-01 00:00:00","60848"
|
||||||
|
"2010-08-01 00:00:00","63660"
|
||||||
|
"2010-09-01 00:00:00","61230"
|
||||||
|
"2010-10-01 00:00:00","63777"
|
||||||
|
"2010-11-01 00:00:00","69693"
|
||||||
|
"2010-12-01 00:00:00","69573"
|
||||||
|
"2011-01-01 00:00:00","79911"
|
||||||
|
"2011-02-01 00:00:00","82942"
|
||||||
|
"2011-03-01 00:00:00","100970"
|
||||||
|
"2011-04-01 00:00:00","95562"
|
||||||
|
"2011-05-01 00:00:00","100229"
|
||||||
|
"2011-06-01 00:00:00","99027"
|
||||||
|
"2011-07-01 00:00:00","100445"
|
||||||
|
"2011-08-01 00:00:00","106917"
|
||||||
|
"2011-09-01 00:00:00","101722"
|
||||||
|
"2011-10-01 00:00:00","101032"
|
||||||
|
"2011-11-01 00:00:00","108450"
|
||||||
|
"2011-12-01 00:00:00","103172"
|
||||||
|
"2012-01-01 00:00:00","115434"
|
||||||
|
"2012-02-01 00:00:00","123299"
|
||||||
|
"2012-03-01 00:00:00","133000"
|
||||||
|
"2012-04-01 00:00:00","128064"
|
||||||
|
"2012-05-01 00:00:00","133515"
|
||||||
|
"2012-06-01 00:00:00","130323"
|
||||||
|
"2012-07-01 00:00:00","141906"
|
||||||
|
"2012-08-01 00:00:00","141528"
|
||||||
|
"2012-09-01 00:00:00","132241"
|
||||||
|
"2012-10-01 00:00:00","150083"
|
||||||
|
"2012-11-01 00:00:00","148310"
|
||||||
|
"2012-12-01 00:00:00","135471"
|
||||||
|
"2013-01-01 00:00:00","157850"
|
||||||
|
"2013-02-01 00:00:00","153402"
|
||||||
|
"2013-03-01 00:00:00","173814"
|
||||||
|
"2013-04-01 00:00:00","171546"
|
||||||
|
"2013-05-01 00:00:00","166468"
|
||||||
|
"2013-06-01 00:00:00","158334"
|
||||||
|
"2013-07-01 00:00:00","176636"
|
||||||
|
"2013-08-01 00:00:00","170215"
|
||||||
|
"2013-09-01 00:00:00","165374"
|
||||||
|
"2013-10-01 00:00:00","184133"
|
||||||
|
"2013-11-01 00:00:00","176033"
|
||||||
|
"2013-12-01 00:00:00","165192"
|
||||||
|
"2014-01-01 00:00:00","188797"
|
||||||
|
"2014-02-01 00:00:00","187751"
|
||||||
|
"2014-03-01 00:00:00","207493"
|
||||||
|
"2014-04-01 00:00:00","194022"
|
||||||
|
"2014-05-01 00:00:00","176017"
|
||||||
|
"2014-06-01 00:00:00","162354"
|
||||||
|
"2014-07-01 00:00:00","177066"
|
||||||
|
"2014-08-01 00:00:00","163406"
|
||||||
|
"2014-09-01 00:00:00","165866"
|
||||||
|
"2014-10-01 00:00:00","172887"
|
||||||
|
"2014-11-01 00:00:00","166598"
|
||||||
|
"2014-12-01 00:00:00","155299"
|
||||||
|
"2015-01-01 00:00:00","165978"
|
||||||
|
"2015-02-01 00:00:00","168513"
|
||||||
|
"2015-03-01 00:00:00","189769"
|
||||||
|
"2015-04-01 00:00:00","189967"
|
||||||
|
"2015-05-01 00:00:00","185878"
|
||||||
|
"2015-06-01 00:00:00","187915"
|
||||||
|
"2015-07-01 00:00:00","195439"
|
||||||
|
"2015-08-01 00:00:00","181413"
|
||||||
|
"2015-09-01 00:00:00","177859"
|
||||||
|
"2015-10-01 00:00:00","186418"
|
||||||
|
"2015-11-01 00:00:00","178205"
|
||||||
|
"2015-12-01 00:00:00","172586"
|
||||||
|
"2016-01-01 00:00:00","181380"
|
||||||
|
"2016-02-01 00:00:00","188742"
|
||||||
|
"2016-03-01 00:00:00","201941"
|
||||||
|
"2016-04-01 00:00:00","197067"
|
||||||
|
"2016-05-01 00:00:00","189772"
|
||||||
|
"2016-06-01 00:00:00","184660"
|
||||||
|
"2016-07-01 00:00:00","176974"
|
||||||
|
"2016-08-01 00:00:00","181960"
|
||||||
|
"2016-09-01 00:00:00","171676"
|
||||||
|
"2016-10-01 00:00:00","175399"
|
||||||
|
"2016-11-01 00:00:00","175326"
|
||||||
|
"2016-12-01 00:00:00","162421"
|
||||||
|
"2017-01-01 00:00:00","176969"
|
||||||
|
"2017-02-01 00:00:00","175280"
|
||||||
|
"2017-03-01 00:00:00","201543"
|
||||||
|
"2017-04-01 00:00:00","178508"
|
||||||
|
"2017-05-01 00:00:00","186511"
|
||||||
|
"2017-06-01 00:00:00","178175"
|
||||||
|
"2017-07-01 00:00:00","179856"
|
||||||
|
"2017-08-01 00:00:00","177742"
|
||||||
|
"2017-09-01 00:00:00","162369"
|
||||||
|
"2017-10-01 00:00:00","170083"
|
||||||
|
"2017-11-01 00:00:00","169012"
|
||||||
|
"2017-12-01 00:00:00","145330"
|
||||||
|
"2018-01-01 00:00:00","160544"
|
||||||
|
"2018-02-01 00:00:00","153155"
|
||||||
|
"2018-03-01 00:00:00","173466"
|
||||||
|
"2018-04-01 00:00:00","162981"
|
||||||
|
"2018-05-01 00:00:00","168104"
|
||||||
|
"2018-06-01 00:00:00","154885"
|
||||||
|
"2018-07-01 00:00:00","160095"
|
||||||
|
"2018-08-01 00:00:00","158544"
|
||||||
|
"2018-09-01 00:00:00","144581"
|
||||||
|
"2018-10-01 00:00:00","160501"
|
||||||
|
"2018-11-01 00:00:00","149820"
|
||||||
|
"2018-12-01 00:00:00","132269"
|
||||||
|
"2019-01-01 00:00:00","149628"
|
||||||
|
"2019-02-01 00:00:00","146421"
|
||||||
|
"2019-03-01 00:00:00","161159"
|
||||||
|
"2019-04-01 00:00:00","153558"
|
||||||
|
"2019-05-01 00:00:00","151393"
|
||||||
|
"2019-06-01 00:00:00","135935"
|
||||||
|
"2019-07-01 00:00:00","151081"
|
||||||
|
"2019-08-01 00:00:00","137181"
|
||||||
|
"2019-09-01 00:00:00","136935"
|
||||||
|
"2019-10-01 00:00:00","152721"
|
||||||
|
"2019-11-01 00:00:00","148338"
|
||||||
|
"2019-12-01 00:00:00","132623"
|
||||||
|
"2020-01-01 00:00:00","146735"
|
||||||
|
"2020-02-01 00:00:00","145191"
|
||||||
|
"2020-03-01 00:00:00","156037"
|
||||||
|
"2020-04-01 00:00:00","183016"
|
||||||
|
"2020-05-01 00:00:00","186573"
|
||||||
|
"2020-06-01 00:00:00","172002"
|
||||||
|
"2020-07-01 00:00:00","166135"
|
||||||
|
"2020-08-01 00:00:00","148452"
|
||||||
|
"2020-09-01 00:00:00","141887"
|
||||||
|
"2020-10-01 00:00:00","141948"
|
||||||
|
"2020-11-01 00:00:00","135088"
|
||||||
|
"2020-12-01 00:00:00","134035"
|
||||||
|
"2021-01-01 00:00:00","140009"
|
||||||
|
"2021-02-01 00:00:00","131810"
|
||||||
|
"2021-03-01 00:00:00","148900"
|
||||||
|
"2021-04-01 00:00:00","136022"
|
||||||
|
"2021-05-01 00:00:00","133911"
|
||||||
|
"2021-06-01 00:00:00","129106"
|
||||||
|
"2021-07-01 00:00:00","124130"
|
||||||
|
"2021-08-01 00:00:00","122273"
|
||||||
|
"2021-09-01 00:00:00","119882"
|
||||||
|
"2021-10-01 00:00:00","119008"
|
||||||
|
"2021-11-01 00:00:00","119260"
|
||||||
|
"2021-12-01 00:00:00","112278"
|
||||||
|
"2022-01-01 00:00:00","119459"
|
||||||
|
"2022-02-01 00:00:00","114114"
|
||||||
|
"2022-03-01 00:00:00","123614"
|
||||||
|
"2022-04-01 00:00:00","114422"
|
||||||
|
"2022-05-01 00:00:00","116346"
|
||||||
|
"2022-06-01 00:00:00","111741"
|
||||||
|
"2022-07-01 00:00:00","111059"
|
||||||
|
"2022-08-01 00:00:00","113048"
|
||||||
|
"2022-09-01 00:00:00","103965"
|
||||||
|
"2022-10-01 00:00:00","106366"
|
||||||
|
"2022-11-01 00:00:00","109719"
|
||||||
|
"2022-12-01 00:00:00","96767"
|
||||||
|
"2023-01-01 00:00:00","97209"
|
||||||
|
"2023-02-01 00:00:00","85973"
|
||||||
|
"2023-03-01 00:00:00","87543"
|
||||||
|
"2023-04-01 00:00:00","68746"
|
||||||
|
"2023-05-01 00:00:00","66749"
|
||||||
|
"2023-06-01 00:00:00","63858"
|
||||||
|
"2023-07-01 00:00:00","62938"
|
||||||
|
"2023-08-01 00:00:00","60319"
|
||||||
|
"2023-09-01 00:00:00","53046"
|
||||||
|
"2023-10-01 00:00:00","52743"
|
||||||
|
"2023-11-01 00:00:00","50646"
|
||||||
|
"2023-12-01 00:00:00","42601"
|
||||||
|
"2024-01-01 00:00:00","47854"
|
||||||
|
"2024-02-01 00:00:00","46292"
|
||||||
|
"2024-03-01 00:00:00","45070"
|
||||||
|
"2024-04-01 00:00:00","42776"
|
||||||
|
"2024-05-01 00:00:00","40485"
|
||||||
|
"2024-06-01 00:00:00","32243"
|
||||||
|
"2024-07-01 00:00:00","31740"
|
||||||
|
"2024-08-01 00:00:00","28059"
|
||||||
|
"2024-09-01 00:00:00","24947"
|
||||||
|
"2024-10-01 00:00:00","23319"
|
||||||
|
"2024-11-01 00:00:00","20891"
|
||||||
|
"2024-12-01 00:00:00","18029"
|
||||||
|
"2025-01-01 00:00:00","22394"
|
||||||
|
"2025-02-01 00:00:00","19340"
|
||||||
|
"2025-03-01 00:00:00","18965"
|
||||||
|
"2025-04-01 00:00:00","14138"
|
||||||
|
"2025-05-01 00:00:00","11824"
|
||||||
|
"2025-06-01 00:00:00","9392"
|
||||||
|
"2025-07-01 00:00:00","7841"
|
||||||
|
"2025-08-01 00:00:00","5885"
|
||||||
|
"2025-09-01 00:00:00","6132"
|
||||||
|
"2025-10-01 00:00:00","5415"
|
||||||
|
"2025-11-01 00:00:00","4366"
|
||||||
|
"2025-12-01 00:00:00","3862"
|
||||||
|
"2026-01-01 00:00:00","321"
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
---
|
---
|
||||||
title: "Resources"
|
title: "Resources"
|
||||||
description: "A collection of useful tools, scripts, and experiments"
|
|
||||||
draft: false
|
draft: false
|
||||||
---
|
---
|
||||||
|
|
||||||
|
# Resources
|
||||||
|
|
||||||
Welcome to my whiteboard of resources. Here you'll find various tools, scripts, and experiments I've built and wanted to share.
|
Welcome to my whiteboard of resources. Here you'll find various tools, scripts, and experiments I've built and wanted to share.
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@
|
||||||
<link rel="stylesheet" href="{{ relURL ($.Site.BaseURL) }}{{ . }}" />
|
<link rel="stylesheet" href="{{ relURL ($.Site.BaseURL) }}{{ . }}" />
|
||||||
{{ end }} {{ block "favicon" . }} {{ partial "site-favicon.html" . }} {{ end
|
{{ end }} {{ block "favicon" . }} {{ partial "site-favicon.html" . }} {{ end
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
<!-- Chart.js for graphs -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js" integrity="sha384-9nhczxUqK87bcKHh20fSQcTGD4qq5GhayNYSYWqwBkINBhOfQLg/P5HG5lF1urn4" crossorigin="anonymous"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,3 @@
|
||||||
<header>
|
<header>
|
||||||
<div>
|
<div>{{ partial "site-navigation.html" . }}</div>
|
||||||
{{ partial "site-navigation.html" . }}
|
</header>
|
||||||
<div>
|
|
||||||
<!-- <h1>
|
|
||||||
{{ .Title | default .Site.Title }}
|
|
||||||
</h1> -->
|
|
||||||
{{ with .Params.description }}
|
|
||||||
<h2>
|
|
||||||
{{ . }}
|
|
||||||
</h2>
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,11 @@
|
||||||
<div class="resources-container">
|
<div class="resources-container">
|
||||||
<div class="whiteboard">
|
<div class="whiteboard">
|
||||||
<div class="whiteboard-header">
|
<div class="whiteboard-header">
|
||||||
<h1>{{ .Title }}</h1>
|
<div class="whiteboard-description">{{ .Content }}</div>
|
||||||
<div class="whiteboard-description">
|
|
||||||
{{ .Content }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="whiteboard-pins">
|
<div class="whiteboard-pins">
|
||||||
{{ range .Pages }}
|
{{ range .Pages }} {{ .Render "summary" }} {{ end }}
|
||||||
{{ .Render "summary" }}
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
350
layouts/shortcodes/graph.html
Normal file
350
layouts/shortcodes/graph.html
Normal file
|
|
@ -0,0 +1,350 @@
|
||||||
|
{{- $id := .Get "id" | default (printf "chart-%d" now.UnixNano) -}}
|
||||||
|
{{- $type := .Get "type" | default "line" -}}
|
||||||
|
{{- $title := .Get "title" | default "" -}}
|
||||||
|
{{- $height := .Get "height" | default "400" -}}
|
||||||
|
{{- $csvFile := .Get "csv" -}}
|
||||||
|
{{- $labelCol := .Get "labelColumn" | default "Month" -}}
|
||||||
|
{{- $dataCol := .Get "dataColumn" | default "Questions" -}}
|
||||||
|
{{- $dataColumns := .Get "dataColumns" | default "" -}}
|
||||||
|
{{- $datasetLabels := .Get "datasetLabels" | default "" -}}
|
||||||
|
{{- $yAxisIDs := .Get "yAxisIDs" | default "" -}}
|
||||||
|
{{- $dateFormat := .Get "dateFormat" | default "2006-01" -}}
|
||||||
|
{{- $skipRows := .Get "skipRows" | default 0 | int -}}
|
||||||
|
{{- $maxRows := .Get "maxRows" | default 0 | int -}}
|
||||||
|
|
||||||
|
{{- $chartData := dict -}}
|
||||||
|
|
||||||
|
{{- if $csvFile -}}
|
||||||
|
{{/* CSV file mode - supports multiple CSV files */}}
|
||||||
|
{{- $csvFiles := strings.Split $csvFile "," -}}
|
||||||
|
{{- $dataColsList := slice -}}
|
||||||
|
{{- if ne $dataColumns "" -}}
|
||||||
|
{{- $dataColsList = strings.Split $dataColumns "," -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- $labelsList := slice -}}
|
||||||
|
{{- if ne $datasetLabels "" -}}
|
||||||
|
{{- $labelsList = strings.Split $datasetLabels "," -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- $yAxisList := slice -}}
|
||||||
|
{{- if ne $yAxisIDs "" -}}
|
||||||
|
{{- $yAxisList = strings.Split $yAxisIDs "," -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- $labels := slice -}}
|
||||||
|
{{- $datasets := slice -}}
|
||||||
|
|
||||||
|
{{/* Process each CSV file */}}
|
||||||
|
{{- range $fileIdx, $csvFileName := $csvFiles -}}
|
||||||
|
{{- $csvFileName = strings.TrimSpace $csvFileName -}}
|
||||||
|
{{- $csvResource := $.Page.Resources.GetMatch $csvFileName -}}
|
||||||
|
{{- if not $csvResource -}}
|
||||||
|
{{- errorf "CSV file '%s' not found in page bundle for %s. Make sure the file exists in the same directory as index.md" $csvFileName $.Page.File.Path -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- $csvData := $csvResource | transform.Unmarshal -}}
|
||||||
|
{{- $data := slice -}}
|
||||||
|
|
||||||
|
{{/* Determine which data column to use */}}
|
||||||
|
{{- $currentDataCol := $dataCol -}}
|
||||||
|
{{- if ge $fileIdx (len $dataColsList) -}}
|
||||||
|
{{- $currentDataCol = $dataCol -}}
|
||||||
|
{{- else -}}
|
||||||
|
{{- $currentDataCol = strings.TrimSpace (index $dataColsList $fileIdx) -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/* Determine dataset label */}}
|
||||||
|
{{- $datasetLabel := $currentDataCol -}}
|
||||||
|
{{- if lt $fileIdx (len $labelsList) -}}
|
||||||
|
{{- $datasetLabel = strings.TrimSpace (index $labelsList $fileIdx) -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/* Determine Y-axis ID */}}
|
||||||
|
{{- $yAxisID := "y" -}}
|
||||||
|
{{- if lt $fileIdx (len $yAxisList) -}}
|
||||||
|
{{- $yAxisID = strings.TrimSpace (index $yAxisList $fileIdx) -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/* Process CSV rows */}}
|
||||||
|
{{- range $idx, $row := $csvData -}}
|
||||||
|
{{- if gt $idx $skipRows -}}
|
||||||
|
{{- if or (eq $maxRows 0) (le (len $data) $maxRows) -}}
|
||||||
|
{{/* Only set labels once from the first CSV file */}}
|
||||||
|
{{- if eq $fileIdx 0 -}}
|
||||||
|
{{/* Get label value */}}
|
||||||
|
{{- $labelRaw := "" -}}
|
||||||
|
{{- if reflect.IsMap $row -}}
|
||||||
|
{{- $labelRaw = index $row $labelCol -}}
|
||||||
|
{{- else -}}
|
||||||
|
{{/* Handle as slice/array - first column is label */}}
|
||||||
|
{{- $labelRaw = index $row 0 -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- $label := $labelRaw -}}
|
||||||
|
|
||||||
|
{{/* Try to parse and format date if it looks like a timestamp */}}
|
||||||
|
{{- $labelStr := printf "%v" $labelRaw -}}
|
||||||
|
{{- if strings.Contains $labelStr " 00:00:00" -}}
|
||||||
|
{{- $parsedTime := time.AsTime $labelStr -}}
|
||||||
|
{{- $label = $parsedTime.Format $dateFormat -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- $labels = $labels | append $label -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/* Get data value */}}
|
||||||
|
{{- $dataValue := 0 -}}
|
||||||
|
{{- if reflect.IsMap $row -}}
|
||||||
|
{{- $dataValue = index $row $currentDataCol | int -}}
|
||||||
|
{{- else -}}
|
||||||
|
{{/* Handle as slice/array - second column is data */}}
|
||||||
|
{{- $dataValue = index $row 1 | int -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- $data = $data | append $dataValue -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/* Add this dataset to the datasets array */}}
|
||||||
|
{{- $datasets = $datasets | append (dict "label" $datasetLabel "data" $data "yAxisID" $yAxisID) -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- $chartData = dict "labels" $labels "datasets" $datasets -}}
|
||||||
|
{{- else -}}
|
||||||
|
{{/* JSON inline mode */}}
|
||||||
|
{{- if .Inner -}}
|
||||||
|
{{- $chartData = .Inner | transform.Unmarshal -}}
|
||||||
|
{{- else -}}
|
||||||
|
{{- errorf "Graph shortcode requires either CSV file or inline JSON data" -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
<div class="graph-container" style="height: {{ $height }}px;">
|
||||||
|
{{- if $title -}}
|
||||||
|
<h3 class="graph-title">{{ $title }}</h3>
|
||||||
|
{{- end -}}
|
||||||
|
<canvas id="{{ $id }}"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
// Wait for Chart.js to load
|
||||||
|
function initChart() {
|
||||||
|
if (typeof Chart === 'undefined') {
|
||||||
|
setTimeout(initChart, 100);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ctx = document.getElementById('{{ $id }}').getContext('2d');
|
||||||
|
|
||||||
|
// Site color scheme - green, blue, amber
|
||||||
|
const colors = {
|
||||||
|
primary: 'rgb(0, 255, 0)', // green
|
||||||
|
secondary: 'rgb(0, 191, 255)', // blue/cyan
|
||||||
|
tertiary: 'rgb(255, 153, 0)', // amber/orange
|
||||||
|
quaternary: 'rgb(173, 255, 47)', // greenyellow
|
||||||
|
background: 'rgba(0, 255, 0, 0.1)',
|
||||||
|
grid: 'rgba(0, 255, 0, 0.2)',
|
||||||
|
text: 'rgb(0, 255, 0)'
|
||||||
|
};
|
||||||
|
|
||||||
|
const chartData = {{ $chartData | jsonify | safeJS }};
|
||||||
|
|
||||||
|
console.log('Chart ID: {{ $id }}');
|
||||||
|
console.log('Chart data:', chartData);
|
||||||
|
console.log('Labels count:', chartData.labels ? chartData.labels.length : 0);
|
||||||
|
console.log('Data count:', chartData.datasets && chartData.datasets[0] ? chartData.datasets[0].data.length : 0);
|
||||||
|
|
||||||
|
// Validate data
|
||||||
|
if (!chartData || !chartData.datasets || chartData.datasets.length === 0) {
|
||||||
|
console.error('Invalid chart data - no datasets found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if any dataset uses the y1 axis
|
||||||
|
const usesY1Axis = chartData.datasets.some(dataset => dataset.yAxisID === 'y1');
|
||||||
|
|
||||||
|
// Apply terminal styling to datasets
|
||||||
|
if (chartData.datasets) {
|
||||||
|
chartData.datasets.forEach((dataset, idx) => {
|
||||||
|
const colorOptions = [colors.primary, colors.secondary, colors.tertiary, colors.quaternary];
|
||||||
|
const baseColor = colorOptions[idx % colorOptions.length];
|
||||||
|
|
||||||
|
console.log('Dataset', idx, 'using color:', baseColor);
|
||||||
|
|
||||||
|
if (!dataset.borderColor) {
|
||||||
|
dataset.borderColor = baseColor;
|
||||||
|
}
|
||||||
|
if (!dataset.backgroundColor && '{{ $type }}' === 'line') {
|
||||||
|
dataset.backgroundColor = baseColor.replace('rgb', 'rgba').replace(')', ', 0.1)');
|
||||||
|
} else if (!dataset.backgroundColor) {
|
||||||
|
dataset.backgroundColor = baseColor.replace('rgb', 'rgba').replace(')', ', 0.6)');
|
||||||
|
}
|
||||||
|
if (dataset.borderWidth === undefined) {
|
||||||
|
dataset.borderWidth = 2;
|
||||||
|
}
|
||||||
|
if (dataset.tension === undefined && '{{ $type }}' === 'line') {
|
||||||
|
dataset.tension = 0.1;
|
||||||
|
}
|
||||||
|
if (dataset.pointBackgroundColor === undefined && '{{ $type }}' === 'line') {
|
||||||
|
dataset.pointBackgroundColor = baseColor;
|
||||||
|
}
|
||||||
|
if (dataset.pointBorderColor === undefined && '{{ $type }}' === 'line') {
|
||||||
|
dataset.pointBorderColor = '#000';
|
||||||
|
}
|
||||||
|
if (dataset.pointRadius === undefined && '{{ $type }}' === 'line') {
|
||||||
|
dataset.pointRadius = 2;
|
||||||
|
}
|
||||||
|
if (dataset.pointHoverRadius === undefined && '{{ $type }}' === 'line') {
|
||||||
|
dataset.pointHoverRadius = 4;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build scales configuration
|
||||||
|
const scales = {
|
||||||
|
x: {
|
||||||
|
offset: true,
|
||||||
|
grid: {
|
||||||
|
color: colors.grid,
|
||||||
|
drawBorder: true,
|
||||||
|
borderColor: colors.secondary,
|
||||||
|
borderWidth: 2
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
color: colors.text,
|
||||||
|
font: {
|
||||||
|
family: 'monospace',
|
||||||
|
size: 10
|
||||||
|
},
|
||||||
|
maxRotation: 45,
|
||||||
|
minRotation: 45,
|
||||||
|
autoSkip: true,
|
||||||
|
maxTicksLimit: 20,
|
||||||
|
padding: 8
|
||||||
|
},
|
||||||
|
afterFit: function(scale) {
|
||||||
|
scale.paddingBottom = 20; // Extra space for rotated labels
|
||||||
|
}
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
type: 'linear',
|
||||||
|
position: 'left',
|
||||||
|
grid: {
|
||||||
|
color: colors.grid,
|
||||||
|
drawBorder: true,
|
||||||
|
borderColor: colors.secondary,
|
||||||
|
borderWidth: 2
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
color: colors.text,
|
||||||
|
font: {
|
||||||
|
family: 'monospace',
|
||||||
|
size: 11
|
||||||
|
},
|
||||||
|
padding: 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Only add y1 axis if it's actually used by any dataset
|
||||||
|
if (usesY1Axis) {
|
||||||
|
scales.y1 = {
|
||||||
|
type: 'linear',
|
||||||
|
position: 'right',
|
||||||
|
grid: {
|
||||||
|
drawOnChartArea: false, // Don't draw grid lines for secondary axis
|
||||||
|
drawBorder: true,
|
||||||
|
borderColor: colors.tertiary,
|
||||||
|
borderWidth: 2
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
color: colors.text,
|
||||||
|
font: {
|
||||||
|
family: 'monospace',
|
||||||
|
size: 11
|
||||||
|
},
|
||||||
|
padding: 5
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
type: '{{ $type }}',
|
||||||
|
data: chartData,
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
layout: {
|
||||||
|
padding: {
|
||||||
|
bottom: 30,
|
||||||
|
left: 10,
|
||||||
|
right: 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: chartData.datasets && chartData.datasets.length > 1,
|
||||||
|
labels: {
|
||||||
|
color: colors.text,
|
||||||
|
font: {
|
||||||
|
family: 'monospace',
|
||||||
|
size: 12
|
||||||
|
},
|
||||||
|
boxWidth: 15,
|
||||||
|
padding: 10,
|
||||||
|
generateLabels: function(chart) {
|
||||||
|
const labels = Chart.defaults.plugins.legend.labels.generateLabels(chart);
|
||||||
|
labels.forEach(label => {
|
||||||
|
label.fillStyle = label.strokeStyle;
|
||||||
|
});
|
||||||
|
return labels;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.9)',
|
||||||
|
titleColor: colors.primary,
|
||||||
|
bodyColor: colors.text,
|
||||||
|
borderColor: colors.secondary,
|
||||||
|
borderWidth: 1,
|
||||||
|
titleFont: {
|
||||||
|
family: 'monospace',
|
||||||
|
size: 14,
|
||||||
|
weight: 'bold'
|
||||||
|
},
|
||||||
|
bodyFont: {
|
||||||
|
family: 'monospace',
|
||||||
|
size: 12
|
||||||
|
},
|
||||||
|
padding: 12,
|
||||||
|
displayColors: true,
|
||||||
|
callbacks: {
|
||||||
|
labelColor: function(context) {
|
||||||
|
return {
|
||||||
|
borderColor: context.dataset.borderColor,
|
||||||
|
backgroundColor: context.dataset.borderColor
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: scales,
|
||||||
|
animation: {
|
||||||
|
duration: 1000,
|
||||||
|
easing: 'easeOutQuart'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
new Chart(ctx, config);
|
||||||
|
console.log('Chart initialized successfully');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', initChart);
|
||||||
|
} else {
|
||||||
|
initChart();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue