Dynamic data loading and updating
Core Concepts of Dynamic Data Loading and Updating
Dynamic data loading and updating in ECharts refers to the process where a chart, after initial rendering, fetches new data through asynchronous or synchronous methods and re-renders accordingly. This mechanism enables charts to reflect data changes in real-time, making it suitable for scenarios like monitoring systems and real-time reporting. The essence of dynamic updating lies in minimizing rendering overhead by updating only the changed parts rather than the entire chart.
Data dynamism is primarily manifested in three aspects:
- Incremental updates: Only transmitting the changed portions of data
- Frequency control: Setting appropriate update intervals to avoid performance issues
- Transition animations: Smooth visual transition effects
Basic Data Update Methods
ECharts provides the fundamental setOption
method for full updates:
// Initialize the chart
const chart = echarts.init(document.getElementById('main'));
chart.setOption({
xAxis: { type: 'category', data: ['A', 'B', 'C'] },
yAxis: { type: 'value' },
series: [{ data: [10, 20, 30], type: 'bar' }]
});
// Dynamically update data
function updateData() {
const newData = [Math.random() * 100, Math.random() * 100, Math.random() * 100];
chart.setOption({
series: [{ data: newData }]
});
}
// Scheduled updates
setInterval(updateData, 2000);
This full update method triggers a complete redraw process and is suitable when both the data structure and content change. Note that the second parameter controls whether to merge options:
// Non-merge mode (complete replacement)
chart.setOption(newOption, false);
// Merge mode (default)
chart.setOption(newOption, true);
Incremental Data Update Strategies
For large datasets, incremental updates are recommended to improve performance:
// Initial large dataset
let largeData = Array(1000).fill(0).map((_, i) => Math.sin(i / 10));
// Incremental update function
function incrementalUpdate() {
// Modify only some data points
const randomIndex = Math.floor(Math.random() * 1000);
largeData[randomIndex] = Math.random() * 2 - 1;
// Mark changes with specific syntax
chart.setOption({
series: [{
data: largeData,
// Mark the changed data position
markPoint: {
data: [{ coord: [randomIndex, largeData[randomIndex]] }]
}
}]
});
}
ECharts 4.0+ supports more efficient incremental update APIs:
// Use appendData for incremental additions
chart.appendData({
seriesIndex: 0, // Series index
data: [newData1, newData2] // New data
});
Real-Time Data Stream Processing
Special optimizations are required for high-frequency data streams:
// Circular buffer implementation
class CircularBuffer {
constructor(capacity) {
this.capacity = capacity;
this.buffer = new Array(capacity);
this.index = 0;
this.size = 0;
}
push(value) {
this.buffer[this.index] = value;
this.index = (this.index + 1) % this.capacity;
if (this.size < this.capacity) this.size++;
}
getData() {
if (this.size < this.capacity) {
return this.buffer.slice(0, this.size);
}
return [
...this.buffer.slice(this.index),
...this.buffer.slice(0, this.index)
];
}
}
// Usage example
const buffer = new CircularBuffer(60); // Retain 60 data points
const chart = echarts.init(document.getElementById('realtime-chart'));
// WebSocket data reception
const ws = new WebSocket('ws://realtime-server');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
buffer.push(data.value);
// Throttle updates
throttleUpdate(() => {
chart.setOption({
series: [{
type: 'line',
data: buffer.getData(),
animationDuration: 0 // Disable animations for better performance
}]
});
}, 100); // Update at most every 100ms
};
// Throttle function
function throttleUpdate(fn, delay) {
let lastCall = 0;
return function(...args) {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
return fn.apply(this, args);
}
};
}
Data Grouping and Chunk Loading
Chunk loading strategy for large datasets:
// Chunk loading function
async function loadDataChunks(chart, totalPoints, chunkSize = 1000) {
let loaded = 0;
while (loaded < totalPoints) {
const chunk = await fetchChunk(loaded, Math.min(chunkSize, totalPoints - loaded));
// Use appendData for incremental addition
chart.appendData({
seriesIndex: 0,
data: chunk
});
loaded += chunk.length;
// Dynamically adjust chunk size
const renderTime = measureRender();
if (renderTime > 50) {
chunkSize = Math.max(100, chunkSize / 2);
} else if (renderTime < 20 && chunkSize < 5000) {
chunkSize = Math.min(5000, chunkSize * 1.5);
}
}
}
// Simulate data fetching
async function fetchChunk(offset, size) {
return new Array(size).fill(0).map((_, i) => {
return Math.sin((offset + i) / 50) + Math.random() * 0.2;
});
}
// Render performance measurement
function measureRender() {
const start = performance.now();
chart.getZr().flush();
return performance.now() - start;
}
Dynamic Data Mapping and Transformation
Real-time transformation during data updates:
// Dynamic data aggregation
function aggregateData(rawData, binSize) {
const aggregated = [];
for (let i = 0; i < rawData.length; i += binSize) {
const chunk = rawData.slice(i, i + binSize);
aggregated.push({
min: Math.min(...chunk),
max: Math.max(...chunk),
avg: chunk.reduce((a, b) => a + b, 0) / chunk.length
});
}
return aggregated;
}
// Apply transformations during updates
function updateWithTransformation() {
fetch('/api/raw-data').then(response => response.json()).then(rawData => {
// Determine aggregation granularity based on current zoom level
const zoomLevel = chart.getModel().getOption().dataZoom[0].end;
const dynamicBinSize = Math.max(1, Math.floor(rawData.length / (100 * zoomLevel)));
const processedData = aggregateData(rawData, dynamicBinSize);
chart.setOption({
series: [{
type: 'custom',
renderItem: function(params, api) {
const index = api.value(0);
return {
type: 'rect',
shape: {
x: params.coordSys.x + index * 10,
y: api.value(1).min * 100,
width: 8,
height: (api.value(1).max - api.value(1).min) * 100
},
style: {
fill: '#5470C6'
}
};
},
data: processedData.map((d, i) => [i, d])
}]
});
});
}
Multi-Chart Linked Updates
Implementing data linkage between multiple charts:
// Master chart
const masterChart = echarts.init(document.getElementById('master'));
masterChart.setOption({
// ...Master chart configuration
dataZoom: [{
type: 'slider',
realtime: true,
filterMode: 'filter'
}]
});
// Detail chart
const detailChart = echarts.init(document.getElementById('detail'));
// Linkage handling
masterChart.on('dataZoom', (params) => {
const option = masterChart.getOption();
const startValue = option.dataZoom[0].startValue;
const endValue = option.dataZoom[0].endValue;
// Get filtered data
const filteredData = originalData.filter(
item => item.timestamp >= startValue && item.timestamp <= endValue
);
// Update detail chart
detailChart.setOption({
dataset: {
source: filteredData
}
}, { replaceMerge: 'dataset' }); // Special merge strategy
});
// Initial load
fetch('/api/big-data').then(res => res.json()).then(data => {
originalData = data;
masterChart.setOption({ dataset: { source: data } });
});
Performance Optimization Techniques
Practical methods to improve dynamic update performance:
- Dirty Rectangle Optimization: Only redraw changed areas
chart.setOption({
animation: false, // Disable animations
silent: true, // Don't trigger events
lazyUpdate: true // Lazy updates
});
- Web Worker Data Processing:
// worker.js
self.onmessage = function(e) {
const { data, transformType } = e.data;
let result;
// Complex data processing
if (transformType === 'aggregate') {
result = heavyDataProcessing(data);
}
self.postMessage(result);
};
// Main thread
const worker = new Worker('worker.js');
worker.onmessage = function(e) {
chart.setOption({
series: [{ data: e.data }]
});
};
function updateViaWorker() {
worker.postMessage({
data: rawDataArray,
transformType: 'aggregate'
});
}
- Canvas Layer Control:
// Use separate zlevel for frequently updated series
chart.setOption({
series: [{
zlevel: 1, // High-frequency update layer
data: dynamicData
}, {
zlevel: 0, // Static background layer
data: staticData
}]
});
Special Scenario Handling
Strategies for handling extreme data changes:
// Outlier detection
function detectOutliers(newData, threshold = 3) {
const mean = newData.reduce((a, b) => a + b, 0) / newData.length;
const std = Math.sqrt(
newData.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / newData.length
);
return newData.map(value => {
return Math.abs(value - mean) > threshold * std ?
{ value, itemStyle: { color: '#FF0000' } } :
value;
});
}
// Apply outlier detection
function updateWithOutlierDetection() {
const newData = fetchLatestData();
const processedData = detectOutliers(newData);
chart.setOption({
series: [{
data: processedData,
markPoint: {
data: processedData
.filter(item => typeof item === 'object')
.map((item, index) => ({
coord: [index, item.value],
symbol: 'triangle',
symbolSize: 10
}))
}
}]
});
}
Dynamic Theme Adaptation
Adjust visual styles synchronously with data updates:
// Dynamically adjust colors based on data characteristics
function getDynamicColorScheme(data) {
const avg = data.reduce((a, b) => a + b, 0) / data.length;
return avg > 50 ?
['#c23531', '#2f4554', '#61a0a8'] :
['#91c7ae', '#749f83', '#ca8622'];
}
function updateChartWithDynamicTheme() {
const newData = [/* ... */];
chart.setOption({
color: getDynamicColorScheme(newData),
series: [{
data: newData,
itemStyle: {
color: function(params) {
// Data-driven gradient
return {
type: 'linear',
x: 0, y: 0, x2: 0, y2: 1,
colorStops: [{
offset: 0,
color: params.value > 50 ? '#c23531' : '#91c7ae'
}, {
offset: 1,
color: params.value > 50 ? '#2f4554' : '#749f83'
}]
};
}
}
}]
});
}
Timeline Dynamic Control
Implementing dynamic updates based on timelines:
// Timeline configuration
chart.setOption({
timeline: {
axisType: 'time',
currentIndex: 0,
autoPlay: true,
playInterval: 1000,
data: []
},
options: []
});
// Dynamically add timeline points
function addTimelinePoint(timestamp, data) {
const option = chart.getOption();
// Add new timeline point
option.timeline[0].data.push(timestamp);
// Add corresponding configuration
option.options.push({
series: [{ data }]
});
// Update chart
chart.setOption(option);
}
// Simulate real-time addition
let counter = 0;
setInterval(() => {
const now = new Date();
const newData = generateDataForTime(now);
addTimelinePoint(now.toISOString(), newData);
// Limit history length
if (++counter > 30) {
const option = chart.getOption();
option.timeline[0].data.shift();
option.options.shift();
chart.setOption(option);
}
}, 1000);
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn