Repository: tradingview/lightweight-charts
Pull request #68 (2019-06-14 12:26:27)

Fixed bug in plot list and finding min/max on range
**Type of PR:** bugfix

**PR checklist:**

- [ ] Addresses an existing issue: fixes #
- [x] Includes tests
- [ ] Documentation update

**Overview of change:**

Fixed bug in `PlotList` in finding min/max on range.

`PlotList::_plotMinMax` method accepts indexes inside that data array, but chunks for cache are split by `TimePointIndex` (it's an index on the time scale).


Patch:
diff --git a/src/model/plot-list.ts b/src/model/plot-list.ts
index 4a2b03faa..5ce6692f7 100644
--- a/src/model/plot-list.ts
+++ b/src/model/plot-list.ts
@@ -491,9 +491,9 @@ export class PlotList<TimeType, PlotValueTuple extends PlotValue[] = PlotValue[]
 
 			let chunkMinMax = minMaxCache.get(chunkIndex);
 			if (chunkMinMax === undefined) {
-				const chunkStart = chunkIndex * CHUNK_SIZE;
-				const chunkEnd = Math.min((chunkIndex + 1) * CHUNK_SIZE - 1, this._items.length - 1);
-				chunkMinMax = this._plotMinMax(chunkStart as TimePointIndex, chunkEnd as TimePointIndex, plotInfo);
+				const chunkStart = this._lowerbound(chunkIndex * CHUNK_SIZE as TimePointIndex);
+				const chunkEnd = this._upperbound((chunkIndex + 1) * CHUNK_SIZE - 1 as TimePointIndex);
+				chunkMinMax = this._plotMinMax(chunkStart, chunkEnd, plotInfo);
 				minMaxCache.set(chunkIndex, chunkMinMax);
 			}
 


Test patch:
diff --git a/tests/e2e/graphics/test-cases/correct-price-range-in-autoscale.js b/tests/e2e/graphics/test-cases/correct-price-range-in-autoscale.js
new file mode 100644
index 000000000..f814b24e8
--- /dev/null
+++ b/tests/e2e/graphics/test-cases/correct-price-range-in-autoscale.js
@@ -0,0 +1,28 @@
+function generateData(valueOffset, daysStep) {
+	var res = [];
+	var endDate = new Date(Date.UTC(2020, 0, 1, 0, 0, 0, 0));
+	var time = new Date(Date.UTC(2018, 0, 1, 0, 0, 0, 0));
+	for (var i = 0; time.getTime() < endDate.getTime(); ++i) {
+		res.push({
+			time: time.getTime() / 1000,
+			value: i + valueOffset,
+		});
+
+		time.setUTCDate(time.getUTCDate() + daysStep);
+	}
+
+	return res;
+}
+
+// eslint-disable-next-line no-unused-vars
+function runTestCase(container) {
+	var chart = LightweightCharts.createChart(container);
+
+	var firstSeries = chart.addLineSeries();
+	var secondSeries = chart.addLineSeries();
+
+	firstSeries.setData(generateData(0, 3));
+	secondSeries.setData(generateData(20, 5));
+
+	chart.timeScale().setVisibleRange({ from: 1570233600, to: 1577750400 });
+}
diff --git a/tests/unittests/plot-list.spec.ts b/tests/unittests/plot-list.spec.ts
index 8d457d0a3..1ae9b734f 100644
--- a/tests/unittests/plot-list.spec.ts
+++ b/tests/unittests/plot-list.spec.ts
@@ -395,6 +395,31 @@ describe('PlotList', () => {
 			expect(ensureNotNull(minMax).min).to.be.equal(1);
 			expect(ensureNotNull(minMax).max).to.be.equal(123);
 		});
+
+		it('should return correct values if the data has gaps and we start search with second-to-last chunk', () => {
+			pl.clear();
+			pl.add(29 as TimePointIndex, 1 as UTCTimestamp, [1, 1, 1, 1, 0]);
+			pl.add(31 as TimePointIndex, 2 as UTCTimestamp, [2, 2, 2, 2, 0]);
+			pl.add(55 as TimePointIndex, 3 as UTCTimestamp, [3, 3, 3, 3, 0]);
+			pl.add(65 as TimePointIndex, 4 as UTCTimestamp, [4, 4, 4, 4, 0]);
+
+			const plots: PlotInfoList = [
+				{
+					name: 'plot1',
+					offset: 0,
+				},
+			];
+
+			const minMax = pl.minMaxOnRangeCached(30 as TimePointIndex, 200 as TimePointIndex, plots);
+			expect(minMax).not.to.be.equal(null);
+			expect(ensureNotNull(minMax).min).to.be.equal(2);
+			expect(ensureNotNull(minMax).max).to.be.equal(4);
+
+			const minMax2 = pl.minMaxOnRangeCached(30 as TimePointIndex, 60 as TimePointIndex, plots);
+			expect(minMax2).not.to.be.equal(null);
+			expect(ensureNotNull(minMax2).min).to.be.equal(2);
+			expect(ensureNotNull(minMax2).max).to.be.equal(3);
+		});
 	});
 
 	describe('minMaxOnRangeByPlotFunction and minMaxOnRangeByPlotFunctionCached', () => {