This is an automated email from the ASF dual-hosted git repository.

susiwen8 pushed a commit to branch codex/fix-21512-datazoom-sampling
in repository https://gitbox.apache.org/repos/asf/echarts.git

commit 11ba3e300d191deb1c202392bd6b8f4dec9135da
Author: susiwen8 <[email protected]>
AuthorDate: Sat May 2 23:38:57 2026 +0800

    Preserve line sampling detail inside dataZoom windows
    
    Line sampling previously used the processed series count as its sampling 
base. When dataZoom used filterMode none or empty, that count still represented 
the full dataset, so a narrow zoom could remain downsampled as if the whole 
dataset were visible.
    
    This bases the sampling rate on points contained by the current base-axis 
extent while keeping the no-sampling path out of the processor. The regression 
HTML case compares filter, none, empty, and raw modes around a narrow zoom 
window.
    
    Constraint: dataZoom filterMode none and empty intentionally keep full 
SeriesData rather than filtering rows before sampling
    
    Rejected: Change dataZoom filtering semantics for none/empty | would break 
the documented promise that data is not filtered
    
    Confidence: high
    
    Scope-risk: narrow
    
    Directive: Sampling rate should follow the visible base-axis extent, not 
only the post-filter SeriesData count
    
    Tested: npm run checktype -- --pretty false
    
    Tested: npm run lint -- --quiet src/processor/dataSample.ts
    
    Tested: test/line-sampling-dataZoom-filterMode.html via Chrome, PASS with 
none/empty counts matching raw
    
    Not-tested: Full visual regression suite
---
 src/processor/dataSample.ts                 |  17 ++-
 test/line-sampling-dataZoom-filterMode.html | 215 ++++++++++++++++++++++++++++
 2 files changed, 230 insertions(+), 2 deletions(-)

diff --git a/src/processor/dataSample.ts b/src/processor/dataSample.ts
index 3c19572e9..e23e1d32b 100644
--- a/src/processor/dataSample.ts
+++ b/src/processor/dataSample.ts
@@ -21,6 +21,8 @@ import { StageHandler, SeriesOption, 
SeriesSamplingOptionMixin } from '../util/t
 import { Dictionary } from 'zrender/src/core/types';
 import SeriesModel from '../model/Series';
 import { isFunction, isString } from 'zrender/src/core/util';
+import type SeriesData from '../data/SeriesData';
+import type Axis from '../coord/Axis';
 
 
 type Sampler = (frame: ArrayLike<number>) => number;
@@ -72,6 +74,16 @@ const indexSampler = function (frame: ArrayLike<number>) {
     return Math.round(frame.length / 2);
 };
 
+function countDataInAxisExtent(data: SeriesData, baseAxis: Axis, baseDim: 
string) {
+    let count = 0;
+    data.each(baseDim, function (value: number) {
+        if (baseAxis.containData(value)) {
+            count++;
+        }
+    });
+    return count;
+}
+
 export default function dataSample(seriesType: string): StageHandler {
     return {
 
@@ -86,14 +98,15 @@ export default function dataSample(seriesType: string): 
StageHandler {
             const coordSys = seriesModel.coordinateSystem;
             const count = data.count();
             // Only cartesian2d support down sampling. Disable it when there 
is few data.
-            if (count > 10 && coordSys.type === 'cartesian2d' && sampling) {
+            if (count > 10 && coordSys.type === 'cartesian2d' && sampling && 
sampling !== 'none') {
                 const baseAxis = coordSys.getBaseAxis();
                 const valueAxis = coordSys.getOtherAxis(baseAxis);
                 const extent = baseAxis.getExtent();
                 const dpr = api.getDevicePixelRatio();
                 // Coordinste system has been resized
                 const size = Math.abs(extent[1] - extent[0]) * (dpr || 1);
-                const rate = Math.round(count / size);
+                const dataCount = countDataInAxisExtent(data, baseAxis, 
data.mapDimension(baseAxis.dim));
+                const rate = Math.round(dataCount / size);
 
                 if (isFinite(rate) && rate > 1) {
                     if (sampling === 'lttb') {
diff --git a/test/line-sampling-dataZoom-filterMode.html 
b/test/line-sampling-dataZoom-filterMode.html
new file mode 100644
index 000000000..4bd316e89
--- /dev/null
+++ b/test/line-sampling-dataZoom-filterMode.html
@@ -0,0 +1,215 @@
+<!DOCTYPE html>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<html>
+    <head>
+        <meta charset="utf-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1" />
+        <link rel="icon" href="data:,">
+        <script src="lib/simpleRequire.js"></script>
+        <script src="lib/config.js"></script>
+        <script>
+            (function () {
+                var echartsPath = new 
URLSearchParams(location.search).get('__ECHARTS_PATH__');
+                if (echartsPath && typeof require !== 'undefined') {
+                    require.config({
+                        paths: {
+                            echarts: echartsPath
+                        }
+                    });
+                }
+            })();
+        </script>
+        <script src="lib/testHelper.js"></script>
+        <link rel="stylesheet" href="lib/reset.css" />
+    </head>
+    <body>
+        <style>
+            body {
+                font-family: sans-serif;
+                padding: 16px;
+            }
+            h1 {
+                font-size: 18px;
+                margin: 0 0 12px;
+            }
+            #status {
+                display: inline-block;
+                margin-bottom: 14px;
+                padding: 6px 10px;
+                border-radius: 3px;
+                background: #fee;
+                color: #a40000;
+                font-size: 13px;
+                font-weight: bold;
+            }
+            #status.pass {
+                background: #e8f6ed;
+                color: #176a32;
+            }
+            .grid {
+                display: grid;
+                grid-template-columns: repeat(2, 580px);
+                gap: 16px;
+                align-items: start;
+            }
+            .panel {
+                border: 1px solid #ddd;
+                padding: 10px;
+            }
+            .panel h2 {
+                font-size: 14px;
+                margin: 0 0 8px;
+            }
+            .chart {
+                width: 560px;
+                height: 260px;
+            }
+            .count {
+                margin-top: 6px;
+                color: #555;
+                font-size: 12px;
+            }
+        </style>
+
+        <h1>Issue #21512: dataZoom filterMode none/empty should not lock line 
sampling to full data extent</h1>
+        <div id="status">Running</div>
+        <div class="grid">
+            <div class="panel">
+                <h2>filterMode: filter, sampling: lttb</h2>
+                <div id="chart-filter" class="chart"></div>
+                <div id="count-filter" class="count"></div>
+            </div>
+            <div class="panel">
+                <h2>filterMode: none, sampling: lttb</h2>
+                <div id="chart-none" class="chart"></div>
+                <div id="count-none" class="count"></div>
+            </div>
+            <div class="panel">
+                <h2>filterMode: empty, sampling: lttb</h2>
+                <div id="chart-empty" class="chart"></div>
+                <div id="count-empty" class="count"></div>
+            </div>
+            <div class="panel">
+                <h2>filterMode: none, sampling: none</h2>
+                <div id="chart-raw" class="chart"></div>
+                <div id="count-raw" class="count"></div>
+            </div>
+        </div>
+
+        <script>
+            require(['echarts'], function (echarts) {
+                var rawCount = 5000;
+                var zoomStart = 2400;
+                var zoomEnd = 2499;
+                var data = [];
+
+                for (var i = 0; i < rawCount; i++) {
+                    var detail = i >= zoomStart && i <= zoomEnd
+                        ? (i % 2 ? 0.95 : -0.95)
+                        : Math.sin(i / 23) * 0.15;
+                    data.push([i, detail + Math.sin(i / 7) * 0.08]);
+                }
+
+                function makeOption(filterMode, sampling) {
+                    return {
+                        animation: false,
+                        grid: {
+                            top: 16,
+                            right: 20,
+                            bottom: 54,
+                            left: 45
+                        },
+                        dataZoom: [
+                            {
+                                id: 'zoom',
+                                type: 'slider',
+                                filterMode: filterMode,
+                                startValue: zoomStart,
+                                endValue: zoomEnd,
+                                height: 22,
+                                bottom: 14
+                            }
+                        ],
+                        xAxis: {
+                            type: 'value',
+                            min: 'dataMin',
+                            max: 'dataMax'
+                        },
+                        yAxis: {
+                            type: 'value',
+                            min: -1.2,
+                            max: 1.2
+                        },
+                        series: [
+                            {
+                                type: 'line',
+                                showSymbol: false,
+                                sampling: sampling,
+                                data: data,
+                                lineStyle: {
+                                    width: 1
+                                }
+                            }
+                        ]
+                    };
+                }
+
+                function render(domId, countId, filterMode, sampling) {
+                    var chart = echarts.init(document.getElementById(domId), 
null, {
+                        renderer: 'canvas'
+                    });
+                    chart.setOption(makeOption(filterMode, sampling));
+                    var count = 
chart.getModel().getSeriesByIndex(0).getData().count();
+                    document.getElementById(countId).innerHTML = 'processed 
data count: ' + count;
+                    return {
+                        chart: chart,
+                        count: count
+                    };
+                }
+
+                var results = {
+                    filter: render('chart-filter', 'count-filter', 'filter', 
'lttb').count,
+                    none: render('chart-none', 'count-none', 'none', 
'lttb').count,
+                    empty: render('chart-empty', 'count-empty', 'empty', 
'lttb').count,
+                    raw: render('chart-raw', 'count-raw', 'none', 'none').count
+                };
+
+                var visibleCount = zoomEnd - zoomStart + 1;
+                var pass = results.filter === visibleCount
+                    && results.none === rawCount
+                    && results.empty === rawCount
+                    && results.raw === rawCount;
+                var statusEl = document.getElementById('status');
+                statusEl.className = pass ? 'pass' : '';
+                statusEl.innerHTML = (pass ? 'PASS' : 'FAIL')
+                    + ' - visible window has ' + visibleCount
+                    + ' points, so none/empty+lttb should preserve raw detail 
in this zoom.';
+
+                window.__issue21512Result = {
+                    pass: pass,
+                    rawCount: rawCount,
+                    visibleCount: visibleCount,
+                    counts: results
+                };
+            });
+        </script>
+    </body>
+</html>


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to