数据转换与预处理
数据转换与预处理
数据可视化过程中,原始数据往往不能直接用于图表渲染。ECharts作为流行的可视化库,提供了丰富的数据处理能力。从数据清洗到格式转换,再到聚合计算,每个环节都影响着最终呈现效果。
数据格式标准化
ECharts支持多种数据格式,但推荐使用键值对数组形式。原始CSV或JSON数据经常需要转换:
// 原始数据
const rawData = [
{ year: '2020', sales: 1250 },
{ year: '2021', sales: 1870 },
{ year: '2022', sales: 2100 }
];
// 转换为ECharts需要的格式
const chartData = {
xAxis: rawData.map(item => item.year),
series: [{
data: rawData.map(item => item.sales)
}]
};
时间数据需要特别注意格式统一。使用moment.js或原生Date对象处理时间格式:
const timeData = [
{ date: '2023-01', value: 42 },
{ date: '2023-02', value: 78 }
];
// 转换为时间戳格式
const processedData = timeData.map(item => ({
...item,
date: new Date(item.date + '-01').getTime()
}));
数据清洗与过滤
异常值处理是数据预处理的关键环节。通过设定阈值过滤不合理数据:
const dirtyData = [12, 45, 999, 32, -5, 28];
// 过滤超出0-100范围的值
const cleanData = dirtyData.filter(
value => value >= 0 && value <= 100
);
缺失值处理可采用插值法。线性插值示例:
const incompleteData = [
{ x: 1, y: 10 },
{ x: 2, y: null },
{ x: 3, y: 30 }
];
// 线性插值补全缺失值
for (let i = 1; i < incompleteData.length - 1; i++) {
if (incompleteData[i].y === null) {
incompleteData[i].y =
(incompleteData[i-1].y + incompleteData[i+1].y) / 2;
}
}
数据聚合与分组
大数据量时需要聚合处理。按时间维度聚合的典型示例:
const dailyData = [
{ date: '2023-01-01', category: 'A', value: 10 },
{ date: '2023-01-01', category: 'B', value: 20 },
// ...更多数据
];
// 按月聚合
const monthlyData = dailyData.reduce((acc, curr) => {
const month = curr.date.substring(0, 7);
if (!acc[month]) acc[month] = 0;
acc[month] += curr.value;
return acc;
}, {});
// 转换为ECharts格式
const seriesData = Object.entries(monthlyData).map(
([month, value]) => [month, value]
);
分类数据分组统计:
const products = [
{ category: '电子', price: 999 },
{ category: '服装', price: 199 },
// ...更多商品
];
// 按类别分组计算平均价格
const categoryStats = products.reduce((acc, product) => {
if (!acc[product.category]) {
acc[product.category] = { sum: 0, count: 0 };
}
acc[product.category].sum += product.price;
acc[product.category].count++;
return acc;
}, {});
// 计算平均值
const result = Object.entries(categoryStats).map(
([category, stats]) => ({
category,
avgPrice: stats.sum / stats.count
})
);
数据映射与转换
将原始值映射到可视化属性是常见需求。颜色映射示例:
const temperatureData = [12, 18, 25, 30, 15];
// 温度到颜色的映射函数
function tempToColor(temp) {
if (temp < 15) return '#3498db'; // 冷色
if (temp < 25) return '#2ecc71'; // 舒适
return '#e74c3c'; // 炎热
}
const coloredData = temperatureData.map(temp => ({
value: temp,
itemStyle: { color: tempToColor(temp) }
}));
数值范围归一化处理:
const rawValues = [50, 120, 80, 200];
// 归一化到0-1范围
const max = Math.max(...rawValues);
const normalized = rawValues.map(v => v / max);
// 再映射到特定范围(如50-200像素)
const range = [50, 200];
const finalValues = normalized.map(
v => range[0] + v * (range[1] - range[0])
);
时间序列处理
处理时间数据时需要特殊转换。周数据转换为日历坐标:
const weekData = [
{ day: '周一', value: 12 },
{ day: '周二', value: 19 },
// ...完整周数据
];
// 转换为日历坐标系需要的格式
const calendarData = weekData.map((item, index) => [
index, // x轴坐标
item.value, // y轴值
item.day // 显示标签
]);
处理不连续时间序列:
const sparseTimeData = [
{ time: '2023-01', value: 10 },
{ time: '2023-03', value: 20 },
{ time: '2023-06', value: 15 }
];
// 补全缺失月份数据
const allMonths = ['01','02','03','04','05','06'].map(m => `2023-${m}`);
const completeData = allMonths.map(month => {
const existing = sparseTimeData.find(d => d.time === month);
return existing || { time: month, value: 0 };
});
多维数据透视
处理多维数据时需要降维展示。使用dataset和dimensions配置:
const multiDimData = [
{ product: '手机', region: '华东', sales: 1200 },
{ product: '电脑', region: '华北', sales: 800 },
// ...更多数据
];
option = {
dataset: {
source: multiDimData,
dimensions: ['product', 'region', 'sales']
},
series: {
type: 'bar',
encode: {
x: 'product',
y: 'sales',
itemName: 'region'
}
}
};
性能优化处理
大数据量时需要进行采样优化。等距采样算法:
const largeData = [...Array(10000)].map((_, i) => ({
x: i,
y: Math.sin(i / 100)
}));
// 等距采样保留100个点
const sampleSize = 100;
const step = Math.floor(largeData.length / sampleSize);
const sampledData = [];
for (let i = 0; i < largeData.length; i += step) {
sampledData.push(largeData[i]);
}
增量数据更新策略:
let allData = [...initialData];
// 新数据到达时
function handleNewData(newPoints) {
// 保留最近1000个点
if (allData.length + newPoints.length > 1000) {
allData = allData.slice(newPoints.length);
}
allData.push(...newPoints);
// 更新图表
myChart.setOption({
series: [{ data: allData }]
});
}
交互数据处理
动态筛选数据示例:
const fullData = [
{ name: '北京', value: 123 },
{ name: '上海', value: 156 },
// ...更多城市数据
];
function filterData(minValue) {
return fullData.filter(item => item.value >= minValue);
}
// 滑块交互
document.getElementById('rangeSlider').addEventListener('input', (e) => {
const filtered = filterData(parseInt(e.target.value));
myChart.setOption({
series: [{ data: filtered }]
});
});
地理数据转换
处理GeoJSON数据时需要特殊转换:
// 从GeoJSON提取坐标边界
function getBounds(features) {
return features.reduce((bounds, feature) => {
const [minX, minY, maxX, maxY] = turf.bbox(feature);
bounds.minX = Math.min(bounds.minX || Infinity, minX);
bounds.minY = Math.min(bounds.minY || Infinity, minY);
bounds.maxX = Math.max(bounds.maxX || -Infinity, maxX);
bounds.maxY = Math.max(bounds.maxY || -Infinity, maxY);
return bounds;
}, {});
}
坐标转换示例:
// WGS84转Web墨卡托
function wgs84ToMercator(lng, lat) {
const x = lng * 20037508.34 / 180;
const y = Math.log(Math.tan((90 + lat) * Math.PI / 360)) / (Math.PI / 180);
return [x, y * 20037508.34 / 180];
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn