Highstock仅导出可见/选中区间CSV/XML及时间戳格式处理问题
Highstock 仅导出可见区间数据并修正时间格式解决方案
实现逻辑
核心逻辑是重写Highcharts.Chart.prototype.getDataRows方法,在遍历原始数据的时候新增可见区间判断条件,仅将x坐标落在xAxis.min和xAxis.max之间的点加入导出数据集,以此实现仅导出当前选中/可见区间的数据。
时间格式异常修复
导出时间为Unix时间戳的问题无需修改重写的核心逻辑,只需要在图表配置的exporting.csv节点下指定符合需求的时间格式化字符串即可,你可以根据业务场景调整时间格式,比如需要秒级精度可改为%Y-%m-%d %H:%M:%S。
完整可运行代码
JS代码
var defined = Highcharts.defined, each = Highcharts.each, pick = Highcharts.pick, win = Highcharts.win, doc = win.document, seriesTypes = Highcharts.seriesTypes, downloadAttrSupported = doc.createElement('a').download !== undefined; Highcharts.Chart.prototype.getDataRows = function (multiLevelHeaders) { var time = this.time, csvOptions = (this.options.exporting && this.options.exporting.csv) || {}, xAxis, xAxes = this.xAxis, rows = {}, rowArr = [], dataRows, topLevelColumnTitles = [], columnTitles = [], columnTitleObj, i, x, xTitle, // 列头格式化逻辑 columnHeaderFormatter = function (item, key, keyLength) { if (csvOptions.columnHeaderFormatter) { var s = csvOptions.columnHeaderFormatter(item, key, keyLength); if (s !== false) { return s; } } if (!item) { return '分类'; } if (item instanceof Highcharts.Axis) { return (item.options.title && item.options.title.text) || (item.isDatetimeAxis ? '时间' : '分类'); } if (multiLevelHeaders) { return { columnTitle: keyLength > 1 ? key : item.name, topLevelColumnTitle: item.name }; } return item.name + (keyLength > 1 ? ' (' + key + ')' : ''); }, xAxisIndices = []; // 遍历序列索引值 i = 0; this.setUpKeyToAxis(); each(this.series, function (series) { var keys = series.options.keys, pointArrayMap = keys || series.pointArrayMap || ['y'], valueCount = pointArrayMap.length, xTaken = !series.requireSorting && {}, categoryMap = {}, datetimeValueAxisMap = {}, xAxisIndex = Highcharts.inArray(series.xAxis, xAxes), mockSeries, j; // 映射值轴的分类 each(pointArrayMap, function (prop) { var axisName = ( (series.keyToAxis && series.keyToAxis[prop]) || prop ) + 'Axis'; categoryMap[prop] = ( series[axisName] && series[axisName].categories ) || []; datetimeValueAxisMap[prop] = ( series[axisName] && series[axisName].isDatetimeAxis ); }); if ( series.options.includeInCSVExport !== false && !series.options.isInternal && series.visible !== false ) { // 构建X轴索引的查找表 if (!Highcharts.find(xAxisIndices, function (index) { return index[0] === xAxisIndex; })) { xAxisIndices.push([xAxisIndex, i]); } // 计算列头 j = 0; while (j < valueCount) { columnTitleObj = columnHeaderFormatter( series, pointArrayMap[j], pointArrayMap.length ); columnTitles.push( columnTitleObj.columnTitle || columnTitleObj ); if (multiLevelHeaders) { topLevelColumnTitles.push( columnTitleObj.topLevelColumnTitle || columnTitleObj ); } j++; } mockSeries = { chart: series.chart, autoIncrement: series.autoIncrement, options: series.options, pointArrayMap: series.pointArrayMap }; // 直接从原始数据遍历,兼容Boost等场景 each(series.options.data, function eachData(options, pIdx) { var key, prop, val, name, point; point = { series: mockSeries }; series.pointClass.prototype.applyOptions.apply( point, [options] ); // 仅保留当前可见区间内的点 if (point.x >= series.xAxis.min && point.x <= series.xAxis.max) { key = point.x; name = series.data[pIdx] && series.data[pIdx].name; if (xTaken) { if (xTaken[key]) { key += '|' + pIdx; } xTaken[key] = true; } j = 0; // 饼图、漏斗图等特殊类型使用点名称作为X值 if (!series.xAxis || series.exportKey === 'name') { key = name; } if (!rows[key]) { rows[key] = []; rows[key].xValues = []; } rows[key].x = point.x; rows[key].name = name; rows[key].xValues[xAxisIndex] = point.x; while (j < valueCount) { prop = pointArrayMap[j]; val = point[prop]; rows[key][i + j] = pick( categoryMap[prop][val], datetimeValueAxisMap[prop] ? time.dateFormat(csvOptions.dateFormat, val) : null, val ); j++; } } }); i = i + j; } }); // 转换为可排序数组 for (x in rows) { if (rows.hasOwnProperty(x)) { rowArr.push(rows[x]); } } var xAxisIndex, column; // 组装最终导出行 dataRows = multiLevelHeaders ? [topLevelColumnTitles, columnTitles] : [columnTitles]; i = xAxisIndices.length; while (i--) { xAxisIndex = xAxisIndices[i][0]; column = xAxisIndices[i][1]; xAxis = xAxes[xAxisIndex]; // 按X值排序 rowArr.sort(function (a, b) { return a.xValues[xAxisIndex] - b.xValues[xAxisIndex]; }); // 添加表头 xTitle = columnHeaderFormatter(xAxis); dataRows[0].splice(column, 0, xTitle); if (multiLevelHeaders && dataRows[1]) { dataRows[1].splice(column, 0, xTitle); } // 添加分类列 each(rowArr, function (row) { var category = row.name; if (xAxis && !defined(category)) { if (xAxis.isDatetimeAxis) { if (row.x instanceof Date) { row.x = row.x.getTime(); } // 时间格式化核心逻辑 category = time.dateFormat( csvOptions.dateFormat, row.x ); } else if (xAxis.categories) { category = pick( xAxis.names[row.x], xAxis.categories[row.x], row.x ); } else { category = row.x; } } row.splice(column, 0, category); }); } dataRows = dataRows.concat(rowArr); Highcharts.fireEvent(this, 'exportData', { dataRows: dataRows }); return dataRows; }; // 初始化Highstock图表 var chart = new Highcharts.StockChart({ chart: { renderTo: 'container' }, navigator: { series: { includeInCSVExport: false } }, series: [{ data: [29.9, 71.5, 106.4, 129.2, 144.0, 176.0, 135.6, 148.5, 216.4, 194.1, 95.6, 54.4, 32, 221, 123, 321, 322, 29, 177, 76, 46, 245, 122, 67], pointStart: Date.UTC(2013, 0, 1), pointInterval: 24 * 36e5 }], exporting: { csv: { dateFormat: '%Y-%m-%d' // 可修改此处调整时间格式 } } }); // 测试导出按钮事件 document.getElementById('getcsv').addEventListener('click', function () { alert(chart.getCSV()); });
HTML代码
<script src="https://code.highcharts.com/stock/highstock.js"></script> <script src="https://code.highcharts.com/modules/export-data.js"></script> <div id="container" style="height: 300px; margin-top: 2em"></div> <button id="getcsv">获取CSV内容</button>
注意事项
- 导航器的序列默认配置了
includeInCSVExport: false,避免导出重复数据 - 该实现兼容Boost数据加速模式,不会出现大数据量下导出异常的问题
内容的提问来源于stack exchange,提问作者Fabio Ribeiro de Carvalho
相关产品推荐
相关产品推荐

