Animated charts — pure GLSL shaders
Render data charts directly inside a fragment shader. No JS library, no DOM — just GLSL and your data in u_data[].
Why render charts as GLSL shaders?
60 fps at any resolution — WebGL renders on the GPU, not on the JS thread.
Zero dependency — a single .glsl file that runs in any WebGL canvas, OBS Browser Source, or TouchDesigner GLSL TOP.
Animate data transitions natively — no GSAP, no D3 animations, just mix() and smoothstep() in the shader.
Bar Chart
Seven animated bars driven by sinusoidal placeholders — swap them out for real values via u_data[0..6]. Supports logarithmic scale, custom colour palettes, and value labels.
Discord command
/dataviz bar --data "12,45,33,78,56,90,22" --label "Jan,Feb,Mar,Apr,May,Jun,Jul"Available uniforms
- uniform float u_data[64] — your normalised data values
- uniform float iTime — elapsed time in seconds
- uniform vec2 iResolution — canvas size
- uniform float u_count — number of active data points
// Animated bar chart — 7 bars driven by sine waves
// Plug your data into u_data[0..6] for live values
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = fragCoord / iResolution.xy;
float t = iTime * 0.5;
// Dark background
vec3 col = vec3(0.04, 0.06, 0.11);
// Horizontal grid lines
col += vec3(0.05) * step(0.96, fract(uv.y * 5.0));
// Axes
float axisX = step(uv.x, 0.02);
float axisY = step(uv.y, 0.015);
col = mix(col, vec3(0.28, 0.30, 0.38), max(axisX, axisY));
// 7 animated bars (replace sin() values with u_data[i] for live data)
float nBars = 7.0;
float slot = uv.x * nBars;
float idx = floor(slot);
float fx = fract(slot);
float pad = 0.14;
if (fx > pad && fx < 1.0 - pad && uv.y > 0.02) {
float phase = idx * 0.9 + 0.3;
float h = 0.15 + 0.75 * (0.5 + 0.5 * sin(t + phase)); // ← swap for u_data[int(idx)]
if (uv.y < h) {
// HSL-style gradient across bars
vec3 hue = 0.5 + 0.5 * cos(vec3(0.0, 2.09, 4.19) + idx * 0.9);
float shine = pow(uv.y / h, 1.4);
col = mix(hue * 0.35, hue, shine);
// Glow cap on top
col += hue * smoothstep(0.06, 0.0, h - uv.y) * 0.55;
}
}
// X-axis tick dashes
col = mix(col, vec3(0.38), step(0.4, fract(uv.x * 28.0)) * step(uv.y, 0.012));
fragColor = vec4(col, 1.0);
}Line Chart
Time-series line with area fill, anti-aliased stroke, and circular data-point markers. Connect a live JSON API via /dataviz-live to stream real values into the shader.
Discord command
/dataviz line --source bitcoin --interval 30sAvailable uniforms
- uniform float u_data[64] — your normalised data values
- uniform float iTime — elapsed time in seconds
- uniform vec2 iResolution — canvas size
- uniform float u_count — number of active data points
// Smooth line chart with area fill and data points
float signal(float x, float t) {
return 0.5 + 0.15 * sin(x * 6.28 + t)
+ 0.07 * sin(x * 18.85 + t * 1.3)
+ 0.03 * cos(x * 37.70 - t * 1.7);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = fragCoord / iResolution.xy;
float t = iTime * 0.4;
vec3 col = vec3(0.04, 0.06, 0.11);
// Grid
col += vec3(0.055) * step(0.97, fract(uv.x * 10.0));
col += vec3(0.055) * step(0.97, fract(uv.y * 6.0));
float y0 = signal(uv.x, t);
float lineW = 1.8 / iResolution.y;
// Area fill under curve
float area = smoothstep(0.0, 0.12, y0 - uv.y) * step(uv.y, y0);
col += vec3(0.05, 0.28, 0.72) * area * 0.30;
// Anti-aliased line
float dist = abs(uv.y - y0);
col = mix(col, vec3(0.20, 0.72, 1.0), smoothstep(lineW * 2.0, 0.0, dist));
// Data-point circles (10 samples across x)
for (float i = 0.0; i < 10.0; i++) {
float px = (i + 0.5) / 10.0;
float py = signal(px, t);
float d = length(uv - vec2(px, py));
col = mix(col, vec3(1.0), smoothstep(0.013, 0.009, d));
col = mix(col, vec3(0.20, 0.72, 1.0), smoothstep(0.009, 0.006, d));
}
fragColor = vec4(col, 1.0);
}Pie / Donut Chart
Five segments with sweep-in animation on load. Segment proportions are static constants in this example — replace bounds[] with u_data[] for dynamic slices.
Discord command
/dataviz pie --data "30,20,25,15,10" --label "A,B,C,D,E"Available uniforms
- uniform float u_data[64] — your normalised data values
- uniform float iTime — elapsed time in seconds
- uniform vec2 iResolution — canvas size
- uniform float u_count — number of active data points
// Animated donut / pie chart — 5 segments
// Returns a colour for each segment index 0..4
vec3 segColor(float i) {
return 0.5 + 0.5 * cos(vec3(0.0, 2.09, 4.19) + i * 1.26);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = (fragCoord - iResolution.xy * 0.5) / min(iResolution.x, iResolution.y);
float t = iTime * 0.25;
vec3 col = vec3(0.04, 0.06, 0.11);
float r = length(uv);
float ang = atan(uv.y, uv.x); // -PI..PI
float norm = ang / (2.0 * 3.14159) + 0.5; // 0..1
// Animated "draw" sweep on first load
float sweep = clamp(t / 2.5, 0.0, 1.0);
// Cumulative segment boundaries (proportions: 30%,20%,25%,15%,10%)
float bounds[6];
bounds[0] = 0.00;
bounds[1] = 0.30;
bounds[2] = 0.50;
bounds[3] = 0.75;
bounds[4] = 0.90;
bounds[5] = 1.00;
if (r > 0.28 && r < 0.46) {
for (int i = 0; i < 5; i++) {
float lo = bounds[i];
float hi = bounds[i + 1];
float visible = clamp((sweep - lo) / (hi - lo), 0.0, 1.0);
float inSeg = step(lo, norm) * step(norm, mix(lo, hi, visible));
if (inSeg > 0.5) {
col = segColor(float(i));
// Slight bevel at edges
float edgeDist = min(abs(r - 0.28), abs(r - 0.46));
col *= 0.7 + 0.3 * smoothstep(0.0, 0.012, edgeDist);
}
}
}
// Centre hole label glow
float glow = smoothstep(0.28, 0.20, r) * smoothstep(0.10, 0.22, r);
col += vec3(0.15, 0.40, 0.90) * glow * 0.18;
// Outer ring
float ring = smoothstep(0.004, 0.0, abs(r - 0.46));
col = mix(col, vec3(0.8), ring * 0.4);
fragColor = vec4(col, 1.0);
}Scatter Plot
40 drifting data points in two colour-coded clusters animated with a micro-drift. Extend to k-means cluster centroids or live sensor readings for real dashboards.
Discord command
/dataviz scatter --source "https://api.example.com/data.json" --x "value.x" --y "value.y"Available uniforms
- uniform float u_data[64] — your normalised data values
- uniform float iTime — elapsed time in seconds
- uniform vec2 iResolution — canvas size
- uniform float u_count — number of active data points
// Scatter plot — 40 animated data points with colour-coded clusters
// LCG pseudo-random
float rand(float seed) {
return fract(sin(seed * 127.1 + 311.7) * 43758.5453);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = fragCoord / iResolution.xy;
float t = iTime * 0.3;
float px = 1.5 / min(iResolution.x, iResolution.y);
vec3 col = vec3(0.04, 0.06, 0.11);
// Grid
col += vec3(0.05) * step(0.97, fract(uv.x * 8.0));
col += vec3(0.05) * step(0.97, fract(uv.y * 6.0));
// Draw 40 points across the plot area (margin 0.08)
float margin = 0.08;
for (float i = 0.0; i < 40.0; i++) {
// Cluster 0 (red/orange): points 0..19 — left-bottom quadrant
// Cluster 1 (blue/cyan): points 20..39 — right-top quadrant
float cluster = step(20.0, i);
float base_x = mix(0.12, 0.55, cluster) + rand(i) * 0.33;
float base_y = mix(0.12, 0.52, cluster) + rand(i + 100.0) * 0.33;
// Gentle drift animation
float drift_x = base_x + 0.012 * sin(t + rand(i + 50.0) * 6.28);
float drift_y = base_y + 0.012 * cos(t + rand(i + 75.0) * 6.28);
float d = length(uv - vec2(drift_x, drift_y));
// Cluster colours
vec3 dotCol = mix(
vec3(1.0, 0.45, 0.15), // cluster 0 — orange
vec3(0.20, 0.72, 1.0), // cluster 1 — cyan
cluster
);
// Filled circle with anti-aliased edge
float fill = smoothstep(0.016, 0.010, d);
float glow = smoothstep(0.036, 0.010, d) * 0.25;
col = mix(col, dotCol, fill);
col += dotCol * glow;
}
fragColor = vec4(col, 1.0);
}Live data pipeline
Connect a data source
Any public JSON API, WebSocket stream, Discord guild stats, or CSV upload. Define the JSONPath expression that extracts your numeric values.
Values land in u_data[]
The bot normalises incoming values to [0, 1] and fills u_data[0..63]. Poll interval is configurable (≥ 5 s). Up to 64 simultaneous metrics.
Render anywhere
The resulting shader URL works as an OBS Browser Source, a `<canvas>` embed, a TouchDesigner GLSL TOP, or directly in a Discord bot reply.
Export your GLSL charts
Download the shader in the format required by your visualisation tool.
Paste the chart shader directly into Shadertoy — iTime and iResolution are pre-wired.
Render the chart as a live OBS Browser Source — perfect for stream dashboards.
GLSL TOP with u_data[] DAT table wired in — live data feeds via Fetch DAT or OSC.
Drop-in <DatavizShader> React component using a <canvas> WebGL context.
Custom GLSL node for Blender Geometry Nodes — visualise data on 3D surfaces.