@progress/kendo-charts 1.20.0-dev.202110131408 → 1.20.0-dev.202111081615

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.
@@ -22,12 +22,13 @@ import { ChartPane, ChartPlotArea, findAxisByName } from './api-elements';
22
22
 
23
23
  import { X, Y, VALUE, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_SERIES_OPACITY } from '../common/constants';
24
24
  import { addClass, Class, setDefaultOptions, deepExtend, defined, find, isObject, isFunction, elementSize, elementOffset,
25
- elementScale, elementStyles, eventCoordinates, bindEvents, unbindEvents, mousewheelDelta, FontLoader, inArray, last, round, HashMap } from '../common';
25
+ elementScale, elementStyles, eventCoordinates, bindEvents, unbindEvents, mousewheelDelta, FontLoader, inArray, last, round,
26
+ HashMap, valueOrDefault } from '../common';
26
27
 
27
28
  import { dateComparer } from '../date-utils';
28
29
 
29
30
  import { DRAG_START, DRAG, DRAG_END, ZOOM_START, ZOOM, ZOOM_END, SELECT_START, SELECT, SELECT_END, PLOT_AREA_HOVER, PLOT_AREA_LEAVE,
30
- RENDER, CATEGORY, PIE, DONUT, FUNNEL, COLUMN, MOUSEWHEEL, MOUSEWHEEL_DELAY, SHOW_TOOLTIP, SERIES_HOVER } from './constants';
31
+ RENDER, CATEGORY, PIE, DONUT, FUNNEL, COLUMN, MOUSEWHEEL, MOUSEWHEEL_DELAY, MOUSEWHEEL_ZOOM_RATE, SHOW_TOOLTIP, SERIES_HOVER } from './constants';
31
32
 
32
33
  import './animations';
33
34
  import './register-charts';
@@ -651,12 +652,13 @@ class Chart extends Class {
651
652
  this._gestureDistance = e.distance;
652
653
  const args = { delta: scaleDelta, axisRanges: axisRanges(this._plotArea.axes), originalEvent: e };
653
654
  if (this._zooming || !this.trigger(ZOOM_START, args)) {
655
+ const coords = this._eventCoordinates(e);
654
656
 
655
657
  if (!this._zooming) {
656
658
  this._zooming = true;
657
659
  }
658
660
 
659
- const ranges = args.axisRanges = mousewheelZoom.updateRanges(scaleDelta);
661
+ const ranges = args.axisRanges = mousewheelZoom.updateRanges(scaleDelta, coords);
660
662
  if (ranges && !this.trigger(ZOOM, args)) {
661
663
  mousewheelZoom.zoom();
662
664
  }
@@ -787,6 +789,12 @@ class Chart extends Class {
787
789
  }
788
790
  }
789
791
 
792
+ _mousewheelZoomRate() {
793
+ const zoomable = this.options.zoomable;
794
+ const mousewheel = (zoomable || {}).mousewheel || {};
795
+ return valueOrDefault(mousewheel.rate, MOUSEWHEEL_ZOOM_RATE);
796
+ }
797
+
790
798
  _mousewheel(e) {
791
799
  const delta = mousewheelDelta(e);
792
800
  const mousewheelZoom = this._mousewheelZoom;
@@ -811,7 +819,7 @@ class Chart extends Class {
811
819
  clearTimeout(this._mwTimeout);
812
820
  }
813
821
 
814
- args.axisRanges = mousewheelZoom.updateRanges(delta);
822
+ args.axisRanges = mousewheelZoom.updateRanges(delta, coords);
815
823
  if (args.axisRanges && !this.trigger(ZOOM, args)) {
816
824
  mousewheelZoom.zoom();
817
825
  }
@@ -844,7 +852,7 @@ class Chart extends Class {
844
852
  const currentAxis = axes[i];
845
853
  const axisName = currentAxis.options.name;
846
854
  if (axisName) {
847
- ranges[axisName] = currentAxis.scaleRange(-totalDelta);
855
+ ranges[axisName] = currentAxis.scaleRange(-totalDelta * this._mousewheelZoomRate(), coords);
848
856
  }
849
857
  }
850
858
 
@@ -94,6 +94,7 @@ const OUTSIDE_END = "outsideEnd";
94
94
 
95
95
  const MOUSEWHEEL = "DOMMouseScroll mousewheel";
96
96
  const MOUSEWHEEL_DELAY = 150;
97
+ const MOUSEWHEEL_ZOOM_RATE = 0.3;
97
98
 
98
99
  export {
99
100
  INITIAL_ANIMATION_DURATION, FADEIN,
@@ -115,7 +116,8 @@ export {
115
116
  LOGARITHMIC, DRAG, DRAG_START, DRAG_END, ZOOM_START, ZOOM, ZOOM_END,
116
117
  SELECT_START, SELECT, SELECT_END, PANE_RENDER, GAP,
117
118
  DONUT, INSIDE_END, INSIDE_BASE, OUTSIDE_END,
118
- MOUSEWHEEL, MOUSEWHEEL_DELAY, SHOW_TOOLTIP, HIDE_TOOLTIP,
119
+ MOUSEWHEEL, MOUSEWHEEL_DELAY, MOUSEWHEEL_ZOOM_RATE,
120
+ SHOW_TOOLTIP, HIDE_TOOLTIP,
119
121
  EQUALLY_SPACED_SERIES, ABOVE, BELOW,
120
122
  HEATMAP
121
123
  };
@@ -9,10 +9,12 @@ class MousewheelZoom extends Class {
9
9
  super();
10
10
 
11
11
  this.chart = chart;
12
- this.options = deepExtend({}, this.options, options);
12
+ this.options = deepExtend({
13
+ rate: 0.3
14
+ }, this.options, options);
13
15
  }
14
16
 
15
- updateRanges(delta) {
17
+ updateRanges(delta, coords) {
16
18
  const lock = (this.options.lock || "").toLowerCase();
17
19
  const axisRanges = [];
18
20
  const axes = this.chart._plotArea.axes;
@@ -22,7 +24,7 @@ class MousewheelZoom extends Class {
22
24
  const vertical = axis.options.vertical;
23
25
 
24
26
  if (!(lock === X && !vertical) && !(lock === Y && vertical) && axis.zoomRange) {
25
- const range = axis.zoomRange(-delta);
27
+ const range = axis.zoomRange(-delta * this.options.rate, coords);
26
28
 
27
29
  if (range) {
28
30
  axisRanges.push({
@@ -55,4 +57,4 @@ class MousewheelZoom extends Class {
55
57
  }
56
58
  }
57
59
 
58
- export default MousewheelZoom;
60
+ export default MousewheelZoom;
@@ -4,7 +4,7 @@ import { DateCategoryAxis, Point } from '../core';
4
4
 
5
5
  import { MOUSEWHEEL_DELAY, MOUSEWHEEL, SELECT_START, SELECT, SELECT_END } from './constants';
6
6
 
7
- import { LEFT, RIGHT, MIN_VALUE, MAX_VALUE } from '../common/constants';
7
+ import { LEFT, RIGHT, MIN_VALUE, MAX_VALUE, X } from '../common/constants';
8
8
  import { addClass, Class, removeClass, deepExtend, elementStyles, eventElement, setDefaultOptions, limitValue, round, bindEvents, unbindEvents, mousewheelDelta, hasClasses } from '../common';
9
9
  import { parseDate } from '../date-utils';
10
10
 
@@ -542,6 +542,45 @@ class Selection extends Class {
542
542
  }
543
543
  }
544
544
 
545
+ zoom(delta, coords) {
546
+ const options = this.options;
547
+ const min = this._index(options.min);
548
+ const max = this._index(options.max);
549
+ const from = this._index(options.from);
550
+ const to = this._index(options.to);
551
+ let range = { from: from, to: to };
552
+ const oldRange = deepExtend({}, range);
553
+
554
+ const { reverse } = this.categoryAxis.options;
555
+ const origin = X + (reverse ? '2' : '1');
556
+ const lineBox = this.categoryAxis.lineBox();
557
+ const relative = Math.abs(lineBox[origin] - coords[X]);
558
+ const size = lineBox.width();
559
+ const position = round(relative / size, 2);
560
+ const minDelta = round(position * delta);
561
+ const maxDelta = round((1 - position) * delta);
562
+
563
+ if (this._state) {
564
+ range = this._state.range;
565
+ }
566
+
567
+ range.from = limitValue(
568
+ limitValue(from - minDelta, 0, to - 1),
569
+ min, max
570
+ );
571
+
572
+ range.to = limitValue(
573
+ limitValue(to + maxDelta, range.from + 1, max),
574
+ min,
575
+ max
576
+ );
577
+
578
+ if (range.from !== oldRange.from || range.to !== oldRange.to) {
579
+ this.set(range.from, range.to);
580
+ return true;
581
+ }
582
+ }
583
+
545
584
  trigger(name, args) {
546
585
  return (this.observer || this.chart).trigger(name, args);
547
586
  }
@@ -1,16 +1,13 @@
1
- import { drawing } from '@progress/kendo-drawing';
2
-
3
1
  export default function mousewheelDelta(e) {
4
2
  let delta = 0;
5
3
 
6
4
  if (e.wheelDelta) {
7
5
  delta = -e.wheelDelta / 120;
8
- delta = delta > 0 ? Math.ceil(delta) : Math.floor(delta);
6
+ } else if (e.detail) {
7
+ delta = e.detail / 3;
9
8
  }
10
9
 
11
- if (e.detail) {
12
- delta = drawing.util.round(e.detail / 3);
13
- }
10
+ delta = delta > 0 ? Math.ceil(delta) : Math.floor(delta);
14
11
 
15
12
  return delta;
16
- }
13
+ }
@@ -818,6 +818,34 @@ class Axis extends ChartElement {
818
818
  };
819
819
  }
820
820
 
821
+ lineInfo() {
822
+ const { vertical, reverse } = this.options;
823
+ const lineBox = this.lineBox();
824
+ const lineSize = vertical ? lineBox.height() : lineBox.width();
825
+ const axis = vertical ? Y : X;
826
+ const axisDir = (vertical ? -1 : 1) * (reverse ? -1 : 1);
827
+ const startEdge = axisDir === 1 ? 1 : 2;
828
+ const axisOrigin = axis + startEdge.toString();
829
+ const lineStart = lineBox[axisOrigin];
830
+
831
+ return {
832
+ axis,
833
+ axisOrigin,
834
+ axisDir,
835
+ lineBox,
836
+ lineSize,
837
+ lineStart
838
+ };
839
+ }
840
+
841
+ pointOffset(point) {
842
+ const { axis, axisDir, axisOrigin, lineBox, lineSize } = this.lineInfo();
843
+ const relative = axisDir > 0 ? point[axis] - lineBox[axisOrigin] : lineBox[axisOrigin] - point[axis];
844
+ const offset = relative / lineSize;
845
+
846
+ return offset;
847
+ }
848
+
821
849
  labelsBetweenTicks() {
822
850
  return !this.options.justified;
823
851
  }
@@ -6,6 +6,7 @@ import { defined, isNumber, last, limitValue, round, setDefaultOptions, valueOrD
6
6
  import { dateEquals } from '../date-utils';
7
7
 
8
8
  const MIN_CATEGORY_POINTS_RANGE = 0.01;
9
+ const MIN_CATEGORY_RANGE = 0.1;
9
10
 
10
11
  function indexOf(value, arr) {
11
12
  if (value instanceof Date) {
@@ -375,27 +376,32 @@ class CategoryAxis extends Axis {
375
376
  };
376
377
  }
377
378
 
378
- zoomRange(rate) {
379
+ scaleRange(scale, cursor) {
380
+ const position = Math.abs(this.pointOffset(cursor));
379
381
  const rangeIndices = this.totalRangeIndices();
380
- const { min: totalMin, max: totalMax } = this.totalRange();
381
- const min = limitValue(rangeIndices.min + rate, totalMin, totalMax);
382
- const max = limitValue(rangeIndices.max - rate, totalMin, totalMax);
383
-
384
- if (max - min > 0) {
385
- return {
386
- min: min,
387
- max: max
388
- };
382
+ const delta = (rangeIndices.max - rangeIndices.min) * Math.min(1, scale);
383
+ const minDelta = position * delta;
384
+ const maxDelta = (1 - position) * delta;
385
+ const min = rangeIndices.min + minDelta;
386
+ let max = rangeIndices.max - maxDelta;
387
+
388
+ if (max - min < MIN_CATEGORY_RANGE) {
389
+ max = min + MIN_CATEGORY_RANGE;
389
390
  }
391
+
392
+ return {
393
+ min: min,
394
+ max: max
395
+ };
390
396
  }
391
397
 
392
- scaleRange(scale) {
393
- const range = this.options.categories.length;
394
- const delta = scale * range;
398
+ zoomRange(scale, cursor) {
399
+ const { min: totalMin, max: totalMax } = this.totalRange();
400
+ const range = this.scaleRange(scale, cursor);
395
401
 
396
402
  return {
397
- min: -delta,
398
- max: range + delta
403
+ min: limitValue(range.min, totalMin, totalMax),
404
+ max: limitValue(range.max, totalMin, totalMax)
399
405
  };
400
406
  }
401
407
 
@@ -459,30 +459,6 @@ class DateCategoryAxis extends CategoryAxis {
459
459
  return range;
460
460
  }
461
461
 
462
- scaleRange(delta) {
463
- let rounds = Math.abs(delta);
464
- let result = this.range();
465
- let { min: from, max: to } = result;
466
-
467
- if (from && to) {
468
- while (rounds--) {
469
- const range = dateDiff(from, to);
470
- const step = Math.round(range * 0.1);
471
- if (delta < 0) {
472
- from = addTicks(from, step);
473
- to = addTicks(to, -step);
474
- } else {
475
- from = addTicks(from, -step);
476
- to = addTicks(to, step);
477
- }
478
- }
479
-
480
- result = { min: from, max: to };
481
- }
482
-
483
- return result;
484
- }
485
-
486
462
  labelsRange() {
487
463
  return {
488
464
  min: this.options.labels.skip,
@@ -541,18 +517,24 @@ class DateCategoryAxis extends CategoryAxis {
541
517
  };
542
518
  }
543
519
 
544
- zoomRange(delta) {
520
+ scaleRange(scale, cursor) {
545
521
  if (this.isEmpty()) {
546
- return null;
522
+ return {};
547
523
  }
548
524
 
549
525
  const options = this.options;
550
526
  const fit = options.userSetBaseUnit === FIT;
551
527
  const totalLimits = this.dataRange.total();
552
528
  const { min: rangeMin, max: rangeMax } = this.dataRange.displayRange();
553
- let { weekStartDay, baseUnit, baseUnitStep } = this.dataRange.options;
554
- let min = addDuration(rangeMin, delta * baseUnitStep, baseUnit, weekStartDay);
555
- let max = addDuration(rangeMax, -delta * baseUnitStep, baseUnit, weekStartDay);
529
+
530
+ const position = Math.abs(this.pointOffset(cursor));
531
+ const delta = (rangeMax - rangeMin) * scale;
532
+ const minDelta = Math.round(position * delta);
533
+ const maxDelta = Math.round((1 - position) * delta);
534
+
535
+ let { baseUnit } = this.dataRange.options;
536
+ let min = new Date(rangeMin.getTime() + minDelta);
537
+ let max = new Date(rangeMax.getTime() - maxDelta);
556
538
 
557
539
  if (fit) {
558
540
  const { autoBaseUnitSteps, maxDateGroups } = options;
@@ -594,13 +576,6 @@ class DateCategoryAxis extends CategoryAxis {
594
576
  }
595
577
  }
596
578
 
597
- if (min < totalLimits.min) {
598
- min = totalLimits.min;
599
- }
600
- if (max > totalLimits.max) {
601
- max = totalLimits.max;
602
- }
603
-
604
579
  if (min && max && dateDiff(max, min) > 0) {
605
580
  return {
606
581
  min: min,
@@ -611,6 +586,22 @@ class DateCategoryAxis extends CategoryAxis {
611
586
  }
612
587
  }
613
588
 
589
+ zoomRange(scale, cursor) {
590
+ const totalLimits = this.dataRange.total();
591
+ const range = this.scaleRange(scale, cursor);
592
+
593
+ if (range) {
594
+ if (range.min < totalLimits.min) {
595
+ range.min = totalLimits.min;
596
+ }
597
+ if (range.max > totalLimits.max) {
598
+ range.max = totalLimits.max;
599
+ }
600
+ }
601
+
602
+ return range;
603
+ }
604
+
614
605
  range() {
615
606
  return this.dataRange.displayRange();
616
607
  }
@@ -12,6 +12,8 @@ import ceil from './utils/ceil';
12
12
  import { toDate, toTime, floorDate, ceilDate, duration, addDuration, addTicks, dateDiff, absoluteDateDiff, dateComparer, parseDate, parseDates, firstDay } from '../date-utils';
13
13
  import { HOURS, DAYS, WEEKS, MONTHS, YEARS, TIME_PER_DAY, TIME_PER_WEEK, TIME_PER_MONTH, TIME_PER_YEAR, TIME_PER_UNIT } from '../date-utils/constants';
14
14
 
15
+ const MIN_VALUE_RANGE = 1000;
16
+
15
17
  class DateValueAxis extends Axis {
16
18
  constructor(seriesMin, seriesMax, axisOptions, chartService) {
17
19
  const min = toDate(seriesMin);
@@ -120,23 +122,22 @@ class DateValueAxis extends Axis {
120
122
  return new AxisLabel(date, text, index, null, labelOptions);
121
123
  }
122
124
 
123
- translateRange(delta, exact) {
125
+ translateRange(delta) {
124
126
  const options = this.options;
125
- const baseUnit = options.baseUnit;
126
- const weekStartDay = options.weekStartDay || 0;
127
127
  const lineBox = this.lineBox();
128
- const size = options.vertical ? lineBox.height() : lineBox.width();
128
+ const { vertical, reverse } = options;
129
+ const size = vertical ? lineBox.height() : lineBox.width();
129
130
  const range = this.range();
130
131
  const scale = size / dateDiff(range.max, range.min);
131
- const offset = round(delta / scale, DEFAULT_PRECISION) * (options.reverse ? -1 : 1);
132
- let from = addTicks(options.min, offset);
133
- let to = addTicks(options.max, offset);
134
132
 
135
- if (!exact) {
136
- from = addDuration(from, 0, baseUnit, weekStartDay);
137
- to = addDuration(to, 0, baseUnit, weekStartDay);
133
+ let offset = round(delta / scale, DEFAULT_PRECISION);
134
+ if ((vertical || reverse) && !(vertical && reverse )) {
135
+ offset = -offset;
138
136
  }
139
137
 
138
+ let from = addTicks(options.min, offset);
139
+ let to = addTicks(options.max, offset);
140
+
140
141
  return {
141
142
  min: from,
142
143
  max: to,
@@ -144,25 +145,6 @@ class DateValueAxis extends Axis {
144
145
  };
145
146
  }
146
147
 
147
- scaleRange(delta) {
148
- let { min: from, max: to } = this.options;
149
- let rounds = Math.abs(delta);
150
-
151
- while (rounds--) {
152
- const range = dateDiff(from, to);
153
- const step = Math.round(range * 0.1);
154
- if (delta < 0) {
155
- from = addTicks(from, step);
156
- to = addTicks(to, -step);
157
- } else {
158
- from = addTicks(from, -step);
159
- to = addTicks(to, step);
160
- }
161
- }
162
-
163
- return { min: from, max: to };
164
- }
165
-
166
148
  shouldRenderNote(value) {
167
149
  const range = this.range();
168
150
 
@@ -193,16 +175,34 @@ class DateValueAxis extends Axis {
193
175
  };
194
176
  }
195
177
 
196
- zoomRange(delta) {
197
- const range = this.scaleRange(delta);
198
- const min = toDate(limitValue(toTime(range.min), this.totalMin, this.totalMax));
199
- const max = toDate(limitValue(toTime(range.max), this.totalMin, this.totalMax));
178
+ scaleRange(scale, cursor) {
179
+ const position = Math.abs(this.pointOffset(cursor));
180
+ const delta = (this.options.max - this.options.min) * Math.min(1, scale);
181
+ const minDelta = position * delta;
182
+ const maxDelta = (1 - position) * delta;
183
+ const min = toDate(toTime(this.options.min) + minDelta);
184
+ let max = toDate(toTime(this.options.max) - maxDelta);
185
+
186
+ if (max - min < MIN_VALUE_RANGE) {
187
+ max = toDate(toTime(min) + MIN_VALUE_RANGE);
188
+ }
200
189
 
201
190
  return {
202
191
  min: min,
203
192
  max: max
204
193
  };
205
194
  }
195
+
196
+ zoomRange(scale, cursor) {
197
+ const range = this.scaleRange(scale, cursor);
198
+ const min = toDate(limitValue(toTime(range.min), this.totalMin, this.totalMax));
199
+ const max = toDate(limitValue(toTime(range.max), this.totalMin, this.totalMax));
200
+
201
+ return {
202
+ min,
203
+ max
204
+ };
205
+ }
206
206
  }
207
207
 
208
208
  function timeUnits(delta) {
@@ -11,6 +11,7 @@ import { DEFAULT_PRECISION, BLACK, X, Y } from '../common/constants';
11
11
  import { deepExtend, defined, inArray, limitValue, round, setDefaultOptions } from '../common';
12
12
 
13
13
  const DEFAULT_MAJOR_UNIT = 10;
14
+ const MIN_VALUE_RANGE = 1e-6;
14
15
 
15
16
  class LogarithmicAxis extends Axis {
16
17
  constructor(seriesMin, seriesMax, options, chartService) {
@@ -126,16 +127,6 @@ class LogarithmicAxis extends Axis {
126
127
  return { min: options.min, max: options.max };
127
128
  }
128
129
 
129
- scaleRange(delta) {
130
- const base = this.options.majorUnit;
131
- const offset = -delta;
132
-
133
- return {
134
- min: Math.pow(base, this.logMin - offset),
135
- max: Math.pow(base, this.logMax + offset)
136
- };
137
- }
138
-
139
130
  translateRange(delta) {
140
131
  const { options, logMin, logMax } = this;
141
132
  const { reverse, vertical, majorUnit: base } = options;
@@ -296,21 +287,33 @@ class LogarithmicAxis extends Axis {
296
287
  };
297
288
  }
298
289
 
299
- zoomRange(delta) {
300
- const { options, totalMin, totalMax } = this;
301
- const newRange = this.scaleRange(delta);
302
- const min = limitValue(newRange.min, totalMin, totalMax);
303
- const max = limitValue(newRange.max, totalMin, totalMax);
304
- const base = options.majorUnit;
305
- const acceptOptionsRange = max > min && options.min && options.max && (round(log(options.max, base) - log(options.min, base), DEFAULT_PRECISION) < 1);
306
- const acceptNewRange = !(options.min === totalMin && options.max === totalMax) && round(log(max, base) - log(min, base), DEFAULT_PRECISION) >= 1;
307
-
308
- if (acceptOptionsRange || acceptNewRange) {
309
- return {
310
- min: min,
311
- max: max
312
- };
290
+ scaleRange(scale, cursor) {
291
+ const { majorUnit: base } = this.options;
292
+ const logMin = log(this.options.min, base);
293
+ const logMax = log(this.options.max, base);
294
+ const position = Math.abs(this.pointOffset(cursor));
295
+ const delta = (logMax - logMin) * Math.min(1, scale);
296
+ const min = Math.pow(base, logMin + position * delta);
297
+ let max = Math.pow(base, logMax - (1 - position) * delta);
298
+
299
+ if (max - min < MIN_VALUE_RANGE) {
300
+ max = min + MIN_VALUE_RANGE;
313
301
  }
302
+
303
+ return {
304
+ min: min,
305
+ max: max
306
+ };
307
+ }
308
+
309
+ zoomRange(scale, cursor) {
310
+ const range = this.scaleRange(scale, cursor);
311
+ const { totalMin, totalMax } = this;
312
+
313
+ return {
314
+ min: limitValue(range.min, totalMin, totalMax),
315
+ max: limitValue(range.max, totalMin, totalMax)
316
+ };
314
317
  }
315
318
 
316
319
  _minorIntervalOptions(power) {
@@ -398,8 +401,8 @@ function throwNegativeValuesError() {
398
401
  throw new Error("Non positive values cannot be used for a logarithmic axis");
399
402
  }
400
403
 
401
- function log(y, x) {
402
- return Math.log(y) / Math.log(x);
404
+ function log(x, base) {
405
+ return Math.log(x) / Math.log(base);
403
406
  }
404
407
 
405
408
  setDefaultOptions(LogarithmicAxis, {
@@ -194,16 +194,6 @@ class NumericAxis extends Axis {
194
194
  };
195
195
  }
196
196
 
197
- scaleRange(delta) {
198
- const options = this.options;
199
- const offset = -delta * options.majorUnit;
200
-
201
- return {
202
- min: options.min - offset,
203
- max: options.max + offset
204
- };
205
- }
206
-
207
197
  labelsCount() {
208
198
  return this.getDivisions(this.options.majorUnit);
209
199
  }
@@ -240,18 +230,32 @@ class NumericAxis extends Axis {
240
230
  }
241
231
  }
242
232
 
243
- zoomRange(delta) {
244
- const { totalMin, totalMax } = this;
245
- const newRange = this.scaleRange(delta);
246
- const min = limitValue(newRange.min, totalMin, totalMax);
247
- const max = limitValue(newRange.max, totalMin, totalMax);
233
+ scaleRange(scale, cursor) {
234
+ const position = Math.abs(this.pointOffset(cursor));
235
+ const delta = (this.options.max - this.options.min) * Math.min(1, scale);
236
+ const minDelta = position * delta;
237
+ const maxDelta = (1 - position) * delta;
238
+ const min = this.options.min + minDelta;
239
+ let max = this.options.max - maxDelta;
248
240
 
249
- if (this.isValidRange(min, max)) {
250
- return {
251
- min: min,
252
- max: max
253
- };
241
+ if (max - min < MIN_VALUE_RANGE) {
242
+ max = min + MIN_VALUE_RANGE;
254
243
  }
244
+
245
+ return {
246
+ min: min,
247
+ max: max
248
+ };
249
+ }
250
+
251
+ zoomRange(scale, cursor) {
252
+ const { totalMin, totalMax } = this;
253
+ const range = this.scaleRange(scale, cursor);
254
+
255
+ return {
256
+ min: limitValue(range.min, totalMin, totalMax),
257
+ max: limitValue(range.max, totalMin, totalMax)
258
+ };
255
259
  }
256
260
 
257
261
  isValidRange(min, max) {
@@ -260,6 +260,7 @@ class Navigator extends Class {
260
260
 
261
261
  const fromIx = mainAxis.categoryIndex(selection.options.from);
262
262
  const toIx = mainAxis.categoryIndex(selection.options.to);
263
+ const coords = this.chart._eventCoordinates(e.originalEvent);
263
264
 
264
265
  e.originalEvent.preventDefault();
265
266
 
@@ -268,11 +269,11 @@ class Navigator extends Class {
268
269
  }
269
270
 
270
271
  if (toIx - fromIx > 1) {
271
- selection.expand(delta);
272
+ selection.zoom(delta, coords);
272
273
  this.readSelection();
273
274
  } else {
274
275
  axis.options.min = select.from;
275
- select.from = axis.scaleRange(-e.delta).min;
276
+ select.from = axis.scaleRange(-e.delta * this.chart._mousewheelZoomRate(), coords).min;
276
277
  }
277
278
 
278
279
  if (liveDrag) {