xyc
11 小时以前 63e67d580739079dd1bf87ed674032d97597bcd3
traffic-audit-server/src/main/java/com/trafficaudit/holiday/service/HolidayTrendChartService.java
@@ -103,6 +103,23 @@
    public byte[] lineChart(String yAxisLabel, List<ChartSeries> series, boolean percentAxis,
                            int valueDecimals, int width, int height) {
        return lineChart(yAxisLabel, series, percentAxis, valueDecimals, DAY_LABELS, width, height);
    }
    public byte[] lineChart(String yAxisLabel, List<ChartSeries> series, boolean percentAxis,
                            int valueDecimals, List<String> dayLabels, int width, int height) {
        return lineChart(yAxisLabel, series, percentAxis, valueDecimals, dayLabels, width, height,
                Collections.emptyList());
    }
    /**
     * Renders a line chart and pins the listed labels to a fixed anchor. The overrides are
     * opt-in: a chart that passes an empty list keeps the automatic placement unchanged.
     */
    public byte[] lineChart(String yAxisLabel, List<ChartSeries> series, boolean percentAxis,
                            int valueDecimals, List<String> dayLabels, int width, int height,
                            List<LineLabelOverride> labelOverrides) {
        List<String> axisLabels = (dayLabels == null || dayLabels.isEmpty()) ? DAY_LABELS : dayLabels;
        XYSeriesCollection dataset = new XYSeriesCollection();
        for (int seriesIndex = 0; seriesIndex < series.size(); seriesIndex++) {
            ChartSeries item = series.get(seriesIndex);
@@ -132,7 +149,7 @@
        plot.setRangeGridlinesVisible(false);
        plot.setDomainTickBandPaint(null);
        plot.setRangeTickBandPaint(null);
        SymbolAxis domainAxis = new SymbolAxis(null, DAY_LABELS.toArray(new String[0]));
        SymbolAxis domainAxis = new SymbolAxis(null, axisLabels.toArray(new String[0]));
        domainAxis.setGridBandPaint(new Color(0, 0, 0, 0));
        domainAxis.setGridBandAlternatePaint(new Color(0, 0, 0, 0));
        domainAxis.setLowerMargin(0.04);
@@ -175,7 +192,7 @@
        }
        plot.setRenderer(renderer);
        return render(chart, width, scaleLineChartHeight(height),
                yAxisLabel, true, percentAxis, valueDecimals);
                yAxisLabel, true, percentAxis, valueDecimals, labelOverrides);
    }
    public byte[] pieChart(String valueSuffix, List<ChartCategory> categories,
@@ -455,6 +472,13 @@
    private byte[] render(JFreeChart chart, int width, int height, String unitLabel,
                         boolean axisChart, boolean percentItemLabels, int valueDecimals) {
        return render(chart, width, height, unitLabel, axisChart, percentItemLabels,
                valueDecimals, Collections.emptyList());
    }
    private byte[] render(JFreeChart chart, int width, int height, String unitLabel,
                         boolean axisChart, boolean percentItemLabels, int valueDecimals,
                         List<LineLabelOverride> labelOverrides) {
        try {
            reserveUnitLabelBand(chart, axisChart, unitLabel);
            ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -474,7 +498,8 @@
                    drawDateLabel(graphics, info.getPlotInfo().getDataArea());
                }
                drawXYItemLabels(graphics, image, chart,
                        info.getPlotInfo().getDataArea(), percentItemLabels, unitLabel, valueDecimals);
                        info.getPlotInfo().getDataArea(), percentItemLabels, unitLabel, valueDecimals,
                        labelOverrides);
                drawStackedBarLabels(graphics, image, chart,
                        info.getPlotInfo().getDataArea(), percentItemLabels);
            } finally {
@@ -890,7 +915,8 @@
    private void drawXYItemLabels(Graphics2D graphics, BufferedImage image, JFreeChart chart,
                                  Rectangle2D dataArea, boolean percentItemLabels,
                                  String unitLabel, int valueDecimals) {
                                  String unitLabel, int valueDecimals,
                                  List<LineLabelOverride> labelOverrides) {
        if (!(chart.getPlot() instanceof XYPlot) || dataArea == null
                || dataArea.getWidth() <= 0 || dataArea.getHeight() <= 0) {
            return;
@@ -932,7 +958,8 @@
            List<List<LineLabelCandidate>> candidateSets = new ArrayList<>();
            for (DayLabel label : labels) {
                List<LineLabelCandidate> candidates = lineLabelCandidates(
                        label, metrics, dataArea);
                        label, metrics, dataArea,
                        forcedAnchor(labelOverrides, label.getSeriesIndex(), itemIndex));
                if (candidates.isEmpty()) {
                    candidateSets.clear();
                    break;
@@ -1051,7 +1078,8 @@
    }
    private List<LineLabelCandidate> lineLabelCandidates(DayLabel label, FontMetrics metrics,
                                                         Rectangle2D dataArea) {
                                                         Rectangle2D dataArea,
                                                         LineLabelAnchor forcedAnchor) {
        List<LineLabelCandidate> result = new ArrayList<>();
        int width = metrics.stringWidth(label.getText());
        int height = metrics.getHeight();
@@ -1061,6 +1089,25 @@
        double farHorizontalOffset = horizontalOffset + 18.0;
        double verticalOffset = ITEM_LABEL_GAP + height / 2.0;
        double farVerticalOffset = verticalOffset + height + 6.0;
        if (forcedAnchor != null) {
            switch (forcedAnchor) {
                case ABOVE:
                    addLabelCandidate(result, x, y, width, height, 0.0, -verticalOffset, dataArea);
                    break;
                case BELOW:
                    addLabelCandidate(result, x, y, width, height, 0.0, verticalOffset, dataArea);
                    break;
                case LEFT:
                    addLabelCandidate(result, x, y, width, height, -horizontalOffset, 0.0, dataArea);
                    break;
                case RIGHT:
                    addLabelCandidate(result, x, y, width, height, horizontalOffset, 0.0, dataArea);
                    break;
                default:
                    break;
            }
            return result;
        }
        addLabelCandidate(result, x, y, width, height, 0.0, -verticalOffset, dataArea);
        addLabelCandidate(result, x, y, width, height, 0.0, verticalOffset, dataArea);
        addLabelCandidate(result, x, y, width, height, horizontalOffset, 0.0, dataArea);
@@ -1104,6 +1151,43 @@
        return result;
    }
    /**
     * Fixed positions a data label can be pinned to, relative to its own point.
     */
    public enum LineLabelAnchor {
        ABOVE, BELOW, LEFT, RIGHT
    }
    /**
     * Opt-in request that pins one data label (series + item) to a fixed anchor.
     */
    public static class LineLabelOverride {
        private final int seriesIndex;
        private final int itemIndex;
        private final LineLabelAnchor anchor;
        public LineLabelOverride(int seriesIndex, int itemIndex, LineLabelAnchor anchor) {
            this.seriesIndex = seriesIndex;
            this.itemIndex = itemIndex;
            this.anchor = anchor;
        }
        public int getSeriesIndex() { return seriesIndex; }
        public int getItemIndex() { return itemIndex; }
        public LineLabelAnchor getAnchor() { return anchor; }
    }
    private static LineLabelAnchor forcedAnchor(List<LineLabelOverride> labelOverrides,
                                               int seriesIndex, int itemIndex) {
        if (labelOverrides == null) return null;
        for (LineLabelOverride override : labelOverrides) {
            if (override.getSeriesIndex() == seriesIndex && override.getItemIndex() == itemIndex) {
                return override.getAnchor();
            }
        }
        return null;
    }
    private void addLabelCandidate(List<LineLabelCandidate> result, double x, double y,
                                   int width, int height, double dx, double dy,
                                   Rectangle2D dataArea) {