🚧 ShaderBot is currently in beta. Some features may evolve.Join the Beta!
Bar · Line · Pie · Scatter

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?

1

60 fps at any resolution — WebGL renders on the GPU, not on the JS thread.

2

Zero dependency — a single .glsl file that runs in any WebGL canvas, OBS Browser Source, or TouchDesigner GLSL TOP.

3

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
shader_bar_chart.glslGLSL ES 3.00
// 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 30s

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
shader_line_chart.glslGLSL ES 3.00
// 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
shader_pie_chart.glslGLSL ES 3.00
// 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
shader_scatter_chart.glslGLSL ES 3.00
// 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.

ShadertoyShadertoyShadertoy

Paste the chart shader directly into Shadertoy — iTime and iResolution are pre-wired.

OBS Browser SourceOBS Browser SourceOBS / WebGL

Render the chart as a live OBS Browser Source — perfect for stream dashboards.

TouchDesigner GLSL TOPTouchDesigner GLSL TOPGLSL TOP

GLSL TOP with u_data[] DAT table wired in — live data feeds via Fetch DAT or OSC.

React / Next.js embedWebGL Canvas

Drop-in <DatavizShader> React component using a <canvas> WebGL context.

p5.js Sketchp5.js Sketchp5.js WEBGL

p5.js sketch in WEBGL mode with the chart shader applied as a custom shader.

Blender Shader NodeBlender Shader NodeGLSL

Custom GLSL node for Blender Geometry Nodes — visualise data on 3D surfaces.

Coming soon:Grafana Panel PluginAfter Effects