| | |
| | | } |
| | | |
| | | public byte[] lineChart(String yAxisLabel, List<ChartSeries> series, boolean percentAxis) { |
| | | return lineChart(yAxisLabel, series, percentAxis, DAY_LABELS, 2, 1100, 590); |
| | | return lineChart(yAxisLabel, series, percentAxis, 1100, 590); |
| | | } |
| | | |
| | | public byte[] lineChart(String yAxisLabel, List<ChartSeries> series, boolean percentAxis, |
| | | int width, int height) { |
| | | return lineChart(yAxisLabel, series, percentAxis, DAY_LABELS, 2, width, height); |
| | | return lineChart(yAxisLabel, series, percentAxis, 2, width, height); |
| | | } |
| | | |
| | | public byte[] lineChart(String yAxisLabel, List<ChartSeries> series, boolean percentAxis, |
| | | int valueDecimals, int width, int height) { |
| | | return lineChart(yAxisLabel, series, percentAxis, DAY_LABELS, valueDecimals, width, height); |
| | | return lineChart(yAxisLabel, series, percentAxis, valueDecimals, DAY_LABELS, width, height); |
| | | } |
| | | |
| | | public byte[] lineChart(String yAxisLabel, List<ChartSeries> series, boolean percentAxis, |
| | | List<String> dayLabels, int valueDecimals, int width, int height) { |
| | | 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); |
| | |
| | | plot.setRangeGridlinesVisible(false); |
| | | plot.setDomainTickBandPaint(null); |
| | | plot.setRangeTickBandPaint(null); |
| | | List<String> axisLabels = dayLabels == null || dayLabels.isEmpty() ? DAY_LABELS : dayLabels; |
| | | 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)); |
| | |
| | | } |
| | | 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, |
| | |
| | | |
| | | 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(); |
| | |
| | | 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 { |
| | |
| | | |
| | | 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; |
| | |
| | | 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; |
| | |
| | | } |
| | | |
| | | 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(); |
| | |
| | | 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); |
| | |
| | | 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) { |