From b2fd6f8bdce8f53f94c849a9c94c3d93d16f7602 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Alain=20B=C3=A9arez?= Date: Thu, 21 Jan 2021 21:13:27 +0000 Subject: [PATCH] Bug 64950: handle doughnut hole size git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1885771 13f79535-47bb-0310-9956-ffa450edef68 --- .../poi/examples/xslf/BarChartDemo.java | 2 +- .../poi/examples/xslf/ChartFromScratch.java | 6 +- .../xslf/DoughnutChartFromScratch.java | 167 ++++++++++++++++++ .../poi/examples/xslf/PieChartDemo.java | 2 +- .../apache/poi/ooxml/util/POIXMLUnits.java | 9 +- .../chart/XDDFDoughnutChartData.java | 71 +++++++- .../usermodel/chart/XDDFPieChartData.java | 2 +- .../usermodel/TestNecessaryOOXMLClasses.java | 1 + 8 files changed, 245 insertions(+), 15 deletions(-) create mode 100644 src/examples/src/org/apache/poi/examples/xslf/DoughnutChartFromScratch.java diff --git a/src/examples/src/org/apache/poi/examples/xslf/BarChartDemo.java b/src/examples/src/org/apache/poi/examples/xslf/BarChartDemo.java index 3c8842d7fc..2c159fbb59 100644 --- a/src/examples/src/org/apache/poi/examples/xslf/BarChartDemo.java +++ b/src/examples/src/org/apache/poi/examples/xslf/BarChartDemo.java @@ -64,7 +64,7 @@ public final class BarChartDemo { } try (FileInputStream argIS = new FileInputStream(args[0]); - BufferedReader modelReader = Files.newBufferedReader(Paths.get(args[1]), StandardCharsets.ISO_8859_1)) { + BufferedReader modelReader = Files.newBufferedReader(Paths.get(args[1]), StandardCharsets.UTF_8)) { String chartTitle = modelReader.readLine(); // first line is chart title String[] series = modelReader.readLine().split(","); diff --git a/src/examples/src/org/apache/poi/examples/xslf/ChartFromScratch.java b/src/examples/src/org/apache/poi/examples/xslf/ChartFromScratch.java index 58978a20e0..bdfc3ea498 100644 --- a/src/examples/src/org/apache/poi/examples/xslf/ChartFromScratch.java +++ b/src/examples/src/org/apache/poi/examples/xslf/ChartFromScratch.java @@ -72,7 +72,7 @@ public final class ChartFromScratch { return; } - try (BufferedReader modelReader = Files.newBufferedReader(Paths.get(args[0]), StandardCharsets.ISO_8859_1)) { + try (BufferedReader modelReader = Files.newBufferedReader(Paths.get(args[0]), StandardCharsets.UTF_8)) { String chartTitle = modelReader.readLine(); // first line is chart title String[] series = modelReader.readLine().split(","); @@ -163,10 +163,10 @@ public final class ChartFromScratch { bar.setBarGrouping(BarGrouping.CLUSTERED); XDDFBarChartData.Series series1 = (XDDFBarChartData.Series) bar.addSeries(categoriesData, valuesData); - series1.setTitle(series[0], chart.setSheetTitle(series[0], COLUMN_COUNTRIES)); + series1.setTitle(series[0], chart.setSheetTitle(series[COLUMN_COUNTRIES - 1], COLUMN_COUNTRIES)); XDDFBarChartData.Series series2 = (XDDFBarChartData.Series) bar.addSeries(categoriesData, valuesData2); - series2.setTitle(series[1], chart.setSheetTitle(series[1], COLUMN_SPEAKERS)); + series2.setTitle(series[1], chart.setSheetTitle(series[COLUMN_SPEAKERS - 1], COLUMN_SPEAKERS)); bar.setVaryColors(true); bar.setBarDirection(BarDirection.COL); diff --git a/src/examples/src/org/apache/poi/examples/xslf/DoughnutChartFromScratch.java b/src/examples/src/org/apache/poi/examples/xslf/DoughnutChartFromScratch.java new file mode 100644 index 0000000000..77a438967f --- /dev/null +++ b/src/examples/src/org/apache/poi/examples/xslf/DoughnutChartFromScratch.java @@ -0,0 +1,167 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + */ + +package org.apache.poi.examples.xslf; + +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.util.Units; +import org.apache.poi.xddf.usermodel.chart.AxisCrossBetween; +import org.apache.poi.xddf.usermodel.chart.AxisCrosses; +import org.apache.poi.xddf.usermodel.chart.AxisPosition; +import org.apache.poi.xddf.usermodel.chart.AxisTickMark; +import org.apache.poi.xddf.usermodel.chart.ChartTypes; +import org.apache.poi.xddf.usermodel.chart.LegendPosition; +import org.apache.poi.xddf.usermodel.chart.XDDFChartAxis; +import org.apache.poi.xddf.usermodel.chart.XDDFChartLegend; +import org.apache.poi.xddf.usermodel.chart.XDDFDataSource; +import org.apache.poi.xddf.usermodel.chart.XDDFDataSourcesFactory; +import org.apache.poi.xddf.usermodel.chart.XDDFDoughnutChartData; +import org.apache.poi.xddf.usermodel.chart.XDDFNumericalDataSource; +import org.apache.poi.xddf.usermodel.chart.XDDFValueAxis; +import org.apache.poi.xslf.usermodel.XMLSlideShow; +import org.apache.poi.xslf.usermodel.XSLFChart; +import org.apache.poi.xslf.usermodel.XSLFGraphicFrame; +import org.apache.poi.xslf.usermodel.XSLFShape; +import org.apache.poi.xslf.usermodel.XSLFSlide; + +import java.awt.geom.Rectangle2D; +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +/** + * Build a chart without reading template file + */ +@SuppressWarnings({"java:S106","java:S4823","java:S1192"}) +public final class DoughnutChartFromScratch { + private DoughnutChartFromScratch() {} + + private static void usage(){ + System.out.println("Usage: DoughnutChartFromScratch "); + System.out.println(" bar-chart-data.txt the model to set. First line is chart title, " + + "then go pairs {axis-label value}"); + } + + public static void main(String[] args) throws Exception { + if(args.length < 1) { + usage(); + return; + } + + try (BufferedReader modelReader = Files.newBufferedReader(Paths.get(args[0]), StandardCharsets.UTF_8)) { + + String chartTitle = modelReader.readLine(); // first line is chart title + String[] series = modelReader.readLine().split(","); + + // Category Axis Data + List listLanguages = new ArrayList<>(10); + + // Values + List listCountries = new ArrayList<>(10); + List listSpeakers = new ArrayList<>(10); + + // set model + String ln; + while((ln = modelReader.readLine()) != null) { + String[] vals = ln.split(","); + listCountries.add(Double.valueOf(vals[0])); + listSpeakers.add(Double.valueOf(vals[1])); + listLanguages.add(vals[2]); + } + + String[] categories = listLanguages.toArray(new String[0]); + Double[] values1 = listCountries.toArray(new Double[0]); + Double[] values2 = listSpeakers.toArray(new Double[0]); + + try (XMLSlideShow ppt = new XMLSlideShow()) { + createSlideWithChart(ppt, chartTitle, series, categories, values1, COLUMN_COUNTRIES); + createSlideWithChart(ppt, chartTitle, series, categories, values2, COLUMN_SPEAKERS); + // save the result + try (OutputStream out = new FileOutputStream("doughnut-chart-from-scratch.pptx")) { + ppt.write(out); + } + } + try (FileInputStream is = new FileInputStream("doughnut-chart-from-scratch.pptx")) { + try (XMLSlideShow ppt = new XMLSlideShow(is)) { + for (XSLFSlide slide : ppt.getSlides()) { + for (XSLFShape shape : slide.getShapes()) { + if (shape instanceof XSLFGraphicFrame) { + XSLFGraphicFrame frame = (XSLFGraphicFrame) shape; + if (frame.hasChart()) { + System.out.println(frame.getChart().getTitleShape().getText()); + } + } + } + } + } + } + } + System.out.println("Done"); + } + + private static void createSlideWithChart(XMLSlideShow ppt, String chartTitle, String[] series, String[] categories, + Double[] values, int valuesColumn) { + XSLFSlide slide = ppt.createSlide(); + XSLFChart chart = ppt.createChart(); + Rectangle2D rect2D = new java.awt.Rectangle(fromCM(1.5), fromCM(4), fromCM(22), fromCM(14)); + slide.addChart(chart, rect2D); + setDoughnutData(chart, chartTitle, series, categories, values, valuesColumn); + } + + private static int fromCM(double cm) { + return (int) (Math.rint(cm * Units.EMU_PER_CENTIMETER)); + } + + private static void setDoughnutData(XSLFChart chart, String chartTitle, String[] series, String[] categories, + Double[] values, int valuesColumn) { + final int numOfPoints = categories.length; + final String categoryDataRange = chart.formatRange(new CellRangeAddress(1, numOfPoints, COLUMN_LANGUAGES, COLUMN_LANGUAGES)); + final String valuesDataRange = chart.formatRange(new CellRangeAddress(1, numOfPoints, valuesColumn, valuesColumn)); + final XDDFDataSource categoriesData = XDDFDataSourcesFactory.fromArray(categories, categoryDataRange, COLUMN_LANGUAGES); + final XDDFNumericalDataSource valuesData = XDDFDataSourcesFactory.fromArray(values, valuesDataRange, valuesColumn); + valuesData.setFormatCode("General"); + + XDDFDoughnutChartData data = (XDDFDoughnutChartData) chart.createData(ChartTypes.DOUGHNUT, null, null); + XDDFDoughnutChartData.Series series1 = (XDDFDoughnutChartData.Series) data.addSeries(categoriesData, valuesData); + series1.setTitle(series[0], chart.setSheetTitle(series[valuesColumn - 1], valuesColumn)); + + data.setVaryColors(true); + data.setHoleSize(42); + data.setFirstSliceAngle(90); + chart.plot(data); + + XDDFChartLegend legend = chart.getOrAddLegend(); + legend.setPosition(LegendPosition.LEFT); + legend.setOverlay(false); + + chart.setTitleText(chartTitle); + chart.setTitleOverlay(false); + chart.setAutoTitleDeleted(false); + } + + private static final int COLUMN_LANGUAGES = 0; + private static final int COLUMN_COUNTRIES = 1; + private static final int COLUMN_SPEAKERS = 2; +} diff --git a/src/examples/src/org/apache/poi/examples/xslf/PieChartDemo.java b/src/examples/src/org/apache/poi/examples/xslf/PieChartDemo.java index 4e9d799050..8bcd014769 100644 --- a/src/examples/src/org/apache/poi/examples/xslf/PieChartDemo.java +++ b/src/examples/src/org/apache/poi/examples/xslf/PieChartDemo.java @@ -61,7 +61,7 @@ public final class PieChartDemo { } try (FileInputStream argIS = new FileInputStream(args[0]); - BufferedReader modelReader = Files.newBufferedReader(Paths.get(args[1]), StandardCharsets.ISO_8859_1)) { + BufferedReader modelReader = Files.newBufferedReader(Paths.get(args[1]), StandardCharsets.UTF_8)) { String chartTitle = modelReader.readLine(); // first line is chart title try (XMLSlideShow pptx = new XMLSlideShow(argIS)) { diff --git a/src/ooxml/java/org/apache/poi/ooxml/util/POIXMLUnits.java b/src/ooxml/java/org/apache/poi/ooxml/util/POIXMLUnits.java index b2538f2ff9..d6e020bcc0 100644 --- a/src/ooxml/java/org/apache/poi/ooxml/util/POIXMLUnits.java +++ b/src/ooxml/java/org/apache/poi/ooxml/util/POIXMLUnits.java @@ -133,6 +133,11 @@ public class POIXMLUnits { } + public static int parsePercent(org.openxmlformats.schemas.drawingml.x2006.chart.STHoleSize pctUnion) { + return parsePercentInner(pctUnion, 1); + } + + private static int parsePercentInner(org.apache.xmlbeans.XmlAnySimpleType pctUnion, int noUnitScale) { String strVal = pctUnion.getStringValue(); if (strVal.endsWith("%")) { @@ -211,7 +216,7 @@ public class POIXMLUnits { /** - * If not unit is specified, DXA (twentieth of a point) is assumed + * If no unit is specified, DXA (twentieth of a point) is assumed * * @return length in EMUs */ @@ -222,7 +227,7 @@ public class POIXMLUnits { /** - * Returns the EMUs for the given measurment (mm|cm|in|pt|pc|pi, defaults to EMUs*noUnitEmuFactor if not specified) + * Returns the EMUs for the given measurement (mm|cm|in|pt|pc|pi, defaults to EMUs*noUnitEmuFactor if not specified) * * @param coordUnion the raw type * @return the EMUs for the given attribute diff --git a/src/ooxml/java/org/apache/poi/xddf/usermodel/chart/XDDFDoughnutChartData.java b/src/ooxml/java/org/apache/poi/xddf/usermodel/chart/XDDFDoughnutChartData.java index b0ac5f9dd6..64f22d7ec7 100644 --- a/src/ooxml/java/org/apache/poi/xddf/usermodel/chart/XDDFDoughnutChartData.java +++ b/src/ooxml/java/org/apache/poi/xddf/usermodel/chart/XDDFDoughnutChartData.java @@ -17,6 +17,7 @@ package org.apache.poi.xddf.usermodel.chart; +import org.apache.poi.ooxml.util.POIXMLUnits; import org.apache.poi.util.Beta; import org.apache.poi.util.Internal; import org.apache.poi.xddf.usermodel.XDDFShapeProperties; @@ -60,10 +61,60 @@ public class XDDFDoughnutChartData extends XDDFChartData { } } + public Integer getFirstSliceAngle() { + if (chart.isSetFirstSliceAng()) { + return chart.getFirstSliceAng().getVal(); + } else { + return null; + } + } + + public void setFirstSliceAngle(Integer angle) { + if (angle == null) { + if (chart.isSetFirstSliceAng()) { + chart.unsetFirstSliceAng(); + } + } else { + if (angle < 0 || 360 < angle) { + throw new IllegalArgumentException("Value of angle must be between 0 and 360, both inclusive."); + } + if (chart.isSetFirstSliceAng()) { + chart.getFirstSliceAng().setVal(angle); + } else { + chart.addNewFirstSliceAng().setVal(angle); + } + } + } + + public Integer getHoleSize() { + if (chart.isSetHoleSize()) { + return POIXMLUnits.parsePercent(chart.getHoleSize().xgetVal()); + } else { + return null; + } + } + + public void setHoleSize(Integer holeSize) { + if (holeSize == null) { + if (chart.isSetHoleSize()) { + chart.unsetHoleSize(); + } + } else { + if (holeSize < 10 || holeSize > 90) { + throw new IllegalArgumentException("Value of holeSize must be between 10 and 90, both inclusive."); + } + if (chart.isSetHoleSize()) { + chart.getHoleSize().setVal(holeSize); + } else { + chart.addNewHoleSize().setVal(holeSize); + } + } + } + @Override public XDDFChartData.Series addSeries(XDDFDataSource category, XDDFNumericalDataSource values) { - final int index = this.series.size(); + final long index = this.parent.incrementSeriesCount(); final CTPieSer ctSer = this.chart.addNewSer(); ctSer.addNewCat(); ctSer.addNewVal(); @@ -133,19 +184,25 @@ public class XDDFDoughnutChartData extends XDDFChartData { } } - public long getExplosion() { + public Long getExplosion() { if (series.isSetExplosion()) { return series.getExplosion().getVal(); } else { - return 0; + return null; } } - public void setExplosion(long explosion) { - if (series.isSetExplosion()) { - series.getExplosion().setVal(explosion); + public void setExplosion(Long explosion) { + if (explosion == null) { + if (series.isSetExplosion()) { + series.unsetExplosion(); + } } else { - series.addNewExplosion().setVal(explosion); + if (series.isSetExplosion()) { + series.getExplosion().setVal(explosion); + } else { + series.addNewExplosion().setVal(explosion); + } } } diff --git a/src/ooxml/java/org/apache/poi/xddf/usermodel/chart/XDDFPieChartData.java b/src/ooxml/java/org/apache/poi/xddf/usermodel/chart/XDDFPieChartData.java index f074925443..2d5cff7eee 100644 --- a/src/ooxml/java/org/apache/poi/xddf/usermodel/chart/XDDFPieChartData.java +++ b/src/ooxml/java/org/apache/poi/xddf/usermodel/chart/XDDFPieChartData.java @@ -75,7 +75,7 @@ public class XDDFPieChartData extends XDDFChartData { } } else { if (angle < 0 || 360 < angle) { - throw new IllegalArgumentException("angle must be between 0 and 360"); + throw new IllegalArgumentException("Value of angle must be between 0 and 360, both inclusive."); } if (chart.isSetFirstSliceAng()) { chart.getFirstSliceAng().setVal(angle); diff --git a/src/ooxml/testcases/org/apache/poi/xddf/usermodel/TestNecessaryOOXMLClasses.java b/src/ooxml/testcases/org/apache/poi/xddf/usermodel/TestNecessaryOOXMLClasses.java index c0f893eb4b..0f458b6524 100644 --- a/src/ooxml/testcases/org/apache/poi/xddf/usermodel/TestNecessaryOOXMLClasses.java +++ b/src/ooxml/testcases/org/apache/poi/xddf/usermodel/TestNecessaryOOXMLClasses.java @@ -177,6 +177,7 @@ class TestNecessaryOOXMLClasses { assertNotNull(CTErrBarType.Factory.newInstance()); assertNotNull(CTErrValType.Factory.newInstance()); assertNotNull(CTErrDir.Factory.newInstance()); + assertNotNull(CTHoleSize.Factory.newInstance()); STErrBarType.Enum e9 = STErrBarType.Enum.forString("both"); assertNotNull(e9); -- 2.39.5