@eturnity/eturnity_reusable_components 8.16.2--EPDM-14330.5 → 8.16.2--EPDM-14330.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/assets/svgIcons/ac_power.svg +4 -0
- package/src/assets/svgIcons/arrow_long_left.svg +3 -0
- package/src/assets/svgIcons/arrow_long_right.svg +3 -0
- package/src/assets/svgIcons/chassis_ground_symbol.svg +27 -0
- package/src/assets/svgIcons/dc_power.svg +8 -0
- package/src/assets/svgIcons/double_arrow_long.svg +4 -0
- package/src/assets/svgIcons/ed_ac.svg +3 -0
- package/src/assets/svgIcons/ed_acgrid.svg +4 -0
- package/src/assets/svgIcons/ed_arrow_both.svg +7 -0
- package/src/assets/svgIcons/ed_arrow_left.svg +7 -0
- package/src/assets/svgIcons/ed_arrow_right.svg +7 -0
- package/src/assets/svgIcons/ed_battery.svg +10 -0
- package/src/assets/svgIcons/ed_batteryacinverter.svg +16 -0
- package/src/assets/svgIcons/ed_batteryintegratedinverter.svg +19 -0
- package/src/assets/svgIcons/ed_cirquitbreaker.svg +4 -0
- package/src/assets/svgIcons/ed_cirquitbreaker_magnetic.svg +6 -0
- package/src/assets/svgIcons/ed_cirquitbreaker_thermal.svg +4 -0
- package/src/assets/svgIcons/ed_cirquitbreaker_thermal_magnetic.svg +5 -0
- package/src/assets/svgIcons/ed_consumption.svg +3 -0
- package/src/assets/svgIcons/ed_dc.svg +6 -0
- package/src/assets/svgIcons/ed_disconnector.svg +4 -0
- package/src/assets/svgIcons/ed_disconnector_fuse.svg +4 -0
- package/src/assets/svgIcons/ed_disconnector_fuse_switch.svg +4 -0
- package/src/assets/svgIcons/ed_disconnector_loadbreak switch.svg +4 -0
- package/src/assets/svgIcons/ed_disconnector_switch.svg +4 -0
- package/src/assets/svgIcons/ed_disconnector_switch_auto_release.svg +5 -0
- package/src/assets/svgIcons/ed_energymanagement_rectangle.svg +3 -0
- package/src/assets/svgIcons/ed_evcharger.svg +19 -0
- package/src/assets/svgIcons/ed_flexiblecomponent_circle.svg +3 -0
- package/src/assets/svgIcons/ed_flexiblecomponent_square.svg +3 -0
- package/src/assets/svgIcons/ed_fuse.svg +3 -0
- package/src/assets/svgIcons/ed_ground.svg +5 -0
- package/src/assets/svgIcons/ed_heatpump.svg +4 -0
- package/src/assets/svgIcons/ed_icon_battery.svg +9 -0
- package/src/assets/svgIcons/ed_icon_circle.svg +3 -0
- package/src/assets/svgIcons/ed_icon_heatpump.svg +3 -0
- package/src/assets/svgIcons/ed_icon_inverter.svg +8 -0
- package/src/assets/svgIcons/ed_icon_optimizer.svg +11 -0
- package/src/assets/svgIcons/ed_integratedbatteryinverter.svg +10 -0
- package/src/assets/svgIcons/ed_inverter-blank.svg +3 -0
- package/src/assets/svgIcons/ed_mainsconnection.svg +3 -0
- package/src/assets/svgIcons/ed_meter_arrowleft.svg +4 -0
- package/src/assets/svgIcons/ed_meter_arrowright.svg +4 -0
- package/src/assets/svgIcons/ed_meter_bidirectional.svg +5 -0
- package/src/assets/svgIcons/ed_networkandsystemprotection_double.svg +14 -0
- package/src/assets/svgIcons/ed_networkandsystemprotection_single.svg +7 -0
- package/src/assets/svgIcons/ed_pvpanel.svg +7 -0
- package/src/assets/svgIcons/ed_rcd.svg +5 -0
- package/src/assets/svgIcons/ed_rcd_simple.svg +3 -0
- package/src/assets/svgIcons/ed_spd.svg +6 -0
- package/src/assets/svgIcons/ed_stringwithoptimizer.svg +33 -0
- package/src/assets/svgIcons/ed_stringwithoutoptimizer.svg +17 -0
- package/src/assets/svgIcons/ed_transformer.svg +3 -0
- package/src/assets/svgIcons/filter.svg +3 -0
- package/src/assets/svgIcons/ground_symbol.svg +28 -0
- package/src/assets/svgIcons/move_left.svg +3 -0
- package/src/assets/svgIcons/move_right.svg +3 -0
- package/src/assets/svgIcons/rectangle.svg +3 -0
- package/src/assets/svgIcons/refresh.svg +3 -0
- package/src/assets/svgIcons/text_icon.svg +3 -0
- package/src/assets/theme.js +17 -1
- package/src/components/banner/infoBanner/InfoBanner.spec.js +29 -42
- package/src/components/barchart/BottomFields.vue +253 -0
- package/src/components/barchart/ChartControls.vue +113 -0
- package/src/components/barchart/SelectionBox.vue +150 -0
- package/src/components/barchart/composables/index.js +5 -0
- package/src/components/barchart/composables/useAxisCalculations.js +104 -0
- package/src/components/barchart/composables/useChartData.js +114 -0
- package/src/components/barchart/composables/useChartScroll.js +61 -0
- package/src/components/barchart/composables/useSelection.js +75 -0
- package/src/components/barchart/composables/useTooltip.js +100 -0
- package/src/components/barchart/index.vue +385 -0
- package/src/components/barchart/styles/bottomFields.js +66 -0
- package/src/components/barchart/styles/chart.js +272 -0
- package/src/components/barchart/styles/chartControls.js +59 -0
- package/src/components/buttons/buttonIcon/index.vue +35 -1
- package/src/components/buttons/splitButtons/index.vue +86 -0
- package/src/components/collapsableInfoText/index.vue +2 -2
- package/src/components/errorMessage/errorMessage.spec.js +34 -0
- package/src/components/errorMessage/errorMessage.stories.js +35 -0
- package/src/components/filter/filterSettings.vue +2 -0
- package/src/components/filterComponent/viewFilter.vue +589 -0
- package/src/components/filterComponent/viewSettings.vue +63 -2
- package/src/components/filterComponent/viewSort.vue +18 -5
- package/src/components/icon/index.vue +32 -9
- package/src/components/infoText/index.vue +2 -2
- package/src/components/infoText/infoText.spec.js +6 -1
- package/src/components/inputs/inputNumber/index.vue +14 -2
- package/src/components/inputs/searchInput/index.vue +19 -3
- package/src/components/inputs/select/index.vue +108 -19
- package/src/components/inputs/select/option/index.vue +5 -0
- package/src/components/modals/actionModal/actionModal.spec.js +52 -0
- package/src/components/modals/actionModal/actionModal.stories.js +53 -0
- package/src/components/modals/actionModal/index.vue +6 -6
- package/src/components/modals/infoModal/index.vue +49 -19
- package/src/components/modals/infoModal/infoModal.spec.js +55 -0
- package/src/components/modals/infoModal/infoModal.stories.js +47 -0
- package/src/components/modals/modal/index.vue +16 -5
- package/src/components/pageSubtitle/PageSubtitle.stories.js +0 -1
- package/src/helpers/isObjectEqual.js +22 -0
- package/src/helpers/translateLang.js +95 -24
- package/src/main.js +1 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
<svg
|
2
|
+
width="50"
|
3
|
+
height="50"
|
4
|
+
viewBox="0 0 25 50"
|
5
|
+
fill='white'
|
6
|
+
>
|
7
|
+
<defs
|
8
|
+
id="defs4" />
|
9
|
+
<g
|
10
|
+
id="layer1">
|
11
|
+
<path
|
12
|
+
d="M 0.5,24.5 L 24.5,24.5"
|
13
|
+
style="fill:lime;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
14
|
+
id="path4098" />
|
15
|
+
<path
|
16
|
+
d="M 4.5,27.5 L 20.5,27.5"
|
17
|
+
style="fill:lime;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
18
|
+
id="path4100" />
|
19
|
+
<path
|
20
|
+
d="M 16.5,30.5 L 8.5,30.5"
|
21
|
+
style="fill:lime;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
22
|
+
id="path4102" />
|
23
|
+
<path
|
24
|
+
d="M 12.5,24.5 L 12.5,4.5"
|
25
|
+
style="fill:lime;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
26
|
+
id="path4104" />
|
27
|
+
</g>
|
28
|
+
</svg>
|
@@ -0,0 +1,3 @@
|
|
1
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
2
|
+
<path class="fix" d="M11.3335 12.5833C12.7477 11.5529 13.6668 9.88392 13.6668 8.00023C13.6668 4.87061 11.1298 2.33356 8.00016 2.33356H7.66683M8.00016 13.6669C4.87055 13.6669 2.3335 11.1298 2.3335 8.00023C2.3335 6.11654 3.25261 4.44751 4.66683 3.41716M7.3335 14.9336L8.66683 13.6002L7.3335 12.2669M8.66683 3.73356L7.3335 2.40023L8.66683 1.06689" stroke="#6F20DC" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
3
|
+
</svg>
|
@@ -0,0 +1,3 @@
|
|
1
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
2
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.5 3C3.22386 3 3 3.22386 3 3.5V4.5C3 4.77614 2.77614 5 2.5 5C2.22386 5 2 4.77614 2 4.5V3.5C2 2.67157 2.67157 2 3.5 2H8H12.5C13.3284 2 14 2.67157 14 3.5V4.5C14 4.77614 13.7761 5 13.5 5C13.2239 5 13 4.77614 13 4.5V3.5C13 3.22386 12.7761 3 12.5 3H8.5V13H10.5C10.7761 13 11 13.2239 11 13.5C11 13.7761 10.7761 14 10.5 14H8H5.5C5.22386 14 5 13.7761 5 13.5C5 13.2239 5.22386 13 5.5 13H7.5V3H3.5Z" fill="white"/>
|
3
|
+
</svg>
|
package/src/assets/theme.js
CHANGED
@@ -180,9 +180,25 @@ const theme = (() => {
|
|
180
180
|
},
|
181
181
|
}
|
182
182
|
|
183
|
+
const chartGradients = {
|
184
|
+
simple: {
|
185
|
+
from: semanticColors.purple[500],
|
186
|
+
to: semanticColors.purple[400],
|
187
|
+
},
|
188
|
+
stacked: [
|
189
|
+
{ from: '#F5EDFF', to: '#DEC5FF' },
|
190
|
+
{ from: '#CAA2FF', to: '#F2E8FF' },
|
191
|
+
{ from: '#B987FC', to: '#904AEF' },
|
192
|
+
{ from: '#8B40F2', to: '#4A1394' },
|
193
|
+
{ from: '#6A05F2', to: '#4905A5' },
|
194
|
+
{ from: '#5402C3', to: '#2B0362' },
|
195
|
+
],
|
196
|
+
}
|
197
|
+
|
183
198
|
return {
|
184
199
|
colors,
|
185
200
|
semanticColors,
|
201
|
+
chartGradients,
|
186
202
|
fonts: {
|
187
203
|
mainFont: '"Figtree", sans-serif',
|
188
204
|
},
|
@@ -312,7 +328,7 @@ const theme = (() => {
|
|
312
328
|
borderColor: semanticColors.grey[300],
|
313
329
|
},
|
314
330
|
hover: {
|
315
|
-
backgroundColor: semanticColors.purple[
|
331
|
+
backgroundColor: semanticColors.purple[100],
|
316
332
|
textColor: semanticColors.purple[700],
|
317
333
|
borderColor: semanticColors.grey[300],
|
318
334
|
},
|
@@ -1,5 +1,4 @@
|
|
1
1
|
/* eslint-disable */
|
2
|
-
import { h } from 'vue'
|
3
2
|
import { mount } from '@vue/test-utils'
|
4
3
|
import InfoBanner from '@/components/banner/infoBanner'
|
5
4
|
import theme from '@/assets/theme'
|
@@ -10,59 +9,47 @@ jest.mock('@/components/icon/iconCache.mjs', () => ({
|
|
10
9
|
}))
|
11
10
|
|
12
11
|
describe('Info Banner Component', () => {
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
12
|
+
let wrapper
|
13
|
+
|
14
|
+
beforeEach(() => {
|
15
|
+
wrapper = mount(InfoBanner, {
|
16
|
+
props: {
|
17
|
+
isOpen: true,
|
18
|
+
buttonLabel: 'Gotcha',
|
19
|
+
},
|
20
|
+
slots: {
|
21
|
+
title: 'Sample title',
|
22
|
+
body: 'Sample body text',
|
23
|
+
},
|
24
|
+
global: {
|
25
|
+
provide: {
|
26
|
+
theme,
|
27
|
+
},
|
28
|
+
},
|
29
|
+
})
|
28
30
|
})
|
29
31
|
|
32
|
+
|
33
|
+
it('info banner is shown when isOpen props is true', async () => {
|
34
|
+
|
30
35
|
const bannerWrapper = wrapper.find('[data-test-id="info_banner_wrapper"]')
|
31
36
|
expect(bannerWrapper.exists()).toBe(true)
|
32
|
-
expect(bannerWrapper.classes()).not.toContain('visible')
|
33
|
-
expect(bannerWrapper.classes()).toContain('hidden')
|
34
|
-
await wrapper.setProps({ isOpen: true })
|
35
|
-
expect(bannerWrapper.classes()).toContain('visible')
|
36
|
-
expect(bannerWrapper.classes()).not.toContain('hidden')
|
37
37
|
})
|
38
38
|
|
39
|
-
|
40
|
-
|
41
|
-
const bodyText = 'Sample body text'
|
42
|
-
|
43
|
-
const wrapper = mount(InfoBanner, {
|
44
|
-
props,
|
45
|
-
slots: {
|
46
|
-
title: titleText,
|
47
|
-
body: bodyText,
|
48
|
-
},
|
49
|
-
global,
|
50
|
-
})
|
51
|
-
|
39
|
+
|
40
|
+
it('info banner slots is display when user passed slots content', () => {
|
52
41
|
const modalTitleEl = wrapper.find('[data-test-id="modal_title"]')
|
53
|
-
expect(modalTitleEl.text()).toBe(
|
42
|
+
expect(modalTitleEl.text()).toBe('Sample title')
|
54
43
|
const modalBodyEl = wrapper.find('[data-test-id="modal_body"]')
|
55
|
-
expect(modalBodyEl.text()).toBe(
|
44
|
+
expect(modalBodyEl.text()).toBe('Sample body text')
|
56
45
|
})
|
57
46
|
|
58
47
|
it('info banner on-close event is emitted when modal close button is clicked', async () => {
|
59
|
-
const wrapper = mount(InfoBanner, {
|
60
|
-
props,
|
61
|
-
global,
|
62
|
-
})
|
63
|
-
|
64
48
|
const modalCloseButton = wrapper.find('.close')
|
65
|
-
|
49
|
+
|
50
|
+
modalCloseButton.trigger('click')
|
51
|
+
await wrapper.vm.$nextTick()
|
52
|
+
|
66
53
|
expect(wrapper.emitted('on-close')).toBeTruthy()
|
67
54
|
const emittedEvent = wrapper.emitted('on-close')
|
68
55
|
expect(emittedEvent).toHaveLength(1)
|
@@ -0,0 +1,253 @@
|
|
1
|
+
<template>
|
2
|
+
<Container :is-chart-controls-shown-in-bottom="isChartControlsShownInBottom">
|
3
|
+
<LabelsColumn :width="yAxisWidth">
|
4
|
+
<LabelRow v-for="series in props.series" :key="series.name">
|
5
|
+
{{ series.name }}
|
6
|
+
</LabelRow>
|
7
|
+
<TotalRow v-if="props.series.length && fieldMode === 'percentage'">
|
8
|
+
{{ $gettext ? $gettext('Total (%)') : 'Total (%)' }}
|
9
|
+
</TotalRow>
|
10
|
+
<TotalRow v-if="props.series.length">
|
11
|
+
{{ $gettext ? $gettext('Total (kWh)') : 'Total (kWh)' }}
|
12
|
+
</TotalRow>
|
13
|
+
</LabelsColumn>
|
14
|
+
|
15
|
+
<FieldsContainer
|
16
|
+
:class="`fields-container-${chartId}`"
|
17
|
+
@scroll="handleFieldsScroll"
|
18
|
+
>
|
19
|
+
<FieldsWrapper>
|
20
|
+
<!-- For stacked bar chart -->
|
21
|
+
<template v-if="props.series.length">
|
22
|
+
<InputRow
|
23
|
+
v-for="series in props.series"
|
24
|
+
:key="series.name"
|
25
|
+
:data-series-name="series.name"
|
26
|
+
>
|
27
|
+
<InputGroup
|
28
|
+
v-for="(item, index) in props.data"
|
29
|
+
:bar-width="barWidth"
|
30
|
+
:is-scrollable="isScrollable"
|
31
|
+
:key="index"
|
32
|
+
>
|
33
|
+
<InputNumber
|
34
|
+
:allow-negative="false"
|
35
|
+
input-height="36px"
|
36
|
+
:number-precision="0"
|
37
|
+
:min-decimals="0"
|
38
|
+
text-align="center"
|
39
|
+
:unit-name="fieldMode === 'percentage' ? '%' : ''"
|
40
|
+
:value="getDisplayValue(series.data, item.label)"
|
41
|
+
@input-blur="handleInputBlur($event, series.name, item.label)"
|
42
|
+
@input-focus="handleInputFocus(series.name, item.label)"
|
43
|
+
/>
|
44
|
+
</InputGroup>
|
45
|
+
</InputRow>
|
46
|
+
|
47
|
+
<TotalInputRow v-if="fieldMode === 'percentage'">
|
48
|
+
<InputGroup
|
49
|
+
v-for="(item, index) in props.data"
|
50
|
+
:bar-width="barWidth"
|
51
|
+
:is-scrollable="isScrollable"
|
52
|
+
:key="index"
|
53
|
+
>
|
54
|
+
<InputNumber
|
55
|
+
:allow-negative="false"
|
56
|
+
input-height="36px"
|
57
|
+
:is-read-only="true"
|
58
|
+
:number-precision="0"
|
59
|
+
:min-decimals="0"
|
60
|
+
text-align="center"
|
61
|
+
:unit-name="fieldMode === 'percentage' ? '%' : ''"
|
62
|
+
:value="calculatePercentageTotal(item.label)"
|
63
|
+
/>
|
64
|
+
</InputGroup>
|
65
|
+
</TotalInputRow>
|
66
|
+
|
67
|
+
<TotalInputRow>
|
68
|
+
<InputGroup
|
69
|
+
v-for="(item, index) in props.data"
|
70
|
+
:bar-width="barWidth"
|
71
|
+
:is-scrollable="isScrollable"
|
72
|
+
:key="index"
|
73
|
+
>
|
74
|
+
<InputNumber
|
75
|
+
input-height="36px"
|
76
|
+
:is-read-only="true"
|
77
|
+
:number-precision="2"
|
78
|
+
:min-decimals="0"
|
79
|
+
text-align="center"
|
80
|
+
:value="calculateTotal(item.label)"
|
81
|
+
/>
|
82
|
+
</InputGroup>
|
83
|
+
</TotalInputRow>
|
84
|
+
</template>
|
85
|
+
|
86
|
+
<!-- For simple bar chart -->
|
87
|
+
<template v-else>
|
88
|
+
<InputRow>
|
89
|
+
<InputGroup
|
90
|
+
v-for="(item, index) in props.data"
|
91
|
+
:bar-width="barWidth"
|
92
|
+
:is-scrollable="isScrollable"
|
93
|
+
:key="index"
|
94
|
+
>
|
95
|
+
<InputNumber
|
96
|
+
input-height="36px"
|
97
|
+
:min-decimals="0"
|
98
|
+
:number-precision="2"
|
99
|
+
text-align="center"
|
100
|
+
:value="item.value"
|
101
|
+
@input-blur="handleInputBlur($event, null, item.label)"
|
102
|
+
@input-focus="handleInputFocus(null, item.label)"
|
103
|
+
/>
|
104
|
+
</InputGroup>
|
105
|
+
</InputRow>
|
106
|
+
</template>
|
107
|
+
</FieldsWrapper>
|
108
|
+
</FieldsContainer>
|
109
|
+
</Container>
|
110
|
+
</template>
|
111
|
+
|
112
|
+
<script setup>
|
113
|
+
import { ref } from 'vue'
|
114
|
+
import InputNumber from '../inputs/inputNumber'
|
115
|
+
|
116
|
+
import {
|
117
|
+
Container,
|
118
|
+
LabelsColumn,
|
119
|
+
LabelRow,
|
120
|
+
TotalRow,
|
121
|
+
FieldsContainer,
|
122
|
+
FieldsWrapper,
|
123
|
+
InputRow,
|
124
|
+
TotalInputRow,
|
125
|
+
InputGroup,
|
126
|
+
} from './styles/bottomFields'
|
127
|
+
|
128
|
+
const props = defineProps({
|
129
|
+
chartId: {
|
130
|
+
type: String,
|
131
|
+
required: true,
|
132
|
+
},
|
133
|
+
data: {
|
134
|
+
type: Array,
|
135
|
+
required: true,
|
136
|
+
},
|
137
|
+
series: {
|
138
|
+
type: Array,
|
139
|
+
required: true,
|
140
|
+
},
|
141
|
+
barWidth: {
|
142
|
+
type: Number,
|
143
|
+
required: true,
|
144
|
+
},
|
145
|
+
yAxisWidth: {
|
146
|
+
type: String,
|
147
|
+
required: true,
|
148
|
+
},
|
149
|
+
isScrollable: {
|
150
|
+
type: Boolean,
|
151
|
+
default: false,
|
152
|
+
},
|
153
|
+
isChartControlsShownInBottom: {
|
154
|
+
type: Boolean,
|
155
|
+
default: false,
|
156
|
+
},
|
157
|
+
fieldMode: {
|
158
|
+
type: String,
|
159
|
+
default: 'absolute',
|
160
|
+
validator: (value) => ['absolute', 'percentage'].includes(value),
|
161
|
+
},
|
162
|
+
})
|
163
|
+
|
164
|
+
const emit = defineEmits([
|
165
|
+
'sync-scroll',
|
166
|
+
'input-blur',
|
167
|
+
'input-focus',
|
168
|
+
'input-blur-all',
|
169
|
+
])
|
170
|
+
|
171
|
+
const focusedInput = ref(null)
|
172
|
+
|
173
|
+
const handleInputFocus = (seriesName, label) => {
|
174
|
+
focusedInput.value = { seriesName, label }
|
175
|
+
emit('input-focus', { seriesName, label })
|
176
|
+
}
|
177
|
+
|
178
|
+
const calculateTotal = (label) => {
|
179
|
+
return props.series.reduce((sum, series) => {
|
180
|
+
const value = series.data.find((d) => d.label === label)?.value || 0
|
181
|
+
return sum + value
|
182
|
+
}, 0)
|
183
|
+
}
|
184
|
+
|
185
|
+
const syncScroll = (scrollLeft) => {
|
186
|
+
const container = document.querySelector(
|
187
|
+
`.fields-container-${props.chartId}`
|
188
|
+
)
|
189
|
+
if (container) {
|
190
|
+
container.scrollLeft = scrollLeft
|
191
|
+
}
|
192
|
+
}
|
193
|
+
const getDisplayValue = (seriesData, label) => {
|
194
|
+
if (props.fieldMode === 'absolute') {
|
195
|
+
return seriesData.find((d) => d.label === label)?.value || ''
|
196
|
+
}
|
197
|
+
|
198
|
+
const value = seriesData.find((d) => d.label === label)?.value || 0
|
199
|
+
const total = calculateTotal(label)
|
200
|
+
return total ? Number(((value / total) * 100).toFixed(0)) : 0
|
201
|
+
}
|
202
|
+
|
203
|
+
const calculatePercentageTotal = (label) => {
|
204
|
+
return props.series.reduce((sum, series) => {
|
205
|
+
const value = series.data.find((d) => d.label === label)?.value || 0
|
206
|
+
const total = calculateTotal(label)
|
207
|
+
const percentage = total ? Number(((value / total) * 100).toFixed(0)) : 0
|
208
|
+
return sum + percentage
|
209
|
+
}, 0)
|
210
|
+
}
|
211
|
+
|
212
|
+
const handleInputBlur = (_value, seriesName, label) => {
|
213
|
+
let value = Number(_value)
|
214
|
+
|
215
|
+
if (props.fieldMode === 'percentage') {
|
216
|
+
const total = calculateTotal(label)
|
217
|
+
value = (value / 100) * total
|
218
|
+
}
|
219
|
+
|
220
|
+
const payload = seriesName ? { seriesName, label, value } : { label, value }
|
221
|
+
emit('input-blur', payload)
|
222
|
+
focusedInput.value = null
|
223
|
+
|
224
|
+
// Check if the related target (element receiving focus) is another input in our chart
|
225
|
+
const relatedTarget = document.activeElement
|
226
|
+
const currentChartContainer = document.querySelector(
|
227
|
+
`.fields-container-${props.chartId}`
|
228
|
+
)
|
229
|
+
const isMovingToAnotherInput =
|
230
|
+
currentChartContainer?.contains(relatedTarget) &&
|
231
|
+
relatedTarget?.classList.contains('input-number')
|
232
|
+
|
233
|
+
if (!isMovingToAnotherInput) {
|
234
|
+
emit('input-blur-all')
|
235
|
+
} else {
|
236
|
+
// If moving to another input, trigger focus event manually
|
237
|
+
const seriesName =
|
238
|
+
relatedTarget.closest('[data-series-name]')?.dataset.seriesName
|
239
|
+
const label = relatedTarget.closest('[data-label]')?.dataset.label
|
240
|
+
if (seriesName && label) {
|
241
|
+
handleInputFocus(seriesName, label)
|
242
|
+
}
|
243
|
+
}
|
244
|
+
}
|
245
|
+
|
246
|
+
const handleFieldsScroll = (event) => {
|
247
|
+
emit('sync-scroll', event.target.scrollLeft)
|
248
|
+
}
|
249
|
+
|
250
|
+
defineExpose({
|
251
|
+
syncScroll,
|
252
|
+
})
|
253
|
+
</script>
|
@@ -0,0 +1,113 @@
|
|
1
|
+
<template>
|
2
|
+
<Container>
|
3
|
+
<LegendAndControlsWrapper :leftMargin="yAxisWidth">
|
4
|
+
<LeftSection>
|
5
|
+
<SplitButtonsContainer
|
6
|
+
v-if="splitButtonOptions.length"
|
7
|
+
:position="position"
|
8
|
+
>
|
9
|
+
<SplitButtons
|
10
|
+
:modelValue="selectedSplitButton"
|
11
|
+
:options="splitButtonOptions"
|
12
|
+
@click="handleSplitButtonClick"
|
13
|
+
/>
|
14
|
+
</SplitButtonsContainer>
|
15
|
+
</LeftSection>
|
16
|
+
|
17
|
+
<LegendGroups v-if="isLegendShown">
|
18
|
+
<LegendRow
|
19
|
+
v-for="(row, rowIndex) in groupedSeries"
|
20
|
+
:key="rowIndex"
|
21
|
+
:rowIndex="rowIndex"
|
22
|
+
>
|
23
|
+
<LegendItem v-for="(series, index) in row" :key="series.name">
|
24
|
+
<LegendColor
|
25
|
+
:gradientFrom="
|
26
|
+
reversedColors[rowIndex * legendsItemPerRow + index].from
|
27
|
+
"
|
28
|
+
:gradientTo="
|
29
|
+
reversedColors[rowIndex * legendsItemPerRow + index].to
|
30
|
+
"
|
31
|
+
/>
|
32
|
+
<LegendText>{{ series.name }}</LegendText>
|
33
|
+
</LegendItem>
|
34
|
+
</LegendRow>
|
35
|
+
</LegendGroups>
|
36
|
+
</LegendAndControlsWrapper>
|
37
|
+
</Container>
|
38
|
+
</template>
|
39
|
+
|
40
|
+
<script setup>
|
41
|
+
import { computed } from 'vue'
|
42
|
+
|
43
|
+
import SplitButtons from '../buttons/splitButtons'
|
44
|
+
|
45
|
+
import {
|
46
|
+
Container,
|
47
|
+
LegendAndControlsWrapper,
|
48
|
+
LeftSection,
|
49
|
+
SplitButtonsContainer,
|
50
|
+
LegendGroups,
|
51
|
+
LegendRow,
|
52
|
+
LegendItem,
|
53
|
+
LegendColor,
|
54
|
+
LegendText,
|
55
|
+
} from './styles/chartControls'
|
56
|
+
|
57
|
+
const props = defineProps({
|
58
|
+
position: {
|
59
|
+
type: String,
|
60
|
+
required: true,
|
61
|
+
},
|
62
|
+
yAxisWidth: {
|
63
|
+
type: String,
|
64
|
+
required: true,
|
65
|
+
},
|
66
|
+
series: {
|
67
|
+
type: Array,
|
68
|
+
default: () => [],
|
69
|
+
},
|
70
|
+
stackedColors: {
|
71
|
+
type: Function,
|
72
|
+
required: true,
|
73
|
+
},
|
74
|
+
splitButtonOptions: {
|
75
|
+
type: Array,
|
76
|
+
default: () => [],
|
77
|
+
},
|
78
|
+
selectedSplitButton: {
|
79
|
+
type: String,
|
80
|
+
default: '',
|
81
|
+
},
|
82
|
+
isLegendShown: {
|
83
|
+
type: Boolean,
|
84
|
+
default: false,
|
85
|
+
},
|
86
|
+
legendsItemPerRow: {
|
87
|
+
type: Number,
|
88
|
+
required: true,
|
89
|
+
},
|
90
|
+
})
|
91
|
+
|
92
|
+
const emit = defineEmits(['select-split-button'])
|
93
|
+
|
94
|
+
const reversedColors = computed(() =>
|
95
|
+
[...props.stackedColors(props.series.length)].reverse()
|
96
|
+
)
|
97
|
+
|
98
|
+
const groupedSeries = computed(() => {
|
99
|
+
const { series, legendsItemPerRow } = props
|
100
|
+
|
101
|
+
return series.reduce((groups, item, index) => {
|
102
|
+
const groupIndex = Math.floor(index / legendsItemPerRow)
|
103
|
+
groups[groupIndex] = groups[groupIndex] || []
|
104
|
+
groups[groupIndex].push(item)
|
105
|
+
|
106
|
+
return groups
|
107
|
+
}, [])
|
108
|
+
})
|
109
|
+
|
110
|
+
const handleSplitButtonClick = (value) => {
|
111
|
+
emit('select-split-button', value)
|
112
|
+
}
|
113
|
+
</script>
|