@eturnity/eturnity_reusable_components 8.16.2--EPDM-14330.4 → 8.16.2--EPDM-14330.6
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 +5 -0
- 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 +577 -0
- package/src/components/filterComponent/viewSort.vue +242 -12
- 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 +18 -2
- package/src/components/inputs/select/index.vue +109 -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,150 @@
|
|
1
|
+
<template>
|
2
|
+
<SelectionBoxWrapper
|
3
|
+
:transform="`translateX(${position}px)`"
|
4
|
+
:width="boxWidth + 'px'"
|
5
|
+
@mousedown="startDrag"
|
6
|
+
/>
|
7
|
+
</template>
|
8
|
+
|
9
|
+
<script setup>
|
10
|
+
import { ref, computed, onMounted } from 'vue'
|
11
|
+
import styled from 'vue3-styled-components'
|
12
|
+
|
13
|
+
const SelectionBoxWrapper = styled('div', {
|
14
|
+
width: String,
|
15
|
+
transform: String,
|
16
|
+
})`
|
17
|
+
position: absolute;
|
18
|
+
height: 100%;
|
19
|
+
background: rgba(151, 71, 255, 0.1);
|
20
|
+
cursor: grab;
|
21
|
+
z-index: 0;
|
22
|
+
pointer-events: auto;
|
23
|
+
width: ${(props) => props.width};
|
24
|
+
transform: ${(props) => props.transform};
|
25
|
+
&:active {
|
26
|
+
cursor: grabbing;
|
27
|
+
}
|
28
|
+
`
|
29
|
+
|
30
|
+
const props = defineProps({
|
31
|
+
barsToShow: {
|
32
|
+
type: Number,
|
33
|
+
required: true,
|
34
|
+
},
|
35
|
+
barWidth: {
|
36
|
+
type: Number,
|
37
|
+
required: true,
|
38
|
+
},
|
39
|
+
totalBars: {
|
40
|
+
type: Number,
|
41
|
+
required: true,
|
42
|
+
},
|
43
|
+
gap: {
|
44
|
+
type: Number,
|
45
|
+
default: 8,
|
46
|
+
},
|
47
|
+
containerWidth: {
|
48
|
+
type: Number,
|
49
|
+
required: true,
|
50
|
+
},
|
51
|
+
isScrollable: {
|
52
|
+
type: Boolean,
|
53
|
+
required: true,
|
54
|
+
},
|
55
|
+
})
|
56
|
+
|
57
|
+
// Padding applied to the selection box
|
58
|
+
const PADDING = 6
|
59
|
+
|
60
|
+
const emit = defineEmits(['update-selection', 'drag-end'])
|
61
|
+
|
62
|
+
// Statess
|
63
|
+
const position = ref(0)
|
64
|
+
const isDragging = ref(false)
|
65
|
+
const currentSelection = ref({ startIndex: 0, endIndex: props.barsToShow })
|
66
|
+
|
67
|
+
// Variables for tracking drag state
|
68
|
+
let startX = 0
|
69
|
+
let startPos = 0
|
70
|
+
|
71
|
+
const actualBarWidth = computed(() => {
|
72
|
+
if (props.isScrollable) {
|
73
|
+
return props.barWidth
|
74
|
+
}
|
75
|
+
|
76
|
+
return props.containerWidth / props.totalBars
|
77
|
+
})
|
78
|
+
|
79
|
+
const boxWidth = computed(() => {
|
80
|
+
if (props.isScrollable) {
|
81
|
+
return (
|
82
|
+
(props.barWidth + props.gap) * props.barsToShow -
|
83
|
+
props.gap +
|
84
|
+
PADDING * 2
|
85
|
+
)
|
86
|
+
}
|
87
|
+
|
88
|
+
return actualBarWidth.value * props.barsToShow
|
89
|
+
})
|
90
|
+
|
91
|
+
const calculateSelection = () => {
|
92
|
+
if (props.isScrollable) {
|
93
|
+
const startIndex = Math.floor(
|
94
|
+
(position.value + PADDING) / (props.barWidth + props.gap)
|
95
|
+
)
|
96
|
+
const endIndex = Math.min(startIndex + props.barsToShow, props.totalBars)
|
97
|
+
return { startIndex, endIndex }
|
98
|
+
}
|
99
|
+
|
100
|
+
const boxStart = position.value
|
101
|
+
const boxEnd = position.value + boxWidth.value
|
102
|
+
const sectionWidth = props.containerWidth / props.totalBars
|
103
|
+
|
104
|
+
const startIndex = Math.round(boxStart / sectionWidth)
|
105
|
+
const endIndex = Math.min(
|
106
|
+
Math.round(boxEnd / sectionWidth),
|
107
|
+
props.totalBars
|
108
|
+
)
|
109
|
+
return { startIndex, endIndex }
|
110
|
+
}
|
111
|
+
|
112
|
+
const startDrag = (e) => {
|
113
|
+
isDragging.value = true
|
114
|
+
startX = e.clientX
|
115
|
+
startPos = position.value
|
116
|
+
|
117
|
+
document.addEventListener('mousemove', handleDrag)
|
118
|
+
document.addEventListener('mouseup', stopDrag)
|
119
|
+
}
|
120
|
+
|
121
|
+
const handleDrag = (e) => {
|
122
|
+
if (!isDragging.value) return
|
123
|
+
|
124
|
+
const delta = e.clientX - startX
|
125
|
+
const newPosition = startPos + delta
|
126
|
+
|
127
|
+
// Constrain position within valid bounds
|
128
|
+
const maxPosition = props.containerWidth - boxWidth.value + PADDING * 2
|
129
|
+
position.value = Math.max(-PADDING, Math.min(newPosition, maxPosition))
|
130
|
+
|
131
|
+
// Update current selection and emit event
|
132
|
+
currentSelection.value = calculateSelection()
|
133
|
+
emit('update-selection', currentSelection.value)
|
134
|
+
}
|
135
|
+
|
136
|
+
const stopDrag = () => {
|
137
|
+
isDragging.value = false
|
138
|
+
document.removeEventListener('mousemove', handleDrag)
|
139
|
+
document.removeEventListener('mouseup', stopDrag)
|
140
|
+
|
141
|
+
emit('update-selection', currentSelection.value)
|
142
|
+
emit('drag-end')
|
143
|
+
}
|
144
|
+
|
145
|
+
onMounted(() => {
|
146
|
+
// Ensure we start with valid selection indices
|
147
|
+
currentSelection.value = { startIndex: 0, endIndex: props.barsToShow }
|
148
|
+
emit('update-selection', currentSelection.value)
|
149
|
+
})
|
150
|
+
</script>
|
@@ -0,0 +1,104 @@
|
|
1
|
+
import { computed } from 'vue'
|
2
|
+
|
3
|
+
export function useAxisCalculations(props, maxValue) {
|
4
|
+
const findNiceNumber = (value) => {
|
5
|
+
// Handle 0 or negative values
|
6
|
+
if (value <= 0) return 0
|
7
|
+
|
8
|
+
if (value < 1) {
|
9
|
+
return Math.ceil(value * 10) / 10
|
10
|
+
}
|
11
|
+
|
12
|
+
const exponent = Math.floor(Math.log10(value))
|
13
|
+
const factor = Math.pow(10, exponent)
|
14
|
+
const normalized = value / factor
|
15
|
+
|
16
|
+
const niceNumbers = [
|
17
|
+
1.0, 1.2, 1.5, 1.6, 2.0, 2.5, 3.0, 4.0, 5.0, 6.0, 8.0, 10.0,
|
18
|
+
]
|
19
|
+
const niceNormalized = niceNumbers.find((n) => n >= normalized) || 10.0
|
20
|
+
|
21
|
+
return niceNormalized * factor
|
22
|
+
}
|
23
|
+
|
24
|
+
// Calculate max value with padding, rounded to nice numbers
|
25
|
+
const paddedMaxValue = computed(() => {
|
26
|
+
const rawMax = maxValue.value
|
27
|
+
const withPadding = rawMax * 1.1
|
28
|
+
const niceNumber = findNiceNumber(withPadding)
|
29
|
+
|
30
|
+
return niceNumber
|
31
|
+
})
|
32
|
+
|
33
|
+
const calculateStepSize = (max) => {
|
34
|
+
const preferredDivisions = [5, 6, 7, 8, 9, 10]
|
35
|
+
|
36
|
+
for (const divisions of preferredDivisions) {
|
37
|
+
const roughStep = max / divisions
|
38
|
+
const niceStep = findNiceNumber(roughStep)
|
39
|
+
|
40
|
+
const numCompleteSteps = Math.floor(max / niceStep)
|
41
|
+
const actualMax = niceStep * numCompleteSteps
|
42
|
+
|
43
|
+
// Only use this step size if it gets us close to our target max
|
44
|
+
// and gives us the right number of ticks
|
45
|
+
if (
|
46
|
+
actualMax >= max * 0.95 && // Within 5% of target max
|
47
|
+
numCompleteSteps + 1 >= 5 &&
|
48
|
+
numCompleteSteps + 1 <= 10
|
49
|
+
) {
|
50
|
+
return niceStep
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
54
|
+
// If no perfect match found, use max/6
|
55
|
+
return findNiceNumber(max / 6)
|
56
|
+
}
|
57
|
+
|
58
|
+
const yAxisLabels = computed(() => {
|
59
|
+
const max = paddedMaxValue.value
|
60
|
+
|
61
|
+
if (max === 0) {
|
62
|
+
return [0]
|
63
|
+
}
|
64
|
+
|
65
|
+
const stepSize = calculateStepSize(max)
|
66
|
+
const labels = []
|
67
|
+
|
68
|
+
// Generate labels including 0 up to numSteps
|
69
|
+
const numSteps = Math.floor(max / stepSize)
|
70
|
+
for (let i = 0; i <= numSteps; i++) {
|
71
|
+
labels.push(i * stepSize)
|
72
|
+
}
|
73
|
+
|
74
|
+
// Ensure we always have at least 2 labels (0 and max)
|
75
|
+
if (labels.length < 2) {
|
76
|
+
labels.push(max)
|
77
|
+
}
|
78
|
+
|
79
|
+
return labels
|
80
|
+
})
|
81
|
+
|
82
|
+
const yAxisHeight = computed(() => {
|
83
|
+
return '100%'
|
84
|
+
})
|
85
|
+
|
86
|
+
const yAxisWidth = computed(() => {
|
87
|
+
return !!props.yAxisTitle || props.isBottomFieldsShown ? '70px' : '60px'
|
88
|
+
})
|
89
|
+
|
90
|
+
const isChartControlsShown = (position) => {
|
91
|
+
return (
|
92
|
+
props.chartControlsPosition === position &&
|
93
|
+
(props.isLegendShown || props.splitButtonOptions.length)
|
94
|
+
)
|
95
|
+
}
|
96
|
+
|
97
|
+
return {
|
98
|
+
yAxisLabels,
|
99
|
+
yAxisHeight,
|
100
|
+
yAxisWidth,
|
101
|
+
isChartControlsShown,
|
102
|
+
paddedMaxValue,
|
103
|
+
}
|
104
|
+
}
|
@@ -0,0 +1,114 @@
|
|
1
|
+
import { computed } from 'vue'
|
2
|
+
import theme from '../../../assets/theme'
|
3
|
+
|
4
|
+
export function useChartData(props, paddedMaxValue) {
|
5
|
+
const GRADIENT_COLORS = theme.chartGradients
|
6
|
+
|
7
|
+
// Get stacked colors based on number of segments
|
8
|
+
const getStackedColors = computed(() => {
|
9
|
+
const colorMap = new Map()
|
10
|
+
|
11
|
+
return (totalSegments) => {
|
12
|
+
if (colorMap.has(totalSegments)) {
|
13
|
+
return colorMap.get(totalSegments)
|
14
|
+
}
|
15
|
+
|
16
|
+
let colors
|
17
|
+
switch (totalSegments) {
|
18
|
+
case 2:
|
19
|
+
colors = [GRADIENT_COLORS.stacked[3], GRADIENT_COLORS.stacked[0]]
|
20
|
+
break
|
21
|
+
case 3:
|
22
|
+
colors = [
|
23
|
+
GRADIENT_COLORS.stacked[5],
|
24
|
+
GRADIENT_COLORS.stacked[3],
|
25
|
+
GRADIENT_COLORS.stacked[1],
|
26
|
+
]
|
27
|
+
break
|
28
|
+
case 4:
|
29
|
+
colors = [
|
30
|
+
GRADIENT_COLORS.stacked[5],
|
31
|
+
GRADIENT_COLORS.stacked[3],
|
32
|
+
GRADIENT_COLORS.stacked[1],
|
33
|
+
GRADIENT_COLORS.stacked[0],
|
34
|
+
]
|
35
|
+
break
|
36
|
+
case 5:
|
37
|
+
colors = [
|
38
|
+
GRADIENT_COLORS.stacked[5],
|
39
|
+
GRADIENT_COLORS.stacked[3],
|
40
|
+
GRADIENT_COLORS.stacked[2],
|
41
|
+
GRADIENT_COLORS.stacked[1],
|
42
|
+
GRADIENT_COLORS.stacked[0],
|
43
|
+
]
|
44
|
+
break
|
45
|
+
case 6:
|
46
|
+
colors = [...GRADIENT_COLORS.stacked].reverse()
|
47
|
+
break
|
48
|
+
default:
|
49
|
+
colors = [GRADIENT_COLORS.stacked[0]]
|
50
|
+
}
|
51
|
+
|
52
|
+
colorMap.set(totalSegments, colors)
|
53
|
+
return colors
|
54
|
+
}
|
55
|
+
})
|
56
|
+
|
57
|
+
// Normalize data for rendering
|
58
|
+
const normalizedData = computed(() => {
|
59
|
+
if (!props.data?.length) return []
|
60
|
+
|
61
|
+
if (props.series.length) {
|
62
|
+
const stackedColors = [
|
63
|
+
...getStackedColors.value(props.series.length),
|
64
|
+
].reverse()
|
65
|
+
return props.data.map((item) => {
|
66
|
+
let accumulated = 0
|
67
|
+
return {
|
68
|
+
label: item.label,
|
69
|
+
segments: [...props.series].reverse().map((series, index) => {
|
70
|
+
const value =
|
71
|
+
series.data.find((d) => d.label === item.label)?.value || 0
|
72
|
+
accumulated += value
|
73
|
+
return {
|
74
|
+
value,
|
75
|
+
percentage: Number(
|
76
|
+
((accumulated / paddedMaxValue.value) * 100).toFixed(4)
|
77
|
+
),
|
78
|
+
gradientFrom: stackedColors[index].from,
|
79
|
+
gradientTo: stackedColors[index].to,
|
80
|
+
name: series.name,
|
81
|
+
}
|
82
|
+
}),
|
83
|
+
total: accumulated,
|
84
|
+
}
|
85
|
+
})
|
86
|
+
}
|
87
|
+
|
88
|
+
return props.data.map((item) => ({
|
89
|
+
label: item.label,
|
90
|
+
segments: [
|
91
|
+
{
|
92
|
+
value: item.value,
|
93
|
+
percentage: Number(
|
94
|
+
((item.value / paddedMaxValue.value) * 100).toFixed(4)
|
95
|
+
),
|
96
|
+
gradientFrom: GRADIENT_COLORS.simple.from,
|
97
|
+
gradientTo: GRADIENT_COLORS.simple.to,
|
98
|
+
name: 'Value',
|
99
|
+
},
|
100
|
+
],
|
101
|
+
}))
|
102
|
+
})
|
103
|
+
|
104
|
+
// Calculate total value of segments
|
105
|
+
const getTotalSegmentValue = (segments) => {
|
106
|
+
return segments.reduce((sum, segment) => sum + segment.value, 0)
|
107
|
+
}
|
108
|
+
|
109
|
+
return {
|
110
|
+
normalizedData,
|
111
|
+
getStackedColors,
|
112
|
+
getTotalSegmentValue,
|
113
|
+
}
|
114
|
+
}
|
@@ -0,0 +1,61 @@
|
|
1
|
+
import { ref, onMounted, onUnmounted } from 'vue'
|
2
|
+
|
3
|
+
export function useChartScroll(
|
4
|
+
chartId,
|
5
|
+
isInputFocused,
|
6
|
+
focusedBarData,
|
7
|
+
showTooltipFromInput
|
8
|
+
) {
|
9
|
+
const chartContent = ref(null)
|
10
|
+
const chartContentWidth = ref(0)
|
11
|
+
|
12
|
+
const updateChartContentWidth = () => {
|
13
|
+
if (chartContent.value) {
|
14
|
+
chartContentWidth.value = chartContent.value.$el.offsetWidth
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
// Handle scroll from chart
|
19
|
+
const handleChartScroll = (event) => {
|
20
|
+
const fieldsContainer = document.querySelector(
|
21
|
+
`.fields-container-${chartId}`
|
22
|
+
)
|
23
|
+
if (fieldsContainer) {
|
24
|
+
fieldsContainer.scrollLeft = event.target.scrollLeft
|
25
|
+
}
|
26
|
+
|
27
|
+
if (isInputFocused.value && focusedBarData.value) {
|
28
|
+
showTooltipFromInput({
|
29
|
+
seriesName: focusedBarData.value.segments[0].name,
|
30
|
+
label: focusedBarData.value.label,
|
31
|
+
})
|
32
|
+
}
|
33
|
+
}
|
34
|
+
|
35
|
+
// Handle scroll from bottom fields
|
36
|
+
const handleBottomFieldsScroll = (scrollLeft) => {
|
37
|
+
const chartContainer = document.querySelector(
|
38
|
+
`.chart-scroll-container-${chartId}`
|
39
|
+
)
|
40
|
+
if (chartContainer) {
|
41
|
+
chartContainer.scrollLeft = scrollLeft
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
onMounted(() => {
|
46
|
+
updateChartContentWidth()
|
47
|
+
window.addEventListener('resize', updateChartContentWidth)
|
48
|
+
})
|
49
|
+
|
50
|
+
onUnmounted(() => {
|
51
|
+
window.removeEventListener('resize', updateChartContentWidth)
|
52
|
+
})
|
53
|
+
|
54
|
+
return {
|
55
|
+
chartContent,
|
56
|
+
chartContentWidth,
|
57
|
+
updateChartContentWidth,
|
58
|
+
handleChartScroll,
|
59
|
+
handleBottomFieldsScroll,
|
60
|
+
}
|
61
|
+
}
|
@@ -0,0 +1,75 @@
|
|
1
|
+
import { ref, computed, watch } from 'vue'
|
2
|
+
import theme from '../../../assets/theme'
|
3
|
+
|
4
|
+
export function useSelection(props, normalizedData, emit) {
|
5
|
+
const selectedIndices = ref({ start: 0, end: props.selectionSize })
|
6
|
+
const currentSelection = ref([])
|
7
|
+
|
8
|
+
watch(
|
9
|
+
() => [props.selectionSize, props.isSelectionEnabled],
|
10
|
+
([newSize, isEnabled]) => {
|
11
|
+
if (isEnabled) {
|
12
|
+
selectedIndices.value = {
|
13
|
+
start: 0,
|
14
|
+
end: newSize,
|
15
|
+
}
|
16
|
+
}
|
17
|
+
},
|
18
|
+
{ immediate: true }
|
19
|
+
)
|
20
|
+
|
21
|
+
// Update visual selection
|
22
|
+
const updateSelectedBars = ({ startIndex, endIndex }) => {
|
23
|
+
selectedIndices.value = { start: startIndex, end: endIndex }
|
24
|
+
currentSelection.value = normalizedData.value.slice(startIndex, endIndex)
|
25
|
+
}
|
26
|
+
|
27
|
+
const handleSelectionDragEnd = () => {
|
28
|
+
emit('selection-change', currentSelection.value)
|
29
|
+
}
|
30
|
+
|
31
|
+
// Check if bar is a selection boundary
|
32
|
+
const isSelectionBoundary = computed(() => {
|
33
|
+
return (index) => {
|
34
|
+
if (!props.isSelectionEnabled) return false
|
35
|
+
return (
|
36
|
+
index === selectedIndices.value.start ||
|
37
|
+
index === selectedIndices.value.end - 1
|
38
|
+
)
|
39
|
+
}
|
40
|
+
})
|
41
|
+
|
42
|
+
// Get segment gradient based on selection state
|
43
|
+
const getSegmentGradient = computed(() => {
|
44
|
+
return (index, segment) => {
|
45
|
+
if (!props.isSelectionEnabled) {
|
46
|
+
return {
|
47
|
+
from: segment.gradientFrom,
|
48
|
+
to: segment.gradientTo,
|
49
|
+
}
|
50
|
+
}
|
51
|
+
|
52
|
+
const isSelected =
|
53
|
+
index >= selectedIndices.value.start &&
|
54
|
+
index < selectedIndices.value.end
|
55
|
+
|
56
|
+
return isSelected
|
57
|
+
? {
|
58
|
+
from: segment.gradientFrom,
|
59
|
+
to: segment.gradientTo,
|
60
|
+
}
|
61
|
+
: {
|
62
|
+
from: theme.semanticColors.grey[500],
|
63
|
+
to: theme.semanticColors.grey[500],
|
64
|
+
}
|
65
|
+
}
|
66
|
+
})
|
67
|
+
|
68
|
+
return {
|
69
|
+
selectedIndices,
|
70
|
+
updateSelectedBars,
|
71
|
+
handleSelectionDragEnd,
|
72
|
+
isSelectionBoundary,
|
73
|
+
getSegmentGradient,
|
74
|
+
}
|
75
|
+
}
|
@@ -0,0 +1,100 @@
|
|
1
|
+
import { ref, onUnmounted } from 'vue'
|
2
|
+
import isObjectEqual from '../../../helpers/isObjectEqual'
|
3
|
+
|
4
|
+
export function useTooltip(chartId, normalizedData) {
|
5
|
+
const showTooltipContent = ref(false)
|
6
|
+
const tooltipData = ref(null)
|
7
|
+
const tooltipStyle = ref({})
|
8
|
+
|
9
|
+
const isInputFocused = ref(false)
|
10
|
+
const focusedBarData = ref(null)
|
11
|
+
const tooltipTimeout = ref(null)
|
12
|
+
|
13
|
+
const showTooltip = (item, event, series) => {
|
14
|
+
if (isInputFocused.value) return
|
15
|
+
|
16
|
+
if (!showTooltipContent.value) {
|
17
|
+
showTooltipContent.value = true
|
18
|
+
}
|
19
|
+
if (isObjectEqual(item, tooltipData.value)) return
|
20
|
+
|
21
|
+
tooltipData.value = { ...item }
|
22
|
+
|
23
|
+
const targetElement = series.length
|
24
|
+
? event.target
|
25
|
+
.closest('.bar-group')
|
26
|
+
.querySelector('.bar-segment:last-child')
|
27
|
+
: event.target
|
28
|
+
const rect = targetElement.getBoundingClientRect()
|
29
|
+
|
30
|
+
tooltipStyle.value = {
|
31
|
+
left: `${rect.left + rect.width / 2}px`,
|
32
|
+
top: `${rect.top - 0}px`,
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
const showTooltipFromInput = ({ seriesName, label }) => {
|
37
|
+
if (tooltipTimeout.value) {
|
38
|
+
clearTimeout(tooltipTimeout.value)
|
39
|
+
tooltipTimeout.value = null
|
40
|
+
}
|
41
|
+
|
42
|
+
isInputFocused.value = true
|
43
|
+
const barData = normalizedData.value.find((item) => item.label === label)
|
44
|
+
|
45
|
+
if (!barData) return
|
46
|
+
focusedBarData.value = barData
|
47
|
+
const barElement = document.querySelector(
|
48
|
+
`.barchart-${chartId} .bar-group:nth-child(${
|
49
|
+
normalizedData.value.indexOf(barData) + 1
|
50
|
+
})`
|
51
|
+
)
|
52
|
+
|
53
|
+
if (!barElement) return
|
54
|
+
// Get the last bar segment, samee as hover behavior
|
55
|
+
const targetElement = barElement.querySelector('.bar-segment:last-child')
|
56
|
+
const rect = targetElement.getBoundingClientRect()
|
57
|
+
|
58
|
+
tooltipStyle.value = {
|
59
|
+
left: `${rect.left + rect.width / 2}px`,
|
60
|
+
top: `${rect.top - 0}px`,
|
61
|
+
}
|
62
|
+
showTooltipContent.value = true
|
63
|
+
tooltipData.value = barData
|
64
|
+
}
|
65
|
+
|
66
|
+
// Small delay to prevent tooltip flicker when switching between inputs
|
67
|
+
const handleInputBlurAll = () => {
|
68
|
+
tooltipTimeout.value = setTimeout(() => {
|
69
|
+
hideTooltip(true)
|
70
|
+
isInputFocused.value = false
|
71
|
+
tooltipTimeout.value = null
|
72
|
+
}, 50)
|
73
|
+
}
|
74
|
+
|
75
|
+
const hideTooltip = (isFromInputBlur = false) => {
|
76
|
+
if (typeof isFromInputBlur !== 'boolean' && isInputFocused.value) {
|
77
|
+
return
|
78
|
+
}
|
79
|
+
|
80
|
+
showTooltipContent.value = false
|
81
|
+
}
|
82
|
+
|
83
|
+
onUnmounted(() => {
|
84
|
+
if (!tooltipTimeout.value) return
|
85
|
+
clearTimeout(tooltipTimeout.value)
|
86
|
+
tooltipTimeout.value = null
|
87
|
+
})
|
88
|
+
|
89
|
+
return {
|
90
|
+
showTooltipContent,
|
91
|
+
tooltipData,
|
92
|
+
tooltipStyle,
|
93
|
+
showTooltip,
|
94
|
+
hideTooltip,
|
95
|
+
isInputFocused,
|
96
|
+
focusedBarData,
|
97
|
+
showTooltipFromInput,
|
98
|
+
handleInputBlurAll,
|
99
|
+
}
|
100
|
+
}
|