@eturnity/eturnity_reusable_components 8.16.9-EPDM-6306.1 → 8.16.9-EPDM-14690.0

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-6306.1",
3
+ "version": "8.16.9-EPDM-14690.0",
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,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,37 @@
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"
36
+ :error-message="null"
35
37
  input-height="36px"
36
- :number-precision="0"
38
+ :is-border-error-only="true"
39
+ :is-error="
40
+ fieldMode === 'percentage'
41
+ ? calculatePercentageTotal(item.label) !== 100
42
+ : false
43
+ "
37
44
  :min-decimals="0"
45
+ :number-precision="fieldMode === 'percentage' ? 2 : 0"
38
46
  text-align="center"
39
47
  :unit-name="fieldMode === 'percentage' ? '%' : ''"
40
48
  :value="getDisplayValue(series.data, item.label)"
41
- @input-blur="handleInputBlur($event, series.name, item.label)"
49
+ @input-blur="
50
+ handleInputBlur($event, series.name, item.label, series.data)
51
+ "
42
52
  @input-focus="handleInputFocus(series.name, item.label)"
43
53
  />
44
54
  </InputGroup>
@@ -47,16 +57,17 @@
47
57
  <TotalInputRow v-if="fieldMode === 'percentage'">
48
58
  <InputGroup
49
59
  v-for="(item, index) in props.data"
60
+ :key="index"
50
61
  :bar-width="barWidth"
51
62
  :is-scrollable="isScrollable"
52
- :key="index"
53
63
  >
54
64
  <InputNumber
55
65
  :allow-negative="false"
66
+ :disabled="isInputsDisabled"
56
67
  input-height="36px"
57
68
  :is-read-only="true"
58
- :number-precision="0"
59
69
  :min-decimals="0"
70
+ :number-precision="fieldMode === 'percentage' ? 2 : 0"
60
71
  text-align="center"
61
72
  :unit-name="fieldMode === 'percentage' ? '%' : ''"
62
73
  :value="calculatePercentageTotal(item.label)"
@@ -67,17 +78,24 @@
67
78
  <TotalInputRow>
68
79
  <InputGroup
69
80
  v-for="(item, index) in props.data"
81
+ :key="index"
70
82
  :bar-width="barWidth"
71
83
  :is-scrollable="isScrollable"
72
- :key="index"
73
84
  >
74
85
  <InputNumber
86
+ :error-message="$gettext('load_profile_not_add_up_to_100')"
75
87
  input-height="36px"
88
+ :is-border-error-only="true"
89
+ :is-error="
90
+ fieldMode === 'percentage'
91
+ ? calculatePercentageTotal(item.label) !== 100
92
+ : false
93
+ "
76
94
  :is-read-only="true"
77
- :number-precision="2"
78
95
  :min-decimals="0"
96
+ :number-precision="2"
79
97
  text-align="center"
80
- :value="calculateTotal(item.label)"
98
+ :value="calculateTotalValue(item.label)"
81
99
  />
82
100
  </InputGroup>
83
101
  </TotalInputRow>
@@ -88,17 +106,19 @@
88
106
  <InputRow>
89
107
  <InputGroup
90
108
  v-for="(item, index) in props.data"
109
+ :key="index"
91
110
  :bar-width="barWidth"
92
111
  :is-scrollable="isScrollable"
93
- :key="index"
94
112
  >
95
113
  <InputNumber
114
+ :allow-negative="false"
115
+ :disabled="isInputsDisabled"
96
116
  input-height="36px"
97
117
  :min-decimals="0"
98
118
  :number-precision="2"
99
119
  text-align="center"
100
120
  :value="item.value"
101
- @input-blur="handleInputBlur($event, null, item.label)"
121
+ @input-blur="handleInputBlur($event, null, item.label, null)"
102
122
  @input-focus="handleInputFocus(null, item.label)"
103
123
  />
104
124
  </InputGroup>
@@ -110,7 +130,8 @@
110
130
  </template>
111
131
 
112
132
  <script setup>
113
- import { ref } from 'vue'
133
+ import { ref, watch, watchEffect, onMounted } from 'vue'
134
+ import styled from 'vue3-styled-components'
114
135
  import InputNumber from '../inputs/inputNumber'
115
136
 
116
137
  import {
@@ -159,6 +180,61 @@
159
180
  default: 'absolute',
160
181
  validator: (value) => ['absolute', 'percentage'].includes(value),
161
182
  },
183
+ isInputsDisabled: {
184
+ type: Boolean,
185
+ default: false,
186
+ },
187
+ })
188
+
189
+ const seriesData = ref([])
190
+
191
+ // onMounted(() => {
192
+ // seriesData.value = props.series.map((item) => {
193
+ // const data = item.data.map((d) => ({
194
+ // label: d.label,
195
+ // value: d.value,
196
+ // originalValue: d.value,
197
+ // }))
198
+
199
+ // return {
200
+ // name: item.name,
201
+ // data,
202
+ // }
203
+ // })
204
+ // })
205
+
206
+ watchEffect(() => {
207
+ let isNewSetOfSeries = false
208
+ const seriesDataCopy = [...seriesData.value]
209
+ if (
210
+ !seriesDataCopy.length ||
211
+ !props.series.length ||
212
+ seriesDataCopy.length !== props.series.length ||
213
+ !seriesDataCopy.some((item) => {
214
+ return props.series.map((s) => s.name).includes(item.name)
215
+ })
216
+ ) {
217
+ isNewSetOfSeries = true
218
+ }
219
+ const currentSeriesData = !isNewSetOfSeries ? seriesDataCopy : []
220
+ const newSeriesData = []
221
+
222
+ props.series.forEach((item, itemIndex) => {
223
+ const data = item.data.map((d, dIndex) => ({
224
+ label: d.label,
225
+ value: d.value,
226
+ originalValue: currentSeriesData.length
227
+ ? currentSeriesData[itemIndex].data[dIndex].originalValue
228
+ : d.value,
229
+ }))
230
+
231
+ newSeriesData.push({
232
+ name: item.name,
233
+ data,
234
+ })
235
+ })
236
+
237
+ seriesData.value = [...newSeriesData]
162
238
  })
163
239
 
164
240
  const emit = defineEmits([
@@ -175,11 +251,13 @@
175
251
  emit('input-focus', { seriesName, label })
176
252
  }
177
253
 
178
- const calculateTotal = (label) => {
179
- return props.series.reduce((sum, series) => {
254
+ const calculateTotalValue = (label) => {
255
+ const total = seriesData.value.reduce((sum, series) => {
180
256
  const value = series.data.find((d) => d.label === label)?.value || 0
181
257
  return sum + value
182
258
  }, 0)
259
+
260
+ return Math.round(total)
183
261
  }
184
262
 
185
263
  const syncScroll = (scrollLeft) => {
@@ -190,34 +268,74 @@
190
268
  container.scrollLeft = scrollLeft
191
269
  }
192
270
  }
193
- const getDisplayValue = (seriesData, label) => {
271
+
272
+ const calculateTotalOriginalValue = (label) => {
273
+ return seriesData.value.reduce((sum, series) => {
274
+ const value =
275
+ series.data.find((d) => d.label === label)?.originalValue || 0
276
+ return sum + value
277
+ }, 0)
278
+ }
279
+
280
+ const getDisplayValue = (data, label, shouldRound = true) => {
194
281
  if (props.fieldMode === 'absolute') {
195
- return seriesData.find((d) => d.label === label)?.value || ''
282
+ return data.find((d) => d.label === label)?.value || ''
196
283
  }
197
284
 
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
285
+ const value = data.find((d) => d.label === label)?.value || 0
286
+
287
+ const total = seriesData.value.reduce((sum, series) => {
288
+ const value =
289
+ series.data.find((d) => d.label === label)?.originalValue || 0
290
+ return sum + value
291
+ }, 0)
292
+
293
+ return shouldRound ? (value / total) * 100 : (value / total) * 100
201
294
  }
202
295
 
203
296
  const calculatePercentageTotal = (label) => {
204
- return props.series.reduce((sum, series) => {
297
+ const originalTotal = seriesData.value.reduce((sum, series) => {
298
+ const originalValue =
299
+ series.data.find((d) => d.label === label)?.originalValue || 0
300
+ return sum + originalValue
301
+ }, 0)
302
+
303
+ const totalPercentage = seriesData.value.reduce((sum, series) => {
205
304
  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
305
+ const percentage = originalTotal
306
+ ? Number((value / originalTotal) * 100)
307
+ : 0
208
308
  return sum + percentage
209
309
  }, 0)
310
+
311
+ return Math.round(totalPercentage)
210
312
  }
211
313
 
212
- const handleInputBlur = (_value, seriesName, label) => {
314
+ const handleInputBlur = (_value, seriesName, label, currentSeriesData) => {
213
315
  let value = Number(_value)
214
316
 
215
317
  if (props.fieldMode === 'percentage') {
216
- const total = calculateTotal(label)
318
+ const total = seriesData.value.reduce((sum, series) => {
319
+ const value =
320
+ series.data.find((d) => d.label === label)?.originalValue || 0
321
+ return sum + value
322
+ }, 0)
323
+
217
324
  value = (value / 100) * total
218
325
  }
219
326
 
220
- const payload = seriesName ? { seriesName, label, value } : { label, value }
327
+ const payload = seriesName
328
+ ? {
329
+ seriesName,
330
+ label,
331
+ value,
332
+ inputValue: _value,
333
+ percentage: Number(
334
+ getDisplayValue(currentSeriesData, label, false).toFixed(2)
335
+ ),
336
+ totalSeriesValue: calculateTotalOriginalValue(label),
337
+ }
338
+ : { label, value }
221
339
  emit('input-blur', payload)
222
340
  focusedInput.value = null
223
341
 
@@ -247,6 +365,11 @@
247
365
  emit('sync-scroll', event.target.scrollLeft)
248
366
  }
249
367
 
368
+ const toFixedNoRounding = (number, decimals) => {
369
+ const factor = Math.pow(10, decimals)
370
+ return Math.floor(number * factor) / factor
371
+ }
372
+
250
373
  defineExpose({
251
374
  syncScroll,
252
375
  })
@@ -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()
@@ -17,7 +17,7 @@
17
17
  />
18
18
  </ChartControlsWrapper>
19
19
  <GraphSection :height="height" :width="width">
20
- <YAxis :width="yAxisWidth" :height="height">
20
+ <YAxis :height="height" :width="yAxisWidth">
21
21
  <YAxisTitleWrapper v-if="yAxisTitle" :height="yAxisHeight">
22
22
  {{ yAxisTitle }}
23
23
  </YAxisTitleWrapper>
@@ -30,15 +30,15 @@
30
30
  )
31
31
  "
32
32
  >
33
- <YAxisLabel>{{ label }}</YAxisLabel>
33
+ <YAxisLabel>{{ getYAxisLabel(label) }}</YAxisLabel>
34
34
  <YAxisLine :y-axis-width="yAxisWidth" />
35
35
  </YAxisRow>
36
36
  </YAxis>
37
37
 
38
38
  <ScrollContainer
39
39
  :class="`chart-scroll-container-${chartId}`"
40
- :is-scrollable="isScrollable"
41
40
  :height="height"
41
+ :is-scrollable="isScrollable"
42
42
  @scroll="handleChartScroll"
43
43
  >
44
44
  <ChartContent
@@ -64,19 +64,19 @@
64
64
  <BarsContainer>
65
65
  <BarGroup
66
66
  v-for="(item, index) in normalizedData"
67
+ :key="index"
67
68
  :bar-width="barWidth"
68
69
  class="bar-group"
69
70
  :is-scrollable="isScrollable"
70
- :key="index"
71
71
  >
72
72
  <BarWrapper>
73
73
  <BarSegment
74
74
  v-for="(segment, segIndex) in item.segments"
75
+ :key="segIndex"
75
76
  class="bar-segment"
76
77
  :gradient-from="getSegmentGradient(index, segment).from"
77
78
  :gradient-to="getSegmentGradient(index, segment).to"
78
79
  :height="`${segment.percentage}%`"
79
- :key="segIndex"
80
80
  :z-index="item.segments.length - segIndex"
81
81
  @mouseenter="showTooltip(item, $event, series)"
82
82
  @mouseleave="hideTooltip"
@@ -96,7 +96,7 @@
96
96
  :left="tooltipStyle.left"
97
97
  :top="tooltipStyle.top"
98
98
  >
99
- <slot :item="tooltipData" name="tooltip" />
99
+ <slot :item="tooltipData" name="tooltip"></slot>
100
100
  <TooltipTextWrapper v-if="!slots.tooltip && tooltipData">
101
101
  <template v-if="!series.length">
102
102
  <TooltipText font-weight="500">{{ tooltipData.label }}</TooltipText>
@@ -128,7 +128,11 @@
128
128
  :gradient-to="segment.gradientTo"
129
129
  />
130
130
  <TooltipText>
131
- {{ handleValueFormatter(segment.value) }}
131
+ {{
132
+ fieldMode === 'absolute' && showPercentageOnTooltip
133
+ ? `${segment.valuePercentage}%`
134
+ : handleValueFormatter(segment.value)
135
+ }}
132
136
  </TooltipText>
133
137
  </TooltipRow>
134
138
  </template>
@@ -159,6 +163,7 @@
159
163
  :data="data"
160
164
  :field-mode="fieldMode"
161
165
  :is-chart-controls-shown-in-bottom="isChartControlsShown('bottom')"
166
+ :is-inputs-disabled="isLoading"
162
167
  :is-scrollable="isScrollable"
163
168
  :series="series"
164
169
  :y-axis-width="yAxisWidth"
@@ -171,12 +176,13 @@
171
176
  </template>
172
177
 
173
178
  <script setup>
174
- import { useSlots, computed } from 'vue'
179
+ import { useSlots, computed, ref } from 'vue'
175
180
 
176
181
  import ChartControls from './ChartControls'
177
182
  import BottomFields from './BottomFields'
178
183
  import SelectionBox from './SelectionBox'
179
184
  import Spinner from '../spinner'
185
+ import { numberToString } from '../../helpers/numberConverter'
180
186
 
181
187
  import {
182
188
  useTooltip,
@@ -296,6 +302,10 @@
296
302
  type: Boolean,
297
303
  default: false,
298
304
  },
305
+ showPercentageOnTooltip: {
306
+ type: Boolean,
307
+ default: false,
308
+ },
299
309
  })
300
310
 
301
311
  const generateChartId = () =>
@@ -378,8 +388,14 @@
378
388
  }
379
389
 
380
390
  const handleValueFormatter = (value) => {
381
- return props.valueFormatter
382
- ? props.valueFormatter(Math.round(value))
383
- : value
391
+ return props.valueFormatter ? props.valueFormatter(value) : value
392
+ }
393
+
394
+ const getYAxisLabel = (label) => {
395
+ return numberToString({
396
+ value: label,
397
+ numberPrecision: 0,
398
+ minDecimals: 0,
399
+ })
384
400
  }
385
401
  </script>
@@ -17,18 +17,18 @@ export const LabelsColumn = styled('div', { width: String })`
17
17
 
18
18
  export const LabelRow = styled.div`
19
19
  height: 32px;
20
+ padding-top: 5px;
20
21
  font-size: 12px;
21
22
  font-weight: 500;
22
23
  color: ${(props) => props.theme.semanticColors.teal[600]};
23
24
  display: flex;
24
- align-items: flex-start;
25
+ align-items: center;
25
26
  `
26
27
 
27
28
  export const TotalRow = styled(LabelRow)``
28
29
 
29
30
  export const FieldsContainer = styled.div`
30
31
  flex: 1;
31
- overflow-x: auto;
32
32
  scrollbar-width: none;
33
33
 
34
34
  &::-webkit-scrollbar {
@@ -63,4 +63,9 @@ export const InputGroup = styled('div', {
63
63
  props.barWidth}px;
64
64
  display: flex;
65
65
  justify-content: center;
66
+ position: relative;
67
+
68
+ input[readonly] {
69
+ border: 1px solid ${(props) => props.theme.colors.grey4} !important;
70
+ }
66
71
  `
@@ -127,6 +127,7 @@ export const BarWrapper = styled.div`
127
127
  height: 100%;
128
128
  width: 100%;
129
129
  position: relative;
130
+ overflow: hidden;
130
131
  `
131
132
 
132
133
  export const BarSegment = styled('div', {
@@ -19,7 +19,7 @@
19
19
  background: ${(props) => props.theme.colors.red};
20
20
  padding: 10px;
21
21
  width: max-content;
22
- max-width: 100%;
22
+ /* max-width: 100%; */
23
23
  min-width: min-content;
24
24
  font-size: 11px;
25
25
  font-weight: 400;
@@ -50,6 +50,7 @@
50
50
  :has-slot="hasSlot"
51
51
  :has-unit="unitName && !!unitName.length"
52
52
  :input-height="inputHeight"
53
+ :is-border-error-only="isBorderErrorOnly"
53
54
  :is-disabled="disabled"
54
55
  :is-error="isError"
55
56
  :is-interactive="isInteractive"
@@ -57,11 +58,12 @@
57
58
  :no-border="noBorder"
58
59
  :placeholder="displayedPlaceholder"
59
60
  :read-only="isReadOnly"
61
+ :readonly="isReadOnly"
60
62
  :show-arrow-controls="showArrowControls"
61
63
  :show-linear-unit-name="showLinearUnitName"
62
64
  :slot-size="slotSize"
63
65
  :text-align="textAlign"
64
- :value="formattedValue"
66
+ :value="formatWithCurrency(value)"
65
67
  @blur="onInputBlur($event)"
66
68
  @focus="focusInput()"
67
69
  @input="onInput($event)"
@@ -75,12 +77,12 @@
75
77
 
76
78
  <UnitContainer
77
79
  v-if="unitName && showLinearUnitName && !hasSlot"
78
- :has-length="hasLength"
80
+ :has-length="!!textInput.length"
79
81
  :is-error="isError"
80
82
  >{{ unitName }}</UnitContainer
81
83
  >
82
84
  <IconWrapper
83
- v-if="isError && !showLinearUnitName"
85
+ v-if="isError && !showLinearUnitName && !isBorderErrorOnly"
84
86
  :margin-right="showSelect ? selectWidth : 0"
85
87
  size="16px"
86
88
  >
@@ -92,7 +94,7 @@
92
94
  :disabled="isSelectDisabled"
93
95
  :select-width="`${selectWidth}px`"
94
96
  :show-border="false"
95
- @input-change="handleSelectChange"
97
+ @input-change="$emit('select-change', $event)"
96
98
  >
97
99
  <template #selector>
98
100
  <SelectText>{{ getSelectValue }}</SelectText>
@@ -134,7 +136,9 @@
134
136
  </ArrowButton>
135
137
  </ArrowControls>
136
138
  </InputWrapper>
137
- <ErrorMessage v-if="isError">{{ errorMessage }}</ErrorMessage>
139
+ <ErrorMessage v-if="isError && errorMessage">{{
140
+ errorMessage
141
+ }}</ErrorMessage>
138
142
  </Container>
139
143
  </template>
140
144
 
@@ -205,6 +209,7 @@
205
209
  colorMode: String,
206
210
  showArrowControls: Boolean,
207
211
  readOnly: Boolean,
212
+ isBorderErrorOnly: Boolean,
208
213
  }
209
214
 
210
215
  const Container = styled('div', inputProps)`
@@ -236,16 +241,17 @@
236
241
  showLinearUnitName,
237
242
  colorMode,
238
243
  showArrowControls,
244
+ isBorderErrorOnly,
239
245
  }) =>
240
246
  showArrowControls
241
247
  ? '40px'
242
248
  : colorMode === 'transparent'
243
249
  ? '0'
244
250
  : slotSize
245
- ? isError && !showLinearUnitName
251
+ ? isError && !showLinearUnitName && !isBorderErrorOnly
246
252
  ? 'calc(' + slotSize + ' + 24px)'
247
253
  : 'calc(' + slotSize + ' + 10px)'
248
- : isError && !showLinearUnitName
254
+ : isError && !showLinearUnitName && !isBorderErrorOnly
249
255
  ? '24px'
250
256
  : '5px'};
251
257
  border-radius: ${(props) =>
@@ -457,18 +463,8 @@
457
463
  background-color: ${({ theme }) => theme.colors.grey4};
458
464
  `
459
465
 
460
- const EVENT_TYPES = {
461
- INPUT_FOCUS: 'input-focus',
462
- INPUT_CHANGE: 'input-change',
463
- INPUT_BLUR: 'input-blur',
464
- PRESS_ENTER: 'on-enter-click',
465
- INPUT_DRAG: 'on-input-drag',
466
- SELECT_CHANGE: 'select-change',
467
- }
468
-
469
466
  export default {
470
467
  name: 'InputNumber',
471
- emits: [...Object.values(EVENT_TYPES)],
472
468
  components: {
473
469
  Container,
474
470
  InputContainer,
@@ -708,13 +704,16 @@
708
704
  type: Boolean,
709
705
  default: false,
710
706
  },
707
+ isBorderErrorOnly: {
708
+ type: Boolean,
709
+ default: false,
710
+ },
711
711
  },
712
712
  data() {
713
713
  return {
714
+ textInput: '',
714
715
  isFocused: false,
715
716
  warningIcon: warningIcon,
716
- inputValue: null,
717
- enteredValue: null,
718
717
  }
719
718
  },
720
719
  computed: {
@@ -737,14 +736,6 @@
737
736
 
738
737
  return item ? item.label : '-'
739
738
  },
740
- formattedValue() {
741
- return this.isFocused
742
- ? this.enteredValue
743
- : this.formatWithCurrency(this.value)
744
- },
745
- hasLength() {
746
- return this.formattedValue !== null && this.formattedValue.length > 0
747
- },
748
739
  },
749
740
  watch: {
750
741
  focus(value) {
@@ -755,19 +746,30 @@
755
746
  clearInput: function (value) {
756
747
  if (value) {
757
748
  // If the value is typed, then we should clear the textInput on Continue
758
- this.inputValue = ''
759
- this.enteredValue = ''
749
+ this.textInput = ''
760
750
  }
761
751
  },
762
- value: {
763
- immediate: true,
764
- handler(val) {
765
- if (this.value !== this.inputValue && !Number.isNaN(this.value)) {
766
- this.inputValue = this.value
767
- this.enteredValue = Number(this.value.toFixed(this.numberPrecision))
768
- }
769
- },
770
- },
752
+ },
753
+ created() {
754
+ if (this.value) {
755
+ this.textInput = numberToString({
756
+ value: this.value,
757
+ numberPrecision: this.numberPrecision,
758
+ minDecimals: this.minDecimals,
759
+ })
760
+ } else if (this.defaultNumber !== null) {
761
+ this.textInput = numberToString({
762
+ value: this.defaultNumber,
763
+ numberPrecision: this.numberPrecision,
764
+ minDecimals: this.minDecimals,
765
+ })
766
+ } else if (this.minNumber !== null) {
767
+ this.textInput = numberToString({
768
+ value: this.minNumber,
769
+ numberPrecision: this.numberPrecision,
770
+ minDecimals: this.minDecimals,
771
+ })
772
+ }
771
773
  },
772
774
  mounted() {
773
775
  if (this.focus) {
@@ -806,28 +808,29 @@
806
808
  }
807
809
  },
808
810
  onEnterPress() {
809
- this.$emit(EVENT_TYPES.PRESS_ENTER)
811
+ this.$emit('on-enter-click')
810
812
  this.$refs.inputField1.$el.blur()
811
813
  },
812
- onChangeHandler(value) {
813
- if (isNaN(value) || value === '') {
814
- value = this.defaultNumber
814
+ onChangeHandler(event) {
815
+ if (isNaN(event) || event === '') {
816
+ event = this.defaultNumber
815
817
  ? this.defaultNumber
816
818
  : this.minNumber || this.minNumber === 0
817
819
  ? this.minNumber
818
- : value
820
+ : event
819
821
  }
820
822
  if (!this.allowNegative) {
821
- value = Math.abs(value)
823
+ event = Math.abs(event)
822
824
  }
823
- value = parseFloat(value)
825
+ event = parseFloat(event)
824
826
  // Need to return an integer rather than a string
825
- return parseFloat(value)
827
+ this.$emit('input-change', event)
826
828
  },
827
- onEvaluateCode(value) {
829
+ onEvaluateCode(event) {
828
830
  // function to perform math on the code
829
831
  // filter the string in case of any malicious content
830
- let filtered = value.replace('(auto)', '').replace(/[^-()\d/*+.,]/g, '')
832
+ const val = event.target.value
833
+ let filtered = val.replace('(auto)', '').replace(/[^-()\d/*+.,]/g, '')
831
834
  filtered = filtered.split(/([-+*/()])/)
832
835
  let formatted = filtered.map((item) => {
833
836
  if (
@@ -885,32 +888,48 @@
885
888
  return array
886
889
  },
887
890
  onInput(event) {
888
- console.log('onInput', event.target.value)
889
- this.enteredValue = event.target.value
890
- if (!this.isFocused || this.enteredValue === this.inputValue) {
891
+ if (!this.isFocused) {
891
892
  return
892
893
  }
894
+ if (event.target.value === '') {
895
+ this.$emit('on-input', '')
896
+ }
893
897
  let evaluatedVal
894
898
  try {
895
- evaluatedVal = this.onEvaluateCode(String(this.enteredValue))
899
+ evaluatedVal = this.onEvaluateCode(event)
896
900
  } finally {
897
- this.inputValue = this.onChangeHandler(evaluatedVal)
898
-
899
- if (this.isFocused && typeof this.enteredValue !== 'number') {
900
- this.$emit(EVENT_TYPES.INPUT_CHANGE, this.inputValue)
901
+ if (evaluatedVal && this.value != evaluatedVal) {
902
+ this.$emit('on-input', evaluatedVal)
901
903
  }
902
904
  }
903
905
  this.textInput = evaluatedVal
904
906
  },
905
907
  onInputBlur(e) {
906
908
  this.isFocused = false
907
- if (!Number.isNaN(this.inputValue)) {
908
- this.enteredValue = this.inputValue
909
+ let value = e.target.value
910
+ let evaluatedInput = this.onEvaluateCode(e)
911
+ this.onChangeHandler(evaluatedInput ? evaluatedInput : value)
912
+ if ((evaluatedInput && value.length) || this.minNumber !== null) {
913
+ this.textInput = numberToString({
914
+ value:
915
+ evaluatedInput && value.length
916
+ ? evaluatedInput
917
+ : this.defaultNumber
918
+ ? this.defaultNumber
919
+ : this.minNumber,
920
+ numberPrecision: this.numberPrecision,
921
+ minDecimals: this.minDecimals,
922
+ })
909
923
  }
910
- this.$emit(
911
- EVENT_TYPES.INPUT_BLUR,
912
- Number(this.onEvaluateCode(String(this.inputValue)))
913
- )
924
+ let adjustedMinValue =
925
+ evaluatedInput && evaluatedInput.length
926
+ ? evaluatedInput
927
+ : this.defaultNumber
928
+ ? this.defaultNumber
929
+ : this.minNumber || this.minNumber === 0
930
+ ? this.minNumber
931
+ : ''
932
+ this.$emit('input-blur', adjustedMinValue)
914
933
  },
915
934
  focusInput() {
916
935
  if (this.disabled) {
@@ -920,7 +939,7 @@
920
939
  this.$nextTick(() => {
921
940
  this.$refs.inputField1.$el.select()
922
941
  })
923
- this.$emit(EVENT_TYPES.INPUT_FOCUS, this.inputValue)
942
+ this.$emit('input-focus')
924
943
  },
925
944
  blurInput() {
926
945
  if (this.disabled) {
@@ -940,7 +959,7 @@
940
959
  : this.minNumber || this.minNumber === 0
941
960
  ? this.minNumber
942
961
  : ''
943
- if (adjustedMinValue || adjustedMinValue === 0) {
962
+ if ((adjustedMinValue || adjustedMinValue === 0) && !this.isFocused) {
944
963
  let input = this.numberToStringEnabled
945
964
  ? numberToString({
946
965
  value: adjustedMinValue,
@@ -953,8 +972,6 @@
953
972
  return input + ' ' + unit
954
973
  } else if (!adjustedMinValue && adjustedMinValue !== 0) {
955
974
  return ''
956
- } else if (this.isFocused) {
957
- return value
958
975
  } else {
959
976
  return this.numberToStringEnabled
960
977
  ? numberToString({
@@ -975,7 +992,14 @@
975
992
  e.preventDefault()
976
993
  let value = parseFloat(this.value || 0)
977
994
  value += parseFloat(this.interactionStep) * parseInt(e.movementX)
978
- this.$emit(EVENT_TYPES.INPUT_DRAG, this.onChangeHandler(value))
995
+ this.$emit('on-input-drag', value)
996
+
997
+ this.textInput = numberToString({
998
+ value: value && value.length ? value : this.minNumber,
999
+ numberPrecision: this.numberPrecision,
1000
+ minDecimals: this.minDecimals,
1001
+ })
1002
+ //this.value=value
979
1003
  },
980
1004
  stopInteract(e) {
981
1005
  e.preventDefault()
@@ -983,9 +1007,6 @@
983
1007
  window.removeEventListener('mouseup', this.stopInteract, false)
984
1008
  this.blurInput()
985
1009
  },
986
- handleSelectChange(value) {
987
- this.$emit(EVENT_TYPES.SELECT_CHANGE, value)
988
- },
989
1010
  },
990
1011
  }
991
1012
  </script>
@@ -1,8 +1,8 @@
1
1
  <template>
2
2
  <TitleWrap data-test-id="page_wrapper" :has-info-text="!!infoText">
3
3
  <TitleText
4
- data-test-id="page_title_text"
5
4
  :color="color"
5
+ data-test-id="page_title_text"
6
6
  :font-size="fontSize"
7
7
  :uppercase="uppercase"
8
8
  >
@@ -10,8 +10,8 @@
10
10
  </TitleText>
11
11
  <InfoText
12
12
  v-if="!!infoText"
13
- data-test-id="page_title_tooltip"
14
13
  :align-arrow="infoAlign"
14
+ data-test-id="page_title_tooltip"
15
15
  :text="infoText"
16
16
  />
17
17
  </TitleWrap>
@@ -44,9 +44,8 @@
44
44
  const titleAttrs = { color: String, fontSize: String, uppercase: Boolean }
45
45
  const TitleText = styled('span', titleAttrs)`
46
46
  color: ${(props) => (props.color ? props.color : props.theme.colors.black)};
47
- font-weight: bold;
47
+ font-weight: 500;
48
48
  font-size: ${(props) => (props.fontSize ? props.fontSize : '20px')};
49
- text-transform: ${(props) => (props.uppercase ? 'uppercase' : 'none')};
50
49
  `
51
50
 
52
51
  export default {
@@ -94,7 +94,7 @@ export const stringToNumber = ({
94
94
 
95
95
  export const numberToString = ({ value, numberPrecision, minDecimals }) => {
96
96
  const minimumRounding = minDecimals ? minDecimals : 0
97
- value = !Number.isNaN(parseFloat(value)) ? parseFloat(value) : 0
97
+ value = parseFloat(value)
98
98
  return value.toLocaleString(langForLocaleString(), {
99
99
  minimumFractionDigits: minimumRounding, // removing this for now. Why do we need this to be a minimum amount?
100
100
  maximumFractionDigits: