css Audio - Active file-generic CSS - Active Generic - Active HTML - Active JS - Active SVG - Active Text - Active file-generic Video - Active header Love html icon-new-collection icon-person icon-team numbered-list123 pop-out spinner split-screen star tv

Pen Settings

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

You're using npm packages, so we've auto-selected Babel for you here, which we require to process imports and make it all work. If you need to use a different JavaScript preprocessor, remove the packages in the npm tab.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Use npm Packages

We can make npm packages available for you to use in your JavaScript. We use webpack to prepare them and make them available to import. We'll also process your JavaScript with Babel.

⚠️ This feature can only be used by logged in users.

Code Indentation

     

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

HTML Settings

Here you can Sed posuere consectetur est at lobortis. Donec ullamcorper nulla non metus auctor fringilla. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec id elit non mi porta gravida at eget metus. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.

            
              <div id="app"></div>

<!--

3D animated chart: React, WebGL, CSS3D, mouse interactivity (try hovering over the bars!)

Libraries and sources:

- React
- ReGL (WebGL helper): http://regl.party/
- glMatrix (math): http://glmatrix.net/
- onecolor (RGB conversion): https://github.com/One-com/one-color
- ColourLovers palettes extracted by @mattdesl (https://twitter.com/mattdesl): https://github.com/Jam3/nice-color-palettes
- React Motion (animation): https://github.com/chenglou/react-motion
- Mockaroo (chart labels): http://www.mockaroo.com/
- Google Fonts Michroma

-->

            
          
!
            
              html, body, #app {
    width: 100%;
    height: 100%;
}

.main {
    display: flex;
    flex-direction: column;
    box-sizing: border-box;
    min-height: 100%;
    padding: 0 0 20px;
    justify-content: center;
    align-items: center;

    > ._chart {
        flex: none;
        position: relative;
        width: 480px;
        height: 360px;
    }
    
    > button {
        flex: none;
        display: block;
        border: 1px solid #fff;
        width: 200px;
        height: 40px;
        padding: 0 0 4px;
        line-height: 36px;
        font-family: Michroma;
        font-size: 18px;
        color: #fff;
        background: none;
        border-radius: 5px;
        cursor: pointer;
        outline: none;
        text-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
    }
}

.random-chart__hover-label {
    position: absolute;
    background: rgba(255, 255, 255, 0.9);
    color: #444;
    padding: 5px 5px 7px; // vertical alignment nudge
    border-radius: 5px;
    font-family: Michroma, Arial, sans-serif;
    font-size: 16px;
    transform: translate(-50%, -100%) translate(0, -8px);

    > ._arrow {
        position: absolute;
        top: 100%;
        left: 50%;
        margin-left: -8px;
        border-left: 8px solid transparent;
        border-right: 8px solid transparent;
        border-top: 8px solid rgba(255, 255, 255, 0.9);
    }
}

            
          
!
            
              const onecolor = one.color;
const { Motion, spring } = ReactMotion;
const reglInit = createREGL;

function hex2vector(cssHex) {
    const pc = onecolor(cssHex);

    return vec3.fromValues(
        pc.red(),
        pc.green(),
        pc.blue()
    );
}

class BarChart3D extends React.PureComponent {
    constructor({
        values,
        width,
        height,
        palette
    }) {
        super();

        // copy value array and coerce to 0..1
        this._values = [].concat(values).map(
            value => Math.max(0, Math.min(1, value)) || 0
        );

        if (this._values.length < 1) {
            throw new Error('missing values');
        }

        this.state = {
            barIsActive: this._values.map(() => false),
            graphicsInitialized: false
        };

        this._width = width;
        this._height = height;

        this._chartAreaW = 500;
        this._chartAreaH = 300;
        this._barSpacing = 10;
        this._barExtraRadius = this._barSpacing * 0.3;
        this._patternSize = 50;

        this._regl = null; // initialized after first render

        // reusable computation elements
        this._barBaseVec2 = vec2.create();
        this._barTopVec3 = vec3.create();
    }

    _setBarIsActive(index, status) {
        // reduce bar status state into new instance
        this.setState(state => ({
            barIsActive: [].concat(
                state.barIsActive.slice(0, index),
                [ !!status ],
                state.barIsActive.slice(index + 1)
            )
        }));
    }

    _handleCanvasRef = (canvas) => {
        // when unlinking, help WebGL context get cleaned up
        if (!canvas) {
            this._regl.destroy();
            this._regl = null; // dereference just in case
            return;
        }

        // initialize graphics
        this._regl = reglInit({
            canvas: canvas
        });

        this._barCommand = this._regl({
            vert: `
                precision mediump float;

                uniform mat4 camera;
                uniform vec2 base;
                uniform float radius, height;

                attribute vec3 position;
                attribute vec3 normal;

                varying vec3 fragPosition;
                varying vec3 fragNormal;

                void main() {
                    float z = position.z * height;

                    fragPosition = vec3(
                        (position.xy + vec2(1.0, 1.0)) * radius,
                        z
                    );
                    fragNormal = normal;

                    gl_Position = camera * vec4(
                        base + position.xy * radius,
                        z,
                        1.0
                    );
                }
            `,

            frag: `
                precision mediump float;

                uniform vec3 baseColor, secondaryColor, highlightColor;
                uniform float height;
                uniform float highlight;
                uniform int patternIndex;
                uniform float patternSize;

                varying vec3 fragPosition;
                varying vec3 fragNormal;

                float stripePattern() {
                    return step(patternSize * 0.5, mod((
                        fragPosition.y
                        - fragPosition.x
                        + (height - fragPosition.z)
                    ), patternSize));
                }

                float stripe2Pattern() {
                    return step(patternSize * 0.5, mod((
                        fragPosition.x
                        - fragPosition.y
                        + (height - fragPosition.z)
                    ), patternSize));
                }

                float checkerPattern() {
                    vec3 cellPosition = vec3(0, 0, height) - fragPosition;
                    float cellSize = patternSize * 0.4;

                    vec3 cellIndex = cellPosition / cellSize;
                    float dotChoice = mod((
                        step(1.0, mod(cellIndex.x, 2.0))
                        + step(1.0, mod(cellIndex.y, 2.0))
                        + step(1.0, mod(cellIndex.z, 2.0))
                    ), 2.0);

                    return dotChoice;
                }

                float dotPattern() {
                    vec3 attachedPos = vec3(0, 0, height) - fragPosition;

                    float dotSize = patternSize * 0.3;
                    vec3 dotPosition = attachedPos + dotSize * 0.5;
                    float dotDistance = length(mod(dotPosition, dotSize) / dotSize - vec3(0.5));

                    vec3 dotIndex = dotPosition / dotSize;
                    float dotChoice = mod((
                        step(1.0, mod(dotIndex.x, 2.0))
                        + step(1.0, mod(dotIndex.y, 2.0))
                        + step(1.0, mod(dotIndex.z, 2.0))
                    ), 2.0);

                    return dotChoice * step(dotDistance, 0.5);
                }

                float pattern() {
                    if (patternIndex == 0) {
                        return stripePattern();
                    }

                    if (patternIndex == 1) {
                        return checkerPattern();
                    }

                    if (patternIndex == 2) {
                        return stripe2Pattern();
                    }

                    if (patternIndex == 3) {
                        return dotPattern();
                    }

                    return 0.0;
                }

                void main() {
                    vec3 pigmentColor = mix(baseColor, secondaryColor, pattern());

                    vec3 lightDir = vec3(-0.5, 0.5, 1.0); // non-normalized to ensure top is at 1
                    float light = max(0.0, dot(fragNormal, lightDir));

                    float highlightMix = 1.75 * max(0.0, min(0.5, highlight - 0.25)); // clip off bouncy edges of value range

                    gl_FragColor = vec4(mix(pigmentColor, highlightColor, highlightMix + (1.0 - highlightMix) * light), 1.0);
                }
            `,

            attributes: {
                position: this._regl.buffer([
                    [ -1, 1, 0 ], [ -1, -1, 0 ], [ -1, 1, 1 ], [ -1, -1, 1 ], // left face
                    [ -1, -1, 1 ], [ -1, -1, 0 ], // degen connector
                    [ -1, -1, 0 ], [ 1, -1, 0 ], [ -1, -1, 1 ], [ 1, -1, 1 ], // front face
                    [ 1, -1, 1 ], [ -1, -1, 1 ], // degen connector
                    [ -1, -1, 1 ], [ 1, -1, 1 ], [ -1, 1, 1 ], [ 1, 1, 1 ] // top face
                ]),

                normal: this._regl.buffer([
                    [ -1, 0, 0 ], [ -1, 0, 0 ], [ -1, 0, 0 ], [ -1, 0, 0 ], // left face
                    [ -1, 0, 0 ], [ 0, -1, 0 ], // degen connector
                    [ 0, -1, 0 ], [ 0, -1, 0 ], [ 0, -1, 0 ], [ 0, -1, 0 ], // front face
                    [ 0, -1, 0 ], [ 0, 0, 1 ], // degen connector
                    [ 0, 0, 1 ], [ 0, 0, 1 ], [ 0, 0, 1 ], [ 0, 0, 1 ] // top face
                ])
            },

            uniforms: {
                camera: this._regl.prop('camera'),
                base: this._regl.prop('base'),
                radius: this._regl.prop('radius'),
                height: this._regl.prop('height'),
                highlight: this._regl.prop('highlight'),
                patternIndex: this._regl.prop('patternIndex'),
                patternSize: this._regl.prop('patternSize'),
                baseColor: this._regl.prop('baseColor'),
                secondaryColor: this._regl.prop('secondaryColor'),
                highlightColor: this._regl.prop('highlightColor')
            },

            primitive: 'triangle strip',
            count: 4 + 2 + 4 + 2 + 4
        });

        this.setState({ graphicsInitialized: true });
    }

    // eslint-disable-next-line max-statements
    render() {
        const baseColor = hex2vector(this.props.baseColor);
        const secondaryColor = hex2vector(this.props.secondaryColor);
        const highlightColor = hex2vector(this.props.highlightColor);
        const labelColorCss = this.props.labelColor;

        // chart 3D layout
        const barCellSize = this._chartAreaW / this._values.length;
        const barRadius = Math.max(this._barSpacing / 2, barCellSize / 2 - this._barSpacing); // padding of 10px
        const startX = -barCellSize * (this._values.length - 1) / 2;

        // animation setup (as single instance to help render scene in one shot)
        const motionDefaultStyle = {};
        const motionStyle = {};

        this._values.forEach((value, index) => {
            const isActive = this.state.barIsActive[index];

            motionDefaultStyle[`v${index}`] = 0;
            motionStyle[`v${index}`] = spring(value, { stiffness: 320, damping: 12 });

            motionDefaultStyle[`r${index}`] = 0;
            motionStyle[`r${index}`] = spring(
                isActive ? this._barExtraRadius : 0, // @todo just animate in 0..1 range
                { stiffness: 600, damping: 18 }
            );
        });

        return <Chart3DScene
            viewportWidth={this._width}
            viewportHeight={this._height}
            distance={this._chartAreaH * 4}
            centerX={0}
            centerY={0}
            centerZ={this._chartAreaH / 2}
            canvasRef={this._handleCanvasRef}
            content3d={{
                [`translate3d(${-this._chartAreaW / 2}px, -40px, ${this._chartAreaH}px) rotateX(90deg)`]: (
                    this._values.map((value, index) => <div
                        key={index}
                        style={{
                            position: 'absolute',
                            top: 0,
                            left: 0,
                            width: '100px', // non-fractional size for better precision via scaling
                            height: this._chartAreaH + 'px',

                            // prevent from showing on mobile tap hover
                            // @todo reconsider for a11y
                            WebkitTapHighlightColor: 'rgba(0, 0, 0, 0)',

                            transformOrigin: '0 0',
                            transform: `translate(${index * barCellSize}px, 0px) scale(${barCellSize / 100}, 1)`
                        }}
                        onMouseEnter={() => { this._setBarIsActive(index, true); }}
                        onMouseLeave={() => { this._setBarIsActive(index, false); }}
                        onClick={() => {
                            if (this.props.onBarClick) {
                                this.props.onBarClick(index);
                            }
                        }}
                    />)
                ),
                // @todo carry out contents
                [`translate(${-this._chartAreaW / 2 + 10}px, -60px)`]: (
                    <div style={{
                        whiteSpace: 'nowrap',

                        fontFamily: 'Michroma, Arial, sans-serif',
                        fontSize: '32px',
                        lineHeight: 1,
                        letterSpacing: '-2px',
                        color: labelColorCss
                    }}>{this.props.xLabel}</div>
                ),

                // @todo carry out contents
                [`translate(${this._chartAreaW / 2 + 10}px, -40px) rotateX(90deg) rotateZ(90deg)`]: (
                    <div style={{
                        whiteSpace: 'nowrap',

                        fontFamily: 'Michroma, Arial, sans-serif',
                        fontSize: '40px',
                        lineHeight: 1,
                        letterSpacing: '-2px',
                        color: labelColorCss
                    }}>{this.props.yLabel}</div>
                )
            }}
        >{(cameraMat4) => <div style={{
            position: 'absolute',
            top: 0,
            left: 0,
            width: '100%',
            height: '100%',
            pointerEvents: 'none' // allow underlying hover areas to work as intended
        }}>
            {/* animate when ready to render */}
            {this.state.graphicsInitialized ? <Motion
                defaultStyle={motionDefaultStyle}
                style={motionStyle}
            >{motion => {
                // general rendering refresh
                this._regl.poll();

                // clear canvas
                // @todo set colour?
                this._regl.clear({
                    depth: 1
                });

                // chart bar display
                this._values.forEach((value, index) => {
                    const motionValue = motion[`v${index}`];
                    const motionExtraRadius = motion[`r${index}`];

                    vec2.set(this._barBaseVec2, (index * barCellSize) + startX, barRadius - 40);

                    // @todo sort out how the ReGL framebuffer clearing works with react-motion framerate
                    this._barCommand({
                        camera: cameraMat4,
                        base: this._barBaseVec2,
                        radius: barRadius + motionExtraRadius,
                        height: this._chartAreaH * motionValue,
                        highlight: motionExtraRadius / this._barExtraRadius,
                        patternIndex: index % 4,
                        patternSize: this._patternSize,
                        baseColor: baseColor,
                        secondaryColor: secondaryColor,
                        highlightColor: highlightColor
                    });
                });

                // manually flush
                this._regl._gl.flush();

                // no element actually displayed
                return null;
            }}</Motion> : null}

            {this._values.map((value, index) => {
                // position overlay content on bar top
                vec3.set(
                    this._barTopVec3,
                    (index * barCellSize) + startX,
                    barRadius - 40,
                    value * this._chartAreaH
                );

                vec3.transformMat4(this._barTopVec3, this._barTopVec3, cameraMat4);

                // convert from GL device space (-1 .. 1) to 2D CSS space
                const x = (0.5 + 0.5 * this._barTopVec3[0]) * 100;
                const y = (0.5 - 0.5 * this._barTopVec3[1]) * 100;

                const barContent = this.props.renderBar && this.props.renderBar(
                    index,
                    this.state.barIsActive[index]
                );

                // set up mouse listeners on overlay content to ensure hover continuity
                return <div
                    key={index}
                    style={{
                        position: 'absolute',
                        top: `${y}%`,
                        left: `${x}%`,
                        pointerEvents: 'auto' // restore interactivity
                    }}
                    onMouseEnter={() => { this._setBarIsActive(index, true); }}
                    onMouseLeave={() => { this._setBarIsActive(index, false); }}
                >{barContent || null}</div>;
            })}
        </div>}</Chart3DScene>;
    }
}

class Chart3DScene extends React.PureComponent {
    constructor() {
        super();

        // reusable computation elements
        this._cameraMat4 = mat4.create();
        this._cameraPositionVec3 = vec3.create();
    }

    render() {
        mat4.perspective(this._cameraMat4, 0.5, this.props.viewportWidth / this.props.viewportHeight, 1, this.props.distance * 2.5);

        // camera distance
        vec3.set(this._cameraPositionVec3, 0, 0, -this.props.distance);
        mat4.translate(this._cameraMat4, this._cameraMat4, this._cameraPositionVec3);

        // camera orbit pitch and yaw
        mat4.rotateX(this._cameraMat4, this._cameraMat4, -1.0);
        mat4.rotateZ(this._cameraMat4, this._cameraMat4, Math.PI / 6);

        // camera offset
        vec3.set(this._cameraPositionVec3, -this.props.centerX, -this.props.centerY, -this.props.centerZ);
        mat4.translate(this._cameraMat4, this._cameraMat4, this._cameraPositionVec3);

        const cameraCssMat = `matrix3d(${this._cameraMat4.join(', ')})`;

        // not clipping contents on root div to allow custom overlay content to spill out
        return <div
            style={{
                position: 'absolute',
                top: 0,
                left: 0,
                width: '100%',
                height: '100%'
            }}
        >
            <canvas
                style={{
                    position: 'absolute',
                    top: 0,
                    left: 0,
                    width: '100%',
                    height: '100%'
                }}
                width={this.props.viewportWidth}
                height={this.props.viewportHeight}
                ref={this.props.canvasRef}
            />

            <div style={{
                position: 'absolute',
                zIndex: 0, // reset stacking context
                top: 0,
                left: 0,

                // apply camera matrix, center transform and emulate WebGL device coord range (-1, 1)
                transformStyle: 'preserve-3d'
            }} ref={(node) => {
                if (node) {
                    const realViewWidth = node.parentElement.offsetWidth;
                    const realViewHeight = node.parentElement.offsetHeight;

                    node.style.transform = `
                        translate(${realViewWidth / 2}px, ${realViewHeight / 2}px)
                        scale(${realViewWidth / 2}, ${-realViewHeight / 2})
                        ${cameraCssMat}
                    `;
                }
            }}>
                {Object.keys(this.props.content3d).map(modelTransform => <div
                    key={modelTransform}
                    style={{
                        position: 'absolute',
                        top: 0,
                        left: 0,

                        // transform in the XY plane, flipping first
                        transformStyle: 'preserve-3d',
                        transformOrigin: '0 0',
                        transform: `${modelTransform} scale(1, -1)`
                    }}
                >{this.props.content3d[modelTransform]}</div>)}
            </div>

            {/* custom overlay content */}
            {this.props.children(this._cameraMat4, cameraCssMat)}
        </div>;
    }
}

// from npmjs/nice-color-palettes
const colorPalettes = [
    ["#69d2e7","#a7dbd8","#e0e4cc","#f38630","#fa6900"],["#fe4365","#fc9d9a","#f9cdad","#c8c8a9","#83af9b"],["#ecd078","#d95b43","#c02942","#542437","#53777a"],["#556270","#4ecdc4","#c7f464","#ff6b6b","#c44d58"],["#774f38","#e08e79","#f1d4af","#ece5ce","#c5e0dc"],
    ["#e8ddcb","#cdb380","#036564","#033649","#031634"],["#490a3d","#bd1550","#e97f02","#f8ca00","#8a9b0f"],["#594f4f","#547980","#45ada8","#9de0ad","#e5fcc2"],["#00a0b0","#6a4a3c","#cc333f","#eb6841","#edc951"],["#e94e77","#d68189","#c6a49a","#c6e5d9","#f4ead5"]
]

const mockStockList = [
    'Trading YTD',
    'Last Session',
    'After Hours'
];

const mockSalesList = [
    'Q1 Prev Year',
    'Q3 This Year',
    'YoY Change',
    'Historical'
];

const mockRequestMetricList = [
    'Response',
    'IO Wait',
    'Peak Lag'
];

// credit: https://gist.github.com/blixt/f17b47c62508be59987b
function randomize(seed) {
    const intSeed = seed % 2147483647;
    const safeSeed = intSeed > 0 ? intSeed : intSeed + 2147483646;
    return safeSeed * 16807 % 2147483647;
}

function getRandomizedFraction(seed) {
    return (seed - 1) / 2147483646;
}

class RandomChart extends React.PureComponent {
    constructor(props) {
        super();

        // seeded random generation for predictable results
        const startSeed = Math.floor(Math.random() * 1000000);
        const random1 = randomize(startSeed);
        const random2 = randomize(random1);
        const random3 = randomize(random2);
        const random4 = randomize(random3);

        const mode = getRandomizedFraction(random1);
        const textSelector = getRandomizedFraction(random2);
        const paletteSelector = getRandomizedFraction(random3);
        const seriesLengthSelector = getRandomizedFraction(random4);

        this._textInfo = null;

        this._idNumber = random2 % 100000;

        if (mode < 0.2) {
            this._textInfo = {
                xLabel: 'STOCK: ' + mockStockList[Math.floor(textSelector * mockStockList.length)],
                yLabel: 'PRICE'
            };
        } else if (mode < 0.6) {
            this._textInfo = {
                xLabel: 'SALES: ' + mockSalesList[Math.floor(textSelector * mockSalesList.length)],
                yLabel: 'VOLUME'
            };
        } else {
            this._textInfo = {
                xLabel: 'REQUEST: ' + mockRequestMetricList[Math.floor(textSelector * mockRequestMetricList.length)],
                yLabel: 'TIME (ms)'
            };
        }

        // start with default palette at first, then randomize
        const paletteIndex = Math.floor(paletteSelector * colorPalettes.length);
        this._palette = colorPalettes[paletteIndex];

        this._series = Array(...new Array(3 + Math.floor(seriesLengthSelector * 10))).reduce(
            (itemSeedList) => {
                const prevSeed = itemSeedList.length > 0 ? itemSeedList[itemSeedList.length - 1] : random4;
                return itemSeedList.concat([ randomize(prevSeed) ]);
            },
            []
        ).map(itemSeed => getRandomizedFraction(itemSeed));
    }

    render() {
        // update body background
        document.body.style.background = this._palette[0];

        return (
            <BarChart3D
                values={this._series}
                width={480}
                height={360}
                xLabel={this._textInfo.xLabel}
                yLabel={this._textInfo.yLabel}
                baseColor={this._palette[3]}
                secondaryColor={this._palette[4]}
                highlightColor={this._palette[2]}
                labelColor={this._palette[1]}
                renderBar={(index, isActive) => isActive
                    ? <span className="random-chart__hover-label">
                        <span className="_arrow" />

                        {'0.' + Math.floor(100 + this._series[index] * 100).toString().slice(-2)}
                    </span>
                    : null
                }
            />
        );
    }
}


class Main extends React.PureComponent {
    constructor() {
        super();

        this.state = {
            count: 0
        };
    }
    
    render() {
        return <div className="main">
            <div className="_chart"><RandomChart key={this.state.count} /></div>
            <button
                type="button"
                onClick={() => this.setState(state => (
                    { count: state.count + 1 }
                ))}
            >Generate</button>
        </div>;
    }
}

WebFont.load({
    google: {
        families: [ 'Michroma' ]
    },

    active: function () {
        ReactDOM.render(
            React.createElement(Main),
            document.getElementById('app')
        );
    }
});

            
          
!
999px
🕑 One or more of the npm packages you are using needs to be built. You're the first person to ever need it! We're building it right now and your preview will start updating again when it's ready.
Loading ..................

Console