Skip to content

Commit 5fdb3f8

Browse files
committed
Improvement - Slicer - Merge tooltips when they collide
1 parent 0000f2f commit 5fdb3f8

File tree

2 files changed

+130
-21
lines changed

2 files changed

+130
-21
lines changed

src/atoms/Slicer.cy.js

Lines changed: 64 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { ref, reactive } from "vue";
22
import Slicer from "./Slicer.vue";
33

4+
const ds = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1]
5+
46
describe("<Slicer />", () => {
57
function mountSlicer() {
68
const FINAL_CONFIG = reactive({
@@ -23,7 +25,9 @@ describe("<Slicer />", () => {
2325
grid: {
2426
xAxis: {
2527
dataLabels: {
26-
values: ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL"],
28+
values: ds.map((d,i) => {
29+
return `____ ${i} ____`
30+
}),
2731
},
2832
},
2933
},
@@ -32,9 +36,9 @@ describe("<Slicer />", () => {
3236
},
3337
});
3438

35-
const maxLength = ref(6);
39+
const maxLength = ref(ds.length);
3640
const slicerStep = ref(1);
37-
const slicer = reactive({ start: 0, end: 6 });
41+
const slicer = reactive({ start: 0, end: ds.length });
3842

3943
return cy.mount({
4044
components: { Slicer },
@@ -44,7 +48,7 @@ describe("<Slicer />", () => {
4448
maxLength,
4549
slicerStep,
4650
slicer,
47-
minimap: [0, 1000, 0, 1000, 0, 1000]
51+
minimap: ds
4852
};
4953
},
5054
template: `
@@ -83,8 +87,8 @@ describe("<Slicer />", () => {
8387
it('shows start & end labels on hover', () => {
8488
mountSlicer();
8589
cy.get('[data-cy="slicer"]').trigger('mouseenter')
86-
cy.get('[data-cy="slicer-label-left"]').as('left').should('exist').and('be.visible').and('contain', 'JAN')
87-
cy.get('[data-cy="slicer-label-right"]').as('right').should('exist').and('be.visible').and('contain', 'JUN')
90+
cy.get('[data-cy="slicer-label-left"]').as('left').should('exist').and('be.visible').and('contain', '____ 0 ____')
91+
cy.get('[data-cy="slicer-label-right"]').as('right').should('exist').and('be.visible').and('contain', `____ ${ds.length-1} ____`)
8892
cy.get('[data-cy="slicer"]').trigger('mouseleave')
8993
cy.get('@right').should('not.be.visible')
9094
cy.get('@left').should('not.be.visible')
@@ -99,14 +103,14 @@ describe("<Slicer />", () => {
99103
.trigger("input", { force: true });
100104

101105
cy.get('[data-cy="slicer-handle-right"]')
102-
.invoke("val", 5)
106+
.invoke("val", 8)
103107
.trigger("input", { force: true });
104108

105109
cy.wrap(slicer).should('have.property', 'start', 1);
106-
cy.wrap(slicer).should('have.property', 'end', 5);
110+
cy.wrap(slicer).should('have.property', 'end', 8);
107111
cy.get('[data-cy="slicer"]').trigger('mouseenter')
108-
cy.get('[data-cy="slicer-label-left"]').as('left').should('exist').and('be.visible').and('contain', 'FEB')
109-
cy.get('[data-cy="slicer-label-right"]').as('right').should('exist').and('be.visible').and('contain', 'MAY')
112+
cy.get('[data-cy="slicer-label-left"]').as('left').should('exist').and('be.visible').and('contain', '____ 1 ____')
113+
cy.get('[data-cy="slicer-label-right"]').as('right').should('exist').and('be.visible').and('contain', '____ 7 ____')
110114
});
111115
});
112116

@@ -118,7 +122,7 @@ describe("<Slicer />", () => {
118122
.trigger("input", { force: true });
119123

120124
cy.get('[data-cy="slicer-handle-right"]')
121-
.invoke("val", 4)
125+
.invoke("val", 10)
122126
.trigger("input", { force: true });
123127

124128
cy.wait(100)
@@ -127,9 +131,9 @@ describe("<Slicer />", () => {
127131
.trigger('mousemove', {force: true, clientX: 400 })
128132

129133
cy.wrap(slicer).should('have.property', 'start', 3);
130-
cy.wrap(slicer).should('have.property', 'end', 5);
131-
cy.get('[data-cy="slicer-label-left"]').as('left').should('exist').and('be.visible').and('contain', 'APR')
132-
cy.get('[data-cy="slicer-label-right"]').as('right').should('exist').and('be.visible').and('contain', 'MAY')
134+
cy.wrap(slicer).should('have.property', 'end', 11);
135+
cy.get('[data-cy="slicer-label-left"]').as('left').should('exist').and('be.visible').and('contain', '____ 3 ____')
136+
cy.get('[data-cy="slicer-label-right"]').as('right').should('exist').and('be.visible').and('contain', '____ 10 ____')
133137
})
134138
})
135139

@@ -141,7 +145,7 @@ describe("<Slicer />", () => {
141145
.trigger("input", { force: true });
142146

143147
cy.get('[data-cy="slicer-handle-right"]')
144-
.invoke("val", 4)
148+
.invoke("val", 10)
145149
.trigger("input", { force: true });
146150

147151
cy.wait(100)
@@ -150,9 +154,51 @@ describe("<Slicer />", () => {
150154
.trigger('mousemove', {force: true, clientX: 400 })
151155

152156
cy.wrap(slicer).should('have.property', 'start', 3);
153-
cy.wrap(slicer).should('have.property', 'end', 5);
154-
cy.get('[data-cy="slicer-label-left"]').as('left').should('exist').and('be.visible').and('contain', 'APR')
155-
cy.get('[data-cy="slicer-label-right"]').as('right').should('exist').and('be.visible').and('contain', 'MAY')
157+
cy.wrap(slicer).should('have.property', 'end', 11);
158+
cy.get('[data-cy="slicer-label-left"]').as('left').should('exist').and('be.visible').and('contain', '____ 3 ____')
159+
cy.get('[data-cy="slicer-label-right"]').as('right').should('exist').and('be.visible').and('contain', '____ 10 ____')
160+
})
161+
})
162+
163+
it('merges tooltips when they collide on the same index', () => {
164+
mountSlicer().then(cmp => {
165+
cy.get('[data-cy="slicer-handle-left"]')
166+
.invoke("val", 2)
167+
.trigger("input", { force: true });
168+
169+
cy.get('[data-cy="slicer-handle-right"]')
170+
.invoke("val", 2)
171+
.trigger("input", { force: true });
172+
173+
cy.wait(100)
174+
cy.get('[data-cy="slicer-range-highlight"]')
175+
.trigger('mousedown', { force: true })
176+
.trigger('mousemove', {force: true, clientX: 400 })
177+
178+
cy.get('[data-cy="slicer-label-merged"]').should('be.visible').and('contain', '____ 3 ____')
179+
cy.get('[data-cy="slicer-label-left"]').should('not.be.visible')
180+
cy.get('[data-cy="slicer-label-right"]').should('not.be.visible')
181+
})
182+
})
183+
184+
it('merges the two labels in a single tooltip when tooltips collide', () => {
185+
mountSlicer().then(cmp => {
186+
cy.get('[data-cy="slicer-handle-left"]')
187+
.invoke("val", 2)
188+
.trigger("input", { force: true });
189+
190+
cy.get('[data-cy="slicer-handle-right"]')
191+
.invoke("val", 4)
192+
.trigger("input", { force: true });
193+
194+
cy.wait(100)
195+
cy.get('[data-cy="slicer-range-highlight"]')
196+
.trigger('mousedown', { force: true })
197+
.trigger('mousemove', {force: true, clientX: 400 })
198+
199+
cy.get('[data-cy="slicer-label-merged"]').should('be.visible').and('contain', '____ 3 ____ - ____ 4 ____');
200+
cy.get('[data-cy="slicer-label-left"]').should('not.be.visible')
201+
cy.get('[data-cy="slicer-label-right"]').should('not.be.visible')
156202
})
157203
})
158204
});

src/atoms/Slicer.vue

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup>
2-
import { ref, computed, watch, onMounted, onBeforeUnmount, nextTick, onUpdated } from 'vue';
2+
import { ref, computed, watch, onMounted, onBeforeUnmount, nextTick, onUpdated, watchEffect } from 'vue';
33
import BaseIcon from './BaseIcon.vue';
44
import { useResponsive } from '../useResponsive';
55
import { throttle } from '../canvas-lib';
@@ -128,13 +128,20 @@ const highlightStyle = computed(() => {
128128
const range = props.max - props.min;
129129
const startPercent = ((startValue.value - props.min) / range) * 100;
130130
const endPercent = ((endValue.value - props.min) / range) * 100;
131+
const centerPercent = (startPercent + endPercent) / 2;
132+
const centerAdjust = overflowsRight.value
133+
? `calc(${centerPercent}% - ${mergeTooltip.value.width}px)`
134+
: overflowsLeft.value
135+
? `calc(${centerPercent}%)`
136+
: `calc(${centerPercent}% - ${mergeTooltip.value.width / 2}px)`
131137
132138
return {
133139
left: `${startPercent}%`,
134140
width: `${endPercent - startPercent}%`,
135141
background: props.selectColor,
136142
tooltipLeft: `calc(${startPercent}% - ${overflowsLeft.value ? 0 : tooltipLeftWidth.value / 2}px)`,
137143
tooltipRight: `calc(${endPercent}% - ${overflowsRight.value ? tooltipRightWidth.value : tooltipRightWidth.value / 2}px)`,
144+
tooltipCenter: centerAdjust,
138145
arrowLeft: !overflowsLeft.value,
139146
arrowRight: !overflowsRight.value
140147
};
@@ -427,6 +434,33 @@ function setLeftLabelZIndex(handle) {
427434
leftLabelZIndex.value = handle === 'start' ? 1 : 0
428435
}
429436
437+
const tooltipsCollide = ref(false);
438+
const mergeTooltip = ref({
439+
width:0,
440+
left: 0,
441+
})
442+
443+
watchEffect(async() => {
444+
const leftEl = tooltipLeft.value;
445+
const rightEl = tooltipRight.value;
446+
const __start = startValue.value; // required for reactivity
447+
const __end = endValue.value; // required for reactivity
448+
const wrapper = zoomWrapper.value;
449+
if (!leftEl || !rightEl || !wrapper) {
450+
tooltipsCollide.value = false;
451+
return;
452+
}
453+
await nextTick();
454+
const { x: leftX, width: leftW} = leftEl.getBoundingClientRect();
455+
const { x: rightX, width: rightW} = rightEl.getBoundingClientRect();
456+
const midX = (rightX + rightW / 2) - (((rightX + rightW / 2) - (leftX + leftW / 2)))
457+
tooltipsCollide.value = (leftX + leftW) > rightX;
458+
mergeTooltip.value = {
459+
width: leftW + rightW,
460+
left: midX - (leftW + rightW / 2)
461+
}
462+
});
463+
430464
onUpdated(() => {
431465
setTooltipLeft();
432466
setTooltipRight();
@@ -666,11 +700,35 @@ defineExpose({
666700
color: adaptColorToBackground(selectColor),
667701
backgroundColor: selectColor,
668702
border: `1px solid ${borderColor}`,
669-
zIndex: `${leftLabelZIndex + 4}`
703+
zIndex: `${leftLabelZIndex + 4}`,
704+
visibility: tooltipsCollide ? 'hidden' : 'visible'
670705
}"
671706
>
672707
{{ labelLeft }}
673708
</div>
709+
710+
<div
711+
v-if="tooltipsCollide"
712+
data-cy="slicer-label-merged"
713+
ref="tooltipMerge"
714+
:class="{
715+
'range-tooltip': true,
716+
'range-tooltip-visible': showTooltip,
717+
'range-tooltip-arrow': true,
718+
'range-tooltip-arrow-left': !highlightStyle.arrowLeft && !verticalHandles,
719+
'range-tooltip-arrow-right': !highlightStyle.arrowRight && !verticalHandles
720+
}"
721+
:style="{
722+
left: highlightStyle.tooltipCenter,
723+
width: mergeTooltip.width + 'px',
724+
color: adaptColorToBackground(selectColor),
725+
backgroundColor: selectColor,
726+
border: `1px solid ${borderColor}`,
727+
zIndex: '4'
728+
}"
729+
>
730+
{{ labelLeft === labelRight ? labelLeft : `${labelLeft} - ${labelRight}` }}
731+
</div>
674732

675733
<div
676734
v-if="labelRight"
@@ -687,7 +745,8 @@ defineExpose({
687745
color: adaptColorToBackground(selectColor),
688746
backgroundColor: selectColor,
689747
border: `1px solid ${borderColor}`,
690-
zIndex: '4'
748+
zIndex: '4',
749+
visibility: tooltipsCollide ? 'hidden' : 'visible'
691750
}"
692751
>
693752
{{ labelRight }}
@@ -917,12 +976,16 @@ input[type="range"]::-ms-thumb {
917976
.range-tooltip-arrow-left {
918977
&::after {
919978
left: 3px;
979+
right: auto;
980+
transform: none;
920981
}
921982
}
922983
923984
.range-tooltip-arrow-right {
924985
&::after {
925986
right: 3px;
987+
left: auto;
988+
transform: none;
926989
}
927990
}
928991

0 commit comments

Comments
 (0)