package com.njcn.echarts.json;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONUtil;
import com.njcn.echarts.pojo.bo.TolerateData;
import com.njcn.echarts.pojo.constant.PicCommonData;
import org.icepear.echarts.Option;
import org.icepear.echarts.charts.bar.BarItemStyle;
import org.icepear.echarts.charts.bar.BarLabel;
import org.icepear.echarts.charts.bar.BarSeries;
import org.icepear.echarts.charts.line.LineSeries;
import org.icepear.echarts.charts.scatter.ScatterSeries;
import org.icepear.echarts.components.coord.*;
import org.icepear.echarts.components.coord.cartesian.CategoryAxis;
import org.icepear.echarts.components.coord.cartesian.LogAxis;
import org.icepear.echarts.components.coord.cartesian.ValueAxis;
import org.icepear.echarts.components.grid.Grid;
import org.icepear.echarts.components.legend.Legend;
import org.icepear.echarts.components.series.LineStyle;
import org.icepear.echarts.components.text.Label;
import org.icepear.echarts.components.title.Title;
import org.icepear.echarts.components.tooltip.Tooltip;
import org.icepear.echarts.origin.util.SeriesOption;
import org.icepear.echarts.render.Engine;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;


/**
 * @author hongawen
 * @version 1.0.0
 * @date 2023年06月21日 10:05
 */
public class LineGenerator {

    private final static Engine ENGINE = new Engine();
    // null段在图表上的固定视觉宽度（数据点数量）
    private static final int NULL_PLACEHOLDER_COUNT = 90;
    // 压缩结果
    private static class CompressedResult {
        // 压缩后的数据
        List<List<Float>> data;
        // 每个null段的[起始索引, 结束索引]（占位点范围）
        List<int[]> nullRegionIndices;
        // null段边界处的真实x值（entry, exit交替）
        List<Float> nullBoundaryRealXValues;
    }


    /***
     * 生成ITIC曲线
     * @author hongawen
     * @date 2023/6/21 10:06
     */
    public static String generateIticOption(TolerateData tolerateData) {
        Option iticOption = new Option();
        //取消渲染动画
        iticOption.setAnimation(false);
        //背景色
        iticOption.setBackgroundColor(PicCommonData.PIC_BACK_COLOR);
        //标题
        iticOption.setTitle(new Title().setLeft(PicCommonData.CENTER).setText("ITIC曲线"));
        //上下左右的图内间距
        iticOption.setGrid(new Grid().setTop("80px").setLeft("40px").setRight("40px").setBottom("10%"));
        //设置图例
        iticOption.setLegend(new Legend().setData(new String[]{"上限", "下限", "可容忍事件", "不可容忍事件"}).setTop("26px").setLeft(0).setBottom("94%"));
        //设置图例对应的颜色
        iticOption.setColor(new String[]{"#FF8C00", "#00BFFF", "green", "red"});
        //横坐标
        iticOption.setXAxis(new LogAxis().setMin("0.001").setMax("1000").setSplitLine(new SplitLine().setShow(false)).setName("s"));
        //纵坐标
        iticOption.setYAxis(new ValueAxis().setName("%").setMinInterval(3).setSplitNumber(10));
        //处理上限配置
        LineSeries upperLimit = new LineSeries()
                .setName("上限")
                .setData(new float[][]{{0.001f, 200}, {0.003f, 140}, {0.003f, 120}, {0.5f, 120}, {0.5f, 110}, {10, 110}, {1000, 110}})
                .setShowSymbol(false)
                .setTooltip(new Tooltip().setShow(false));

        //处理下限配置
        LineSeries lowerLimit = new LineSeries()
                .setName("下限")
                .setData(new float[][]{{0.02f, 0}, {0.02f, 70}, {0.5f, 70}, {0.5f, 80}, {10, 80}, {10, 90}, {1000, 90}})
                .setShowSymbol(false)
                .setTooltip(new Tooltip().setShow(false));
        //配置可容忍点数据
        ScatterSeries tolerate = new ScatterSeries()
                .setName("可容忍事件")
                .setSymbol("circle")
                .setData(tolerateData.getTolerateData());
        //配置不可容忍点数据
        ScatterSeries unTolerate = new ScatterSeries()
                .setName("不可容忍事件")
                .setSymbol("circle")
                .setData(tolerateData.getUnTolerateData());
        iticOption.setSeries(new SeriesOption[]{upperLimit, lowerLimit, tolerate, unTolerate});
        return ENGINE.renderJsonOption(iticOption);
    }


    /***
     * 生成F47曲线
     * @author hongawen
     * @date 2023/6/21 10:06
     */
    public static String generateF47Option(TolerateData tolerateData) {
        Option f47Option = new Option();
        //取消渲染动画
        f47Option.setAnimation(false);
        //背景色
        f47Option.setBackgroundColor(PicCommonData.PIC_BACK_COLOR);
        //标题
        f47Option.setTitle(new Title().setLeft(PicCommonData.CENTER).setText("SEMI F47曲线"));
        //上下左右的图内间距
        f47Option.setGrid(new Grid().setTop("80px").setLeft("40px").setRight("40px").setBottom("10%"));
        //设置图例
        f47Option.setLegend(new Legend().setData(new String[]{"边界线", "可容忍事件", "不可容忍事件"}).setTop("26px").setLeft(0).setBottom("94%"));
        //设置图例对应的颜色
        f47Option.setColor(new String[]{"yellow", "green", "red"});
        //横坐标
        f47Option.setXAxis(new LogAxis().setMin("0.001").setMax("1000").setSplitLine(new SplitLine().setShow(false)).setName("s"));
        //纵坐标
        f47Option.setYAxis(new ValueAxis().setName("%").setMinInterval(0.1).setSplitNumber(10).setMax(100));
        //处理边界线
        LineSeries borderLimit = new LineSeries()
                .setName("边界线")
                .setData(new float[][]{{0.05f, 0}, {0.05f, 50}, {0.2f, 50}, {0.2f, 70}, {0.5f, 70}, {0.5f, 80}, {10, 80}, {10, 90}, {1000, 90}})
                .setShowSymbol(false)
                .setTooltip(new Tooltip().setShow(false));
        //配置可容忍点数据
        ScatterSeries tolerate = new ScatterSeries()
                .setName("可容忍事件")
                .setSymbol("circle")
                .setData(tolerateData.getTolerateData());
        //配置不可容忍点数据
        ScatterSeries unTolerate = new ScatterSeries()
                .setName("不可容忍事件")
                .setSymbol("circle")
                .setData(tolerateData.getUnTolerateData());
        f47Option.setSeries(new SeriesOption[]{borderLimit, tolerate, unTolerate});
        return ENGINE.renderJsonOption(f47Option);
    }


    /***
     * 生成暂降幅值
     * @author hongawen
     * @date 2023/6/21 10:06
     */
    public static String generateEventAmplitudeOption(List<String> ylinedata, List<String> ybardata) {
        Option eventAmplitudeOption = new Option();
        //取消渲染动画
        eventAmplitudeOption.setAnimation(false);
        //背景色
        eventAmplitudeOption.setBackgroundColor(PicCommonData.PIC_BACK_COLOR);
        //标题
        eventAmplitudeOption.setTitle(new Title().setLeft(PicCommonData.CENTER).setText("暂降幅值的概率分布"));
        //设置图例
        eventAmplitudeOption.setLegend(new Legend().setData(new String[]{"概率分布", "占比"}).setLeft(10).setShow(true));
        //横坐标
        eventAmplitudeOption.setXAxis(new CategoryAxis()
                .setBoundaryGap(true)
                .setName("暂降幅值")
                .setNameTextStyle(new AxisNameTextStyle().setFontStyle("15px"))
                .setData(new String[]{"0", "10%", "20%", "30%", "40%", "50%", "60%", "70%", "80%", "90%"})
        );
        //纵坐标
        eventAmplitudeOption.setYAxis(new ValueAxis[]{
                new ValueAxis()
                        .setName("%")
                        .setNameTextStyle(new AxisNameTextStyle().setFontStyle("15px"))
                        .setAxisLabel(new ValueAxisLabel().setFormatter("{value}%"))
        });

        //配置概率分布
        LineSeries probability = new LineSeries()
                .setName("概率分布")
                .setData(ylinedata);
        //配置占比
        BarSeries proportion = new BarSeries()
                .setName("占比")
                .setBarWidth(30)
                .setData(ybardata);

        eventAmplitudeOption.setSeries(new SeriesOption[]{probability, proportion});
        return ENGINE.renderJsonOption(eventAmplitudeOption);
    }

    /***
     * 生成持续时间
     * @author hongawen
     * @date 2023/6/21 10:06
     */
    public static String generatePersistentTimeOption(List<String> ylinedata, List<String> ybardata) {
        Option persistentTimeOption = new Option();
        //取消渲染动画
        persistentTimeOption.setAnimation(false);
        //背景色
        persistentTimeOption.setBackgroundColor(PicCommonData.PIC_BACK_COLOR);
        //标题
        persistentTimeOption.setTitle(new Title().setLeft(PicCommonData.CENTER).setText("持续时间的概率分布"));
        //设置图例
        persistentTimeOption.setLegend(new Legend().setData(new String[]{"概率分布", "占比"}).setLeft(10).setShow(true));
        //横坐标
        persistentTimeOption.setXAxis(new CategoryAxis()
                .setBoundaryGap(true)
                .setName("暂态持续时间(s)")
                .setNameTextStyle(new AxisNameTextStyle().setFontStyle("15px"))
                .setData(new String[]{"0.01", "0.1", "0.25", "0.5", "1", "3", "20", "60", "180"})
        );
        //纵坐标
        persistentTimeOption.setYAxis(new ValueAxis[]{
                new ValueAxis()
                        .setName("%")
                        .setNameTextStyle(new AxisNameTextStyle().setFontStyle("15px"))
                        .setAxisLabel(new ValueAxisLabel().setFormatter("{value}%"))
        });

        //配置概率分布
        LineSeries probability = new LineSeries()
                .setName("概率分布")
                .setData(ylinedata);
        //配置占比
        BarSeries proportion = new BarSeries()
                .setName("占比")
                .setBarWidth(30)
                .setData(ybardata);

        persistentTimeOption.setSeries(new SeriesOption[]{probability, proportion});
        return ENGINE.renderJsonOption(persistentTimeOption);
    }

    /***
     * 生成区域统计
     * @author hongawen
     * @date 2023/6/21 10:06
     */
    public static String generateRegionOption(List<String> xData, List<Integer> yData) {
        Option persistentTimeOption = new Option();
        //取消渲染动画
        persistentTimeOption.setAnimation(false);
        //背景色
        persistentTimeOption.setBackgroundColor(PicCommonData.PIC_BACK_COLOR);
        //标题
        persistentTimeOption.setTitle(new Title().setLeft(PicCommonData.CENTER).setText("区域统计"));
        //设置图例
        persistentTimeOption.setLegend(new Legend().setData(new String[]{"暂降次数"}).setLeft(10).setShow(true));
        //颜色
        persistentTimeOption.setColor("orange");
        //横坐标
        persistentTimeOption.setXAxis(new CategoryAxis()
                .setBoundaryGap(true)
                .setName("地区\n(监测点数)")
                .setAxisLabel(new LogAxisLabel().setShow(true).setFontSize(10).setRotate(0))
                .setNameTextStyle(new AxisNameTextStyle().setFontStyle("15px"))
                .setData(xData.toArray())
        );
        //纵坐标
        persistentTimeOption.setYAxis(new ValueAxis()
                .setName("(次)")
                .setNameTextStyle(new AxisNameTextStyle().setFontStyle("15px"))
        );


        //配置占比
        BarSeries proportion = new BarSeries()
                .setName("暂降次数")
                .setBarWidth(30)
                .setLabel(new BarLabel().setShow(true).setColor("#8B008B"))
                .setData(yData);
        persistentTimeOption.setSeries(new SeriesOption[]{proportion});
        return ENGINE.renderJsonOption(persistentTimeOption);
    }

    /***
     * 生成电压暂降，已关联未关联得次数
     * @author hongawen
     * @date 2023/6/21 10:06
     */
    public static String generateAssociatedUnassociatedOption(List<String> eventValue, List<Integer> pereventValue, List<Integer> pereventValue1, String xName) {
        Option persistentTimeOption = new Option();
        //取消渲染动画
        persistentTimeOption.setAnimation(false);
        //背景色
        persistentTimeOption.setBackgroundColor(PicCommonData.PIC_BACK_COLOR);
        //标题
        persistentTimeOption.setTitle(new Title().setLeft(PicCommonData.CENTER).setText(xName));
        //设置图例
        persistentTimeOption.setLegend(new Legend().setData(new String[]{"未关联暂降次数", "已关联处理事件"}).setLeft(10).setShow(true));
        //横坐标
        persistentTimeOption.setXAxis(new CategoryAxis()
                .setBoundaryGap(true)
                .setName(xName)
                .setAxisLabel(new LogAxisLabel().setShow(true).setFontSize(7).setRotate(0))
                .setNameTextStyle(new AxisNameTextStyle().setFontStyle("15px"))
                .setData(eventValue.toArray())
        );
        //纵坐标
        persistentTimeOption.setYAxis(new ValueAxis()
                .setName("(次)")
                .setNameTextStyle(new AxisNameTextStyle().setFontStyle("15px"))
        );


        //配置占比
        BarSeries proportion = new BarSeries()
                .setName("未关联暂降次数")
                .setStack("事件总数")
                .setItemStyle(new BarItemStyle().setColor("orange").setBorderColor("orange").setBorderWidth(2).setBorderRadius(0))
                .setLabel(new BarLabel().setShow(true).setColor("#8B008B"))
                .setData(pereventValue);
        BarSeries proportion2 = new BarSeries()
                .setName("已关联处理事件")
                .setStack("事件总数")
                .setItemStyle(new BarItemStyle().setColor("#ccc").setBorderColor("#ccc").setBorderWidth(2).setBorderRadius(0))
                .setLabel(new BarLabel().setShow(true).setColor("#8B008B"))
                .setData(pereventValue1);
        persistentTimeOption.setSeries(new SeriesOption[]{proportion, proportion2});
        return ENGINE.renderJsonOption(persistentTimeOption);
    }

    /***
     * 生成电压等级统计
     * @author hongawen
     * @date 2023/6/21 10:06
     */
    public static String generateVoltageLevelOption(List<String> xData, List<Integer> yData) {
        Option persistentTimeOption = new Option();
        //取消渲染动画
        persistentTimeOption.setAnimation(false);
        //背景色
        persistentTimeOption.setBackgroundColor(PicCommonData.PIC_BACK_COLOR);
        //标题
        persistentTimeOption.setTitle(new Title().setLeft(PicCommonData.CENTER).setText("电压等级"));
        //设置图例
        persistentTimeOption.setLegend(new Legend().setData(new String[]{"暂降次数"}).setLeft(10).setShow(true));
        //颜色
        persistentTimeOption.setColor("orange");
        //横坐标
        persistentTimeOption.setXAxis(new CategoryAxis()
                .setBoundaryGap(true)
                .setName("电压等级\n(监测点数)")
                .setAxisLabel(new LogAxisLabel().setShow(true).setFontSize(8).setRotate(0).setFontWeight("bold"))
                .setNameTextStyle(new AxisNameTextStyle().setFontStyle("15px"))
                .setData(xData.toArray())
        );
        //纵坐标
        persistentTimeOption.setYAxis(new ValueAxis()
                .setName("(次)")
                .setNameTextStyle(new AxisNameTextStyle().setFontStyle("15px"))
        );

        //配置占比
        BarSeries proportion = new BarSeries()
                .setName("暂降次数")
                .setBarWidth(30)
                .setLabel(new BarLabel().setShow(true).setColor("#8B008B"))
                .setData(yData);
        persistentTimeOption.setSeries(new SeriesOption[]{proportion});
        return ENGINE.renderJsonOption(persistentTimeOption);
    }


    /***
     * 生成设备运行情况
     * @author hongawen
     * @date 2023/6/21 10:06
     */
    public static String generateRegionDeviceOption(List<String> xData, List<Integer> onlineData, List<Integer> offlineData, String subtext, String xName) {
        Option persistentTimeOption = new Option();
        //取消渲染动画
        persistentTimeOption.setAnimation(false);
        //背景色
        persistentTimeOption.setBackgroundColor(PicCommonData.PIC_BACK_COLOR);
        //标题
        persistentTimeOption.setTitle(new Title().setLeft(PicCommonData.CENTER).setText("设备运行情况")
                .setSubtext(subtext)
        );
        //设置图例
        persistentTimeOption.setLegend(new Legend().setData(new String[]{"在线", "离线"}).setLeft(10).setShow(true));
        //横坐标
        persistentTimeOption.setXAxis(new CategoryAxis()
                .setBoundaryGap(true)
                .setName(xName)
                .setAxisLabel(new LogAxisLabel().setShow(true).setFontSize(7).setRotate(0).setFontWeight("bold"))
                .setNameTextStyle(new AxisNameTextStyle().setFontStyle("15px"))
                .setData(xData.toArray())
        );
        //纵坐标
        persistentTimeOption.setYAxis(new ValueAxis()
                .setName("(次)")
                .setNameTextStyle(new AxisNameTextStyle().setFontStyle("15px"))
        );


        //配置占比
        BarSeries proportion = new BarSeries()
                .setName("在线")
                .setStack("account")
                .setItemStyle(new BarItemStyle().setColor("green").setBorderColor("green").setBorderWidth(2).setBorderRadius(0))
                .setLabel(new BarLabel().setShow(true).setColor("#8B008B"))
                .setData(onlineData);
        BarSeries proportion2 = new BarSeries()
                .setName("离线")
                .setStack("account")
                .setItemStyle(new BarItemStyle().setColor("orange").setBorderColor("orange").setBorderWidth(2).setBorderRadius(0))
                .setLabel(new BarLabel().setShow(true).setColor("#8B008B"))
                .setData(offlineData);
        persistentTimeOption.setSeries(new SeriesOption[]{proportion, proportion2});
        return ENGINE.renderJsonOption(persistentTimeOption);
    }

    /***
     * 生成暂态事件次数
     * @author hongawen
     * @date 2023/6/21 10:06
     */
    public static String generateFrequencyOption(List<String> xData, List<Integer> dip, List<Integer> interruptions, List<Integer> rise, String subtext, String xName) {
        Option persistentTimeOption = new Option();
        //取消渲染动画
        persistentTimeOption.setAnimation(false);
        //背景色
        persistentTimeOption.setBackgroundColor(PicCommonData.PIC_BACK_COLOR);
        //标题
        persistentTimeOption.setTitle(new Title().setLeft(PicCommonData.CENTER).setText("暂态事件次数")
                .setSubtext(subtext)
        );
        //设置图例
        persistentTimeOption.setLegend(new Legend().setData(new String[]{"暂降次数", "中断次数", "暂升次数"}).setLeft(10).setShow(true));
        //横坐标
        persistentTimeOption.setXAxis(new CategoryAxis()
                .setBoundaryGap(true)
                .setName(xName)
                .setAxisLabel(new LogAxisLabel().setShow(true).setFontSize(7).setRotate(0).setFontWeight("bold"))
                .setNameTextStyle(new AxisNameTextStyle().setFontStyle("15px"))
                .setData(xData.toArray())
        );
        //纵坐标
        persistentTimeOption.setYAxis(new ValueAxis()
                .setName("(次)")
                .setNameTextStyle(new AxisNameTextStyle().setFontStyle("15px"))
        );


        //配置占比
        BarSeries proportion = new BarSeries()
                .setName("暂降次数")
                .setStack("account")
                .setItemStyle(new BarItemStyle().setColor("#ff9966").setBorderColor("#ff9966").setBorderWidth(2).setBorderRadius(0))
                .setLabel(new BarLabel().setShow(true).setColor("#8B008B"))
                .setData(dip);
        BarSeries proportion2 = new BarSeries()
                .setName("中断次数")
                .setStack("account")
                .setItemStyle(new BarItemStyle().setColor("#00dd00").setBorderColor("#00dd00").setBorderWidth(2).setBorderRadius(0))
                .setLabel(new BarLabel().setShow(true).setColor("#8B008B"))
                .setData(interruptions);
        BarSeries proportion3 = new BarSeries()
                .setName("暂升次数")
                .setStack("account")
                .setItemStyle(new BarItemStyle().setColor("#0066ff").setBorderColor("#0066ff").setBorderWidth(2).setBorderRadius(0))
                .setLabel(new BarLabel().setShow(true).setColor("#8B008B"))
                .setData(rise);
        persistentTimeOption.setSeries(new SeriesOption[]{proportion, proportion2, proportion3});
        return ENGINE.renderJsonOption(persistentTimeOption);
    }

    /***
     * 准备波形json
     * @author hongawen
     * @date 2023/9/21 9:41
     * @return String
     */
    public static String generateWaveOption(String title, List<List<Float>> aValue, List<List<Float>> bValue, List<List<Float>> cValue, String unit, Float max, Float min, String a, String b, String c, List<String> colors, Boolean isOpen) {
        DecimalFormat df1 = new DecimalFormat("#.00");
        DecimalFormat df2 = new DecimalFormat("#");
        Option instantOption = new Option();
        //取消渲染动画
        instantOption.setAnimation(false);
        //背景色
        instantOption.setBackgroundColor(PicCommonData.PIC_BACK_COLOR);
        //标题
        instantOption.setTitle(new Title()
                .setText(title)
                .setLeft(PicCommonData.CENTER)
                .setTextStyle(new Label().setFontSize(14))
        );
        //配置颜色
        instantOption.setColor(colors.toArray(new String[colors.size()]));
        //配置图例
        if (isOpen) {
            instantOption.setLegend(new Legend().setData(new String[]{a, b}).setLeft("10px"));
        } else {
            instantOption.setLegend(new Legend().setData(new String[]{a, b, c}).setLeft("10px"));
        }
        //上下左右的图内间距
        instantOption.setGrid(new Grid().setTop("60px").setLeft("70px").setRight("40px").setBottom("10%"));
        //横坐标
        instantOption.setXAxis(new ValueAxis().setName("ms")
                .setMax(df2.format(aValue.get(0).get(0)))
                .setMin(df2.format(aValue.get(aValue.size() - 1).get(0)))
                .setSplitLine(new SplitLine().setShow(false))
                .setAxisLine(new AxisLine().setOnZero(false))
                .setMinInterval(1)
        );
        //纵坐标
        instantOption.setYAxis(new ValueAxis()
                .setName(unit)
                .setMax(df1.format(max * 1.1))
                .setMin(df1.format(min * 1.1))
                .setPosition(PicCommonData.LEFT)
                .setAxisLine(new AxisLine().setShow(false).setOnZero(false))
        );
        //数据信息
        LineSeries aSeries = new LineSeries()
                .setName(a)
                .setSmooth(true)
                .setSymbol("none")
                .setData(aValue);
        LineSeries bSeries = new LineSeries()
                .setName(b)
                .setSmooth(true)
                .setSymbol("none")
                .setData(bValue);
        LineSeries cSeries = new LineSeries()
                .setName(c)
                .setSmooth(true)
                .setSymbol("none")
                .setData(cValue);
        instantOption.setSeries(new LineSeries[]{aSeries, bSeries, cSeries});
        return ENGINE.renderJsonOption(instantOption);
    }

    public static String generateWaveOption(String title, List<List<Float>> aValue, List<List<Float>> bValue, List<List<Float>> cValue, String unit, Float max, Float min, String a, String b, String c, List<String> colors, Boolean isOpen, Double lastTime, List<Float> sharedNullBoundaryXValues, Integer nPush) {
        DecimalFormat df1 = new DecimalFormat("#.00");
        // 压缩null数据：如果有共享边界则用共享边界，否则独立压缩
        CompressedResult aCompressed;
        CompressedResult bCompressed;
        CompressedResult cCompressed;
        if (sharedNullBoundaryXValues != null && !sharedNullBoundaryXValues.isEmpty()) {
            aCompressed = compressNullDataWithSharedBoundaries(aValue, sharedNullBoundaryXValues);
            bCompressed = compressNullDataWithSharedBoundaries(bValue, sharedNullBoundaryXValues);
            cCompressed = compressNullDataWithSharedBoundaries(cValue, sharedNullBoundaryXValues);
        } else {
            aCompressed = compressNullData(aValue);
            bCompressed = compressNullData(bValue);
            cCompressed = compressNullData(cValue);
        }

        Option instantOption = new Option();
        //取消渲染动画
        instantOption.setAnimation(false);
        //背景色
        instantOption.setBackgroundColor(PicCommonData.PIC_BACK_COLOR);
        //标题
        instantOption.setTitle(new Title()
                .setText(title)
                .setLeft(PicCommonData.CENTER)
                .setTextStyle(new Label().setFontSize(14))
        );
        //配置颜色
        instantOption.setColor(colors.toArray(new String[colors.size()]));
        //配置图例
        if (isOpen) {
            instantOption.setLegend(new Legend().setData(new String[]{a, b}).setLeft("10px"));
        } else {
            instantOption.setLegend(new Legend().setData(new String[]{a, b, c}).setLeft("10px"));
        }
        //上下左右的图内间距
        instantOption.setGrid(new Grid().setTop("60px").setLeft("70px").setRight("40px").setBottom("10%"));
        //横坐标（使用压缩后的数据）
        instantOption.setXAxis(new ValueAxis().setName("ms")
                .setSplitLine(new SplitLine().setShow(false))
                .setAxisLine(new AxisLine().setOnZero(false))
                .setType("category")
                .setMinInterval(1)
        );
        //纵坐标
        instantOption.setYAxis(new ValueAxis()
                .setName(unit)
                .setMax(df1.format(max * 1.1))
                .setMin(df1.format(min * 1.1))
                .setPosition(PicCommonData.LEFT)
                .setAxisLine(new AxisLine().setShow(false).setOnZero(false))
        );
        //数据信息（使用压缩后的数据）
        LineSeries aSeries = new LineSeries()
                .setName(a)
                .setSmooth(true)
                .setSymbol("none")
                .setConnectNulls(false)
                .setData(aCompressed.data);
        LineSeries bSeries = new LineSeries()
                .setName(b)
                .setSmooth(true)
                .setSymbol("none")
                .setConnectNulls(false)
                .setData(bCompressed.data);
        LineSeries cSeries = new LineSeries()
                .setName(c)
                .setSmooth(true)
                .setSymbol("none")
                .setConnectNulls(false)
                .setData(cCompressed.data);
        instantOption.setSeries(new LineSeries[]{aSeries, bSeries, cSeries});

        // 渲染基础JSON
        String jsonStr = ENGINE.renderJsonOption(instantOption);
        cn.hutool.json.JSONObject optionJson = JSONUtil.parseObj(jsonStr);
        cn.hutool.json.JSONArray seriesArray = optionJson.getJSONArray("series");

        // 隐藏x轴默认标签，设置boundaryGap=false，只通过markLine显示关键节点
        Object xAxisRaw = optionJson.get("xAxis");
        cn.hutool.json.JSONObject xAxisJson;
        if (xAxisRaw instanceof cn.hutool.json.JSONArray) {
            xAxisJson = ((cn.hutool.json.JSONArray) xAxisRaw).getJSONObject(0);
        } else {
            xAxisJson = (cn.hutool.json.JSONObject) xAxisRaw;
        }
        cn.hutool.json.JSONObject axisLabelJson = new cn.hutool.json.JSONObject();
        axisLabelJson.set("show", false);
        xAxisJson.set("axisLabel", axisLabelJson);
        xAxisJson.set("boundaryGap", false);

        // 添加null markLine/markArea（基于压缩后的数据）
        addNullMarkLinesForCategoryAxis(seriesArray.getJSONObject(0), aCompressed, lastTime);
        addNullMarkLinesForCategoryAxis(seriesArray.getJSONObject(1), bCompressed, lastTime);
        addNullMarkLinesForCategoryAxis(seriesArray.getJSONObject(2), cCompressed, lastTime);
        // 在第一个series追加自定义横坐标标签markLine
        addAxisLabelMarkLines(seriesArray.getJSONObject(0), aCompressed, lastTime, nPush);
        return optionJson.toString();
    }

    /** 提取null边界的真实x值，供其他数据集共享使用 */
    public static List<Float> extractNullBoundaryXValues(List<List<Float>> data) {
        List<Integer> boundaries = findNullSegmentBoundaryIndices(data);
        List<Float> xValues = new ArrayList<>();
        for (int idx : boundaries) {
            xValues.add(data.get(idx).get(0));
        }
        return xValues;
    }

    /** 使用共享的null边界x值来压缩数据，确保不同数据集压缩结果结构一致 */
    private static CompressedResult compressNullDataWithSharedBoundaries(List<List<Float>> originalData, List<Float> sharedBoundaryXValues) {
        if (sharedBoundaryXValues == null || sharedBoundaryXValues.isEmpty()) {
            return compressNullData(originalData);
        }

        // 用共享的x值找到本数据集中对应的索引
        List<Integer> boundaries = new ArrayList<>();
        for (Float x : sharedBoundaryXValues) {
            boundaries.add(findClosestValidIndex(originalData, x));
        }

        CompressedResult result = new CompressedResult();
        result.data = new ArrayList<>();
        result.nullRegionIndices = new ArrayList<>();
        result.nullBoundaryRealXValues = new ArrayList<>(sharedBoundaryXValues);

        int srcIdx = 0;
        for (int b = 0; b < boundaries.size(); b += 2) {
            int entryBoundary = boundaries.get(b);
            int exitBoundary = boundaries.get(b + 1);

            while (srcIdx <= entryBoundary) {
                result.data.add(originalData.get(srcIdx));
                srcIdx++;
            }

            int nullStartIdx = result.data.size();
            float entryX = sharedBoundaryXValues.get(b);
            float exitX = sharedBoundaryXValues.get(b + 1);

            for (int p = 0; p < NULL_PLACEHOLDER_COUNT; p++) {
                List<Float> point = new ArrayList<>();
                point.add(entryX + (exitX - entryX) * p / NULL_PLACEHOLDER_COUNT);
                point.add(null);
                result.data.add(point);
            }

            int nullEndIdx = result.data.size() - 1;
            result.nullRegionIndices.add(new int[]{nullStartIdx, nullEndIdx});

            srcIdx = exitBoundary;
        }

        while (srcIdx < originalData.size()) {
            result.data.add(originalData.get(srcIdx));
            srcIdx++;
        }

        return result;
    }



    /**
     * 压缩null数据：删除所有null数据点，替换为固定NULL_PLACEHOLDER_COUNT个占位点
     * 这样null段在图表上永远只占固定视觉宽度，前后有效数据正常展开
     */
    private static CompressedResult compressNullData(List<List<Float>> originalData) {
        List<Integer> boundaries = findNullSegmentBoundaryIndices(originalData);

        CompressedResult result = new CompressedResult();
        result.data = new ArrayList<>();
        result.nullRegionIndices = new ArrayList<>();
        result.nullBoundaryRealXValues = new ArrayList<>();

        if (boundaries.isEmpty()) {
            result.data.addAll(originalData);
            return result;
        }

        int srcIdx = 0;
        for (int b = 0; b < boundaries.size(); b += 2) {
            // 最后一个有效点的索引（null段入口边界）
            int entryBoundary = boundaries.get(b);
            // 第一个恢复有效点的索引（null段出口边界）
            int exitBoundary = boundaries.get(b + 1);

            // 复制有效数据：从srcIdx到entryBoundary
            while (srcIdx <= entryBoundary) {
                result.data.add(originalData.get(srcIdx));
                srcIdx++;
            }

            // 记录边界真实x值
            result.nullBoundaryRealXValues.add(originalData.get(entryBoundary).get(0));
            result.nullBoundaryRealXValues.add(originalData.get(exitBoundary).get(0));

            // 插入固定数量的null占位点
            int nullStartIdx = result.data.size();
            float entryX = originalData.get(entryBoundary).get(0);
            float exitX = originalData.get(exitBoundary).get(0);

            for (int p = 0; p < NULL_PLACEHOLDER_COUNT; p++) {
                List<Float> point = new ArrayList<>();
                point.add(entryX + (exitX - entryX) * p / NULL_PLACEHOLDER_COUNT);
                point.add(null);
                result.data.add(point);
            }

            int nullEndIdx = result.data.size() - 1;
            result.nullRegionIndices.add(new int[]{nullStartIdx, nullEndIdx});

            // 跳过原始null数据点，srcIdx跳到exitBoundary
            srcIdx = exitBoundary;
        }

        // 复制最后一个null段之后的剩余有效数据
        while (srcIdx < originalData.size()) {
            result.data.add(originalData.get(srcIdx));
            srcIdx++;
        }

        return result;
    }

    /**
     * 在压缩数据中查找最接近目标x值的有效数据点索引（跳过null点）
     */
    private static int findClosestValidIndex(List<List<Float>> data, float targetX) {
        int closestIdx = -1;
        float minDiff = Float.MAX_VALUE;
        for (int i = 0; i < data.size(); i++) {
            Float y = data.get(i).get(1);
            if (y == null) continue;
            float diff = Math.abs(data.get(i).get(0) - targetX);
            if (diff < minDiff) {
                minDiff = diff;
                closestIdx = i;
            }
        }
        return closestIdx;
    }

    private static void addAxisLabelMarkLines(cn.hutool.json.JSONObject seriesObj, CompressedResult compressed, Double lastTime, Integer nPush) {
        boolean hasNull = !compressed.nullRegionIndices.isEmpty();

        // 收集需要标注的横坐标位置：[数据索引, 显示的时间值]
        List<int[]> labelPositions = new ArrayList<>();
        // 开头ms（始终显示）
        labelPositions.add(new int[]{findClosestValidIndex(compressed.data, -nPush), -nPush});
        // 0ms（始终显示）
        labelPositions.add(new int[]{findClosestValidIndex(compressed.data, 0f), 0});
        // npush ms（仅在有null时显示）
        if (hasNull) {
            labelPositions.add(new int[]{findClosestValidIndex(compressed.data, nPush), nPush});
        }
        // null段边界真实时间值（仅在有null时显示）
        if (hasNull) {
            for (Float realX : compressed.nullBoundaryRealXValues) {
                int idx = findClosestValidIndex(compressed.data, realX);
                if (idx >= 0) {
                    labelPositions.add(new int[]{idx, (int) Math.round(realX)});
                }
            }
        }
        // lastTime（始终显示）
        if (lastTime != null) {
            labelPositions.add(new int[]{findClosestValidIndex(compressed.data, lastTime.floatValue()), lastTime.intValue()});
        }

        // 最后一组数据（始终显示，数据结束位置横坐标）
        if (lastTime != null) {
            float lastTime20 = lastTime.floatValue() + nPush;
            int lastTime20Idx = findClosestValidIndex(compressed.data, lastTime20);
            if (lastTime20Idx >= 0) {
                labelPositions.add(new int[]{(lastTime20Idx-2), (int) Math.round(lastTime20)});
            }
        }

        // 获取或创建markLine
        cn.hutool.json.JSONObject existingMarkLine = seriesObj.getJSONObject("markLine");
        cn.hutool.json.JSONArray markLineData;

        if (existingMarkLine == null) {
            // 无null的情况：自己创建markLine，包含虚线和横坐标标签
            markLineData = new cn.hutool.json.JSONArray();

            // x=0 虚线
            int zeroIdx = findClosestValidIndex(compressed.data, 0f);
            if (zeroIdx >= 0) {
                cn.hutool.json.JSONObject zeroLine = new cn.hutool.json.JSONObject();
                zeroLine.set("xAxis", zeroIdx);
                cn.hutool.json.JSONObject zeroLabel = new cn.hutool.json.JSONObject();
                zeroLabel.set("show", false);
                zeroLine.set("label", zeroLabel);
                markLineData.add(zeroLine);
            }
            // lastTime 虚线
            if (lastTime != null) {
                int lastTimeIdx = findClosestValidIndex(compressed.data, lastTime.floatValue());
                if (lastTimeIdx >= 0) {
                    cn.hutool.json.JSONObject lastTimeLine = new cn.hutool.json.JSONObject();
                    lastTimeLine.set("xAxis", lastTimeIdx);
                    cn.hutool.json.JSONObject lastTimeLabelObj = new cn.hutool.json.JSONObject();
                    lastTimeLabelObj.set("show", false);
                    lastTimeLine.set("label", lastTimeLabelObj);
                    markLineData.add(lastTimeLine);
                }
            }

            // 先收集所有标签到markLineData
            for (int[] pos : labelPositions) {
                cn.hutool.json.JSONObject line = new cn.hutool.json.JSONObject();
                line.set("xAxis", pos[0]);
                cn.hutool.json.JSONObject lineLineStyle = new cn.hutool.json.JSONObject();
                lineLineStyle.set("width", 0);
                line.set("lineStyle", lineLineStyle);
                cn.hutool.json.JSONObject lineLabel = new cn.hutool.json.JSONObject();
                lineLabel.set("show", true);
                lineLabel.set("formatter", String.valueOf(pos[1]));
                lineLabel.set("position", "start");
                lineLabel.set("color", "#333");
                lineLabel.set("fontSize", 10);
                line.set("label", lineLabel);
                markLineData.add(line);
            }
            // 最后一次性设置到markLine和series
            existingMarkLine = new cn.hutool.json.JSONObject();
            existingMarkLine.set("symbol", new String[]{"none", "none"});
            cn.hutool.json.JSONObject lineStyle = new cn.hutool.json.JSONObject();
            lineStyle.set("type", "dashed");
            lineStyle.set("color", "#999");
            lineStyle.set("width", 1);
            existingMarkLine.set("lineStyle", lineStyle);
            cn.hutool.json.JSONObject globalLabel = new cn.hutool.json.JSONObject();
            globalLabel.set("show", false);
            existingMarkLine.set("label", globalLabel);
            existingMarkLine.set("data", markLineData);
            seriesObj.set("markLine", existingMarkLine);
        } else {
            // 有null的情况：追加标签到已有的markLine
            markLineData = existingMarkLine.getJSONArray("data");
            for (int[] pos : labelPositions) {
                cn.hutool.json.JSONObject line = new cn.hutool.json.JSONObject();
                line.set("xAxis", pos[0]);
                cn.hutool.json.JSONObject lineLineStyle = new cn.hutool.json.JSONObject();
                lineLineStyle.set("width", 0);
                line.set("lineStyle", lineLineStyle);
                cn.hutool.json.JSONObject lineLabel = new cn.hutool.json.JSONObject();
                lineLabel.set("show", true);
                lineLabel.set("formatter", String.valueOf(pos[1]));
                lineLabel.set("position", "start");
                lineLabel.set("color", "#333");
                lineLabel.set("fontSize", 10);
                line.set("label", lineLabel);
                markLineData.add(line);
            }
        }
    }

    private static List<Integer> findNullSegmentBoundaryIndices(List<List<Float>> data) {
        List<Integer> indices = new ArrayList<>();
        boolean inNullSegment = false;
        for (int i = 0; i < data.size(); i++) {
            Float y = data.get(i).get(1);
            if (y == null) {
                if (!inNullSegment) {
                    indices.add(i > 0 ? i - 1 : i);
                    inNullSegment = true;
                }
            } else {
                if (inNullSegment) {
                    indices.add(i);
                    inNullSegment = false;
                }
            }
        }
        if (inNullSegment) {
            indices.add(data.size() - 1);
        }
        return indices;
    }

    private static void addNullMarkLinesForCategoryAxis(cn.hutool.json.JSONObject seriesObj, CompressedResult compressed, Double lastTime) {
        if (compressed.nullRegionIndices.isEmpty()) {
            return;
        }
        cn.hutool.json.JSONArray markLineData = new cn.hutool.json.JSONArray();
        // x=0 虚线
        int zeroIdx = findClosestValidIndex(compressed.data, 0f);
        if (zeroIdx >= 0) {
            cn.hutool.json.JSONObject zeroLine = new cn.hutool.json.JSONObject();
            zeroLine.set("xAxis", zeroIdx);
            cn.hutool.json.JSONObject zeroLabel = new cn.hutool.json.JSONObject();
            zeroLabel.set("show", false);
            zeroLine.set("label", zeroLabel);
            markLineData.add(zeroLine);
        }
        // null段边界的垂直虚线
        for (int r = 0; r < compressed.nullRegionIndices.size(); r++) {
            int[] region = compressed.nullRegionIndices.get(r);
            int nullStartIdx = region[0];   // 第一个占位点索引
            int nullEndIdx = region[1];     // 最后一个占位点索引

            // 入口虚线：nullStartIdx - 1（null前最后一个有效数据点）
            cn.hutool.json.JSONObject entryLine = new cn.hutool.json.JSONObject();
            entryLine.set("xAxis", nullStartIdx - 1);
            cn.hutool.json.JSONObject entryLabel = new cn.hutool.json.JSONObject();
            entryLabel.set("show", false);
            entryLine.set("label", entryLabel);
            markLineData.add(entryLine);

            // 出口虚线：nullEndIdx（null后第一个有效数据点）
            cn.hutool.json.JSONObject exitLine = new cn.hutool.json.JSONObject();
            exitLine.set("xAxis", nullEndIdx);
            cn.hutool.json.JSONObject exitLabel = new cn.hutool.json.JSONObject();
            exitLabel.set("show", false);
            exitLine.set("label", exitLabel);
            markLineData.add(exitLine);
        }
        // 时间持续时间结束的数据点
        if (lastTime != null) {
            int lastTimeIdx = findClosestValidIndex(compressed.data, lastTime.floatValue());
            if (lastTimeIdx >= 0) {
                cn.hutool.json.JSONObject lastTimeLine = new cn.hutool.json.JSONObject();
                lastTimeLine.set("xAxis", lastTimeIdx);
                cn.hutool.json.JSONObject lastTimeLabel = new cn.hutool.json.JSONObject();
                lastTimeLabel.set("show", false);
                lastTimeLine.set("label", lastTimeLabel);
                markLineData.add(lastTimeLine);
            }
        }

        cn.hutool.json.JSONObject markLine = new cn.hutool.json.JSONObject();
        markLine.set("symbol", new String[]{"none", "none"});
        cn.hutool.json.JSONObject lineStyle = new cn.hutool.json.JSONObject();
        lineStyle.set("type", "dashed");
        lineStyle.set("color", "#999");
        lineStyle.set("width", 1);
        markLine.set("lineStyle", lineStyle);
        cn.hutool.json.JSONObject globalLabel = new cn.hutool.json.JSONObject();
        globalLabel.set("show", false);
        markLine.set("label", globalLabel);
        markLine.set("data", markLineData);
        seriesObj.set("markLine", markLine);

        // markArea - 灰色背景覆盖null区域（从入口边界到出口边界）
        cn.hutool.json.JSONArray markAreaData = new cn.hutool.json.JSONArray();
        for (int r = 0; r < compressed.nullRegionIndices.size(); r++) {
            int[] region = compressed.nullRegionIndices.get(r);
            int nullStartIdx = region[0];
            int nullEndIdx = region[1];

            cn.hutool.json.JSONArray areaPair = new cn.hutool.json.JSONArray();
            cn.hutool.json.JSONObject start = new cn.hutool.json.JSONObject();
            start.set("xAxis", nullStartIdx - 1);    // 从入口边界有效数据点
            cn.hutool.json.JSONObject end = new cn.hutool.json.JSONObject();
            end.set("xAxis", nullEndIdx);         // 到出口边界有效数据点
            areaPair.add(start);
            areaPair.add(end);
            markAreaData.add(areaPair);
        }
        cn.hutool.json.JSONObject markArea = new cn.hutool.json.JSONObject();
        cn.hutool.json.JSONObject itemStyle = new cn.hutool.json.JSONObject();
        itemStyle.set("color", "rgba(180, 180, 180, 0.2)");
        itemStyle.set("borderWidth", 0);
        markArea.set("itemStyle", itemStyle);
        markArea.set("data", markAreaData);
        seriesObj.set("markArea", markArea);
    }

    /**
     * @param title     标题
     * @param values    数据值
     * @param limitName 限制值标题
     * @param limit     限制值
     * @Description: App越线指标图(单相)
     * @return: java.lang.String
     * @Author: wr
     * @Date: 2023/11/6 11:38
     */
    public static String generateSteadyTargetOption(String title, Map<String, List<List<Object>>> values, String limitName, List<List<Object>> limit) {
        Option persistentTimeOption = new Option();
        //取消渲染动画
        persistentTimeOption.setAnimation(false);
        //背景色
        persistentTimeOption.setBackgroundColor(PicCommonData.PIC_BACK_COLOR);
        //标题
        persistentTimeOption.setTitle(new Title().setLeft(PicCommonData.CENTER).setText(title));
        //设置图例
        List<String> name = values.keySet().stream().collect(Collectors.toList());
        name.add(limitName);
        persistentTimeOption.setLegend(new Legend().setData(name.stream().toArray(String[]::new)).setLeft(10).setShow(true));
        //设置边距
        persistentTimeOption.setGrid(new Grid().setLeft("3%").setRight("5%").setBottom("3%").setContainLabel(true));
        //横坐标
        persistentTimeOption.setXAxis(new CategoryAxis()
                .setType("time")
                .setName("(时间)")
                .setBoundaryGap(false)
        );
        if(values.size()==2){
            persistentTimeOption.setColor(new String[]{"#CCC", "#FF33FF"});
        }else{
            persistentTimeOption.setColor(new String[]{"#FFCC33", "#339966","#CC0000","#FF33FF","#3399FF"});
        }
        Integer max = 0;
        if (CollUtil.isNotEmpty(limit)) {
            Integer num = (Integer) limit.get(0).get(1);
            max = (int) (num * 0.2) + num;
        }
        //纵坐标
        persistentTimeOption.setYAxis(new ValueAxis()
                .setAxisLine(new AxisLine().setShow(true))
                .setMin(0).setMax(max)
                .setNameTextStyle(new AxisNameTextStyle().setFontStyle("15px"))
        );
        List<SeriesOption> list = new ArrayList<>();
        values.forEach((key, value) -> {
            LineSeries proportion = new LineSeries()
                    .setName(key)
                    .setType("line")
                    .setSmooth(true)
                    .setSymbol("none")
                    .setLabel(new BarLabel().setShow(true).setPosition("bottom"))
                    .setData(value);
            list.add(proportion);
        });
        LineSeries proportion2 = new LineSeries()
                .setName(limitName)
                .setSmooth(true)
                .setSymbol("none")
                .setLineStyle(new LineStyle().setColor("#FF33FF").setWidth(2).setType("dotted").setOpacity(1))
                .setData(limit);
        list.add(proportion2);
        SeriesOption[] seriesOptions = list.stream().toArray(SeriesOption[]::new);
        persistentTimeOption.setSeries(seriesOptions);
        return JSONUtil.parseObj(persistentTimeOption).toString();
    }


}
