├── src ├── lib-components │ ├── index.js │ └── vue-funnel-graph.vue ├── entry.js └── example.vue ├── .gitignore ├── .eslintrc.js ├── CHANGELOG.md ├── LICENSE ├── package.json ├── README.md └── dist ├── vue-funnel-graph.min.js ├── vue-funnel-graph.esm.js └── vue-funnel-graph.umd.js /src/lib-components/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | export { default as VueFunnelGraph } from './vue-funnel-graph.vue'; 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist/demo.html 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true, 5 | }, 6 | extends: [ 7 | 'plugin:vue/essential', 8 | // '@vue/airbnb', 9 | ], 10 | rules: { 11 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 13 | //'indent': ['error', 4], 14 | 'no-plusplus': 'off', 15 | 'max-len': ['error', { 'code': 121 }], 16 | 'comma-dangle': ['error', 'never'], 17 | 'import/no-unresolved': 'off', 18 | 'import/extensions': 'off' 19 | }, 20 | parserOptions: { 21 | parser: 'babel-eslint', 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 0.1.8 (Feb 2, 2020) 2 | 3 | * Use lifecycle hook compatible with SSR 4 | 5 | ### 0.1.7 (Feb 2, 2020) 6 | 7 | * Add support for `subLabelValue` 8 | * Update packages 9 | * Fix minor issue 10 | 11 | ### 0.1.6 (Jul 31, 2019) 12 | 13 | * Fix issue where funnel was not generated vertically on load 14 | 15 | ### 0.1.3 (Mar 14, 2019) 16 | 17 | * Update documentation and add shields 18 | 19 | ### 0.1.2 (Mar 7, 2019) 20 | 21 | * Add legend for 2 dimensional graphs sub-labels 22 | 23 | ### 0.1.1 (Mar 1, 2019) 24 | 25 | * Add animations 26 | * Add percentage display for 2 dimensional graphs 27 | 28 | ### 0.1.0 (Feb 22, 2019) 29 | 30 | Initial release 31 | -------------------------------------------------------------------------------- /src/entry.js: -------------------------------------------------------------------------------- 1 | // Import vue components 2 | import * as components from './lib-components/index'; 3 | 4 | // install function executed by Vue.use() 5 | function install(Vue) { 6 | if (install.installed) return; 7 | install.installed = true; 8 | Object.keys(components).forEach((componentName) => { 9 | Vue.component(componentName, components[componentName]); 10 | }); 11 | } 12 | 13 | // Create module definition for Vue.use() 14 | const plugin = { 15 | install 16 | }; 17 | 18 | // To auto-install when vue is found 19 | /* global window global */ 20 | let GlobalVue = null; 21 | if (typeof window !== 'undefined') { 22 | GlobalVue = window.Vue; 23 | } else if (typeof global !== 'undefined') { 24 | GlobalVue = global.Vue; 25 | } 26 | if (GlobalVue) { 27 | GlobalVue.use(plugin); 28 | } 29 | 30 | // Default export is library as a whole, registered via Vue.use() 31 | export default plugin; 32 | 33 | // To allow individual component use, export components 34 | // each can be registered via Vue.component() 35 | export * from './lib-components/index'; 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Greg Hovanesyan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-funnel-graph-js", 3 | "version": "0.1.8", 4 | "description": "", 5 | "main": "dist/vue-funnel-graph.umd.js", 6 | "module": "dist/vue-funnel-graph.esm.js", 7 | "unpkg": "dist/vue-funnel-graph.min.js", 8 | "browser": { 9 | "./sfc": "src/lib-components/vue-funnel-graph.vue" 10 | }, 11 | "files": [ 12 | "dist/*", 13 | "src/*", 14 | "!src/lib-dev.vue" 15 | ], 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/greghub/vue-funnel-graph-js.git" 19 | }, 20 | "licence": "MIT", 21 | "keywords": [ 22 | "funnel", 23 | "chart", 24 | "graph", 25 | "funnel-chart", 26 | "funnel-graph", 27 | "svg-funnel-chart", 28 | "svg-funnel-graph", 29 | "vue", 30 | "vue-funnel-graph", 31 | "vue-funnel-chart", 32 | "vue-graph", 33 | "vue-plot", 34 | "vue-funnel" 35 | ], 36 | "scripts": { 37 | "build": "npm run build:unpkg & npm run build:es & npm run build:umd", 38 | "build:umd": "cross-env NODE_ENV=production rollup --config build/rollup.config.js --format umd --file dist/vue-funnel-graph.umd.js", 39 | "build:es": "cross-env NODE_ENV=production rollup --config build/rollup.config.js --format es --file dist/vue-funnel-graph.esm.js", 40 | "build:unpkg": "cross-env NODE_ENV=production rollup --config build/rollup.config.js --format iife --file dist/vue-funnel-graph.min.js" 41 | }, 42 | "dependencies": { 43 | "@tweenjs/tween.js": "^17.4.0", 44 | "funnel-graph-js": "^1.4.1", 45 | "node-sass": "^4.13.1", 46 | "polymorph-js": "^0.2.4", 47 | "sass-loader": "^7.3.1" 48 | }, 49 | "devDependencies": { 50 | "cross-env": "^5.2.1", 51 | "minimist": "^1.2.0", 52 | "rollup": "^1.31.0", 53 | "rollup-plugin-buble": "^0.19.8", 54 | "rollup-plugin-commonjs": "^9.3.4", 55 | "rollup-plugin-replace": "^2.2.0", 56 | "rollup-plugin-terser": "^4.0.4", 57 | "rollup-plugin-uglify-es": "0.0.1", 58 | "rollup-plugin-vue": "^4.6.2", 59 | "vue": "^2.6.11", 60 | "vue-template-compiler": "^2.6.11", 61 | "webpack": "^4.41.5" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VueFunnelGraph.js 2 | 3 | ![npm](https://img.shields.io/npm/v/vue-funnel-graph-js.svg) 4 | ![GitHub](https://img.shields.io/github/license/greghub/vue-funnel-graph-js.svg) 5 | ![GitHub last commit](https://img.shields.io/github/last-commit/greghub/vue-funnel-graph-js.svg) 6 | [![Gitter](https://img.shields.io/gitter/room/greghub/funnel-graph-js.svg)](https://gitter.im/funnel-graph-js/community) 7 | 8 | Funnel graph drawing library for Vue.js. 9 | 10 | * SVG charts 11 | * Values, Labels, Percentages display 12 | * Two-dimensional graph support 13 | * Legend display 14 | * Detailed percentage breakdown on hover 15 | * Animated 16 | * Solid color and gradient fill 17 | * Horizontal and vertical charts 18 | 19 | 20 | 21 | This is the Vue.js version of FunnelGraph.js, learn more about the library and see documentation [here.](https://github.com/greghub/funnel-graph-js) 22 | 23 | ## Demo 24 | 25 | #### Online Demo 26 | 27 | [CodePen Demo](https://codepen.io/gregh/full/gEBXPK) 28 | 29 | #### Development Demo 30 | 31 | * Clone the repo 32 | * Navigate to `src` folder 33 | * Run `vue serve example.vue` 34 | * Visit the URL displayed 35 | 36 | ## Installation 37 | 38 | #### NPM 39 | ```js 40 | npm i vue-funnel-graph-js 41 | ``` 42 | 43 | #### UNPKG 44 | ```html 45 | 46 | ``` 47 | 48 | #### CDN 49 | ```html 50 | 51 | ``` 52 | 53 | ## Usage 54 | 55 | After installing, import the `VueFunnelGraph` component: 56 | 57 | ```js 58 | import { VueFunnelGraph } from 'vue-funnel-graph-js'; 59 | ``` 60 | 61 | You can now use the custom element: 62 | ```vue 63 | 68 | ``` 69 | 70 | The values are passed to props: 71 | ```js 72 | export default { 73 | name: 'app', 74 | components: { 75 | VueFunnelGraph 76 | }, 77 | data() { 78 | return { 79 | labels: ['Impressions', 'Add To Cart', 'Buy'], 80 | subLabels: ['Direct', 'Social Media', 'Ads'], 81 | values: [ 82 | // with the given Labels and SubLabels here's what the values represent: 83 | // 84 | // Direct, Social, Ads 85 | // | | | 86 | // v v v 87 | [3000, 2500, 6500], // Segments of "Impressions" from top to bottom 88 | [3000, 1700, 1000], // Segments of "Add To Cart" 89 | [600, 200, 130] // Segments of "Buy" 90 | ], 91 | colors: [ 92 | ['#FFB178', '#FF3C8E'], // color set for "Impressions" segment 93 | ['#A0BBFF', '#EC77FF'], // color set for "Add To Cart" segment 94 | ['#A0F9FF', '#7795FF'] // color set for "Buy" segment 95 | ], 96 | direction: 'horizontal', 97 | gradientDirection: 'horizontal', 98 | height: 300, 99 | width: 800 100 | }; 101 | } 102 | } 103 | ``` 104 | 105 | ## Options 106 | 107 | | Option | Description | Type | Required | Options | Default | Example | 108 | |--------|-------------|------|----------|---------|---------|---------| 109 | | `width` | Width of the funnel graph | `number` | Yes | | 0 | 800 | 110 | | `height` | Height of the funnel graph | `number` | Yes | | 0 | 300 | 111 | | `labels` | Title of each data part | `array` | Yes | | | ['Impressions', 'Add To Cart', 'Buy'] | 112 | | `values` | Numbers that the funnel chart visualizes | `array` | Yes | | | [12000, 4700, 930] | 113 | | `colors` | Colors of the graph. If a string or array with one element passed it fills the graph with a solid color, if the array contains more than one element it fill the graph with a gradient. For two-dimensional charts and array of arrays shall be passed to fill each segment with a separate gradient. The array can contain arrays and strings mixed. If a there are more segments than colors provided, up to 10 extra segments will be filled with pre-defined solid colors | `array⎮string` | Yes | | | [12000, 4700, 930] | 114 | | `subLabels (:sub-labels)` | Title of each data segment | `array` | Yes for two-dimensional graphs | | | ['Direct', 'Social Media', 'Ads'] | 115 | | `direction` | Whether the chart visualization is displayed vertically or horizontally | `string` | No | 'vertical', 'horizontal' | 'horizontal' | | 116 | | `gradientDirection (:gradient-direction)` | Whether the gradient applied to the segments of the graph is displayed from top to bottom or from left to right | `string` | No | 'vertical', 'horizontal' | 'horizontal' | 117 | | `animated` | Whether any change in graph shape will be displayed with a smooth transition | `boolean` | No | `true`, `false` | `true` | `false` | 118 | | `displayPercentage (:display-percentage)` | Whether to display the automatically calculated percentage values below the labels | `boolean` | No | `true`, `false` | `true` | | 119 | -------------------------------------------------------------------------------- /src/example.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 128 | 129 | 172 | -------------------------------------------------------------------------------- /dist/vue-funnel-graph.min.js: -------------------------------------------------------------------------------- 1 | var VueFunnelGraph=function(e,t,a,n,s,i){"use strict";a=a&&a.hasOwnProperty("default")?a.default:a,n=n&&n.hasOwnProperty("default")?n.default:n;var r={name:"VueFunnelGraph",props:{animated:{type:Boolean,default:!1},width:[String,Number],height:[String,Number],values:Array,labels:Array,colors:{type:Array,default:function(){return[]}},subLabels:Array,subLabelValue:{type:String,default:"percent"},direction:{type:String,default:"horizontal"},gradientDirection:{type:String,default:"horizontal"},displayPercentage:{type:Boolean,default:!0}},data:function(){return{paths:[],prevPaths:[],graph:null,tween:null,defaultColors:i.getDefaultColors(10)}},computed:{valuesFormatted:function(){return this.graph.is2d()?this.graph.getValues2d().map(function(e){return s.formatNumber(e)}):this.values.map(function(e){return s.formatNumber(e)})},colorSet:function(){for(var e=[],t=0,a=0;a 2 |
3 |
4 | 5 | 6 | 11 | 16 | 17 | 18 | 21 | 22 |
23 | 26 |
29 |
{{ value }}
30 |
{{ labels[index] }}
31 |
32 | {{ percentages()[index] }}% 33 |
34 |
35 |
    36 |
  • 37 | {{ subLabel }}: 38 | {{ twoDimPercentages()[index][j] }}% 39 | {{ values[index][j] | format }} 40 |
  • 41 |
42 |
43 |
44 |
45 | 46 |
47 |
51 |
53 |
{{ subLabel }}
54 |
55 |
56 |
57 |
58 | 59 | 60 | 279 | 280 | 309 | -------------------------------------------------------------------------------- /dist/vue-funnel-graph.esm.js: -------------------------------------------------------------------------------- 1 | import { interpolate } from 'polymorph-js'; 2 | import TWEEN from '@tweenjs/tween.js'; 3 | import FunnelGraph from 'funnel-graph-js'; 4 | import { formatNumber } from 'funnel-graph-js/src/js/number'; 5 | import { getDefaultColors, generateLegendBackground } from 'funnel-graph-js/src/js/graph'; 6 | import 'funnel-graph-js/src/scss/main.scss'; 7 | import 'funnel-graph-js/src/scss/theme.scss'; 8 | 9 | // 10 | 11 | var script = { 12 | name: 'VueFunnelGraph', 13 | props: { 14 | animated: { 15 | type: Boolean, 16 | default: false 17 | }, 18 | width: [String, Number], 19 | height: [String, Number], 20 | values: Array, 21 | labels: Array, 22 | colors: { 23 | type: Array, 24 | default: function default$1() { return []; } 25 | }, 26 | subLabels: Array, 27 | subLabelValue: { 28 | type: String, 29 | default: 'percent' 30 | }, 31 | direction: { 32 | type: String, 33 | default: 'horizontal' 34 | }, 35 | gradientDirection: { 36 | type: String, 37 | default: 'horizontal' 38 | }, 39 | displayPercentage: { 40 | type: Boolean, 41 | default: true 42 | } 43 | }, 44 | data: function data() { 45 | return { 46 | paths: [], 47 | prevPaths: [], // paths before update, used for animations 48 | graph: null, 49 | tween: null, 50 | defaultColors: getDefaultColors(10) 51 | }; 52 | }, 53 | computed: { 54 | valuesFormatted: function valuesFormatted() { 55 | if (this.graph.is2d()) { 56 | return this.graph.getValues2d().map(function (value) { return formatNumber(value); }); 57 | } 58 | return this.values.map(function (value) { return formatNumber(value); }); 59 | }, 60 | colorSet: function colorSet() { 61 | var colorSet = []; 62 | var gradientCount = 0; 63 | 64 | for (var i = 0; i < this.paths.length; i++) { 65 | var values = this.graph.is2d() ? this.getColors[i] : this.getColors; 66 | var fillMode = (typeof values === 'string' || values.length === 1) ? 'solid' : 'gradient'; 67 | if (fillMode === 'gradient') { gradientCount += 1; } 68 | colorSet.push({ 69 | values: values, 70 | fillMode: fillMode, 71 | fill: fillMode === 'solid' ? values : ("url('#funnelGradient-" + gradientCount + "')") 72 | }); 73 | } 74 | return colorSet; 75 | }, 76 | gradientSet: function gradientSet() { 77 | var gradientSet = []; 78 | this.colorSet.forEach(function (colors) { 79 | if (colors.fillMode === 'gradient') { 80 | gradientSet.push(colors); 81 | } 82 | }); 83 | return gradientSet; 84 | }, 85 | getColors: function getColors() { 86 | if (this.colors instanceof Array && this.colors.length === 0) { 87 | return getDefaultColors(this.is2d() ? this.values[0].length : 2); 88 | } 89 | if (this.colors.length < this.paths.length) { 90 | return [].concat( this.colors ).concat( 91 | [].concat( this.defaultColors ).splice(this.paths.length, this.paths.length - this.colors.length) 92 | ); 93 | } 94 | return this.colors; 95 | }, 96 | gradientAngle: function gradientAngle() { 97 | return ("rotate(" + (this.gradientDirection === 'vertical' ? 90 : 0) + ")"); 98 | } 99 | }, 100 | methods: { 101 | enterTransition: function enterTransition(el, done) { 102 | if (!this.animated) { done(); } 103 | setTimeout(function () { return done(); }, 700); 104 | }, 105 | leaveTransition: function leaveTransition(el, done) { 106 | if (!this.animated) { done(); } 107 | setTimeout(function () { return done(); }, 700); 108 | }, 109 | is2d: function is2d() { 110 | return this.graph.is2d(); 111 | }, 112 | percentages: function percentages() { 113 | return this.graph.createPercentages(); 114 | }, 115 | twoDimPercentages: function twoDimPercentages() { 116 | if (!this.is2d()) { 117 | return []; 118 | } 119 | return this.graph.getPercentages2d(); 120 | }, 121 | subLabelBackgrounds: function subLabelBackgrounds(index) { 122 | if (!this.is2d()) { 123 | return []; 124 | } 125 | return generateLegendBackground(this.getColors[index], this.gradientDirection); 126 | }, 127 | offsetColor: function offsetColor(index, length) { 128 | return ((Math.round(100 * index / (length - 1))) + "%"); 129 | }, 130 | makeAnimations: function makeAnimations() { 131 | var this$1 = this; 132 | 133 | if (this.tween !== null) { this.tween.stop(); } 134 | var interpolators = []; 135 | var dimensionChanged = this.prevPaths.length !== this.paths.length; 136 | 137 | var origin = { x: 0.5, y: 0.5 }; 138 | if (dimensionChanged) { 139 | origin = { x: 0, y: 0.5 }; 140 | if (this.graph.isVertical()) { 141 | origin = { x: 1, y: 1 }; 142 | } 143 | if (!this.graph.is2d()) { 144 | origin = { x: 0, y: 1 }; 145 | } 146 | } 147 | 148 | this.paths.forEach(function (path, index) { 149 | var oldPath = this$1.prevPaths[index] || this$1.graph.getPathMedian(index); 150 | if (dimensionChanged) { oldPath = this$1.graph.getPathMedian(index); } 151 | var interpolator = interpolate([oldPath, path], { 152 | addPoints: 1, 153 | origin: origin, 154 | optimize: 'fill', 155 | precision: 1 156 | }); 157 | 158 | interpolators.push(interpolator); 159 | }); 160 | 161 | function animate() { 162 | if (TWEEN.update()) { 163 | requestAnimationFrame(animate); 164 | } 165 | } 166 | 167 | var position = { value: 0 }; 168 | this.tween = new TWEEN.Tween(position) 169 | .to({ value: 1 }, 700) 170 | .easing(TWEEN.Easing.Cubic.InOut) 171 | .onUpdate(function () { 172 | for (var index = 0; index < this$1.paths.length; index++) { 173 | this$1.$set(this$1.paths, index, interpolators[index](position.value)); 174 | } 175 | }); 176 | 177 | this.tween.start(); 178 | animate(); 179 | }, 180 | drawPaths: function drawPaths() { 181 | var this$1 = this; 182 | 183 | this.prevPaths = this.paths; 184 | this.paths = []; 185 | var definitions = this.graph.getPathDefinitions(); 186 | 187 | definitions.forEach(function (d) { 188 | this$1.paths.push(d); 189 | }); 190 | } 191 | }, 192 | created: function created() { 193 | this.graph = new FunnelGraph({ 194 | height: this.height, 195 | width: this.width, 196 | direction: this.direction, 197 | data: { 198 | labels: this.labels, 199 | values: this.values 200 | } 201 | }); 202 | this.drawPaths(); 203 | if (this.animated) { this.makeAnimations(); } 204 | }, 205 | watch: { 206 | values: function values() { 207 | this.graph.setValues(this.values); 208 | this.drawPaths(); 209 | if (this.animated) { this.makeAnimations(); } 210 | }, 211 | direction: function direction() { 212 | this.graph.setDirection(this.direction) 213 | .setWidth(this.width) 214 | .setHeight(this.height); 215 | this.drawPaths(); 216 | } 217 | }, 218 | filters: { 219 | format: function (value) { 220 | return formatNumber(value) 221 | } 222 | } 223 | }; 224 | 225 | function normalizeComponent(template, style, script, scopeId, isFunctionalTemplate, moduleIdentifier 226 | /* server only */ 227 | , shadowMode, createInjector, createInjectorSSR, createInjectorShadow) { 228 | if (typeof shadowMode !== 'boolean') { 229 | createInjectorSSR = createInjector; 230 | createInjector = shadowMode; 231 | shadowMode = false; 232 | } // Vue.extend constructor export interop. 233 | 234 | 235 | var options = typeof script === 'function' ? script.options : script; // render functions 236 | 237 | if (template && template.render) { 238 | options.render = template.render; 239 | options.staticRenderFns = template.staticRenderFns; 240 | options._compiled = true; // functional template 241 | 242 | if (isFunctionalTemplate) { 243 | options.functional = true; 244 | } 245 | } // scopedId 246 | 247 | 248 | if (scopeId) { 249 | options._scopeId = scopeId; 250 | } 251 | 252 | var hook; 253 | 254 | if (moduleIdentifier) { 255 | // server build 256 | hook = function hook(context) { 257 | // 2.3 injection 258 | context = context || // cached call 259 | this.$vnode && this.$vnode.ssrContext || // stateful 260 | this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext; // functional 261 | // 2.2 with runInNewContext: true 262 | 263 | if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') { 264 | context = __VUE_SSR_CONTEXT__; 265 | } // inject component styles 266 | 267 | 268 | if (style) { 269 | style.call(this, createInjectorSSR(context)); 270 | } // register component module identifier for async chunk inference 271 | 272 | 273 | if (context && context._registeredComponents) { 274 | context._registeredComponents.add(moduleIdentifier); 275 | } 276 | }; // used by ssr in case component is cached and beforeCreate 277 | // never gets called 278 | 279 | 280 | options._ssrRegister = hook; 281 | } else if (style) { 282 | hook = shadowMode ? function () { 283 | style.call(this, createInjectorShadow(this.$root.$options.shadowRoot)); 284 | } : function (context) { 285 | style.call(this, createInjector(context)); 286 | }; 287 | } 288 | 289 | if (hook) { 290 | if (options.functional) { 291 | // register for functional component in vue file 292 | var originalRender = options.render; 293 | 294 | options.render = function renderWithStyleInjection(h, context) { 295 | hook.call(context); 296 | return originalRender(h, context); 297 | }; 298 | } else { 299 | // inject component registration as beforeCreate hook 300 | var existing = options.beforeCreate; 301 | options.beforeCreate = existing ? [].concat(existing, hook) : [hook]; 302 | } 303 | } 304 | 305 | return script; 306 | } 307 | 308 | var normalizeComponent_1 = normalizeComponent; 309 | 310 | var isOldIE = typeof navigator !== 'undefined' && /msie [6-9]\\b/.test(navigator.userAgent.toLowerCase()); 311 | function createInjector(context) { 312 | return function (id, style) { 313 | return addStyle(id, style); 314 | }; 315 | } 316 | var HEAD = document.head || document.getElementsByTagName('head')[0]; 317 | var styles = {}; 318 | 319 | function addStyle(id, css) { 320 | var group = isOldIE ? css.media || 'default' : id; 321 | var style = styles[group] || (styles[group] = { 322 | ids: new Set(), 323 | styles: [] 324 | }); 325 | 326 | if (!style.ids.has(id)) { 327 | style.ids.add(id); 328 | var code = css.source; 329 | 330 | if (css.map) { 331 | // https://developer.chrome.com/devtools/docs/javascript-debugging 332 | // this makes source maps inside style tags work properly in Chrome 333 | code += '\n/*# sourceURL=' + css.map.sources[0] + ' */'; // http://stackoverflow.com/a/26603875 334 | 335 | code += '\n/*# sourceMappingURL=data:application/json;base64,' + btoa(unescape(encodeURIComponent(JSON.stringify(css.map)))) + ' */'; 336 | } 337 | 338 | if (!style.element) { 339 | style.element = document.createElement('style'); 340 | style.element.type = 'text/css'; 341 | if (css.media) { style.element.setAttribute('media', css.media); } 342 | HEAD.appendChild(style.element); 343 | } 344 | 345 | if ('styleSheet' in style.element) { 346 | style.styles.push(code); 347 | style.element.styleSheet.cssText = style.styles.filter(Boolean).join('\n'); 348 | } else { 349 | var index = style.ids.size - 1; 350 | var textNode = document.createTextNode(code); 351 | var nodes = style.element.childNodes; 352 | if (nodes[index]) { style.element.removeChild(nodes[index]); } 353 | if (nodes.length) { style.element.insertBefore(textNode, nodes[index]); }else { style.element.appendChild(textNode); } 354 | } 355 | } 356 | } 357 | 358 | var browser = createInjector; 359 | 360 | /* script */ 361 | var __vue_script__ = script; 362 | 363 | /* template */ 364 | var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"funnel svg-funnel-js",class:{'svg-funnel-js--vertical': _vm.direction === 'vertical'}},[_c('div',{staticClass:"svg-funnel-js__container"},[_c('svg',{attrs:{"width":_vm.width,"height":_vm.height}},[_c('defs',_vm._l((_vm.gradientSet),function(colors,index){return _c('linearGradient',{key:index,attrs:{"id":("funnelGradient-" + ((index+1))),"gradientTransform":_vm.gradientAngle}},_vm._l((colors.values),function(color,index){return _c('stop',{key:index,attrs:{"stop-color":color,"offset":_vm.offsetColor(index, colors.values.length)}})}),1)}),1),_vm._v(" "),_vm._l((_vm.paths),function(path,index){return _c('path',{key:index,attrs:{"fill":_vm.colorSet[index].fill,"stroke":_vm.colorSet[index].fill,"d":path}})})],2)]),_vm._v(" "),_c('transition-group',{staticClass:"svg-funnel-js__labels",attrs:{"name":"appear","tag":"div"},on:{"enter":_vm.enterTransition,"leave":_vm.leaveTransition}},_vm._l((_vm.valuesFormatted),function(value,index){return _c('div',{key:_vm.labels[index].toLowerCase().split(' ').join('-'),staticClass:"svg-funnel-js__label",class:("label-" + ((index+1)))},[_c('div',{staticClass:"label__value"},[_vm._v(_vm._s(value))]),_vm._v(" "),(_vm.labels)?_c('div',{staticClass:"label__title"},[_vm._v(_vm._s(_vm.labels[index]))]):_vm._e(),_vm._v(" "),(_vm.displayPercentage && _vm.percentages()[index] !== 100)?_c('div',{staticClass:"label__percentage"},[_vm._v("\n "+_vm._s(_vm.percentages()[index])+"%\n ")]):_vm._e(),_vm._v(" "),(_vm.is2d())?_c('div',{staticClass:"label__segment-percentages"},[_c('ul',{staticClass:"segment-percentage__list"},_vm._l((_vm.subLabels),function(subLabel,j){return _c('li',{key:j},[_vm._v("\n "+_vm._s(subLabel)+":\n "),(_vm.subLabelValue === 'percent')?_c('span',{staticClass:"percentage__list-label"},[_vm._v(_vm._s(_vm.twoDimPercentages()[index][j])+"%")]):_c('span',{staticClass:"percentage__list-label"},[_vm._v(_vm._s(_vm._f("format")(_vm.values[index][j])))])])}),0)]):_vm._e()])}),0),_vm._v(" "),_c('transition',{attrs:{"name":"fade"},on:{"enter":_vm.enterTransition,"leave":_vm.leaveTransition}},[(_vm.is2d())?_c('div',{staticClass:"svg-funnel-js__subLabels"},_vm._l((_vm.subLabels),function(subLabel,index){return _c('div',{key:index,class:("svg-funnel-js__subLabel svg-funnel-js__subLabel-" + ((index + 1)))},[_c('div',{staticClass:"svg-funnel-js__subLabel--color",style:(_vm.subLabelBackgrounds(index))}),_vm._v(" "),_c('div',{staticClass:"svg-funnel-js__subLabel--title"},[_vm._v(_vm._s(subLabel))])])}),0):_vm._e()])],1)}; 365 | var __vue_staticRenderFns__ = []; 366 | 367 | /* style */ 368 | var __vue_inject_styles__ = function (inject) { 369 | if (!inject) { return } 370 | inject("data-v-52d787cd_0", { source: ".appear-enter-active[data-v-52d787cd],.appear-leave-active[data-v-52d787cd]{transition:all .7s ease-in-out}.appear-enter-to[data-v-52d787cd],.appear-leave[data-v-52d787cd]{max-width:100%;max-height:100%;opacity:1}.appear-enter[data-v-52d787cd],.appear-leave-to[data-v-52d787cd]{max-width:0;max-height:0;opacity:0}.fade-enter-active[data-v-52d787cd],.fade-leave-active[data-v-52d787cd]{transition:all .3s ease}.fade-enter-to[data-v-52d787cd],.fade-leave[data-v-52d787cd]{opacity:1}.fade-enter[data-v-52d787cd],.fade-leave-to[data-v-52d787cd]{opacity:0}", map: undefined, media: undefined }); 371 | 372 | }; 373 | /* scoped */ 374 | var __vue_scope_id__ = "data-v-52d787cd"; 375 | /* module identifier */ 376 | var __vue_module_identifier__ = undefined; 377 | /* functional template */ 378 | var __vue_is_functional_template__ = false; 379 | /* style inject SSR */ 380 | 381 | 382 | 383 | var vueFunnelGraph = normalizeComponent_1( 384 | { render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ }, 385 | __vue_inject_styles__, 386 | __vue_script__, 387 | __vue_scope_id__, 388 | __vue_is_functional_template__, 389 | __vue_module_identifier__, 390 | browser, 391 | undefined 392 | ); 393 | 394 | /* eslint-disable import/prefer-default-export */ 395 | 396 | var components = /*#__PURE__*/Object.freeze({ 397 | __proto__: null, 398 | VueFunnelGraph: vueFunnelGraph 399 | }); 400 | 401 | // Import vue components 402 | 403 | // install function executed by Vue.use() 404 | function install(Vue) { 405 | if (install.installed) { return; } 406 | install.installed = true; 407 | Object.keys(components).forEach(function (componentName) { 408 | Vue.component(componentName, components[componentName]); 409 | }); 410 | } 411 | 412 | // Create module definition for Vue.use() 413 | var plugin = { 414 | install: install 415 | }; 416 | 417 | // To auto-install when vue is found 418 | /* global window global */ 419 | var GlobalVue = null; 420 | if (typeof window !== 'undefined') { 421 | GlobalVue = window.Vue; 422 | } else if (typeof global !== 'undefined') { 423 | GlobalVue = global.Vue; 424 | } 425 | if (GlobalVue) { 426 | GlobalVue.use(plugin); 427 | } 428 | 429 | export default plugin; 430 | export { vueFunnelGraph as VueFunnelGraph }; 431 | -------------------------------------------------------------------------------- /dist/vue-funnel-graph.umd.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('polymorph-js'), require('@tweenjs/tween.js'), require('funnel-graph-js'), require('funnel-graph-js/src/js/number'), require('funnel-graph-js/src/js/graph'), require('funnel-graph-js/src/scss/main.scss'), require('funnel-graph-js/src/scss/theme.scss')) : 3 | typeof define === 'function' && define.amd ? define(['exports', 'polymorph-js', '@tweenjs/tween.js', 'funnel-graph-js', 'funnel-graph-js/src/js/number', 'funnel-graph-js/src/js/graph', 'funnel-graph-js/src/scss/main.scss', 'funnel-graph-js/src/scss/theme.scss'], factory) : 4 | (global = global || self, factory(global.VueFunnelGraph = {}, global.interpolate, global.TWEEN, global.FunnelGraph, global.formatNumber, global.getDefaultColors)); 5 | }(this, (function (exports, polymorphJs, TWEEN, FunnelGraph, number, graph) { 'use strict'; 6 | 7 | TWEEN = TWEEN && TWEEN.hasOwnProperty('default') ? TWEEN['default'] : TWEEN; 8 | FunnelGraph = FunnelGraph && FunnelGraph.hasOwnProperty('default') ? FunnelGraph['default'] : FunnelGraph; 9 | 10 | // 11 | 12 | var script = { 13 | name: 'VueFunnelGraph', 14 | props: { 15 | animated: { 16 | type: Boolean, 17 | default: false 18 | }, 19 | width: [String, Number], 20 | height: [String, Number], 21 | values: Array, 22 | labels: Array, 23 | colors: { 24 | type: Array, 25 | default: function default$1() { return []; } 26 | }, 27 | subLabels: Array, 28 | subLabelValue: { 29 | type: String, 30 | default: 'percent' 31 | }, 32 | direction: { 33 | type: String, 34 | default: 'horizontal' 35 | }, 36 | gradientDirection: { 37 | type: String, 38 | default: 'horizontal' 39 | }, 40 | displayPercentage: { 41 | type: Boolean, 42 | default: true 43 | } 44 | }, 45 | data: function data() { 46 | return { 47 | paths: [], 48 | prevPaths: [], // paths before update, used for animations 49 | graph: null, 50 | tween: null, 51 | defaultColors: graph.getDefaultColors(10) 52 | }; 53 | }, 54 | computed: { 55 | valuesFormatted: function valuesFormatted() { 56 | if (this.graph.is2d()) { 57 | return this.graph.getValues2d().map(function (value) { return number.formatNumber(value); }); 58 | } 59 | return this.values.map(function (value) { return number.formatNumber(value); }); 60 | }, 61 | colorSet: function colorSet() { 62 | var colorSet = []; 63 | var gradientCount = 0; 64 | 65 | for (var i = 0; i < this.paths.length; i++) { 66 | var values = this.graph.is2d() ? this.getColors[i] : this.getColors; 67 | var fillMode = (typeof values === 'string' || values.length === 1) ? 'solid' : 'gradient'; 68 | if (fillMode === 'gradient') { gradientCount += 1; } 69 | colorSet.push({ 70 | values: values, 71 | fillMode: fillMode, 72 | fill: fillMode === 'solid' ? values : ("url('#funnelGradient-" + gradientCount + "')") 73 | }); 74 | } 75 | return colorSet; 76 | }, 77 | gradientSet: function gradientSet() { 78 | var gradientSet = []; 79 | this.colorSet.forEach(function (colors) { 80 | if (colors.fillMode === 'gradient') { 81 | gradientSet.push(colors); 82 | } 83 | }); 84 | return gradientSet; 85 | }, 86 | getColors: function getColors() { 87 | if (this.colors instanceof Array && this.colors.length === 0) { 88 | return graph.getDefaultColors(this.is2d() ? this.values[0].length : 2); 89 | } 90 | if (this.colors.length < this.paths.length) { 91 | return [].concat( this.colors ).concat( 92 | [].concat( this.defaultColors ).splice(this.paths.length, this.paths.length - this.colors.length) 93 | ); 94 | } 95 | return this.colors; 96 | }, 97 | gradientAngle: function gradientAngle() { 98 | return ("rotate(" + (this.gradientDirection === 'vertical' ? 90 : 0) + ")"); 99 | } 100 | }, 101 | methods: { 102 | enterTransition: function enterTransition(el, done) { 103 | if (!this.animated) { done(); } 104 | setTimeout(function () { return done(); }, 700); 105 | }, 106 | leaveTransition: function leaveTransition(el, done) { 107 | if (!this.animated) { done(); } 108 | setTimeout(function () { return done(); }, 700); 109 | }, 110 | is2d: function is2d() { 111 | return this.graph.is2d(); 112 | }, 113 | percentages: function percentages() { 114 | return this.graph.createPercentages(); 115 | }, 116 | twoDimPercentages: function twoDimPercentages() { 117 | if (!this.is2d()) { 118 | return []; 119 | } 120 | return this.graph.getPercentages2d(); 121 | }, 122 | subLabelBackgrounds: function subLabelBackgrounds(index) { 123 | if (!this.is2d()) { 124 | return []; 125 | } 126 | return graph.generateLegendBackground(this.getColors[index], this.gradientDirection); 127 | }, 128 | offsetColor: function offsetColor(index, length) { 129 | return ((Math.round(100 * index / (length - 1))) + "%"); 130 | }, 131 | makeAnimations: function makeAnimations() { 132 | var this$1 = this; 133 | 134 | if (this.tween !== null) { this.tween.stop(); } 135 | var interpolators = []; 136 | var dimensionChanged = this.prevPaths.length !== this.paths.length; 137 | 138 | var origin = { x: 0.5, y: 0.5 }; 139 | if (dimensionChanged) { 140 | origin = { x: 0, y: 0.5 }; 141 | if (this.graph.isVertical()) { 142 | origin = { x: 1, y: 1 }; 143 | } 144 | if (!this.graph.is2d()) { 145 | origin = { x: 0, y: 1 }; 146 | } 147 | } 148 | 149 | this.paths.forEach(function (path, index) { 150 | var oldPath = this$1.prevPaths[index] || this$1.graph.getPathMedian(index); 151 | if (dimensionChanged) { oldPath = this$1.graph.getPathMedian(index); } 152 | var interpolator = polymorphJs.interpolate([oldPath, path], { 153 | addPoints: 1, 154 | origin: origin, 155 | optimize: 'fill', 156 | precision: 1 157 | }); 158 | 159 | interpolators.push(interpolator); 160 | }); 161 | 162 | function animate() { 163 | if (TWEEN.update()) { 164 | requestAnimationFrame(animate); 165 | } 166 | } 167 | 168 | var position = { value: 0 }; 169 | this.tween = new TWEEN.Tween(position) 170 | .to({ value: 1 }, 700) 171 | .easing(TWEEN.Easing.Cubic.InOut) 172 | .onUpdate(function () { 173 | for (var index = 0; index < this$1.paths.length; index++) { 174 | this$1.$set(this$1.paths, index, interpolators[index](position.value)); 175 | } 176 | }); 177 | 178 | this.tween.start(); 179 | animate(); 180 | }, 181 | drawPaths: function drawPaths() { 182 | var this$1 = this; 183 | 184 | this.prevPaths = this.paths; 185 | this.paths = []; 186 | var definitions = this.graph.getPathDefinitions(); 187 | 188 | definitions.forEach(function (d) { 189 | this$1.paths.push(d); 190 | }); 191 | } 192 | }, 193 | created: function created() { 194 | this.graph = new FunnelGraph({ 195 | height: this.height, 196 | width: this.width, 197 | direction: this.direction, 198 | data: { 199 | labels: this.labels, 200 | values: this.values 201 | } 202 | }); 203 | this.drawPaths(); 204 | if (this.animated) { this.makeAnimations(); } 205 | }, 206 | watch: { 207 | values: function values() { 208 | this.graph.setValues(this.values); 209 | this.drawPaths(); 210 | if (this.animated) { this.makeAnimations(); } 211 | }, 212 | direction: function direction() { 213 | this.graph.setDirection(this.direction) 214 | .setWidth(this.width) 215 | .setHeight(this.height); 216 | this.drawPaths(); 217 | } 218 | }, 219 | filters: { 220 | format: function (value) { 221 | return number.formatNumber(value) 222 | } 223 | } 224 | }; 225 | 226 | function normalizeComponent(template, style, script, scopeId, isFunctionalTemplate, moduleIdentifier 227 | /* server only */ 228 | , shadowMode, createInjector, createInjectorSSR, createInjectorShadow) { 229 | if (typeof shadowMode !== 'boolean') { 230 | createInjectorSSR = createInjector; 231 | createInjector = shadowMode; 232 | shadowMode = false; 233 | } // Vue.extend constructor export interop. 234 | 235 | 236 | var options = typeof script === 'function' ? script.options : script; // render functions 237 | 238 | if (template && template.render) { 239 | options.render = template.render; 240 | options.staticRenderFns = template.staticRenderFns; 241 | options._compiled = true; // functional template 242 | 243 | if (isFunctionalTemplate) { 244 | options.functional = true; 245 | } 246 | } // scopedId 247 | 248 | 249 | if (scopeId) { 250 | options._scopeId = scopeId; 251 | } 252 | 253 | var hook; 254 | 255 | if (moduleIdentifier) { 256 | // server build 257 | hook = function hook(context) { 258 | // 2.3 injection 259 | context = context || // cached call 260 | this.$vnode && this.$vnode.ssrContext || // stateful 261 | this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext; // functional 262 | // 2.2 with runInNewContext: true 263 | 264 | if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') { 265 | context = __VUE_SSR_CONTEXT__; 266 | } // inject component styles 267 | 268 | 269 | if (style) { 270 | style.call(this, createInjectorSSR(context)); 271 | } // register component module identifier for async chunk inference 272 | 273 | 274 | if (context && context._registeredComponents) { 275 | context._registeredComponents.add(moduleIdentifier); 276 | } 277 | }; // used by ssr in case component is cached and beforeCreate 278 | // never gets called 279 | 280 | 281 | options._ssrRegister = hook; 282 | } else if (style) { 283 | hook = shadowMode ? function () { 284 | style.call(this, createInjectorShadow(this.$root.$options.shadowRoot)); 285 | } : function (context) { 286 | style.call(this, createInjector(context)); 287 | }; 288 | } 289 | 290 | if (hook) { 291 | if (options.functional) { 292 | // register for functional component in vue file 293 | var originalRender = options.render; 294 | 295 | options.render = function renderWithStyleInjection(h, context) { 296 | hook.call(context); 297 | return originalRender(h, context); 298 | }; 299 | } else { 300 | // inject component registration as beforeCreate hook 301 | var existing = options.beforeCreate; 302 | options.beforeCreate = existing ? [].concat(existing, hook) : [hook]; 303 | } 304 | } 305 | 306 | return script; 307 | } 308 | 309 | var normalizeComponent_1 = normalizeComponent; 310 | 311 | var isOldIE = typeof navigator !== 'undefined' && /msie [6-9]\\b/.test(navigator.userAgent.toLowerCase()); 312 | function createInjector(context) { 313 | return function (id, style) { 314 | return addStyle(id, style); 315 | }; 316 | } 317 | var HEAD = document.head || document.getElementsByTagName('head')[0]; 318 | var styles = {}; 319 | 320 | function addStyle(id, css) { 321 | var group = isOldIE ? css.media || 'default' : id; 322 | var style = styles[group] || (styles[group] = { 323 | ids: new Set(), 324 | styles: [] 325 | }); 326 | 327 | if (!style.ids.has(id)) { 328 | style.ids.add(id); 329 | var code = css.source; 330 | 331 | if (css.map) { 332 | // https://developer.chrome.com/devtools/docs/javascript-debugging 333 | // this makes source maps inside style tags work properly in Chrome 334 | code += '\n/*# sourceURL=' + css.map.sources[0] + ' */'; // http://stackoverflow.com/a/26603875 335 | 336 | code += '\n/*# sourceMappingURL=data:application/json;base64,' + btoa(unescape(encodeURIComponent(JSON.stringify(css.map)))) + ' */'; 337 | } 338 | 339 | if (!style.element) { 340 | style.element = document.createElement('style'); 341 | style.element.type = 'text/css'; 342 | if (css.media) { style.element.setAttribute('media', css.media); } 343 | HEAD.appendChild(style.element); 344 | } 345 | 346 | if ('styleSheet' in style.element) { 347 | style.styles.push(code); 348 | style.element.styleSheet.cssText = style.styles.filter(Boolean).join('\n'); 349 | } else { 350 | var index = style.ids.size - 1; 351 | var textNode = document.createTextNode(code); 352 | var nodes = style.element.childNodes; 353 | if (nodes[index]) { style.element.removeChild(nodes[index]); } 354 | if (nodes.length) { style.element.insertBefore(textNode, nodes[index]); }else { style.element.appendChild(textNode); } 355 | } 356 | } 357 | } 358 | 359 | var browser = createInjector; 360 | 361 | /* script */ 362 | var __vue_script__ = script; 363 | 364 | /* template */ 365 | var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"funnel svg-funnel-js",class:{'svg-funnel-js--vertical': _vm.direction === 'vertical'}},[_c('div',{staticClass:"svg-funnel-js__container"},[_c('svg',{attrs:{"width":_vm.width,"height":_vm.height}},[_c('defs',_vm._l((_vm.gradientSet),function(colors,index){return _c('linearGradient',{key:index,attrs:{"id":("funnelGradient-" + ((index+1))),"gradientTransform":_vm.gradientAngle}},_vm._l((colors.values),function(color,index){return _c('stop',{key:index,attrs:{"stop-color":color,"offset":_vm.offsetColor(index, colors.values.length)}})}),1)}),1),_vm._v(" "),_vm._l((_vm.paths),function(path,index){return _c('path',{key:index,attrs:{"fill":_vm.colorSet[index].fill,"stroke":_vm.colorSet[index].fill,"d":path}})})],2)]),_vm._v(" "),_c('transition-group',{staticClass:"svg-funnel-js__labels",attrs:{"name":"appear","tag":"div"},on:{"enter":_vm.enterTransition,"leave":_vm.leaveTransition}},_vm._l((_vm.valuesFormatted),function(value,index){return _c('div',{key:_vm.labels[index].toLowerCase().split(' ').join('-'),staticClass:"svg-funnel-js__label",class:("label-" + ((index+1)))},[_c('div',{staticClass:"label__value"},[_vm._v(_vm._s(value))]),_vm._v(" "),(_vm.labels)?_c('div',{staticClass:"label__title"},[_vm._v(_vm._s(_vm.labels[index]))]):_vm._e(),_vm._v(" "),(_vm.displayPercentage && _vm.percentages()[index] !== 100)?_c('div',{staticClass:"label__percentage"},[_vm._v("\n "+_vm._s(_vm.percentages()[index])+"%\n ")]):_vm._e(),_vm._v(" "),(_vm.is2d())?_c('div',{staticClass:"label__segment-percentages"},[_c('ul',{staticClass:"segment-percentage__list"},_vm._l((_vm.subLabels),function(subLabel,j){return _c('li',{key:j},[_vm._v("\n "+_vm._s(subLabel)+":\n "),(_vm.subLabelValue === 'percent')?_c('span',{staticClass:"percentage__list-label"},[_vm._v(_vm._s(_vm.twoDimPercentages()[index][j])+"%")]):_c('span',{staticClass:"percentage__list-label"},[_vm._v(_vm._s(_vm._f("format")(_vm.values[index][j])))])])}),0)]):_vm._e()])}),0),_vm._v(" "),_c('transition',{attrs:{"name":"fade"},on:{"enter":_vm.enterTransition,"leave":_vm.leaveTransition}},[(_vm.is2d())?_c('div',{staticClass:"svg-funnel-js__subLabels"},_vm._l((_vm.subLabels),function(subLabel,index){return _c('div',{key:index,class:("svg-funnel-js__subLabel svg-funnel-js__subLabel-" + ((index + 1)))},[_c('div',{staticClass:"svg-funnel-js__subLabel--color",style:(_vm.subLabelBackgrounds(index))}),_vm._v(" "),_c('div',{staticClass:"svg-funnel-js__subLabel--title"},[_vm._v(_vm._s(subLabel))])])}),0):_vm._e()])],1)}; 366 | var __vue_staticRenderFns__ = []; 367 | 368 | /* style */ 369 | var __vue_inject_styles__ = function (inject) { 370 | if (!inject) { return } 371 | inject("data-v-52d787cd_0", { source: ".appear-enter-active[data-v-52d787cd],.appear-leave-active[data-v-52d787cd]{transition:all .7s ease-in-out}.appear-enter-to[data-v-52d787cd],.appear-leave[data-v-52d787cd]{max-width:100%;max-height:100%;opacity:1}.appear-enter[data-v-52d787cd],.appear-leave-to[data-v-52d787cd]{max-width:0;max-height:0;opacity:0}.fade-enter-active[data-v-52d787cd],.fade-leave-active[data-v-52d787cd]{transition:all .3s ease}.fade-enter-to[data-v-52d787cd],.fade-leave[data-v-52d787cd]{opacity:1}.fade-enter[data-v-52d787cd],.fade-leave-to[data-v-52d787cd]{opacity:0}", map: undefined, media: undefined }); 372 | 373 | }; 374 | /* scoped */ 375 | var __vue_scope_id__ = "data-v-52d787cd"; 376 | /* module identifier */ 377 | var __vue_module_identifier__ = undefined; 378 | /* functional template */ 379 | var __vue_is_functional_template__ = false; 380 | /* style inject SSR */ 381 | 382 | 383 | 384 | var vueFunnelGraph = normalizeComponent_1( 385 | { render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ }, 386 | __vue_inject_styles__, 387 | __vue_script__, 388 | __vue_scope_id__, 389 | __vue_is_functional_template__, 390 | __vue_module_identifier__, 391 | browser, 392 | undefined 393 | ); 394 | 395 | /* eslint-disable import/prefer-default-export */ 396 | 397 | var components = /*#__PURE__*/Object.freeze({ 398 | __proto__: null, 399 | VueFunnelGraph: vueFunnelGraph 400 | }); 401 | 402 | // Import vue components 403 | 404 | // install function executed by Vue.use() 405 | function install(Vue) { 406 | if (install.installed) { return; } 407 | install.installed = true; 408 | Object.keys(components).forEach(function (componentName) { 409 | Vue.component(componentName, components[componentName]); 410 | }); 411 | } 412 | 413 | // Create module definition for Vue.use() 414 | var plugin = { 415 | install: install 416 | }; 417 | 418 | // To auto-install when vue is found 419 | /* global window global */ 420 | var GlobalVue = null; 421 | if (typeof window !== 'undefined') { 422 | GlobalVue = window.Vue; 423 | } else if (typeof global !== 'undefined') { 424 | GlobalVue = global.Vue; 425 | } 426 | if (GlobalVue) { 427 | GlobalVue.use(plugin); 428 | } 429 | 430 | exports.VueFunnelGraph = vueFunnelGraph; 431 | exports.default = plugin; 432 | 433 | Object.defineProperty(exports, '__esModule', { value: true }); 434 | 435 | }))); 436 | --------------------------------------------------------------------------------