Vue.js SVG example

by sukobuto

HTML

<script src="http://vuejs.org/js/vue.min.js"></script>
<!-- template for the polygraph component. -->
<script type="text/x-template" id="polygraph-template">
    <polygon v-attr="points: points"></polygon>
    <circle cx="100" cy="100" r="80"></circle>
    <text v-component="axis-label" v-repeat="stats" v-attr="x:x, y:y">
        {{label}}
    </text>
</script>

<div id="demo">
    <!-- Use the component -->
    <svg width="200" height="200" transform="scale(0.5)" v-component="polygraph" v-with="stats:stats"></svg>
    <svg width="200" height="200" transform="scale(0.5)" v-component="polygraph" v-with="stats:stats"></svg>
    <svg width="200" height="200" v-component="polygraph" v-with="stats:stats"></svg>
    <svg width="200" height="200" v-component="polygraph" v-with="stats:stats"></svg>
    <svg width="200" height="200" v-component="polygraph" v-with="stats:stats"></svg>
    <!-- controls -->
    <div v-repeat="stats">
        <label>{{label}}</label>
        <input type="range" v-model="value" min="0" max="100" />
        <span>{{value}}</span>
        <button v-on="click:remove(this)">X</button>
    </div>
    <input v-model="newLabel"> <button v-on="click:add">Add a Stat</button>
    <pre id="raw">{{stats | format}}</pre>
</div>
    
<p style="font-size:12px">* input[type="range"] requires IE10 or above.</p>

CSS

body {
    font-family: Helvetica Neue, Arial, sans-serif;
}

polygon {
    fill: #42b983;
    opacity: .75;
}

circle {
    fill: transparent;
    stroke: #999;
}

text {
    font-family: Helvetica Neue, Arial, sans-serif;
    font-size: 10px;
    fill: #666;
}

label {
    display: inline-block;
    margin-left: 10px;
    width: 20px;
}

#raw {
    position: absolute;
    top: 0;
    left: 300px;
}
}

JavaScript

// The raw data to observe
var stats = [
    { label: 'A', value: 100 },
    { label: 'B', value: 100 },
    { label: 'C', value: 100 },
    { label: 'D', value: 100 },
    { label: 'E', value: 100 },
    { label: 'F', value: 100 }
]

// A resusable polygon graph component
Vue.component('polygraph', {
    template: '#polygraph-template',
    replace: true,
    computed: {
        // a computed property for the polygon's points
        points: function () {
            var total = this.stats.length
            return this.stats.map(function (stat, i) {
                var point = valueToPoint(stat.value, i, total)
                return point.x + ',' + point.y
            }).join(' ')
        }
    },
    components: {
        // a sub component for the labels
        'axis-label': {
            computed: {
                point: function () {
                    return valueToPoint(+this.value + 10, this.$index, this.$parent.stats.length)
                },
                x: function () {
                    return this.point.x - 4
                },
                y: function () {
                    return this.point.y + 4
                }
            }
        }
    }
})

// math helper...
function valueToPoint (value, index, total) {
    var x     = 0,
        y     = -value * 0.8,
        angle = Math.PI * 2 / total * index,
        cos   = Math.cos(angle),
        sin   = Math.sin(angle),
        tx    = x * cos - y * sin + 100,
        ty    = x * sin + y * cos + 100
    return {
        x: tx,
        y: ty
    }
}

// bootstrap the demo
new Vue({
    el: '#demo',
    data: {
        newLabel: '',
        stats: stats
    },
    filters: {
        format: function (stats) {
            return JSON.stringify(stats, null, 2)
        }
    },
    methods: {
        add: function () {
            if (!this.newLabel) return
            this.stats.push({
                label: this.newLabel,
                value: 100
            })
            this.newLabel...