1+ <script setup>
2+ import { ref , computed , onMounted } from " vue" ;
3+ import { calcMarkerOffsetX , calcMarkerOffsetY , calcNutArrowPath , makeDonut , palette , convertColorToHex , opacity , makeXls , createUid } from ' ../lib' ;
4+ import pdf from " ../pdf" ;
5+ import img from " ../img" ;
6+ import mainConfig from " ../default_configs.json" ;
7+ import Title from " ../atoms/Title.vue" ;
8+ import { useNestedProp } from " ../useNestedProp" ;
9+ import UserOptions from " ../atoms/UserOptions.vue" ;
10+ import DataTable from " ../atoms/DataTable.vue" ;
11+ import Tooltip from " ../atoms/Tooltip.vue" ;
12+ import Legend from " ../atoms/Legend.vue" ;
13+
14+ const props = defineProps ({
15+ config: {
16+ type: Object ,
17+ default () {
18+ return {}
19+ }
20+ },
21+ dataset: {
22+ type: Object ,
23+ default () {
24+ return {}
25+ }
26+ },
27+ });
28+
29+ const uid = ref (createUid ());
30+
31+ const defaultConfig = ref (mainConfig .vue_ui_3d_bar );
32+
33+ const isPrinting = ref (false );
34+ const isImaging = ref (false );
35+ const bar3dChart = ref (null );
36+ const details = ref (null );
37+ const isTooltip = ref (false );
38+ const tooltipContent = ref (" " );
39+ const selectedSerie = ref (null );
40+
41+ const barConfig = computed (() => {
42+ return useNestedProp ({
43+ userConfig: props .config ,
44+ defaultConfig: defaultConfig .value
45+ });
46+ });
47+
48+ const svg = computed (() => {
49+ return {
50+ height: barConfig .value .style .chart .box .dimensions .height ,
51+ width: barConfig .value .style .chart .box .dimensions .width ,
52+ top: barConfig .value .style .chart .box .dimensions .top ,
53+ bottom: barConfig .value .style .chart .box .dimensions .bottom ,
54+ left: barConfig .value .style .chart .box .dimensions .left ,
55+ right: barConfig .value .style .chart .box .dimensions .right ,
56+ perspective: barConfig .value .style .chart .box .dimensions .perspective
57+ }
58+ });
59+
60+ const box = computed (() => {
61+ return {
62+ right: ` M${ svg .value .width / 2 } ,${ svg .value .top } ${ svg .value .width - svg .value .right } , ${ svg .value .top + svg .value .perspective } ${ svg .value .width - svg .value .right } ,${ svg .value .height - svg .value .bottom - svg .value .perspective } ${ svg .value .width / 2 } ,${ svg .value .height - svg .value .bottom } ` ,
63+ left: ` M${ svg .value .width / 2 } ,${ svg .value .top } ${ svg .value .left } ,${ svg .value .top + svg .value .perspective } ${ svg .value .left } ,${ svg .value .height - svg .value .bottom - svg .value .perspective } ${ svg .value .width / 2 } ,${ svg .value .height - svg .value .bottom } ` ,
64+ side: ` M${ svg .value .width / 2 } ,${ svg .value .height - svg .value .bottom } ${ svg .value .width / 2 } ,${ svg .value .top + (svg .value .perspective * 2 )} ` ,
65+ topSides: ` M${ svg .value .left } ,${ svg .value .top + svg .value .perspective } ${ svg .value .width / 2 } ,${ svg .value .top + (svg .value .perspective * 2 )} ${ svg .value .width - svg .value .right } ,${ svg .value .top + svg .value .perspective } `
66+ }
67+ });
68+
69+ const activeValue = ref (barConfig .value .style .chart .animation .use ? 0 : props .dataset .percentage );
70+
71+ onMounted (() => {
72+ let acceleration = 0 ;
73+ let speed = barConfig .value .style .chart .animation .speed ;
74+ let incr = (0.005 ) * barConfig .value .style .chart .animation .acceleration ;
75+ function animate () {
76+ activeValue .value += speed + acceleration;
77+ acceleration += incr;
78+ if (activeValue .value < props .dataset .percentage ) {
79+ requestAnimationFrame (animate)
80+ } else {
81+ activeValue .value = props .dataset .percentage
82+ }
83+ }
84+
85+ if (barConfig .value .style .chart .animation .use ) {
86+ activeValue .value = 0 ;
87+ animate ()
88+ }
89+ })
90+
91+ const fill = computed (() => {
92+ const proportion = activeValue .value / 100 ;
93+ const height = svg .value .height - svg .value .bottom - svg .value .top - (svg .value .perspective * 2 );
94+ return {
95+ right: ` M${ svg .value .width / 2 } ,${ svg .value .height - svg .value .bottom } ${ svg .value .width / 2 } ,${ svg .value .height - svg .value .bottom - height * proportion} ${ svg .value .width - svg .value .right } ,${ svg .value .height - svg .value .bottom - svg .value .perspective - height * proportion} ${ svg .value .width - svg .value .right } ,${ svg .value .height - svg .value .bottom - svg .value .perspective } Z` ,
96+ left: ` M${ svg .value .width / 2 } ,${ svg .value .height - svg .value .bottom } ${ svg .value .width / 2 } ,${ svg .value .height - svg .value .bottom - height * proportion} ${ svg .value .left } , ${ svg .value .height - svg .value .bottom - svg .value .perspective - height * proportion} ${ svg .value .left } ,${ svg .value .height - svg .value .bottom - svg .value .perspective } Z` ,
97+ top: ` M${ svg .value .width / 2 } ,${ svg .value .height - svg .value .bottom - height * proportion} ${ svg .value .left } ,${ svg .value .height - svg .value .bottom - svg .value .perspective - height * proportion} ${ svg .value .width / 2 } ,${ svg .value .height - svg .value .bottom - (svg .value .perspective * 2 ) - (height * proportion)} ${ svg .value .width - svg .value .right } ,${ svg .value .height - svg .value .bottom - svg .value .perspective - height * proportion} Z`
98+ }
99+ })
100+
101+ const __to__ = ref (null );
102+
103+ function showSpinnerPdf () {
104+ isPrinting .value = true ;
105+ }
106+
107+ function generatePdf (){
108+ showSpinnerPdf ();
109+ clearTimeout (__to__ .value );
110+ __to__ .value = setTimeout (() => {
111+ pdf ({
112+ domElement: document .getElementById (` 3d_bar_${ uid .value } ` ),
113+ fileName: barConfig .value .style .chart .title .text || ' vue-ui-3d-bar'
114+ }).finally (() => {
115+ isPrinting .value = false ;
116+ });
117+ }, 100 )
118+
119+ }
120+
121+ function showSpinnerImage () {
122+ isImaging .value = true ;
123+ }
124+
125+ function generateImage () {
126+ showSpinnerImage ();
127+ clearTimeout (__to__ .value );
128+ __to__ .value = setTimeout (() => {
129+ img ({
130+ domElement: document .getElementById (` 3d_bar_${ uid .value } ` ),
131+ fileName: barConfig .value .style .chart .title .text || ' vue-ui-3d-bar' ,
132+ format: ' png'
133+ }).finally (() => {
134+ isImaging .value = false ;
135+ })
136+ }, 100 )
137+ }
138+
139+ defineExpose ({
140+ generatePdf,
141+ generateImage
142+ });
143+
144+
145+ </script >
146+
147+ <template >
148+ <div :ref =" `bar3dChart`" :class =" `vue-ui-3d-bar`" :style =" `font-family:${barConfig.style.fontFamily};width:100%; text-align:center;background:${barConfig.style.chart.backgroundColor}`" :id =" `3d_bar_${uid}`" >
149+
150+ <div v-if =" barConfig.style.chart.title.text" :style =" `width:100%;background:${barConfig.style.chart.backgroundColor}`" >
151+ <!-- TITLE AS DIV -->
152+ <Title
153+ :config =" {
154+ title: {
155+ cy: '3dBar-div-title',
156+ text: barConfig.style.chart.title.text,
157+ color: barConfig.style.chart.title.color,
158+ fontSize: barConfig.style.chart.title.fontSize,
159+ bold: barConfig.style.chart.title.bold
160+ },
161+ subtitle: {
162+ cy: '3dBar-div-subtitle',
163+ text: barConfig.style.chart.title.subtitle.text,
164+ color: barConfig.style.chart.title.subtitle.color,
165+ fontSize: barConfig.style.chart.title.subtitle.fontSize,
166+ bold: barConfig.style.chart.title.subtitle.bold
167+ }
168+ }"
169+ />
170+ </div >
171+
172+ <!-- OPTIONS -->
173+ <UserOptions
174+ ref =" details"
175+ v-if =" barConfig.userOptions.show"
176+ :backgroundColor =" barConfig.style.chart.backgroundColor"
177+ :color =" barConfig.style.chart.color"
178+ :isPrinting =" isPrinting"
179+ :isImaging =" isImaging"
180+ :title =" barConfig.userOptions.title"
181+ :uid =" uid"
182+ hasImg
183+ :hasXls =" false"
184+ @generatePdf =" generatePdf"
185+ @generateImage =" generateImage"
186+ />
187+
188+ <svg data-cy =" 3d-bar-svg" :viewBox =" `0 0 ${svg.width} ${svg.height}`" :style =" `max-width:100%; overflow: visible; background:${barConfig.style.chart.backgroundColor};color:${barConfig.style.chart.color}`" >
189+
190+ <!-- DEFS -->
191+ <defs >
192+ <radialGradient :id =" `gradient_top${uid}`" >
193+ <stop offset =" 0%" :stop-color =" `${convertColorToHex(barConfig.style.chart.backgroundColor)}00`" />
194+ <stop offset =" 100%" :stop-color =" `${barConfig.style.chart.bar.color}`" />
195+ </radialGradient >
196+ <radialGradient :id =" `gradient_left${uid}`" >
197+ <stop offset =" 0%" :stop-color =" `${convertColorToHex(barConfig.style.chart.backgroundColor)}00`" />
198+ <stop offset =" 100%" :stop-color =" `${barConfig.style.chart.bar.color}33`" />
199+ </radialGradient >
200+ <radialGradient :id =" `gradient_right${uid}`" >
201+ <stop offset =" 0%" :stop-color =" `${convertColorToHex(barConfig.style.chart.backgroundColor)}00`" />
202+ <stop offset =" 100%" :stop-color =" `${barConfig.style.chart.bar.color}33`" />
203+ </radialGradient >
204+ </defs >
205+
206+ <text
207+ :x =" svg.width / 2"
208+ :y =" svg.top - barConfig.style.chart.dataLabel.fontSize / 2"
209+ :font-size =" barConfig.style.chart.dataLabel.fontSize"
210+ :font-weight =" barConfig.style.chart.dataLabel.bold ? 'bold': 'normal'"
211+ :fill =" barConfig.style.chart.dataLabel.color"
212+ text-anchor =" middle"
213+ >
214+ {{Number((isNaN(activeValue) ? 0 : activeValue).toFixed(barConfig.style.chart.dataLabel.rounding)).toLocaleString() }} %
215+ </text >
216+
217+ <!-- BOX SKELETON -->
218+ <path :stroke-dasharray =" barConfig.style.chart.box.strokeDasharray" :d =" box.right" :stroke =" barConfig.style.chart.box.stroke" stroke-linejoin =" round" stroke-linecap =" round" fill =" none" />
219+ <path :stroke-dasharray =" barConfig.style.chart.box.strokeDasharray" :d =" box.left" :stroke =" barConfig.style.chart.box.stroke" stroke-linejoin =" round" stroke-linecap =" round" fill =" none" />
220+ <path :stroke-dasharray =" barConfig.style.chart.box.strokeDasharray" :d =" box.side" :stroke =" barConfig.style.chart.box.stroke" stroke-linejoin =" round" stroke-linecap =" round" fill =" none" />
221+ <path :stroke-dasharray =" barConfig.style.chart.box.strokeDasharray" :d =" box.topSides" :stroke =" barConfig.style.chart.box.stroke" stroke-linejoin =" round" stroke-linecap =" round" fill =" none" />
222+
223+ <!-- FILL BOX -->
224+ <path :d =" fill.right" :stroke =" barConfig.style.chart.bar.stroke" :stroke-width =" barConfig.style.chart.bar.strokeWidth" stroke-linejoin =" round" stroke-linecap =" round" :fill =" `url(#gradient_right${uid})`" />
225+ <path :d =" fill.left" :stroke =" barConfig.style.chart.bar.stroke" :stroke-width =" barConfig.style.chart.bar.strokeWidth" stroke-linejoin =" round" stroke-linecap =" round" :fill =" `url(#gradient_left${uid})`" />
226+ <path :d =" fill.top" :stroke =" barConfig.style.chart.bar.stroke" :stroke-width =" barConfig.style.chart.bar.strokeWidth" stroke-linejoin =" round" stroke-linecap =" round" :fill =" `url(#gradient_top${uid})`" />
227+
228+ <slot name =" svg" :svg =" svg" />
229+ </svg >
230+ </div >
231+ </template >
232+
233+ <style scoped>
234+ .vue-ui-3d-bar * {
235+ transition : unset ;
236+ }
237+ .vue-ui-3d-bar {
238+ user-select : none ;
239+ position : relative ;
240+ }
241+ </style >
0 commit comments