Skip to content

Commit 4cd70e4

Browse files
committed
New feature - Slicer, SlicerPreview - Add focus-on-drag option
1 parent 377b1e3 commit 4cd70e4

17 files changed

+203
-14
lines changed

TestingArena/ArenaVueUiCandlestick.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,9 @@ const model = ref([
140140
{ key: 'style.zoom.endIndex', def: null, type: 'number', min: 0, max: 1000},
141141
{ key: 'style.zoom.enableRangeHandles', def: true, type: 'chexkbox'},
142142
{ key: 'style.zoom.enableSelectionDrag', def: true, type: 'chexkbox'},
143-
143+
{ key: 'style.zoom.focusOnDrag', def: true, type: 'checkbox'},
144+
{ key: 'style.zoom.focusRangeRatio', def: 0.2, type: 'number', min: 0.1, max: 0.9, step: 0.1},
145+
144146
{ key: 'style.title.text', def: 'At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis', type: 'text'},
145147
{ key: 'style.title.color', def: '#1A1A1A', type: 'color'},
146148
{ key: 'style.title.fontSize', def: 20, type: 'number', min: 8, max: 48},

TestingArena/ArenaVueUiDonutEvolution.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@ const model = ref([
153153
{ key: 'style.chart.zoom.endIndex', def: null, type: 'number', min: 0, max: 100},
154154
{ key: 'style.chart.zoom.enableRangeHandles', def: true, type: 'checkbox'},
155155
{ key: 'style.chart.zoom.enableSelectionDrag', def: true, type: 'checkbox'},
156+
{ key: 'style.chart.zoom.focusOnDrag', def: true, type: 'checkbox'},
157+
{ key: 'style.chart.zoom.focusRangeRatio', def: 0.2, type: 'number', min: 0.1, max: 0.9, step: 0.1},
156158
157159
{ key: 'table.show', def: false, type: 'checkbox'},
158160
{ key: 'table.useDialog', def: true, type: 'checkbox'},

TestingArena/ArenaVueUiQuickChart.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ function alterDataset() {
134134
}
135135
136136
137-
const selectedSerie = ref('shortObjectMixed');
137+
const selectedSerie = ref('longArray');
138138
139139
const model = ref([
140140
{ key: 'debug', def: true, type: 'checkbox'},
@@ -249,6 +249,8 @@ const model = ref([
249249
{ key: 'zoomEndIndex', def: null, type: 'number', min: 0, max: 100},
250250
{ key: 'zoomEnableRangeHandles', def: true, type: 'chexkbox'},
251251
{ key: 'zoomEnableSelectionDrag', def: true, type: 'chexkbox'},
252+
{ key: 'zoomFocusOnDrag', def: true, type: 'checkbox'},
253+
{ key: 'zoomFocusRangeRatio', def: 0.2, type: 'number', min: 0.1, max: 0.9, step: 0.1},
252254
]);
253255
254256
const themeOptions = ref([

TestingArena/ArenaVueUiStackbar.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ const model = ref([
126126
{ key: 'style.chart.zoom.endIndex', def: null, type: 'number', min: 0, max: 100},
127127
{ key: 'style.chart.zoom.enableRangeHandles', def: true, type: 'chexkbox'},
128128
{ key: 'style.chart.zoom.enableSelectionDrag', def: true, type: 'chexkbox'},
129+
{ key: 'style.chart.zoom.focusOnDrag', def: true, type: 'checkbox'},
130+
{ key: 'style.chart.zoom.focusRangeRatio', def: 0.2, type: 'number', min: 0.1, max: 0.9, step: 0.1},
129131
130132
{ key: 'style.chart.tooltip.show', def: true, type: 'checkbox'},
131133
{ key: 'style.chart.tooltip.backgroundColor', def: '#FFFFFF', type: 'color'},

TestingArena/ArenaVueUiXy.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,8 @@ const model = ref([
658658
{ key: 'chart.zoom.useResetSlot', def: false, type: 'checkbox' },
659659
{ key: 'chart.zoom.enableRangeHandles', def: true, type: 'chexkbox' },
660660
{ key: 'chart.zoom.enableSelectionDrag', def: true, type: 'checkbox' },
661+
{ key: 'chart.zoom.focusOnDrag', def: true, type: 'checkbox'},
662+
{ key: 'chart.zoom.focusRangeRatio', def: 0.2, type: 'number', min: 0.1, max: 0.9 },
661663
662664
{ key: 'chart.zoom.minimap.show', def: true, type: 'checkbox' },
663665
{ key: 'chart.zoom.minimap.smooth', def: false, type: 'checkbox' },
@@ -669,10 +671,10 @@ const model = ref([
669671
{ key: 'chart.zoom.minimap.verticalHandles', def: false, type: 'checkbox' },
670672
{ key: 'chart.zoom.minimap.compact', def: true, type: 'checkbox' },
671673
{ key: 'chart.zoom.minimap.merged', def: false, type: 'checkbox' },
672-
674+
673675
{ key: 'chart.zoom.startIndex', def: null, type: 'number', min: 0, max: 100 },
674676
{ key: 'chart.zoom.endIndex', def: null, type: 'number', min: 0, max: 100 },
675-
{ key: 'chart.zoom.preview.enable', def: true, type: 'checkbox'},
677+
{ key: 'chart.zoom.preview.enable', def: false, type: 'checkbox'},
676678
{ key: 'chart.zoom.preview.stroke', def: '#1f77b4', type: 'color'},
677679
{ key: 'chart.zoom.preview.fill', def: '#1f77b420', type: 'color'},
678680
{ key: 'chart.zoom.preview.strokeDasharray', def: 0, type: 'number', min: 0, max: 12},

TestingArena/ArenaVueUiXyCanvas.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ const model = ref([
110110
{ key: 'style.chart.zoom.endIndex', def: null, type: 'number', min: 0, max: 1000},
111111
{ key: 'style.chart.zoom.enableRangeHandles', def: true, type: 'checkbox'},
112112
{ key: 'style.chart.zoom.enableSelectionDrag', def: true, type: 'checkbox'},
113+
{ key: 'style.chart.zoom.focusOnDrag', def: true, type: 'checkbox'},
114+
{ key: 'style.chart.zoom.focusRangeRatio', def: 0.2, type: 'number', min: 0.1, max: 0.9, step: 0.1},
113115
114116
{ key: 'style.chart.selector.show', def: true, type: 'checkbox' },
115117
{ key: 'style.chart.selector.color', def: '#1A1A1A', type: 'color' },

src/atoms/Slicer.vue

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
createStraightPath,
1616
createStraightPathWithCuts,
1717
createUid,
18+
triggerEvent,
1819
XMLNS,
1920
} from '../lib';
2021
import { debounce, throttle } from '../canvas-lib';
@@ -147,6 +148,14 @@ const props = defineProps({
147148
cutNullValues: {
148149
type: Boolean,
149150
default: false,
151+
},
152+
focusOnDrag: {
153+
type: Boolean,
154+
default: false,
155+
},
156+
focusRangeRatio: {
157+
type: Number,
158+
default: 0.1
150159
}
151160
});
152161
@@ -616,6 +625,10 @@ const currentRange = computed(() => {
616625
return props.valueEnd - props.valueStart;
617626
});
618627
628+
const isZoom = computed(() => {
629+
return currentRange.value < (props.max - props.min);
630+
});
631+
619632
const isDragging = ref(false);
620633
let initialMouseX = ref(null);
621634
@@ -645,7 +658,24 @@ let activeEndEvent = null;
645658
let activeMoveHandler = null;
646659
let activeEndHandler = null;
647660
648-
const startDragging = (event) => {
661+
const dragStartIndex = ref(props.min);
662+
663+
function clientXToIndex(clientX) {
664+
if (!zoomWrapper.value) return props.min;
665+
666+
const rect = zoomWrapper.value.getBoundingClientRect();
667+
const left = rect.left + TRACK_PADDING / 2;
668+
const right = rect.right - TRACK_PADDING / 2;
669+
const trackWidth = Math.max(1, right - left);
670+
671+
const x = Math.max(left, Math.min(clientX, right));
672+
const pct = (x - left) / trackWidth;
673+
674+
const span = Math.max(1, props.max - props.min);
675+
return Math.round(props.min + pct * span);
676+
}
677+
678+
const startDragging = async (event) => {
649679
showTooltip.value = true;
650680
if (!props.enableSelectionDrag) return;
651681
@@ -663,6 +693,27 @@ const startDragging = (event) => {
663693
const x = isTouch ? (touch0 ? touch0.clientX : 0) : event.clientX;
664694
initialMouseX.value = x;
665695
dragStartX.value = x;
696+
697+
if (props.focusOnDrag && !isZoom.value && zoomWrapper.value) {
698+
dragStartIndex.value = clientXToIndex(x);
699+
const ratio = Math.min(0.95, Math.max(0.05, props.focusRangeRatio));
700+
701+
const total = props.max - props.min;
702+
const span = Math.max(1, Math.round(total * ratio));
703+
const half = Math.floor(span / 2);
704+
705+
let focusStart = dragStartIndex.value - half;
706+
focusStart = Math.max(props.min, Math.min(focusStart, props.max - span));
707+
const focusEnd = Math.min(props.max, focusStart + span);
708+
709+
setStartValue(focusStart);
710+
setEndValue(focusEnd);
711+
712+
triggerEvent(zoomWrapper.value, 'mouseup');
713+
await nextTick();
714+
triggerEvent(zoomWrapper.value, 'mousedown', { clientX: x });
715+
}
716+
666717
dragStartStart.value = Number(startValue.value);
667718
dragStartEnd.value = Number(endValue.value);
668719
ippAtStart.value = indicesPerPixel.value;
@@ -700,14 +751,20 @@ function handleTouchDragging(event) {
700751
701752
function updateDragging(currentX) {
702753
if (!isDragging.value) return;
703-
const dx = currentX - dragStartX.value;
704-
const shift = dx * ippAtStart.value;
705-
let newStart = Math.round(dragStartStart.value + shift);
706-
newStart = Math.max(props.min, Math.min(newStart, props.max - currentRange.value));
707-
const newEnd = newStart + currentRange.value;
754+
const i0 = clientXToIndex(dragStartX.value);
755+
const i1 = clientXToIndex(currentX);
756+
const deltaIdx = i1 - i0;
757+
const span = Math.max(1, dragStartEnd.value - dragStartStart.value);
758+
let newStart = Math.round(dragStartStart.value + deltaIdx);
759+
const minStart = props.min;
760+
const maxStart = props.max - span;
761+
if (newStart < minStart) newStart = minStart;
762+
if (newStart > maxStart) newStart = maxStart;
763+
const newEnd = newStart + span;
708764
setStartValue(newStart);
709765
setEndValue(newEnd);
710766
}
767+
711768
function stopDragging() {
712769
endDragging();
713770
}

src/atoms/SlicerPreview.vue

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
createStraightPathWithCuts,
1717
createUid,
1818
isFunction,
19+
triggerEvent,
1920
XMLNS,
2021
} from '../lib';
2122
import { debounce, throttle } from '../canvas-lib';
@@ -171,6 +172,14 @@ const props = defineProps({
171172
cutNullValues: {
172173
type: Boolean,
173174
default: false,
175+
},
176+
focusOnDrag: {
177+
type: Boolean,
178+
default: false,
179+
},
180+
focusRangeRatio: {
181+
type: Number,
182+
default: 0.1
174183
}
175184
});
176185
@@ -678,6 +687,10 @@ const currentRange = computed(() => {
678687
return props.valueEnd - props.valueStart;
679688
});
680689
690+
const isZoom = computed(() => {
691+
return currentRange.value < (props.max - props.min);
692+
});
693+
681694
const isDragging = ref(false);
682695
let initialMouseX = ref(null);
683696
@@ -706,7 +719,24 @@ let activeEndEvent = null;
706719
let activeMoveHandler = null;
707720
let activeEndHandler = null;
708721
709-
const startDragging = (event) => {
722+
const dragStartIndex = ref(props.min);
723+
724+
function clientXToIndex(clientX) {
725+
if (!zoomWrapper.value) return props.min;
726+
727+
const rect = zoomWrapper.value.getBoundingClientRect();
728+
const left = rect.left + TRACK_PADDING / 2;
729+
const right = rect.right - TRACK_PADDING / 2;
730+
const trackWidth = Math.max(1, right - left);
731+
732+
const x = Math.max(left, Math.min(clientX, right));
733+
const pct = (x - left) / trackWidth;
734+
735+
const span = Math.max(1, props.max - props.min);
736+
return Math.round(props.min + pct * span);
737+
}
738+
739+
const startDragging = async (event) => {
710740
isRanging.value = true;
711741
712742
showTooltip.value = true;
@@ -728,6 +758,29 @@ const startDragging = (event) => {
728758
const x = isTouch ? (touch0 ? touch0.clientX : 0) : event.clientX;
729759
initialMouseX.value = x;
730760
dragStartX.value = x;
761+
762+
if (props.focusOnDrag && !isZoom.value && zoomWrapper.value) {
763+
dragStartIndex.value = clientXToIndex(x);
764+
const ratio = Math.min(0.95, Math.max(0.05, props.focusRangeRatio));
765+
766+
const total = props.max - props.min;
767+
const span = Math.max(1, Math.round(total * ratio));
768+
const half = Math.floor(span / 2);
769+
770+
let focusStart = dragStartIndex.value - half;
771+
focusStart = Math.max(props.min, Math.min(focusStart, props.max - span));
772+
const focusEnd = Math.min(props.max, focusStart + span);
773+
774+
start.value = focusStart;
775+
end.value = focusEnd;
776+
emitFutureStart(focusStart);
777+
emitFutureEnd(focusEnd);
778+
779+
triggerEvent(zoomWrapper.value, 'mouseup');
780+
await nextTick();
781+
triggerEvent(zoomWrapper.value, 'mousedown', { clientX: x });
782+
}
783+
731784
dragStartStart.value = startValue.value;
732785
dragStartEnd.value = endValue.value;
733786
ippAtStart.value = indicesPerPixel.value;
@@ -765,9 +818,10 @@ function handleTouchDragging(event) {
765818
766819
function updateDragging(currentX) {
767820
if (!isDragging.value) return;
768-
const dx = currentX - dragStartX.value;
769-
const shift = dx * ippAtStart.value;
770-
let newStart = Math.round(dragStartStart.value + shift);
821+
const i0 = clientXToIndex(dragStartX.value);
822+
const i1 = clientXToIndex(currentX);
823+
const deltaIdx = i1 - i0;
824+
let newStart = Math.round(dragStartStart.value + deltaIdx);
771825
newStart = Math.max(props.min, Math.min(newStart, props.max - currentRange.value));
772826
const newEnd = newStart + currentRange.value;
773827
start.value = newStart;

src/components/vue-ui-candlestick.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1322,6 +1322,8 @@ defineExpose({
13221322
:refreshEndPoint="FINAL_CONFIG.style.zoom.endIndex !== null ? FINAL_CONFIG.style.zoom.endIndex + 1 : len"
13231323
:enableRangeHandles="FINAL_CONFIG.style.zoom.enableRangeHandles"
13241324
:enableSelectionDrag="FINAL_CONFIG.style.zoom.enableSelectionDrag"
1325+
:focusOnDrag="FINAL_CONFIG.style.zoom.focusOnDrag"
1326+
:focusRangeRatio="FINAL_CONFIG.style.zoom.focusRangeRatio"
13251327
@reset="refreshSlicer"
13261328
>
13271329
<template #reset-action="{ reset }">

src/components/vue-ui-donut-evolution.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1396,6 +1396,8 @@ defineExpose({
13961396
:refreshEndPoint="FINAL_CONFIG.style.chart.zoom.endIndex !== null ? FINAL_CONFIG.style.chart.zoom.endIndex + 1 : maxLength"
13971397
:enableRangeHandles="FINAL_CONFIG.style.chart.zoom.enableRangeHandles"
13981398
:enableSelectionDrag="FINAL_CONFIG.style.chart.zoom.enableSelectionDrag"
1399+
:focusOnDrag="FINAL_CONFIG.style.chart.zoom.focusOnDrag"
1400+
:focusRangeRatio="FINAL_CONFIG.style.chart.zoom.focusRangeRatio"
13991401
@reset="refreshSlicer"
14001402
>
14011403
<template #reset-action="{ reset }">

0 commit comments

Comments
 (0)