<div id="pills">
<svg version="1.1" width="540" height="420">
<svg-pill v-for="stat in stats" :stat="stat" :index="$index" :width="500"></svg-pill>
</svg>
</div>
<script type="text/x-template" id="pill-template">
<g>
<rect x="0" :y="offsetY" rx="20" ry="20" :width="width" height="30" style="fill:#dfdfef" />
<rect x="0" :y="pillY" rx="20" ry="20" :width="pillWidth" :height="pillHeight" style="fill:darkorange" />
<foreignObject x="0" :y="inputY" width="500" :height="pillHeight" requiredExtensions="http://www.w3.org/1999/xhtml">
<body xmlns="http://www.w3.org/1999/xhtml">
<form>
<input type="range" v-model="stat.value" min="0" max="100" style="width:{{inputWidth}}px" />
</form>
</body>
</foreignObject>
</g>
</script>
Vue.component('SvgPill', {
props: {
stat: Object,
index: Number,
width: Number
},
template: '#pill-template',
replace: true,
computed: {
offsetY: function() {
// The vertical position of the slider control. Based on the array index,
// so the first slider will be at y=0 and the rest stacked at fixed
// intervals below.
return this.index * 70
},
pillY: function() {
// The vertical position of the coloured value pill. This is the same as
// the background pill vertical position until the size of the pill is
// reduced to the point where it becomes a circle (the width is then equal
// to the height). At this width and smaller we need to add an offset to make
// sure the pill stays centered rather than drifting to the top left of
// the background pill.
var y = this.offsetY;
var width = this.pillWidth;
var offset = width < 30 ? (30 - width) / 2.0 : 0;
return y + offset
},
pillWidth: function() {
// The values are taken to be in the range 0..100, but the width of the
// svg element may be larger than this (in pixels), so here we scale the
// width of the coloured pill relative to the svg width.
return this.stat.value * (this.width / 100)
},
pillHeight: function() {
// The height is fixed at 30px until the size of the pill is reduced to the
// point where it becomes a circle (height==width). After this the height is
// set to be equal to the width, ie. the pill becomes a smaller and smaller circle.
var width = this.pillWidth;
return width < 30 ? width : 30
},
inputY: function() {
// The vertical position of the HTML range input.
return this.offsetY + 30
},
inputWidth: function() {
// The width of the HTML range input.
return this.width - 20
}
}
});
vue = new Vue({
el: '#pills',
data: {
stats: [{
value: 10
}, {
value: 30
}, {
value: 65
}]
}
});
External resources loaded into this fiddle: