@eturnity/eturnity_reusable_components 8.16.9-EPDM-11600.8 → 8.16.9-EPDM-14690.5

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eturnity/eturnity_reusable_components",
3
- "version": "8.16.9-EPDM-11600.8",
3
+ "version": "8.16.9-EPDM-14690.5",
4
4
  "files": [
5
5
  "dist",
6
6
  "src"
@@ -0,0 +1,229 @@
1
+ <template>
2
+ <div
3
+ style="
4
+ margin-top: 100px;
5
+ margin-left: 80px;
6
+ padding-bottom: 100px;
7
+ display: flex;
8
+ flex-direction: column;
9
+ "
10
+ >
11
+ <!-- Simple bar chart -->
12
+ <BarChart
13
+ :bar-width="60"
14
+ chart-controls-position="bottom"
15
+ :data="monthlyData"
16
+ height="400px"
17
+ :is-bottom-fields-shown="true"
18
+ :is-scrollable="false"
19
+ :is-selection-enabled="true"
20
+ :selection-size="3"
21
+ :value-formatter="valueFormatter"
22
+ width="700px"
23
+ @selection-change="handleSelectionChange"
24
+ />
25
+ <br />
26
+ <br />
27
+
28
+ <!-- Stacked bar chart -->
29
+ <BarChart
30
+ :bar-width="60"
31
+ :data="monthLabels"
32
+ height="400px"
33
+ :is-bottom-fields-shown="true"
34
+ :is-legend-shown="true"
35
+ :legends-item-per-row="4"
36
+ :selected-split-button="selectedTimeFrame"
37
+ :series="tariffZones"
38
+ :show-percentage-on-tooltip="true"
39
+ :split-button-options="options"
40
+ :value-formatter="valueFormatter"
41
+ width="700px"
42
+ y-axis-title="Energy (kWh)"
43
+ @input-blur="handleInputBlur"
44
+ @select-split-button="handleSelectSplitButton"
45
+ />
46
+
47
+ <!-- Stacked bar chart -->
48
+ <BarChart
49
+ :bar-width="60"
50
+ :data="monthLabels"
51
+ field-mode="percentage"
52
+ height="400px"
53
+ :is-bottom-fields-shown="true"
54
+ :is-legend-shown="false"
55
+ :legends-item-per-row="4"
56
+ :series="tariffZones"
57
+ :value-formatter="valueFormatter"
58
+ width="700px"
59
+ @input-blur="handleInputBlur"
60
+ @select-split-button="handleSelectSplitButton"
61
+ >
62
+ <!-- <template #tooltip="{ item, segment }">
63
+ <div style="display: flex; flex-direction: column">
64
+ {{ $c.log(item, segment) }}
65
+ <div>{{ item.label }}</div>
66
+ <div>{{ item.segments[0].value }} kWh</div>
67
+ </div>
68
+ </template> -->
69
+ </BarChart>
70
+ </div>
71
+ </template>
72
+
73
+ <script setup>
74
+ import { ref } from 'vue'
75
+ import BarChart from '@/components/barchart/index.vue'
76
+
77
+ const options = [
78
+ { label: 'Day', value: 'day' },
79
+ { label: 'Month', value: 'month' },
80
+ { label: 'Year', value: 'year' },
81
+ ]
82
+
83
+ const selectedTimeFrame = ref('day')
84
+
85
+ const handleSelectSplitButton = (value) => {
86
+ selectedTimeFrame.value = value
87
+ }
88
+
89
+ const monthlyData = [
90
+ { label: 'Jan', value: 300 },
91
+ { label: 'Feb', value: 600 },
92
+ { label: 'Mar', value: 1000 },
93
+ { label: 'Apr', value: 1200 },
94
+ { label: 'May', value: 1400 },
95
+ { label: 'Jun', value: 1810 },
96
+ { label: 'Jul', value: 1400 },
97
+ { label: 'Aug', value: 1200 },
98
+ { label: 'Sep', value: 1000 },
99
+ // { label: 'Oct', value: 800 },
100
+ // { label: 'Nov', value: 600 },
101
+ // { label: 'Dec', value: 400 },
102
+ // { label: 'Jan', value: 300 },
103
+ // { label: 'Feb', value: 600 },
104
+ // { label: 'Mar', value: 1000 },
105
+ // { label: 'Apr', value: 1200 },
106
+ // { label: 'May', value: 1400 },
107
+ // { label: 'Jun', value: 1810 },
108
+ // { label: 'Jul', value: 1400 },
109
+ // { label: 'Aug', value: 1200 },
110
+ // { label: 'Sep', value: 1000 },
111
+ // { label: 'Oct', value: 800 },
112
+ // { label: 'Nov', value: 600 },
113
+ // { label: 'Dec', value: 400 },
114
+
115
+ // ... more months
116
+ ]
117
+
118
+ const monthLabels = [
119
+ { label: 'Jan' },
120
+ { label: 'Feb' },
121
+ { label: 'Mar' },
122
+ { label: 'Apr' },
123
+ { label: 'May' },
124
+ { label: 'Jun' },
125
+ // ... more months
126
+ ]
127
+
128
+ const tariffZones = ref([
129
+ {
130
+ name: 'Tariff Zone 1',
131
+ data: [
132
+ { label: 'Jan', value: 200 },
133
+ { label: 'Feb', value: 130 },
134
+ { label: 'Mar', value: 220 },
135
+ { label: 'Apr', value: 230 },
136
+ { label: 'May', value: 200 },
137
+ { label: 'Jun', value: 210 },
138
+ // ... more months
139
+ ],
140
+ },
141
+ {
142
+ name: 'Tariff Zone 2',
143
+ data: [
144
+ { label: 'Jan', value: 200 },
145
+ { label: 'Feb', value: 100 },
146
+ { label: 'Mar', value: 270 },
147
+ { label: 'Apr', value: 180 },
148
+ { label: 'May', value: 300 },
149
+ { label: 'Jun', value: 250 },
150
+ // ... more months
151
+ ],
152
+ },
153
+ {
154
+ name: 'Tariff Zone 3',
155
+ data: [
156
+ { label: 'Jan', value: 200 },
157
+ { label: 'Feb', value: 100 },
158
+ { label: 'Mar', value: 210 },
159
+ { label: 'Apr', value: 220 },
160
+ { label: 'May', value: 300 },
161
+ { label: 'Jun', value: 190 },
162
+ // ... more months
163
+ ],
164
+ },
165
+ {
166
+ name: 'Tariff Zone 4',
167
+ data: [
168
+ { label: 'Jan', value: 200 },
169
+ { label: 'Feb', value: 100 },
170
+ { label: 'Mar', value: 210 },
171
+ { label: 'Apr', value: 220 },
172
+ { label: 'May', value: 300 },
173
+ { label: 'Jun', value: 190 },
174
+ // ... more months
175
+ ],
176
+ },
177
+ {
178
+ name: 'Tariff Zone 5',
179
+ data: [
180
+ { label: 'Jan', value: 200 },
181
+ { label: 'Feb', value: 100 },
182
+ { label: 'Mar', value: 210 },
183
+ { label: 'Apr', value: 220 },
184
+ { label: 'May', value: 300 },
185
+ { label: 'Jun', value: 190 },
186
+ // ... more months
187
+ ],
188
+ },
189
+ {
190
+ name: 'Tariff Zone 6',
191
+ data: [
192
+ { label: 'Jan', value: 200 },
193
+ { label: 'Feb', value: 100 },
194
+ { label: 'Mar', value: 210 },
195
+ { label: 'Apr', value: 220 },
196
+ { label: 'May', value: 300 },
197
+ { label: 'Jun', value: 190 },
198
+ // ... more months
199
+ ],
200
+ },
201
+ // ... more tariff zones
202
+ ])
203
+
204
+ const valueFormatter = (value) => {
205
+ return `${value} kWh`
206
+ }
207
+
208
+ const handleSelectionChange = (selectedBars) => {
209
+ console.log('selectedBars', selectedBars)
210
+ }
211
+
212
+ const handleInputBlur = (payload) => {
213
+ const newVal = [...tariffZones.value].map((zone) => {
214
+ if (zone.name === payload.seriesName) {
215
+ zone.data = zone.data.map((item) => {
216
+ if (item.label === payload.label) {
217
+ item.value = payload.value
218
+ }
219
+ return item
220
+ })
221
+ }
222
+ return zone
223
+ })
224
+
225
+ tariffZones.value = newVal
226
+ }
227
+ </script>
228
+
229
+ <style lang="scss" scoped></style>
@@ -1,3 +1,4 @@
1
- <svg width="22" height="21" viewBox="0 0 22 21" fill="none" xmlns="http://www.w3.org/2000/svg">
2
- <path d="M18.4389 3.06113C14.3574 -1.02038 7.64263 -1.02038 3.56113 3.06113C-0.520376 7.14263 -0.520376 13.8574 3.56113 17.9389C7.64263 22.0204 14.3574 22.0204 18.4389 17.9389C22.5204 13.8574 22.5204 7.2743 18.4389 3.06113ZM16.464 14.2524L14.6207 16.0956L10.9342 12.4091L7.24765 16.0956L5.40439 14.2524L9.09091 10.5658L5.40439 6.87931L7.24765 5.03605L10.9342 8.72257L14.6207 5.03605L16.464 6.87931L12.7774 10.5658L16.464 14.2524Z" fill="#FF5656"/>
1
+ <svg fill="none" height="16" viewbox="12 12 16 16" width="16" xmlns="http://www.w3.org/2000/svg">
2
+ <circle cx="20" cy="20" r="7"></circle>
3
+ <path d="M24.9592 15.0408C22.2382 12.3197 17.7618 12.3197 15.0408 15.0408C12.3197 17.7618 12.3197 22.2382 15.0408 24.9592C17.7618 27.6803 22.2382 27.6803 24.9592 24.9592C27.6803 22.2382 27.6803 17.8495 24.9592 15.0408ZM23.6426 22.5016L22.4138 23.7304L19.9561 21.2727L17.4984 23.7304L16.2696 22.5016L18.7273 20.0439L16.2696 17.5862L17.4984 16.3574L19.9561 18.815L22.4138 16.3574L23.6426 17.5862L21.185 20.0439L23.6426 22.5016Z" fill="#FF5656"></path>
3
4
  </svg>
@@ -333,7 +333,7 @@ const theme = (() => {
333
333
  borderColor: semanticColors.grey[300],
334
334
  },
335
335
  active: {
336
- backgroundColor: semanticColors.purple[50],
336
+ backgroundColor: semanticColors.blue[100],
337
337
  textColor: semanticColors.purple[600],
338
338
  borderColor: semanticColors.grey[600],
339
339
  },
@@ -1,14 +1,14 @@
1
1
  <template>
2
2
  <Container :is-chart-controls-shown-in-bottom="isChartControlsShownInBottom">
3
3
  <LabelsColumn :width="yAxisWidth">
4
- <LabelRow v-for="series in props.series" :key="series.name">
4
+ <LabelRow v-for="series in seriesData" :key="series.name">
5
5
  {{ series.name }}
6
6
  </LabelRow>
7
- <TotalRow v-if="props.series.length && fieldMode === 'percentage'">
8
- {{ $gettext ? $gettext('Total (%)') : 'Total (%)' }}
7
+ <TotalRow v-if="seriesData.length && fieldMode === 'percentage'">
8
+ {{ $gettext ? `${$gettext('Total')} (%)` : 'Total (%)' }}
9
9
  </TotalRow>
10
- <TotalRow v-if="props.series.length">
11
- {{ $gettext ? $gettext('Total (kWh)') : 'Total (kWh)' }}
10
+ <TotalRow v-if="seriesData.length">
11
+ {{ $gettext ? `${$gettext('Total')} (kWh)` : 'Total (kWh)' }}
12
12
  </TotalRow>
13
13
  </LabelsColumn>
14
14
 
@@ -18,27 +18,35 @@
18
18
  >
19
19
  <FieldsWrapper>
20
20
  <!-- For stacked bar chart -->
21
- <template v-if="props.series.length">
21
+ <template v-if="seriesData.length">
22
22
  <InputRow
23
- v-for="series in props.series"
23
+ v-for="series in seriesData"
24
24
  :key="series.name"
25
25
  :data-series-name="series.name"
26
26
  >
27
27
  <InputGroup
28
28
  v-for="(item, index) in props.data"
29
+ :key="index"
29
30
  :bar-width="barWidth"
30
31
  :is-scrollable="isScrollable"
31
- :key="index"
32
32
  >
33
33
  <InputNumber
34
34
  :allow-negative="false"
35
+ :disabled="isInputsDisabled"
35
36
  input-height="36px"
36
- :number-precision="0"
37
+ :is-info-border="
38
+ fieldMode === 'percentage'
39
+ ? calculatePercentageTotal(item.label) !== 100
40
+ : false
41
+ "
37
42
  :min-decimals="0"
43
+ :number-precision="fieldMode === 'percentage' ? 2 : 0"
38
44
  text-align="center"
39
45
  :unit-name="fieldMode === 'percentage' ? '%' : ''"
40
46
  :value="getDisplayValue(series.data, item.label)"
41
- @input-blur="handleInputBlur($event, series.name, item.label)"
47
+ @input-blur="
48
+ handleInputBlur($event, series.name, item.label, series.data)
49
+ "
42
50
  @input-focus="handleInputFocus(series.name, item.label)"
43
51
  />
44
52
  </InputGroup>
@@ -47,16 +55,17 @@
47
55
  <TotalInputRow v-if="fieldMode === 'percentage'">
48
56
  <InputGroup
49
57
  v-for="(item, index) in props.data"
58
+ :key="index"
50
59
  :bar-width="barWidth"
51
60
  :is-scrollable="isScrollable"
52
- :key="index"
53
61
  >
54
62
  <InputNumber
55
63
  :allow-negative="false"
64
+ :disabled="isInputsDisabled"
56
65
  input-height="36px"
57
66
  :is-read-only="true"
58
- :number-precision="0"
59
67
  :min-decimals="0"
68
+ :number-precision="fieldMode === 'percentage' ? 2 : 0"
60
69
  text-align="center"
61
70
  :unit-name="fieldMode === 'percentage' ? '%' : ''"
62
71
  :value="calculatePercentageTotal(item.label)"
@@ -67,17 +76,23 @@
67
76
  <TotalInputRow>
68
77
  <InputGroup
69
78
  v-for="(item, index) in props.data"
79
+ :key="index"
70
80
  :bar-width="barWidth"
71
81
  :is-scrollable="isScrollable"
72
- :key="index"
73
82
  >
74
83
  <InputNumber
75
84
  input-height="36px"
85
+ :is-border-error-only="true"
86
+ :is-info-border="
87
+ fieldMode === 'percentage'
88
+ ? calculatePercentageTotal(item.label) !== 100
89
+ : false
90
+ "
76
91
  :is-read-only="true"
77
- :number-precision="2"
78
92
  :min-decimals="0"
93
+ :number-precision="0"
79
94
  text-align="center"
80
- :value="calculateTotal(item.label)"
95
+ :value="calculateTotalValue(item.label)"
81
96
  />
82
97
  </InputGroup>
83
98
  </TotalInputRow>
@@ -88,17 +103,19 @@
88
103
  <InputRow>
89
104
  <InputGroup
90
105
  v-for="(item, index) in props.data"
106
+ :key="index"
91
107
  :bar-width="barWidth"
92
108
  :is-scrollable="isScrollable"
93
- :key="index"
94
109
  >
95
110
  <InputNumber
111
+ :allow-negative="false"
112
+ :disabled="isInputsDisabled"
96
113
  input-height="36px"
97
114
  :min-decimals="0"
98
- :number-precision="2"
115
+ :number-precision="0"
99
116
  text-align="center"
100
117
  :value="item.value"
101
- @input-blur="handleInputBlur($event, null, item.label)"
118
+ @input-blur="handleInputBlur($event, null, item.label, null)"
102
119
  @input-focus="handleInputFocus(null, item.label)"
103
120
  />
104
121
  </InputGroup>
@@ -107,11 +124,22 @@
107
124
  </FieldsWrapper>
108
125
  </FieldsContainer>
109
126
  </Container>
127
+ <InfoCardContainer
128
+ v-if="hasAnySegmentNotTotatTo100Percent && fieldMode === 'percentage'"
129
+ :yAxisWidth="yAxisWidth"
130
+ >
131
+ <InfoCard align-items="center" type="info">
132
+ <InfoCardBody>
133
+ {{ $gettext('load_profile_not_add_up_to_100') }}
134
+ </InfoCardBody>
135
+ </InfoCard>
136
+ </InfoCardContainer>
110
137
  </template>
111
138
 
112
139
  <script setup>
113
- import { ref } from 'vue'
140
+ import { ref, computed, watchEffect } from 'vue'
114
141
  import InputNumber from '../inputs/inputNumber'
142
+ import InfoCard from '../infoCard'
115
143
 
116
144
  import {
117
145
  Container,
@@ -123,6 +151,8 @@
123
151
  InputRow,
124
152
  TotalInputRow,
125
153
  InputGroup,
154
+ InfoCardContainer,
155
+ InfoCardBody,
126
156
  } from './styles/bottomFields'
127
157
 
128
158
  const props = defineProps({
@@ -159,6 +189,47 @@
159
189
  default: 'absolute',
160
190
  validator: (value) => ['absolute', 'percentage'].includes(value),
161
191
  },
192
+ isInputsDisabled: {
193
+ type: Boolean,
194
+ default: false,
195
+ },
196
+ })
197
+
198
+ const seriesData = ref([])
199
+
200
+ watchEffect(() => {
201
+ let isNewSetOfSeries = false
202
+ const seriesDataCopy = [...seriesData.value]
203
+ if (
204
+ !seriesDataCopy.length ||
205
+ !props.series.length ||
206
+ seriesDataCopy.length !== props.series.length ||
207
+ !seriesDataCopy.some((item) => {
208
+ return props.series.map((s) => s.name).includes(item.name)
209
+ })
210
+ ) {
211
+ isNewSetOfSeries = true
212
+ }
213
+ const currentSeriesData = !isNewSetOfSeries ? seriesDataCopy : []
214
+ const newSeriesData = []
215
+
216
+ props.series.forEach((item, itemIndex) => {
217
+ const data = item.data.map((d, dIndex) => ({
218
+ label: d.label,
219
+ value: d.value,
220
+ percentage: d.percentage,
221
+ originalValue: currentSeriesData.length
222
+ ? currentSeriesData[itemIndex].data[dIndex].originalValue
223
+ : d.value,
224
+ }))
225
+
226
+ newSeriesData.push({
227
+ name: item.name,
228
+ data,
229
+ })
230
+ })
231
+
232
+ seriesData.value = [...newSeriesData]
162
233
  })
163
234
 
164
235
  const emit = defineEmits([
@@ -175,11 +246,13 @@
175
246
  emit('input-focus', { seriesName, label })
176
247
  }
177
248
 
178
- const calculateTotal = (label) => {
179
- return props.series.reduce((sum, series) => {
249
+ const calculateTotalValue = (label) => {
250
+ const total = seriesData.value.reduce((sum, series) => {
180
251
  const value = series.data.find((d) => d.label === label)?.value || 0
181
252
  return sum + value
182
253
  }, 0)
254
+
255
+ return Math.round(total)
183
256
  }
184
257
 
185
258
  const syncScroll = (scrollLeft) => {
@@ -190,34 +263,43 @@
190
263
  container.scrollLeft = scrollLeft
191
264
  }
192
265
  }
193
- const getDisplayValue = (seriesData, label) => {
266
+
267
+ const calculateTotalOriginalValue = (label) => {
268
+ return seriesData.value.reduce((sum, series) => {
269
+ const value =
270
+ series.data.find((d) => d.label === label)?.originalValue || 0
271
+ return sum + value
272
+ }, 0)
273
+ }
274
+
275
+ const getDisplayValue = (data, label, shouldRound = true) => {
194
276
  if (props.fieldMode === 'absolute') {
195
- return seriesData.find((d) => d.label === label)?.value || ''
277
+ return data.find((d) => d.label === label)?.value
196
278
  }
197
279
 
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
280
+ return data.find((d) => d.label === label)?.percentage
201
281
  }
202
282
 
203
283
  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
284
+ const percentageTotal = seriesData.value.reduce((sum, series) => {
285
+ const percentage =
286
+ series.data.find((d) => d.label === label)?.percentage || 0
208
287
  return sum + percentage
209
288
  }, 0)
289
+
290
+ return Math.round(percentageTotal)
210
291
  }
211
292
 
212
- const handleInputBlur = (_value, seriesName, label) => {
293
+ const handleInputBlur = (_value, seriesName, label, currentSeriesData) => {
213
294
  let value = Number(_value)
214
295
 
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 }
296
+ const payload = seriesName
297
+ ? {
298
+ seriesName,
299
+ label,
300
+ value,
301
+ }
302
+ : { label, value }
221
303
  emit('input-blur', payload)
222
304
  focusedInput.value = null
223
305
 
@@ -243,6 +325,12 @@
243
325
  }
244
326
  }
245
327
 
328
+ const hasAnySegmentNotTotatTo100Percent = computed(() => {
329
+ return props.data.some((d) => {
330
+ return calculatePercentageTotal(d.label) !== 100
331
+ })
332
+ })
333
+
246
334
  const handleFieldsScroll = (event) => {
247
335
  emit('sync-scroll', event.target.scrollLeft)
248
336
  }
@@ -84,7 +84,7 @@ export function useAxisCalculations(props, maxValue) {
84
84
  })
85
85
 
86
86
  const yAxisWidth = computed(() => {
87
- return !!props.yAxisTitle || props.isBottomFieldsShown ? '70px' : '60px'
87
+ return !!props.yAxisTitle || props.isBottomFieldsShown ? '80px' : '60px'
88
88
  })
89
89
 
90
90
  const isChartControlsShown = (position) => {
@@ -66,7 +66,7 @@ export function useChartData(props, paddedMaxValue) {
66
66
  let accumulated = 0
67
67
  return {
68
68
  label: item.label,
69
- segments: [...props.series].reverse().map((series, index) => {
69
+ segments: [...props.series].map((series, index) => {
70
70
  const value =
71
71
  series.data.find((d) => d.label === item.label)?.value || 0
72
72
  accumulated += value
@@ -16,7 +16,21 @@ export function useTooltip(chartId, normalizedData) {
16
16
  if (!showTooltipContent.value) {
17
17
  showTooltipContent.value = true
18
18
  }
19
- if (isObjectEqual(item, tooltipData.value)) return
19
+ if (isObjectEqual(item, tooltipData.value)) {
20
+ return
21
+ }
22
+
23
+ const totalValue = item.segments.reduce((acc, segment) => {
24
+ return acc + segment.value
25
+ }, 0)
26
+
27
+ const segments = item.segments.map((segment) => {
28
+ let valuePercentage = (segment.value / totalValue) * 100
29
+ segment.valuePercentage = Math.round(valuePercentage)
30
+
31
+ return segment
32
+ })
33
+ item.segments = segments
20
34
 
21
35
  tooltipData.value = { ...item }
22
36
 
@@ -41,16 +55,27 @@ export function useTooltip(chartId, normalizedData) {
41
55
 
42
56
  isInputFocused.value = true
43
57
  const barData = normalizedData.value.find((item) => item.label === label)
44
-
45
58
  if (!barData) return
59
+
60
+ const totalValue = barData.segments.reduce((acc, segment) => {
61
+ return acc + segment.value
62
+ }, 0)
63
+ const segments = barData.segments.map((segment) => {
64
+ let valuePercentage = (segment.value / totalValue) * 100
65
+ segment.valuePercentage = Math.round(valuePercentage)
66
+
67
+ return segment
68
+ })
69
+ barData.segments = segments
46
70
  focusedBarData.value = barData
71
+
47
72
  const barElement = document.querySelector(
48
73
  `.barchart-${chartId} .bar-group:nth-child(${
49
74
  normalizedData.value.indexOf(barData) + 1
50
75
  })`
51
76
  )
52
-
53
77
  if (!barElement) return
78
+
54
79
  // Get the last bar segment, samee as hover behavior
55
80
  const targetElement = barElement.querySelector('.bar-segment:last-child')
56
81
  const rect = targetElement.getBoundingClientRect()