如何从QuestDB取数并实现带无限滚动的DevExpress缩放滚动图表
解决QuestDB数据获取与DevExtreme无限滚动图表实现问题
一、先解决QuestDB数据获取卡点
核心请求逻辑
QuestDB的REST API支持通过SQL查询获取数据,针对trips表的需求,需要构造查询pickup_datetime和trip_distance的请求,同时处理时间格式和数据转换:
// 封装数据请求函数,支持按时间范围分页 async function fetchTripData(startTime, endTime) { // 构造带时间过滤的SQL,限制每页1000条数据 const sql = ` SELECT pickup_datetime, trip_distance FROM trips WHERE pickup_datetime BETWEEN '${startTime.toISOString()}' AND '${endTime.toISOString()}' ORDER BY pickup_datetime ASC LIMIT 1000 `; // 编码SQL避免URL格式问题,指定JSON格式返回 const encodedQuery = encodeURIComponent(sql); const response = await fetch(`https://demo.questdb.io/exec?query=${encodedQuery}&format=json`); if (!response.ok) throw new Error('数据请求失败'); const rawData = await response.json(); // 转换为DevExtreme图表兼容的格式:时间转Date对象,键值对结构 return rawData.dataset.map(row => ({ pickupDatetime: new Date(row[0]), tripDistance: row[1] })); }
取数常见问题排查
- 跨域错误:demo.questdb.io已开启CORS支持,若仍报错,可在CodeSandbox项目中启用内置代理(项目设置→Server→Add Proxy)
- SQL语法错误:确保时间字符串为ISO格式(
toISOString()自动生成),避免特殊字符未编码 - 无返回数据:
trips表的时间范围集中在2013年左右,初始化时不要用过早的时间范围
二、整合到DevExtreme图表模板
基于DevExtreme的ZoomingAndScrolling React模板,修改数据源和状态管理:
import React, { useState, useEffect, useRef } from 'react'; import { Chart, ArgumentAxis, ValueAxis, LineSeries, Tooltip } from 'devextreme-react/chart'; export default function App() { const [chartData, setChartData] = useState([]); const lastLoadedEndTime = useRef(null); // 初始化加载第一页数据 useEffect(() => { async function loadInitialData() { // 先获取表中最早的记录时间 const minTimeRes = await fetch(`https://demo.questdb.io/exec?query=${encodeURIComponent('SELECT MIN(pickup_datetime) FROM trips')}&format=json`); const minTimeRaw = await minTimeRes.json(); const minTime = new Date(minTimeRaw.dataset[0][0]); // 第一页取7天的数据(可根据数据密度调整) const endTime = new Date(minTime.getTime() + 7 * 24 * 60 * 60 * 1000); const initialData = await fetchTripData(minTime, endTime); setChartData(initialData); lastLoadedEndTime.current = endTime; } loadInitialData(); }, []); // 加载下一页数据 async function loadNextPage() { if (!lastLoadedEndTime.current) return; const startTime = lastLoadedEndTime.current; const endTime = new Date(startTime.getTime() + 7 * 24 * 60 * 60 * 1000); const newData = await fetchTripData(startTime, endTime); if (newData.length === 0) return; // 无更多数据 setChartData(prev => [...prev, ...newData]); lastLoadedEndTime.current = endTime; } // 监听滚动事件,触发无限加载 const handleScroll = (e) => { const scrollElement = e.component.getScrollableElement(); const { scrollLeft, clientWidth, scrollWidth } = scrollElement; // 滚动到右侧90%位置时加载下一页 if (scrollLeft + clientWidth >= scrollWidth * 0.9) { loadNextPage(); } }; return ( <Chart dataSource={chartData} onScroll={handleScroll} zoomMode="x" scrollingMode="infinite" height={600} > <ArgumentAxis argumentType="datetime" title={{ text: '取车时间' }} /> <ValueAxis title={{ text: '行程距离' }} /> <LineSeries argumentField="pickupDatetime" valueField="tripDistance" name="行程距离" /> <Tooltip enabled={true} /> </Chart> ); } // 数据请求函数 async function fetchTripData(startTime, endTime) { const sql = ` SELECT pickup_datetime, trip_distance FROM trips WHERE pickup_datetime BETWEEN '${startTime.toISOString()}' AND '${endTime.toISOString()}' ORDER BY pickup_datetime ASC LIMIT 1000 `; const encodedQuery = encodeURIComponent(sql); const response = await fetch(`https://demo.questdb.io/exec?query=${encodedQuery}&format=json`); if (!response.ok) throw new Error('数据请求失败'); const rawData = await response.json(); return rawData.dataset.map(row => ({ pickupDatetime: new Date(row[0]), tripDistance: row[1] })); }
三、验证无限滚动功能
- 初始化时图表加载第一页7天的行程数据
- 向右滚动图表到接近边缘时,自动加载下7天的数据
- 重复滚动可持续加载,直到
trips表数据耗尽
关键配置说明
scrollingMode="infinite":开启DevExtreme图表的无限滚动模式onScroll事件:监听滚动位置,触发分页加载- 时间范围分页:通过
BETWEEN条件和LIMIT实现高效的数据分段获取,避免一次性加载全部数据导致性能问题
内容的提问来源于stack exchange,提问作者Omkar Lolge
相关产品推荐
相关产品推荐

