You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.11 03:30:02