| /* |
| * ==================================================================== |
| * 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.xddf.usermodel.chart; |
| |
| import static org.apache.poi.ooxml.POIXMLTypeLoader.DEFAULT_XML_OPTIONS; |
| |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.function.Function; |
| import java.util.function.Predicate; |
| |
| import javax.xml.namespace.QName; |
| |
| import org.apache.poi.ooxml.POIXMLDocument; |
| import org.apache.poi.ooxml.POIXMLDocumentPart; |
| import org.apache.poi.ooxml.POIXMLException; |
| import org.apache.poi.ooxml.POIXMLFactory; |
| import org.apache.poi.ooxml.POIXMLRelation; |
| import org.apache.poi.openxml4j.exceptions.InvalidFormatException; |
| import org.apache.poi.openxml4j.exceptions.NotOfficeXmlFileException; |
| import org.apache.poi.openxml4j.opc.PackagePart; |
| import org.apache.poi.openxml4j.opc.PackageRelationship; |
| import org.apache.poi.ss.util.CellRangeAddress; |
| import org.apache.poi.ss.util.CellReference; |
| import org.apache.poi.util.Beta; |
| import org.apache.poi.util.Internal; |
| import org.apache.poi.xddf.usermodel.XDDFShapeProperties; |
| import org.apache.poi.xddf.usermodel.text.TextContainer; |
| import org.apache.poi.xddf.usermodel.text.XDDFTextBody; |
| import org.apache.poi.xssf.usermodel.XSSFCell; |
| import org.apache.poi.xssf.usermodel.XSSFRow; |
| import org.apache.poi.xssf.usermodel.XSSFSheet; |
| import org.apache.poi.xssf.usermodel.XSSFWorkbook; |
| import org.apache.xmlbeans.XmlException; |
| import org.apache.xmlbeans.XmlOptions; |
| import org.openxmlformats.schemas.drawingml.x2006.chart.CTArea3DChart; |
| import org.openxmlformats.schemas.drawingml.x2006.chart.CTAreaChart; |
| import org.openxmlformats.schemas.drawingml.x2006.chart.CTBar3DChart; |
| import org.openxmlformats.schemas.drawingml.x2006.chart.CTBarChart; |
| import org.openxmlformats.schemas.drawingml.x2006.chart.CTBoolean; |
| import org.openxmlformats.schemas.drawingml.x2006.chart.CTCatAx; |
| import org.openxmlformats.schemas.drawingml.x2006.chart.CTChart; |
| import org.openxmlformats.schemas.drawingml.x2006.chart.CTChartSpace; |
| import org.openxmlformats.schemas.drawingml.x2006.chart.CTDateAx; |
| import org.openxmlformats.schemas.drawingml.x2006.chart.CTDoughnutChart; |
| import org.openxmlformats.schemas.drawingml.x2006.chart.CTExternalData; |
| import org.openxmlformats.schemas.drawingml.x2006.chart.CTLine3DChart; |
| import org.openxmlformats.schemas.drawingml.x2006.chart.CTLineChart; |
| import org.openxmlformats.schemas.drawingml.x2006.chart.CTPie3DChart; |
| import org.openxmlformats.schemas.drawingml.x2006.chart.CTPieChart; |
| import org.openxmlformats.schemas.drawingml.x2006.chart.CTPlotArea; |
| import org.openxmlformats.schemas.drawingml.x2006.chart.CTRadarChart; |
| import org.openxmlformats.schemas.drawingml.x2006.chart.CTScatterChart; |
| import org.openxmlformats.schemas.drawingml.x2006.chart.CTSerAx; |
| import org.openxmlformats.schemas.drawingml.x2006.chart.CTSurface; |
| import org.openxmlformats.schemas.drawingml.x2006.chart.CTSurface3DChart; |
| import org.openxmlformats.schemas.drawingml.x2006.chart.CTSurfaceChart; |
| import org.openxmlformats.schemas.drawingml.x2006.chart.CTTitle; |
| import org.openxmlformats.schemas.drawingml.x2006.chart.CTValAx; |
| import org.openxmlformats.schemas.drawingml.x2006.chart.CTView3D; |
| import org.openxmlformats.schemas.drawingml.x2006.chart.ChartSpaceDocument; |
| import org.openxmlformats.schemas.drawingml.x2006.main.CTShapeProperties; |
| import org.openxmlformats.schemas.drawingml.x2006.main.CTTextCharacterProperties; |
| import org.openxmlformats.schemas.drawingml.x2006.main.CTTextParagraphProperties; |
| |
| @Beta |
| public abstract class XDDFChart extends POIXMLDocumentPart implements TextContainer { |
| |
| /** |
| * default width of chart in emu |
| */ |
| public static final int DEFAULT_WIDTH = 500000; |
| |
| /** |
| * default height of chart in emu |
| */ |
| public static final int DEFAULT_HEIGHT = 500000; |
| |
| /** |
| * default x-coordinate of chart in emu |
| */ |
| public static final int DEFAULT_X = 10; |
| |
| /** |
| * default y-coordinate value of chart in emu |
| */ |
| public static final int DEFAULT_Y = 10; |
| |
| /** |
| * Underlying workbook |
| */ |
| private XSSFWorkbook workbook; |
| |
| private int chartIndex = 0; |
| |
| protected List<XDDFChartAxis> axes = new ArrayList<>(); |
| |
| /** |
| * Root element of the Chart part |
| */ |
| protected final CTChartSpace chartSpace; |
| |
| |
| /** |
| * Construct a chart. |
| */ |
| protected XDDFChart() { |
| super(); |
| |
| chartSpace = CTChartSpace.Factory.newInstance(); |
| chartSpace.addNewChart().addNewPlotArea(); |
| } |
| |
| /** |
| * Construct a DrawingML chart from a package part. |
| * |
| * @param part |
| * the package part holding the chart data, the content type must |
| * be |
| * <code>application/vnd.openxmlformats-officedocument.drawingml.chart+xml</code> |
| * @since POI 3.14-Beta1 |
| */ |
| protected XDDFChart(PackagePart part) throws IOException, XmlException { |
| super(part); |
| |
| chartSpace = ChartSpaceDocument.Factory.parse(part.getInputStream(), DEFAULT_XML_OPTIONS).getChartSpace(); |
| } |
| |
| /** |
| * Return the underlying CTChartSpace bean, the root element of the Chart |
| * part. |
| * |
| * @return the underlying CTChartSpace bean |
| */ |
| @Internal |
| public CTChartSpace getCTChartSpace() { |
| return chartSpace; |
| } |
| |
| /** |
| * Return the underlying CTChart bean, within the Chart Space |
| * |
| * @return the underlying CTChart bean |
| */ |
| @Internal |
| public CTChart getCTChart() { |
| return chartSpace.getChart(); |
| } |
| |
| /** |
| * Return the underlying CTPlotArea bean, within the Chart |
| * |
| * @return the underlying CTPlotArea bean |
| */ |
| @Internal |
| protected CTPlotArea getCTPlotArea() { |
| return getCTChart().getPlotArea(); |
| } |
| |
| /** |
| * Clear all properties, as if a new instance had just been created. |
| * @since POI 4.1.2 |
| */ |
| public void clear() { |
| axes.clear(); |
| seriesCount = 0; |
| if (workbook != null) { |
| workbook.removeSheetAt(0); |
| workbook.createSheet(); |
| } |
| getCTChart().set(CTChart.Factory.newInstance()); |
| getCTChart().addNewPlotArea(); |
| } |
| |
| /** |
| * @return true if only visible cells will be present on the chart, false |
| * otherwise |
| */ |
| public boolean isPlotOnlyVisibleCells() { |
| if (getCTChart().isSetPlotVisOnly()) { |
| return getCTChart().getPlotVisOnly().getVal(); |
| } else { |
| return false; |
| } |
| } |
| |
| /** |
| * @param only |
| * a flag specifying if only visible cells should be present on |
| * the chart |
| */ |
| public void setPlotOnlyVisibleCells(boolean only) { |
| if (!getCTChart().isSetPlotVisOnly()) { |
| getCTChart().setPlotVisOnly(CTBoolean.Factory.newInstance()); |
| } |
| getCTChart().getPlotVisOnly().setVal(only); |
| } |
| |
| public void setFloor(int thickness) { |
| if (!getCTChart().isSetFloor()) { |
| getCTChart().setFloor(CTSurface.Factory.newInstance()); |
| } |
| getCTChart().getFloor().getThickness().setVal(thickness); |
| } |
| |
| public void setBackWall(int thickness) { |
| if (!getCTChart().isSetBackWall()) { |
| getCTChart().setBackWall(CTSurface.Factory.newInstance()); |
| } |
| getCTChart().getBackWall().getThickness().setVal(thickness); |
| } |
| |
| public void setSideWall(int thickness) { |
| if (!getCTChart().isSetSideWall()) { |
| getCTChart().setSideWall(CTSurface.Factory.newInstance()); |
| } |
| getCTChart().getSideWall().getThickness().setVal(thickness); |
| } |
| |
| public void setAutoTitleDeleted(boolean deleted) { |
| if (!getCTChart().isSetAutoTitleDeleted()) { |
| getCTChart().setAutoTitleDeleted(CTBoolean.Factory.newInstance()); |
| } |
| getCTChart().getAutoTitleDeleted().setVal(deleted); |
| if (deleted && getCTChart().isSetTitle()) { |
| getCTChart().unsetTitle(); |
| } |
| } |
| |
| /** |
| * @since 4.0.1 |
| * |
| */ |
| public void displayBlanksAs(DisplayBlanks as) { |
| if (as == null){ |
| if (getCTChart().isSetDispBlanksAs()) { |
| getCTChart().unsetDispBlanksAs(); |
| } |
| } else { |
| if (getCTChart().isSetDispBlanksAs()) { |
| getCTChart().getDispBlanksAs().setVal(as.underlying); |
| } else { |
| getCTChart().addNewDispBlanksAs().setVal(as.underlying); |
| } |
| } |
| } |
| |
| /** |
| * @since 4.0.1 |
| */ |
| public Boolean getTitleOverlay() { |
| if (getCTChart().isSetTitle()) { |
| CTTitle title = getCTChart().getTitle(); |
| if (title.isSetOverlay()) { |
| return title.getOverlay().getVal(); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * @since 4.0.1 |
| */ |
| public void setTitleOverlay(boolean overlay) { |
| if (!getCTChart().isSetTitle()) { |
| getCTChart().addNewTitle(); |
| } |
| new XDDFTitle(this, getCTChart().getTitle()).setOverlay(overlay); |
| } |
| |
| /** |
| * Sets the title text as a static string. |
| * |
| * @param text |
| * to use as new title |
| * @since 4.0.1 |
| */ |
| public void setTitleText(String text) { |
| if (!getCTChart().isSetTitle()) { |
| getCTChart().addNewTitle(); |
| } |
| new XDDFTitle(this, getCTChart().getTitle()).setText(text); |
| } |
| |
| /** |
| * @since 4.0.1 |
| */ |
| public XDDFTitle getTitle() { |
| if (getCTChart().isSetTitle()) { |
| return new XDDFTitle(this, getCTChart().getTitle()); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Remove the chart title. |
| * @since POI 5.0.0 |
| */ |
| public void removeTitle() { |
| setAutoTitleDeleted(true); |
| } |
| |
| /** |
| * Get or Add chart 3D view into chart |
| * |
| * @return this method will add 3D view |
| */ |
| public XDDFView3D getOrAddView3D() { |
| CTView3D view3D; |
| if (getCTChart().isSetView3D()) { |
| view3D = getCTChart().getView3D(); |
| } else { |
| view3D = getCTChart().addNewView3D(); |
| } |
| return new XDDFView3D(view3D); |
| } |
| |
| /** |
| * Get the chart title body if there is one, i.e. title is set and is not a |
| * formula. |
| * |
| * @return text body or null, if title is a formula or no title is set. |
| */ |
| @Beta |
| public XDDFTextBody getFormattedTitle() { |
| if (!getCTChart().isSetTitle()) { |
| return null; |
| } |
| return new XDDFTitle(this, getCTChart().getTitle()).getBody(); |
| } |
| |
| @Override |
| public <R> Optional<R> findDefinedParagraphProperty(Predicate<CTTextParagraphProperties> isSet, |
| Function<CTTextParagraphProperties, R> getter) { |
| // TODO Auto-generated method stub |
| return Optional.empty(); |
| } |
| |
| @Override |
| public <R> Optional<R> findDefinedRunProperty(Predicate<CTTextCharacterProperties> isSet, |
| Function<CTTextCharacterProperties, R> getter) { |
| // TODO Auto-generated method stub |
| return Optional.empty(); |
| } |
| |
| public XDDFShapeProperties getOrAddShapeProperties() { |
| CTPlotArea plotArea = getCTPlotArea(); |
| CTShapeProperties properties; |
| if (plotArea.isSetSpPr()) { |
| properties = plotArea.getSpPr(); |
| } else { |
| properties = plotArea.addNewSpPr(); |
| } |
| return new XDDFShapeProperties(properties); |
| } |
| |
| public void deleteShapeProperties() { |
| if (getCTPlotArea().isSetSpPr()) { |
| getCTPlotArea().unsetSpPr(); |
| } |
| } |
| |
| public XDDFChartLegend getOrAddLegend() { |
| return new XDDFChartLegend(getCTChart()); |
| } |
| |
| public void deleteLegend() { |
| if (getCTChart().isSetLegend()) { |
| getCTChart().unsetLegend(); |
| } |
| } |
| |
| public XDDFManualLayout getOrAddManualLayout() { |
| return new XDDFManualLayout(getCTPlotArea()); |
| } |
| |
| private long seriesCount = 0; |
| protected long incrementSeriesCount() { |
| return seriesCount++; |
| } |
| |
| public void plot(XDDFChartData data) { |
| XSSFSheet sheet = getSheet(); |
| for (int idx = 0; idx < data.getSeriesCount(); idx++) { |
| XDDFChartData.Series series = data.getSeries(idx); |
| series.plot(); |
| XDDFDataSource<?> categoryDS = series.getCategoryData(); |
| XDDFNumericalDataSource<? extends Number> valuesDS = series.getValuesData(); |
| if (categoryDS == null || valuesDS == null |
| || categoryDS.isCellRange() || valuesDS.isCellRange() |
| || categoryDS.isLiteral() || valuesDS.isLiteral()) { |
| // let's assume the data is already in the sheet |
| } else { |
| fillSheet(sheet, categoryDS, valuesDS); |
| } |
| } |
| } |
| |
| public List<XDDFChartData> getChartSeries() { |
| List<XDDFChartData> series = new LinkedList<>(); |
| CTPlotArea plotArea = getCTPlotArea(); |
| Map<Long, XDDFChartAxis> categories = getCategoryAxes(); |
| Map<Long, XDDFValueAxis> values = getValueAxes(); |
| |
| for (int i = 0; i < plotArea.sizeOfAreaChartArray(); i++) { |
| CTAreaChart areaChart = plotArea.getAreaChartArray(i); |
| series.add(new XDDFAreaChartData(this, areaChart, categories, values)); |
| } |
| |
| for (int i = 0; i < plotArea.sizeOfArea3DChartArray(); i++) { |
| CTArea3DChart areaChart = plotArea.getArea3DChartArray(i); |
| series.add(new XDDFArea3DChartData(this, areaChart, categories, values)); |
| } |
| |
| for (int i = 0; i < plotArea.sizeOfBarChartArray(); i++) { |
| CTBarChart barChart = plotArea.getBarChartArray(i); |
| series.add(new XDDFBarChartData(this, barChart, categories, values)); |
| } |
| |
| for (int i = 0; i < plotArea.sizeOfBar3DChartArray(); i++) { |
| CTBar3DChart barChart = plotArea.getBar3DChartArray(i); |
| series.add(new XDDFBar3DChartData(this, barChart, categories, values)); |
| } |
| |
| for (int i = 0; i < plotArea.sizeOfDoughnutChartArray(); i++) { |
| CTDoughnutChart doughnutChart = plotArea.getDoughnutChartArray(i); |
| series.add(new XDDFDoughnutChartData(this, doughnutChart)); |
| } |
| |
| for (int i = 0; i < plotArea.sizeOfLineChartArray(); i++) { |
| CTLineChart lineChart = plotArea.getLineChartArray(i); |
| series.add(new XDDFLineChartData(this, lineChart, categories, values)); |
| } |
| |
| for (int i = 0; i < plotArea.sizeOfLine3DChartArray(); i++) { |
| CTLine3DChart lineChart = plotArea.getLine3DChartArray(i); |
| series.add(new XDDFLine3DChartData(this, lineChart, categories, values)); |
| } |
| |
| for (int i = 0; i < plotArea.sizeOfPieChartArray(); i++) { |
| CTPieChart pieChart = plotArea.getPieChartArray(i); |
| series.add(new XDDFPieChartData(this, pieChart)); |
| } |
| |
| for (int i = 0; i < plotArea.sizeOfPie3DChartArray(); i++) { |
| CTPie3DChart pieChart = plotArea.getPie3DChartArray(i); |
| series.add(new XDDFPie3DChartData(this, pieChart)); |
| } |
| |
| for (int i = 0; i < plotArea.sizeOfRadarChartArray(); i++) { |
| CTRadarChart radarChart = plotArea.getRadarChartArray(i); |
| series.add(new XDDFRadarChartData(this, radarChart, categories, values)); |
| } |
| |
| for (int i = 0; i < plotArea.sizeOfScatterChartArray(); i++) { |
| CTScatterChart scatterChart = plotArea.getScatterChartArray(i); |
| series.add(new XDDFScatterChartData(this, scatterChart, categories, values)); |
| } |
| |
| for (int i = 0; i < plotArea.sizeOfSurfaceChartArray(); i++) { |
| CTSurfaceChart surfaceChart = plotArea.getSurfaceChartArray(i); |
| series.add(new XDDFSurfaceChartData(this, surfaceChart, categories, values)); |
| } |
| |
| for (int i = 0; i < plotArea.sizeOfSurface3DChartArray(); i++) { |
| CTSurface3DChart surfaceChart = plotArea.getSurface3DChartArray(i); |
| series.add(new XDDFSurface3DChartData(this, surfaceChart, categories, values)); |
| } |
| // TODO repeat above code for missing charts: Bubble, OfPie and Stock |
| |
| seriesCount = series.size(); |
| return series; |
| } |
| |
| /** |
| * Clear all chart series, as if a new instance had just been created. |
| * @since POI 4.1.2 |
| */ |
| public void clearChartSeries() { |
| CTPlotArea plotArea = getCTPlotArea(); |
| |
| for (int i = plotArea.sizeOfAreaChartArray(); i > 0; i--) { |
| plotArea.removeAreaChart(i - 1); |
| } |
| |
| for (int i = plotArea.sizeOfArea3DChartArray(); i > 0; i--) { |
| plotArea.removeArea3DChart(i - 1); |
| } |
| |
| for (int i = plotArea.sizeOfBarChartArray(); i > 0; i--) { |
| plotArea.removeBarChart(i - 1); |
| } |
| |
| for (int i = plotArea.sizeOfBar3DChartArray(); i > 0; i--) { |
| plotArea.removeBar3DChart(i - 1); |
| } |
| |
| for (int i = plotArea.sizeOfBubbleChartArray(); i > 0; i--) { |
| plotArea.removeBubbleChart(i - 1); |
| } |
| |
| for (int i = plotArea.sizeOfDoughnutChartArray(); i > 0; i--) { |
| plotArea.removeDoughnutChart(i - 1); |
| } |
| |
| for (int i = plotArea.sizeOfLineChartArray(); i > 0; i--) { |
| plotArea.removeLineChart(i - 1); |
| } |
| |
| for (int i = plotArea.sizeOfLine3DChartArray(); i > 0; i--) { |
| plotArea.removeLine3DChart(i - 1); |
| } |
| |
| for (int i = plotArea.sizeOfOfPieChartArray(); i > 0; i--) { |
| plotArea.removeOfPieChart(i - 1); |
| } |
| |
| for (int i = plotArea.sizeOfPieChartArray(); i > 0; i--) { |
| plotArea.removePieChart(i - 1); |
| } |
| |
| for (int i = plotArea.sizeOfPie3DChartArray(); i > 0; i--) { |
| plotArea.removePie3DChart(i - 1); |
| } |
| |
| for (int i = plotArea.sizeOfRadarChartArray(); i > 0; i--) { |
| plotArea.removeRadarChart(i - 1); |
| } |
| |
| for (int i = plotArea.sizeOfScatterChartArray(); i > 0; i--) { |
| plotArea.removeScatterChart(i - 1); |
| } |
| |
| for (int i = plotArea.sizeOfStockChartArray(); i > 0; i--) { |
| plotArea.removeStockChart(i - 1); |
| } |
| |
| for (int i = plotArea.sizeOfSurfaceChartArray(); i > 0; i--) { |
| plotArea.removeSurfaceChart(i - 1); |
| } |
| |
| for (int i = plotArea.sizeOfSurface3DChartArray(); i > 0; i--) { |
| plotArea.removeSurface3DChart(i - 1); |
| } |
| } |
| |
| private Map<Long, XDDFChartAxis> getCategoryAxes() { |
| CTPlotArea plotArea = getCTPlotArea(); |
| int sizeOfArray = plotArea.sizeOfCatAxArray(); |
| Map<Long, XDDFChartAxis> axesMap = new HashMap<>(sizeOfArray); |
| for (int i = 0; i < sizeOfArray; i++) { |
| CTCatAx category = plotArea.getCatAxArray(i); |
| axesMap.put(category.getAxId().getVal(), new XDDFCategoryAxis(category)); |
| } |
| return axesMap; |
| } |
| |
| private Map<Long, XDDFValueAxis> getValueAxes() { |
| CTPlotArea plotArea = getCTPlotArea(); |
| int sizeOfArray = plotArea.sizeOfValAxArray(); |
| Map<Long, XDDFValueAxis> axesMap = new HashMap<>(sizeOfArray); |
| for (int i = 0; i < sizeOfArray; i++) { |
| CTValAx values = plotArea.getValAxArray(i); |
| axesMap.put(values.getAxId().getVal(), new XDDFValueAxis(values)); |
| } |
| return axesMap; |
| } |
| |
| public XDDFValueAxis createValueAxis(AxisPosition pos) { |
| XDDFValueAxis valueAxis = new XDDFValueAxis(getCTPlotArea(), pos); |
| addAxis(valueAxis); |
| return valueAxis; |
| } |
| |
| /** |
| * this method will return series axis with specified position |
| * |
| * @param pos axis position Left, Right, Top, Bottom |
| * @return series axis with specified position |
| */ |
| public XDDFSeriesAxis createSeriesAxis(AxisPosition pos) { |
| XDDFSeriesAxis seriesAxis = new XDDFSeriesAxis(getCTPlotArea(), pos); |
| addAxis(seriesAxis); |
| return seriesAxis; |
| } |
| |
| public XDDFCategoryAxis createCategoryAxis(AxisPosition pos) { |
| XDDFCategoryAxis categoryAxis = new XDDFCategoryAxis(getCTPlotArea(), pos); |
| addAxis(categoryAxis); |
| return categoryAxis; |
| } |
| |
| public XDDFDateAxis createDateAxis(AxisPosition pos) { |
| XDDFDateAxis dateAxis = new XDDFDateAxis(getCTPlotArea(), pos); |
| addAxis(dateAxis); |
| return dateAxis; |
| } |
| |
| private void addAxis(XDDFChartAxis newAxis) { |
| if (axes.size() == 1) { |
| XDDFChartAxis axis = axes.get(0); |
| axis.crossAxis(newAxis); |
| newAxis.crossAxis(axis); |
| axis.setCrosses(AxisCrosses.AUTO_ZERO); |
| newAxis.setCrosses(AxisCrosses.AUTO_ZERO); |
| } |
| axes.add(newAxis); |
| } |
| |
| /** |
| * this method will return specified chart data with category and series values |
| * |
| * @param type chart type |
| * @param category category values of chart |
| * @param values series values of chart |
| * @return specified chart data. |
| */ |
| public XDDFChartData createData(ChartTypes type, XDDFChartAxis category, XDDFValueAxis values) { |
| Map<Long, XDDFChartAxis> categories = null; |
| Map<Long, XDDFValueAxis> mapValues = null; |
| |
| if (ChartTypes.PIE != type && ChartTypes.PIE3D != type && ChartTypes.DOUGHNUT != type) { |
| categories = Collections.singletonMap(category.getId(), category); |
| mapValues = Collections.singletonMap(values.getId(), values); |
| } |
| |
| final CTPlotArea plotArea = getCTPlotArea(); |
| switch (type) { |
| case AREA: |
| return new XDDFAreaChartData(this, plotArea.addNewAreaChart(), categories, mapValues); |
| case AREA3D: |
| return new XDDFArea3DChartData(this, plotArea.addNewArea3DChart(), categories, mapValues); |
| case BAR: |
| return new XDDFBarChartData(this, plotArea.addNewBarChart(), categories, mapValues); |
| case BAR3D: |
| return new XDDFBar3DChartData(this, plotArea.addNewBar3DChart(), categories, mapValues); |
| case DOUGHNUT: |
| return new XDDFDoughnutChartData(this, plotArea.addNewDoughnutChart()); |
| case LINE: |
| return new XDDFLineChartData(this, plotArea.addNewLineChart(), categories, mapValues); |
| case LINE3D: |
| return new XDDFLine3DChartData(this, plotArea.addNewLine3DChart(), categories, mapValues); |
| case PIE: |
| return new XDDFPieChartData(this, plotArea.addNewPieChart()); |
| case PIE3D: |
| return new XDDFPie3DChartData(this, plotArea.addNewPie3DChart()); |
| case RADAR: |
| return new XDDFRadarChartData(this, plotArea.addNewRadarChart(), categories, mapValues); |
| case SCATTER: |
| return new XDDFScatterChartData(this, plotArea.addNewScatterChart(), categories, mapValues); |
| case SURFACE: |
| return new XDDFSurfaceChartData(this, plotArea.addNewSurfaceChart(), categories, mapValues); |
| case SURFACE3D: |
| return new XDDFSurface3DChartData(this, plotArea.addNewSurface3DChart(), categories, mapValues); |
| // TODO repeat above code for missing charts: Bubble, OfPie and Stock |
| default: |
| return null; |
| } |
| } |
| |
| public List<? extends XDDFChartAxis> getAxes() { |
| if (axes.isEmpty() && hasAxes()) { |
| parseAxes(); |
| } |
| return axes; |
| } |
| |
| private boolean hasAxes() { |
| CTPlotArea ctPlotArea = getCTPlotArea(); |
| int totalAxisCount = ctPlotArea.sizeOfValAxArray() + ctPlotArea.sizeOfCatAxArray() + ctPlotArea |
| .sizeOfDateAxArray() + ctPlotArea.sizeOfSerAxArray(); |
| return totalAxisCount > 0; |
| } |
| |
| private void parseAxes() { |
| for (CTCatAx catAx : getCTPlotArea().getCatAxArray()) { |
| axes.add(new XDDFCategoryAxis(catAx)); |
| } |
| for (CTDateAx dateAx : getCTPlotArea().getDateAxArray()) { |
| axes.add(new XDDFDateAxis(dateAx)); |
| } |
| for (CTSerAx serAx : getCTPlotArea().getSerAxArray()) { |
| axes.add(new XDDFSeriesAxis(serAx)); |
| } |
| for (CTValAx valAx : getCTPlotArea().getValAxArray()) { |
| axes.add(new XDDFValueAxis(valAx)); |
| } |
| } |
| |
| /** |
| * Set value range (basic Axis Options) |
| * |
| * @param axisIndex |
| * 0 - primary axis, 1 - secondary axis |
| * @param minimum |
| * minimum value; Double.NaN - automatic; null - no change |
| * @param maximum |
| * maximum value; Double.NaN - automatic; null - no change |
| * @param majorUnit |
| * major unit value; Double.NaN - automatic; null - no change |
| * @param minorUnit |
| * minor unit value; Double.NaN - automatic; null - no change |
| */ |
| public void setValueRange(int axisIndex, Double minimum, Double maximum, Double majorUnit, Double minorUnit) { |
| XDDFChartAxis axis = getAxes().get(axisIndex); |
| if (axis == null) { |
| return; |
| } |
| if (minimum != null) { |
| axis.setMinimum(minimum); |
| } |
| if (maximum != null) { |
| axis.setMaximum(maximum); |
| } |
| if (majorUnit != null) { |
| axis.setMajorUnit(majorUnit); |
| } |
| if (minorUnit != null) { |
| axis.setMinorUnit(minorUnit); |
| } |
| } |
| |
| /** |
| * method to create relationship with embedded part for example writing xlsx |
| * file stream into output stream |
| * |
| * @param chartRelation |
| * relationship object |
| * @param chartFactory |
| * ChartFactory object |
| * @param chartIndex |
| * index used to suffix on file |
| * @return return relation part which used to write relation in .rels file |
| * and get relation id |
| * @since POI 4.0.0 |
| */ |
| public PackageRelationship createRelationshipInChart(POIXMLRelation chartRelation, POIXMLFactory chartFactory, |
| int chartIndex) { |
| POIXMLDocumentPart documentPart = |
| createRelationship(chartRelation, chartFactory, chartIndex, true).getDocumentPart(); |
| return addRelation(null, chartRelation, documentPart).getRelationship(); |
| } |
| |
| /** |
| * if embedded part was null then create new part |
| * |
| * @param chartWorkbookRelation |
| * chart workbook relation object |
| * @param chartFactory |
| * factory object of POIXMLFactory (XWPFFactory/XSLFFactory) |
| * @return return the new package part |
| * @throws InvalidFormatException |
| * @since POI 4.0.0 |
| */ |
| private PackagePart createWorksheetPart(POIXMLRelation chartWorkbookRelation, POIXMLFactory chartFactory) |
| throws InvalidFormatException { |
| PackageRelationship xlsx = createRelationshipInChart(chartWorkbookRelation, chartFactory, chartIndex); |
| setExternalId(xlsx.getId()); |
| return getTargetPart(xlsx); |
| } |
| |
| /** |
| * this method write the XSSFWorkbook object data into embedded excel file |
| * |
| * @param workbook |
| * XSSFworkbook object |
| * @throws IOException |
| * @throws InvalidFormatException |
| * @since POI 4.0.0 |
| */ |
| public void saveWorkbook(XSSFWorkbook workbook) throws IOException, InvalidFormatException { |
| PackagePart worksheetPart = getWorksheetPart(); |
| if (worksheetPart == null) { |
| POIXMLRelation chartWorkbookRelation = getChartWorkbookRelation(); |
| POIXMLFactory chartFactory = getChartFactory(); |
| if (chartWorkbookRelation != null && chartFactory != null) { |
| worksheetPart = createWorksheetPart(chartWorkbookRelation, chartFactory); |
| } else { |
| throw new InvalidFormatException("unable to determine chart relations"); |
| } |
| } |
| try (OutputStream xlsOut = worksheetPart.getOutputStream()) { |
| setWorksheetPartCommitted(); |
| workbook.write(xlsOut); |
| } |
| } |
| |
| /** |
| * |
| * @return the chart relation in the implementing subclass. |
| * @since POI 4.0.0 |
| */ |
| protected abstract POIXMLRelation getChartRelation(); |
| |
| /** |
| * |
| * @return the chart workbook relation in the implementing subclass. |
| * @since POI 4.0.0 |
| */ |
| protected abstract POIXMLRelation getChartWorkbookRelation(); |
| |
| /** |
| * |
| * @return the chart factory in the implementing subclass. |
| * @since POI 4.0.0 |
| */ |
| protected abstract POIXMLFactory getChartFactory(); |
| |
| /** |
| * this method writes the data into sheet |
| * |
| * @param sheet |
| * sheet of embedded excel |
| * @param categoryData |
| * category values |
| * @param valuesData |
| * data values |
| * @since POI 4.0.0 |
| */ |
| protected void fillSheet(XSSFSheet sheet, XDDFDataSource<?> categoryData, XDDFNumericalDataSource<?> valuesData) { |
| int numOfPoints = categoryData.getPointCount(); |
| for (int i = 0; i < numOfPoints; i++) { |
| XSSFRow row = getRow(sheet, i + 1); // first row is for title |
| Object category = categoryData.getPointAt(i); |
| if (category != null) { |
| getCell(row, categoryData.getColIndex()).setCellValue(category.toString()); |
| } |
| Number value = valuesData.getPointAt(i); |
| if (value != null) { |
| getCell(row, valuesData.getColIndex()).setCellValue(value.doubleValue()); |
| } |
| } |
| } |
| |
| /** |
| * this method return row on given index if row is null then create new row |
| * |
| * @param sheet |
| * current sheet object |
| * @param index |
| * index of current row |
| * @return this method return sheet row on given index |
| * @since POI 4.0.0 |
| */ |
| private XSSFRow getRow(XSSFSheet sheet, int index) { |
| XSSFRow row = sheet.getRow(index); |
| if (row == null) { |
| return sheet.createRow(index); |
| } else { |
| return row; |
| } |
| } |
| |
| /** |
| * this method return cell on given index if cell is null then create new |
| * cell |
| * |
| * @param row |
| * current row object |
| * @param index |
| * index of current cell |
| * @return this method return sheet cell on given index |
| * @since POI 4.0.0 |
| */ |
| private XSSFCell getCell(XSSFRow row, int index) { |
| XSSFCell cell = row.getCell(index); |
| if (cell == null) { |
| return row.createCell(index); |
| } else { |
| return cell; |
| } |
| } |
| |
| /** |
| * import content from other chart to created chart |
| * |
| * @param other |
| * chart object |
| * @since POI 4.0.0 |
| */ |
| public void importContent(XDDFChart other) { |
| getCTChartSpace().set(other.getCTChartSpace()); |
| } |
| |
| /** |
| * save chart xml |
| */ |
| @Override |
| protected void commit() throws IOException { |
| XmlOptions xmlOptions = new XmlOptions(DEFAULT_XML_OPTIONS); |
| xmlOptions.setSaveSyntheticDocumentElement( |
| new QName(CTChartSpace.type.getName().getNamespaceURI(), "chartSpace", "c")); |
| |
| if (workbook != null) { |
| try { |
| saveWorkbook(workbook); |
| } catch (InvalidFormatException e) { |
| throw new POIXMLException(e); |
| } |
| } |
| |
| PackagePart part = getPackagePart(); |
| try (OutputStream out = part.getOutputStream()) { |
| chartSpace.save(out, xmlOptions); |
| } |
| } |
| |
| /** |
| * set sheet title in excel file |
| * |
| * @param title |
| * title of sheet |
| * @param column |
| * column index |
| * @return return cell reference |
| * @since POI 4.0.0 |
| */ |
| public CellReference setSheetTitle(String title, int column) { |
| XSSFSheet sheet = getSheet(); |
| if (sheet == null) { |
| return null; |
| } |
| XSSFRow row = getRow(sheet, 0); |
| XSSFCell cell = getCell(row, column); |
| cell.setCellValue(title); |
| |
| return new CellReference(sheet.getSheetName(), 0, column, true, true); |
| } |
| |
| /** |
| * @param range |
| * @return |
| * @since POI 4.0.0 |
| */ |
| public String formatRange(CellRangeAddress range) { |
| final XSSFSheet sheet = getSheet(); |
| return (sheet == null) ? null : range.formatAsString(sheet.getSheetName(), true); |
| } |
| |
| /** |
| * get sheet object of embedded excel file |
| * |
| * @return excel sheet object |
| * @since POI 4.0.0 |
| */ |
| private XSSFSheet getSheet() { |
| XSSFSheet sheet = null; |
| try { |
| sheet = getWorkbook().getSheetAt(0); |
| } catch (InvalidFormatException | IOException ife) { |
| } |
| return sheet; |
| } |
| |
| /** |
| * this method is used to get worksheet part if call is from saveworkbook |
| * method then check isCommitted isCommitted variable shows that we are |
| * writing xssfworkbook object into output stream of embedded part |
| * |
| * @return returns the packagepart of embedded file |
| * @throws InvalidFormatException |
| * @since POI 4.0.0 |
| */ |
| private PackagePart getWorksheetPart() throws InvalidFormatException { |
| for (RelationPart part : getRelationParts()) { |
| if (POIXMLDocument.PACK_OBJECT_REL_TYPE.equals(part.getRelationship().getRelationshipType())) { |
| return getTargetPart(part.getRelationship()); |
| } |
| } |
| return null; |
| } |
| |
| private void setWorksheetPartCommitted() { |
| for (RelationPart part : getRelationParts()) { |
| if (POIXMLDocument.PACK_OBJECT_REL_TYPE.equals(part.getRelationship().getRelationshipType())) { |
| part.getDocumentPart().setCommitted(true); |
| break; |
| } |
| } |
| } |
| |
| /** |
| * @return returns the workbook object of embedded excel file |
| * @throws IOException |
| * @throws InvalidFormatException |
| * @since POI 4.0.0 |
| */ |
| public XSSFWorkbook getWorkbook() throws IOException, InvalidFormatException { |
| if (workbook == null) { |
| try { |
| PackagePart worksheetPart = getWorksheetPart(); |
| if (worksheetPart == null) { |
| workbook = new XSSFWorkbook(); |
| workbook.createSheet(); |
| } else { |
| workbook = new XSSFWorkbook(worksheetPart.getInputStream()); |
| } |
| } catch (NotOfficeXmlFileException e) { |
| workbook = new XSSFWorkbook(); |
| workbook.createSheet(); |
| } |
| } |
| return workbook; |
| } |
| |
| /** |
| * while reading chart from template file then we need to parse and store |
| * embedded excel file in chart object show that we can modify value |
| * according to use |
| * |
| * @param workbook |
| * workbook object which we read from chart embedded part |
| * @since POI 4.0.0 |
| */ |
| public void setWorkbook(XSSFWorkbook workbook) { |
| this.workbook = workbook; |
| } |
| |
| /** |
| * set the relation id of embedded excel relation id into external data |
| * relation tag |
| * |
| * @param id |
| * relation id of embedded excel relation id into external data |
| * relation tag |
| * @since POI 4.0.0 |
| */ |
| public void setExternalId(String id) { |
| getCTChartSpace().addNewExternalData().setId(id); |
| CTChartSpace ctChartSpace = getCTChartSpace(); |
| CTExternalData externalData = ctChartSpace.isSetExternalData() |
| ? ctChartSpace.getExternalData() |
| : ctChartSpace.addNewExternalData(); |
| externalData.setId(id); |
| } |
| |
| /** |
| * @return method return chart index |
| * @since POI 4.0.0 |
| */ |
| protected int getChartIndex() { |
| return chartIndex; |
| } |
| |
| /** |
| * Set chart index which can be used for relation part. |
| * |
| * @param chartIndex |
| * chart index which can be used for relation part. |
| */ |
| public void setChartIndex(int chartIndex) { |
| this.chartIndex = chartIndex; |
| } |
| |
| /** |
| * Replace references to the sheet name in the data supporting the chart. |
| * |
| * @param newSheet |
| * sheet to be used in the data references. |
| * |
| * @since POI 5.0.1 |
| */ |
| public void replaceReferences(XSSFSheet newSheet) { |
| for (XDDFChartData data : getChartSeries()) { |
| for (XDDFChartData.Series series : data.series) { |
| XDDFDataSource newCategory = series.categoryData; |
| XDDFNumericalDataSource newValues = series.valuesData; |
| try { |
| if (series.categoryData != null && series.categoryData.isReference()) { |
| String ref = series.categoryData.getDataRangeReference(); |
| CellRangeAddress rangeAddress = CellRangeAddress.valueOf(ref.substring(ref.indexOf('!') + 1)); |
| newCategory = series.categoryData.isNumeric() |
| ? XDDFDataSourcesFactory.fromNumericCellRange(newSheet, rangeAddress) |
| : XDDFDataSourcesFactory.fromStringCellRange(newSheet, rangeAddress); |
| if (newCategory.isNumeric()) { |
| ((XDDFNumericalDataSource) newCategory).setFormatCode(series.categoryData.getFormatCode()); |
| } |
| } |
| if (series.valuesData!= null && series.valuesData.isReference()) { |
| String ref = series.valuesData.getDataRangeReference(); |
| CellRangeAddress rangeAddress = CellRangeAddress.valueOf(ref.substring(ref.indexOf('!') + 1)); |
| newValues = XDDFDataSourcesFactory.fromNumericCellRange(newSheet, rangeAddress); |
| newValues.setFormatCode(series.valuesData.getFormatCode()); |
| } |
| } catch (IllegalArgumentException iae) { |
| // keep old values when cell range cannot be parsed |
| } |
| series.replaceData(newCategory, newValues); |
| series.plot(); |
| } |
| } |
| } |
| } |