diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFChart.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFChart.java index 6f2accf..d5d57ad 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFChart.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFChart.java @@ -22,6 +22,8 @@ import java.io.OutputStream; import java.util.HashMap; import java.util.Map; +import javax.xml.namespace.QName; + import org.apache.poi.POIXMLDocumentPart; import org.apache.poi.openxml4j.opc.PackagePart; import org.apache.poi.openxml4j.opc.PackageRelationship; @@ -33,14 +35,29 @@ import org.openxmlformats.schemas.drawingml.x2006.chart.CTChart; import org.openxmlformats.schemas.drawingml.x2006.chart.CTChartSpace; import org.openxmlformats.schemas.drawingml.x2006.chart.CTTitle; import org.openxmlformats.schemas.drawingml.x2006.chart.ChartSpaceDocument; +import org.openxmlformats.schemas.drawingml.x2006.chart.CTLayout; +import org.openxmlformats.schemas.drawingml.x2006.chart.CTManualLayout; +import org.openxmlformats.schemas.drawingml.x2006.chart.CTPlotArea; +import org.openxmlformats.schemas.drawingml.x2006.chart.CTPrintSettings; +import org.openxmlformats.schemas.drawingml.x2006.chart.CTPageMargins; +import org.openxmlformats.schemas.drawingml.x2006.chart.STLayoutTarget; +import org.openxmlformats.schemas.drawingml.x2006.chart.STLayoutMode; import org.openxmlformats.schemas.officeDocument.x2006.relationships.STRelationshipId; import org.w3c.dom.NodeList; import org.w3c.dom.Text; /** * Represents a SpreadsheetML Chart + * @author Nick Burch + * @author Roman Kashitsyn */ public final class XSSFChart extends POIXMLDocumentPart { + + /** + * Parent graphic frame. + */ + private XSSFGraphicFrame frame; + /** * Root element of the SpreadsheetML Chart part */ @@ -81,7 +98,29 @@ public final class XSSFChart extends POIXMLDocumentPart { private void createChart() { chartSpace = CTChartSpace.Factory.newInstance(); chart = chartSpace.addNewChart(); - } + CTPlotArea plotArea = chart.addNewPlotArea(); + CTLayout layout = plotArea.addNewLayout(); + CTManualLayout manualLayout = layout.addNewManualLayout(); + manualLayout.addNewLayoutTarget().setVal(STLayoutTarget.INNER); + manualLayout.addNewXMode().setVal(STLayoutMode.EDGE); + manualLayout.addNewYMode().setVal(STLayoutMode.EDGE); + manualLayout.addNewX().setVal(0); + manualLayout.addNewY().setVal(0); + manualLayout.addNewW().setVal(0.65); + manualLayout.addNewH().setVal(0.8); + chart.addNewPlotVisOnly().setVal(true); + CTPrintSettings printSettings = chartSpace.addNewPrintSettings(); + printSettings.addNewHeaderFooter(); + + CTPageMargins pageMargins = printSettings.addNewPageMargins(); + pageMargins.setB(0.75); + pageMargins.setL(0.70); + pageMargins.setR(0.70); + pageMargins.setT(0.75); + pageMargins.setHeader(0.30); + pageMargins.setFooter(0.30); + printSettings.addNewPageSetup(); + } /** * Return the underlying CTChartSpace bean, the root element of the SpreadsheetML Chart part. @@ -107,8 +146,17 @@ public final class XSSFChart extends POIXMLDocumentPart { protected void commit() throws IOException { XmlOptions xmlOptions = new XmlOptions(DEFAULT_XML_OPTIONS); + /* + Saved chart space must have the following namespaces set: + + */ + xmlOptions.setSaveSyntheticDocumentElement(new QName(CTChartSpace.type.getName().getNamespaceURI(), "chartSpace", "c")); Map map = new HashMap(); map.put(XSSFDrawing.NAMESPACE_A, "a"); + map.put(XSSFDrawing.NAMESPACE_C, "c"); map.put(STRelationshipId.type.getName().getNamespaceURI(), "r"); xmlOptions.setSaveSuggestedPrefixes(map); @@ -117,6 +165,61 @@ public final class XSSFChart extends POIXMLDocumentPart { chartSpace.save(out, xmlOptions); out.close(); } + + /** + * Returns the parent graphic frame. + * @return the graphic frame this chart belongs to + */ + public XSSFGraphicFrame getGraphicFrame() { + return frame; + } + + /** + * Sets the parent graphic frame. + */ + protected void setGraphicFrame(XSSFGraphicFrame frame) { + this.frame = frame; + } + + /** + * Sets the width ratio of the chart. + * Chart width is ratio multiplied by parent frame width. + * @param ratio a number between 0 and 1. + */ + public void setWidthRatio(double ratio) { + chart.getPlotArea().getLayout().getManualLayout().getW().setVal(ratio); + } + + /** + * @return relative chart width + */ + public double getWidthRatio() { + return chart.getPlotArea().getLayout().getManualLayout().getW().getVal(); + } + + /** + * Sets the height ratio of the chart. + * Chart height is ratio multiplied by parent frame height. + * @param ratio a number between 0 and 1. + */ + public void setHeightRatio(double ratio) { + chart.getPlotArea().getLayout().getManualLayout().getH().setVal(ratio); + } + + /** + * @return relative chart height + */ + public double getHeightRatio() { + return chart.getPlotArea().getLayout().getManualLayout().getH().getVal(); + } + + public boolean isPlotOnlyVisibleCells() { + return chart.getPlotVisOnly().getVal(); + } + + public void setPlotOnlyVisibleCells(boolean plotVisOnly) { + chart.getPlotVisOnly().setVal(plotVisOnly); + } /** * Returns the title, or null if none is set @@ -144,4 +247,21 @@ public final class XSSFChart extends POIXMLDocumentPart { return new XSSFRichTextString(text.toString()); } + /** + * Returns or creates chart legend. + * @return existing or new chart legend + */ + public XSSFChartLegend getOrCreateLegend() { + return new XSSFChartLegend(chart); + } + + /** + * Remove chart legend if it exists. + */ + public void removeLegend() { + if (chart.isSetLegend()) { + chart.unsetLegend(); + } + } + } diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFChartLegend.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFChartLegend.java new file mode 100644 index 0000000..1d74732 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFChartLegend.java @@ -0,0 +1,121 @@ +/* ==================================================================== + 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.xssf.usermodel; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.Map; + +import org.apache.poi.util.Internal; +import org.openxmlformats.schemas.drawingml.x2006.chart.CTChart; +import org.openxmlformats.schemas.drawingml.x2006.chart.CTLegend; +import org.openxmlformats.schemas.drawingml.x2006.chart.STLegendPos; + +/** + * Represents a SpreadsheetML chart legend + * @author Roman Kashitsyn + */ +public final class XSSFChartLegend { + + /** + * Underlaying CTLagend bean + */ + private CTLegend legend; + + /** + * Create a new SpreadsheetML chart legend + */ + protected XSSFChartLegend(CTChart chart) { + this.legend = (chart.isSetLegend()) ? + chart.getLegend() : + chart.addNewLegend(); + } + + /** + * Return the underlying CTLegend bean. + * + * @return the underlying CTLegend bean + */ + @Internal + public CTLegend getCTLegend(){ + return legend; + } + + /** + * @param position new legend position + */ + public void setPosition(Position position) { + if (!legend.isSetLegendPos()) { + legend.addNewLegendPos(); + } + legend.getLegendPos().setVal(position.getLegendPosition()); + } + + /** + * @return current legend position + */ + public Position getPosition() { + if (legend.isSetLegendPos()) { + return Position.getByLegendPos(legend.getLegendPos().getVal()); + } else { + return Position.RIGHT; + } + } + + /** + * Encapsulates DrawingML STLegendPos simple type. + * According to ECMA-376 default position is RIGHT. + * @author Roman Kashitsyn + */ + public static enum Position { + + BOTTOM(STLegendPos.B), + LEFT(STLegendPos.L), + RIGHT(STLegendPos.R), + TOP(STLegendPos.T), + TOP_RIGHT(STLegendPos.TR); + + private STLegendPos.Enum legendPosition; + + private Position(STLegendPos.Enum legendPosition) { + this.legendPosition = legendPosition; + } + + /** + * @return underlaying position enumeration value + */ + protected STLegendPos.Enum getLegendPosition() { + return legendPosition; + } + + /** + * @return STLegendPos.Enum encapsulation. + */ + protected static Position getByLegendPos(STLegendPos.Enum pos) { + switch (pos.intValue()) { + case STLegendPos.INT_B: return Position.BOTTOM; + case STLegendPos.INT_L: return Position.LEFT; + case STLegendPos.INT_R: return Position.RIGHT; + case STLegendPos.INT_T: return Position.TOP; + case STLegendPos.INT_TR: return Position.TOP_RIGHT; + default: return Position.RIGHT; + } + } + } +} diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFDrawing.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFDrawing.java index 956f08f..5ea5327 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFDrawing.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFDrawing.java @@ -44,6 +44,7 @@ import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTPicture; import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTShape; import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTTwoCellAnchor; import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.STEditAs; +import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTGraphicalObjectFrame; import org.openxmlformats.schemas.officeDocument.x2006.relationships.STRelationshipId; /** @@ -59,6 +60,9 @@ public final class XSSFDrawing extends POIXMLDocumentPart implements Drawing { private boolean isNew; protected static final String NAMESPACE_A = "http://schemas.openxmlformats.org/drawingml/2006/main"; + protected static final String NAMESPACE_C = "http://schemas.openxmlformats.org/drawingml/2006/chart"; + + private List frames; /** * Create a new SpreadsheetML drawing @@ -68,6 +72,7 @@ public final class XSSFDrawing extends POIXMLDocumentPart implements Drawing { protected XSSFDrawing() { super(); drawing = newDrawing(); + frames = new ArrayList(); isNew = true; } @@ -169,6 +174,28 @@ public final class XSSFDrawing extends POIXMLDocumentPart implements Drawing { return createPicture((XSSFClientAnchor)anchor, pictureIndex); } + /** + * Creates a new chart. + * + * @param anchor the client anchor describes how this chart is attached to + * the sheet. + * @return the newly created graphic frame. + */ + + public XSSFChart createChart(XSSFClientAnchor anchor) { + int chartNumber = getPackagePart().getPackage(). + getPartsByContentType(XSSFRelation.CHART.getContentType()).size() + 1; + + XSSFChart chart = (XSSFChart) createRelationship( + XSSFRelation.CHART, XSSFFactory.getInstance(), chartNumber); + String chartRelId = chart.getPackageRelationship().getId(); + + XSSFGraphicFrame frame = createGraphicFrame(anchor); + frame.setChart(chart, chartRelId); + + return chart; + } + /** * Add the indexed picture to this drawing relations * @@ -266,6 +293,27 @@ public final class XSSFDrawing extends POIXMLDocumentPart implements Drawing { shape.setRow(ca.getRow1()); return shape; } + + /** + * Creates a new graphic frame. + * + * @param anchor the client anchor describes how this frame is attached + * to the sheet. + * @return the newly created graphic frame. + */ + private XSSFGraphicFrame createGraphicFrame(XSSFClientAnchor anchor) { + CTTwoCellAnchor ctAnchor = createTwoCellAnchor(anchor); + CTGraphicalObjectFrame ctGraphicFrame = ctAnchor.addNewGraphicFrame(); + ctGraphicFrame.set(XSSFGraphicFrame.prototype()); + + long frameId = frames.size() + 1; + XSSFGraphicFrame graphicFrame = new XSSFGraphicFrame(this, ctGraphicFrame); + graphicFrame.setAnchor(anchor); + graphicFrame.setId(frameId); + graphicFrame.setName("Diagramm" + frameId); + frames.add(graphicFrame); + return graphicFrame; + } /** * Returns all charts in this drawing. diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFGraphicFrame.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFGraphicFrame.java new file mode 100644 index 0000000..b536171 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFGraphicFrame.java @@ -0,0 +1,187 @@ +/* ==================================================================== + 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.xssf.usermodel; + +import javax.xml.namespace.QName; + +import org.apache.poi.openxml4j.opc.PackageRelationship; +import org.apache.poi.util.Internal; +import org.apache.xmlbeans.XmlObject; +import org.apache.xmlbeans.XmlCursor; +import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTGraphicalObjectFrame; +import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTGraphicalObjectFrameNonVisual; +import org.openxmlformats.schemas.drawingml.x2006.main.CTGraphicalObject; +import org.openxmlformats.schemas.drawingml.x2006.main.CTGraphicalObjectData; +import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTransform2D; +import org.openxmlformats.schemas.drawingml.x2006.main.CTPoint2D; +import org.openxmlformats.schemas.drawingml.x2006.main.CTPositiveSize2D; +import org.openxmlformats.schemas.officeDocument.x2006.relationships.STRelationshipId; + +/** + * Represents DrawingML GraphicalObjectFrame. + * + * @author Roman Kashitsyn + */ +public final class XSSFGraphicFrame { + + private static CTGraphicalObjectFrame prototype = null; + + private CTGraphicalObjectFrame graphicFrame; + private XSSFDrawing drawing; + private XSSFClientAnchor anchor; + + /** + * Construct a new XSSFGraphicFrame object. + * + * @param drawing the XSSFDrawing that owns this frame + * @param ctGraphicFrame the XML bean that stores this frame content + */ + protected XSSFGraphicFrame(XSSFDrawing drawing, CTGraphicalObjectFrame ctGraphicFrame) { + this.drawing = drawing; + this.graphicFrame = ctGraphicFrame; + } + + @Internal + public CTGraphicalObjectFrame getCTGraphicalObjectFrame() { + return graphicFrame; + } + + /** + * Initialize default structure of a new graphic frame + */ + protected static CTGraphicalObjectFrame prototype() { + if (prototype == null) { + CTGraphicalObjectFrame graphicFrame = CTGraphicalObjectFrame.Factory.newInstance(); + + CTGraphicalObjectFrameNonVisual nvGraphic = graphicFrame.addNewNvGraphicFramePr(); + CTNonVisualDrawingProps props = nvGraphic.addNewCNvPr(); + props.setId(0); + props.setName("Diagramm 1"); + nvGraphic.addNewCNvGraphicFramePr(); + + CTTransform2D transform = graphicFrame.addNewXfrm(); + CTPositiveSize2D extPoint = transform.addNewExt(); + CTPoint2D offPoint = transform.addNewOff(); + + extPoint.setCx(0); + extPoint.setCy(0); + offPoint.setX(0); + offPoint.setY(0); + + CTGraphicalObject graphic = graphicFrame.addNewGraphic(); + + prototype = graphicFrame; + } + return prototype; + } + + /** + * Sets the frame macro. + */ + public void setMacro(String macro) { + graphicFrame.setMacro(macro); + } + + /** + * Sets the frame name. + */ + public void setName(String name) { + getNonVisualProperties().setName(name); + } + + /** + * Returns the frame name. + * @return name of the frame + */ + public String getName() { + return getNonVisualProperties().getName(); + } + + private CTNonVisualDrawingProps getNonVisualProperties() { + CTGraphicalObjectFrameNonVisual nvGraphic = graphicFrame.getNvGraphicFramePr(); + return nvGraphic.getCNvPr(); + } + + /** + * Attaches frame to an anchor. + */ + protected void setAnchor(XSSFClientAnchor anchor) { + this.anchor = anchor; + } + + /** + * Returns the frame anchor. + * @return the anchor this frame is attached to + */ + public XSSFClientAnchor getAnchor() { + return anchor; + } + + /** + * Assign a DrawingML chart to the graphic frame. + */ + protected void setChart(XSSFChart chart, String relId) { + CTGraphicalObjectData data = graphicFrame.getGraphic().addNewGraphicData(); + appendChartElement(data, relId); + chart.setGraphicFrame(this); + return; + } + + /** + * Gets the frame id. + */ + public long getId() { + return graphicFrame.getNvGraphicFramePr().getCNvPr().getId(); + } + + /** + * Sets the frame id. + */ + protected void setId(long id) { + graphicFrame.getNvGraphicFramePr().getCNvPr().setId(id); + } + + /** + * The low level code to insert {@code } tag into + * {@code}. + * + * Here is the schema (ECMA-376): + *
+	 * {@code
+	 * 
+	 *   
+	 *     
+	 *   
+	 *   
+	 * 
+	 * }
+	 * 
+ */ + private void appendChartElement(CTGraphicalObjectData data, String id) { + String r_namespaceUri = STRelationshipId.type.getName().getNamespaceURI(); + String c_namespaceUri = XSSFDrawing.NAMESPACE_C; + XmlCursor cursor = data.newCursor(); + cursor.toNextToken(); + cursor.beginElement(new QName(c_namespaceUri, "chart", "c")); + cursor.insertAttributeWithValue(new QName(r_namespaceUri, "id", "r"), id); + cursor.dispose(); + data.setUri(c_namespaceUri); + } + +} diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRelation.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRelation.java index f0b9a9b..21094c4 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRelation.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRelation.java @@ -90,12 +90,12 @@ public final class XSSFRelation extends POIXMLRelation { "/xl/worksheets/sheet#.xml", XSSFSheet.class ); - public static final XSSFRelation CHARTSHEET = new XSSFRelation( - "application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml", - "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartsheet", - "/xl/chartsheets/sheet#.xml", - XSSFChartSheet.class - ); + public static final XSSFRelation CHARTSHEET = new XSSFRelation( + "application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml", + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartsheet", + "/xl/chartsheets/sheet#.xml", + XSSFChartSheet.class + ); public static final XSSFRelation SHARED_STRINGS = new XSSFRelation( "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings", diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFChart.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFChart.java index 3ddd636..00b0e36 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFChart.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFChart.java @@ -55,4 +55,20 @@ public final class TestXSSFChart extends TestCase { chart = s3.createDrawingPatriarch().getCharts().get(0); assertEquals("Sheet 3 Chart with Title", chart.getTitle().getString()); } + + public void testAddChartsToNewWorkbook() throws Exception { + XSSFWorkbook wb = new XSSFWorkbook(); + XSSFSheet s1 = wb.createSheet(); + XSSFDrawing d1 = s1.createDrawingPatriarch(); + XSSFClientAnchor a1 = new XSSFClientAnchor(0, 0, 0, 0, 1, 1, 10, 30); + XSSFChart c1 = d1.createChart(a1); + + assertEquals(1, d1.getCharts().size()); + assertNotNull(c1.getGraphicFrame()); + assertNotNull(c1.getOrCreateLegend()); + + XSSFClientAnchor a2 = new XSSFClientAnchor(0, 0, 0, 0, 1, 11, 10, 60); + XSSFChart c2 = d1.createChart(a2); + assertEquals(2, d1.getCharts().size()); + } }