--- src/java/org/apache/poi/hssf/record/chart/ChartEndBlockRecord.java (revision 962890) +++ src/java/org/apache/poi/hssf/record/chart/ChartEndBlockRecord.java (working copy) @@ -35,6 +35,9 @@ private short iObjectKind; private byte[] unused; + public ChartEndBlockRecord() { + } + public ChartEndBlockRecord(RecordInputStream in) { rt = in.readShort(); grbitFrt = in.readShort(); @@ -80,4 +83,16 @@ buffer.append("[/ENDBLOCK]\n"); return buffer.toString(); } + + @Override + public ChartEndBlockRecord clone() { + ChartEndBlockRecord record = new ChartEndBlockRecord(); + + record.rt = rt ; + record.grbitFrt = grbitFrt ; + record.iObjectKind = iObjectKind ; + record.unused = unused.clone() ; + + return record; + } } --- src/java/org/apache/poi/hssf/record/chart/ChartStartBlockRecord.java (revision 962890) +++ src/java/org/apache/poi/hssf/record/chart/ChartStartBlockRecord.java (working copy) @@ -37,6 +37,9 @@ private short iObjectInstance1; private short iObjectInstance2; + public ChartStartBlockRecord() { + } + public ChartStartBlockRecord(RecordInputStream in) { rt = in.readShort(); grbitFrt = in.readShort(); @@ -80,4 +83,18 @@ buffer.append("[/STARTBLOCK]\n"); return buffer.toString(); } + + @Override + public ChartStartBlockRecord clone() { + ChartStartBlockRecord record = new ChartStartBlockRecord(); + + record.rt = rt; + record.grbitFrt = grbitFrt; + record.iObjectKind = iObjectKind; + record.iObjectContext = iObjectContext; + record.iObjectInstance1 = iObjectInstance1; + record.iObjectInstance2 = iObjectInstance2; + + return record; + } } --- src/java/org/apache/poi/hssf/record/RecordFactory.java (revision 962890) +++ src/java/org/apache/poi/hssf/record/RecordFactory.java (working copy) @@ -221,6 +221,7 @@ WriteAccessRecord.class, WriteProtectRecord.class, WSBoolRecord.class, + DataFormatRecord.class, // chart records BeginRecord.class, --- src/java/org/apache/poi/ss/util/CellRangeAddressBase.java (revision 962890) +++ src/java/org/apache/poi/ss/util/CellRangeAddressBase.java (working copy) @@ -27,14 +27,14 @@ * * @author Josh Micich */ -public abstract class CellRangeAddressBase { +public class CellRangeAddressBase { private int _firstRow; private int _firstCol; private int _lastRow; private int _lastCol; - protected CellRangeAddressBase(int firstRow, int lastRow, int firstCol, int lastCol) { + public CellRangeAddressBase(int firstRow, int lastRow, int firstCol, int lastCol) { _firstRow = firstRow; _lastRow = lastRow; _firstCol = firstCol; --- src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFChart.java (revision 962890) +++ src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFChart.java (working copy) @@ -18,6 +18,7 @@ package org.apache.poi.hssf.usermodel; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import org.apache.poi.hssf.record.chart.*; @@ -35,8 +36,9 @@ import org.apache.poi.hssf.record.UnknownRecord; import org.apache.poi.hssf.record.VCenterRecord; import org.apache.poi.hssf.record.formula.Area3DPtg; +import org.apache.poi.hssf.record.formula.AreaPtgBase; import org.apache.poi.hssf.record.formula.Ptg; -import org.apache.poi.hssf.record.chart.LinkedDataRecord; +import org.apache.poi.ss.util.CellRangeAddressBase; /** * Has methods for construction of a chart object. @@ -44,6 +46,7 @@ * @author Glen Stampoultzis (glens at apache.org) */ public final class HSSFChart { + private HSSFSheet sheet; private ChartRecord chartRecord; private LegendRecord legendRecord; @@ -51,10 +54,54 @@ private SeriesTextRecord chartTitleText; private List valueRanges = new ArrayList(); + private HSSFChartType type = HSSFChartType.Unknown; + private List series = new ArrayList(); - private HSSFChart(ChartRecord chartRecord) { + public enum HSSFChartType { + Area { + @Override + public short getSid() { + return 0x101A; + } + }, + Bar { + @Override + public short getSid() { + return 0x1017; + } + }, + Line { + @Override + public short getSid() { + return 0x1018; + } + }, + Pie { + @Override + public short getSid() { + return 0x1019; + } + }, + Scatter { + @Override + public short getSid() { + return 0x101B; + } + }, + Unknown { + @Override + public short getSid() { + return 0; + } + }; + + public abstract short getSid(); + } + + private HSSFChart(HSSFSheet sheet, ChartRecord chartRecord) { this.chartRecord = chartRecord; + this.sheet = sheet; } /** @@ -146,22 +193,20 @@ for(RecordBase r : records) { if(r instanceof ChartRecord) { - lastChart = new HSSFChart((ChartRecord)r); + lastSeries = null; + + lastChart = new HSSFChart(sheet,(ChartRecord)r); charts.add(lastChart); - } - if(r instanceof LegendRecord) { + } else if(r instanceof LegendRecord) { lastChart.legendRecord = (LegendRecord)r; - } - if(r instanceof SeriesRecord) { + } else if(r instanceof SeriesRecord) { HSSFSeries series = lastChart.new HSSFSeries( (SeriesRecord)r ); lastChart.series.add(series); lastSeries = series; - } - if(r instanceof ChartTitleFormatRecord) { + } else if(r instanceof ChartTitleFormatRecord) { lastChart.chartTitleFormat = (ChartTitleFormatRecord)r; - } - if(r instanceof SeriesTextRecord) { + } else if(r instanceof SeriesTextRecord) { // Applies to a series, unless we've seen // a legend already SeriesTextRecord str = (SeriesTextRecord)r; @@ -173,13 +218,28 @@ } else { lastChart.chartTitleText = str; } - } - if(r instanceof LinkedDataRecord) { - LinkedDataRecord data = (LinkedDataRecord)r; - lastSeries.insertData( data ); - } - if(r instanceof ValueRangeRecord){ + } else if (r instanceof LinkedDataRecord) { + LinkedDataRecord linkedDataRecord = (LinkedDataRecord) r; + if (lastSeries != null) { + lastSeries.insertData(linkedDataRecord); + } + } else if(r instanceof ValueRangeRecord){ lastChart.valueRanges.add((ValueRangeRecord)r); + } else if (r instanceof Record) { + if (lastChart != null) + { + Record record = (Record) r; + for (HSSFChartType type : HSSFChartType.values()) { + if (type == HSSFChartType.Unknown) + { + continue; + } + if (record.getSid() == type.getSid()) { + lastChart.type = type ; + break; + } + } + } } } @@ -908,14 +968,13 @@ private LinkedDataRecord dataValues; private LinkedDataRecord dataCategoryLabels; private LinkedDataRecord dataSecondaryCategoryLabels; - private int dataReaded = 0; /* package */ HSSFSeries(SeriesRecord series) { this.series = series; } - public void insertData(LinkedDataRecord data){ - switch(dataReaded){ + /* package */ void insertData(LinkedDataRecord data){ + switch(data.getLinkType()){ case 0: dataName = data; break; case 1: dataValues = data; @@ -925,9 +984,13 @@ case 3: dataSecondaryCategoryLabels = data; break; } - dataReaded++; } + /* package */ void setSeriesTitleText(SeriesTextRecord seriesTitleText) + { + this.seriesTitleText = seriesTitleText; + } + public short getNumValues() { return series.getNumValues(); } @@ -996,5 +1059,281 @@ public SeriesRecord getSeries() { return series; } + + private CellRangeAddressBase getCellRange(LinkedDataRecord linkedDataRecord) { + if (linkedDataRecord == null) + { + return null ; + } + + int firstRow = 0; + int lastRow = 0; + int firstCol = 0; + int lastCol = 0; + + for (Ptg ptg : linkedDataRecord.getFormulaOfLink()) { + if (ptg instanceof AreaPtgBase) { + AreaPtgBase areaPtg = (AreaPtgBase) ptg; + + firstRow = areaPtg.getFirstRow(); + lastRow = areaPtg.getLastRow(); + + firstCol = areaPtg.getFirstColumn(); + lastCol = areaPtg.getLastColumn(); + } + } + + return new CellRangeAddressBase(firstRow, lastRow, firstCol, lastCol); + } + + public CellRangeAddressBase getValuesCellRange() { + return getCellRange(dataValues); + } + + public CellRangeAddressBase getCategoryLabelsCellRange() { + return getCellRange(dataCategoryLabels); + } + + private Integer setVerticalCellRange(LinkedDataRecord linkedDataRecord, + CellRangeAddressBase range) { + if (linkedDataRecord == null) + { + return null; + } + + List ptgList = new ArrayList(); + + int rowCount = (range.getLastRow() - range.getFirstRow()) + 1; + int colCount = (range.getLastColumn() - range.getFirstColumn()) + 1; + + for (Ptg ptg : linkedDataRecord.getFormulaOfLink()) { + if (ptg instanceof AreaPtgBase) { + AreaPtgBase areaPtg = (AreaPtgBase) ptg; + + areaPtg.setFirstRow(range.getFirstRow()); + areaPtg.setLastRow(range.getLastRow()); + + areaPtg.setFirstColumn(range.getFirstColumn()); + areaPtg.setLastColumn(range.getLastColumn()); + ptgList.add(areaPtg); + } + } + + linkedDataRecord.setFormulaOfLink(ptgList.toArray(new Ptg[ptgList.size()])); + + return rowCount * colCount; + } + + public void setValuesCellRange(CellRangeAddressBase range) { + Integer count = setVerticalCellRange(dataValues, range); + if (count == null) + { + return; + } + + series.setNumValues((short)(int)count); + } + + public void setCategoryLabelsCellRange(CellRangeAddressBase range) { + Integer count = setVerticalCellRange(dataCategoryLabels, range); + if (count == null) + { + return; + } + + series.setNumCategories((short)(int)count); + } } + + public HSSFSeries createSeries() throws Exception { + ArrayList seriesTemplate = new ArrayList(); + boolean seriesTemplateFilled = false; + + int idx = 0; + int deep = 0; + int chartRecordIdx = -1; + int chartDeep = -1; + int lastSeriesDeep = -1; + int endSeriesRecordIdx = -1; + int seriesIdx = 0; + final List records = sheet.getSheet().getRecords(); + + /* store first series as template and find last series index */ + for(final RecordBase record : records) { + + idx++; + + if (record instanceof BeginRecord) { + deep++; + } else if (record instanceof EndRecord) { + deep--; + + if (lastSeriesDeep == deep) { + lastSeriesDeep = -1; + endSeriesRecordIdx = idx; + if (!seriesTemplateFilled) { + seriesTemplate.add(record); + seriesTemplateFilled = true; + } + } + + if (chartDeep == deep) { + break; + } + } + + if (record instanceof ChartRecord) { + if (record == chartRecord) { + chartRecordIdx = idx; + chartDeep = deep; + } + } else if (record instanceof SeriesRecord) { + if (chartRecordIdx != -1) { + seriesIdx++; + lastSeriesDeep = deep; + } + } + + if (lastSeriesDeep != -1 && !seriesTemplateFilled) { + seriesTemplate.add(record) ; + } + } + + /* check if a series was found */ + if (endSeriesRecordIdx == -1) { + return null; + } + + /* next index in the records list where the new series can be inserted */ + idx = endSeriesRecordIdx + 1; + + HSSFSeries newSeries = null; + + /* duplicate record of the template series */ + ArrayList clonedRecords = new ArrayList(); + for(final RecordBase record : seriesTemplate) { + + Record newRecord = null; + + if (record instanceof BeginRecord) { + newRecord = new BeginRecord(); + } else if (record instanceof EndRecord) { + newRecord = new EndRecord(); + } else if (record instanceof SeriesRecord) { + SeriesRecord seriesRecord = (SeriesRecord) ((SeriesRecord)record).clone(); + newSeries = new HSSFSeries(seriesRecord); + newRecord = seriesRecord; + } else if (record instanceof LinkedDataRecord) { + LinkedDataRecord linkedDataRecord = (LinkedDataRecord) ((LinkedDataRecord)record).clone(); + if (newSeries != null) { + newSeries.insertData(linkedDataRecord); + } + newRecord = linkedDataRecord; + } else if (record instanceof DataFormatRecord) { + DataFormatRecord dataFormatRecord = (DataFormatRecord) ((DataFormatRecord)record).clone(); + + dataFormatRecord.setSeriesIndex((short)seriesIdx) ; + dataFormatRecord.setSeriesNumber((short)seriesIdx) ; + + newRecord = dataFormatRecord; + } else if (record instanceof SeriesTextRecord) { + SeriesTextRecord seriesTextRecord = (SeriesTextRecord) ((SeriesTextRecord)record).clone(); + if (newSeries != null) { + newSeries.setSeriesTitleText(seriesTextRecord); + } + newRecord = seriesTextRecord; + } else if (record instanceof Record) { + newRecord = (Record) ((Record)record).clone(); + } + + if (newRecord != null) + { + clonedRecords.add(newRecord); + } + } + + /* check if a user model series object was created */ + if (newSeries == null) + { + return null; + } + + /* transfer series to record list */ + for(final RecordBase record : clonedRecords) { + records.add(idx++, record); + } + + return newSeries; + } + + public boolean removeSeries(HSSFSeries series) { + int idx = 0; + int deep = 0; + int chartDeep = -1; + int lastSeriesDeep = -1; + int seriesIdx = -1; + boolean removeSeries = false; + boolean chartEntered = false; + boolean result = false; + final List records = sheet.getSheet().getRecords(); + + /* store first series as template and find last series index */ + Iterator iter = records.iterator(); + while (iter.hasNext()) { + RecordBase record = iter.next(); + idx++; + + if (record instanceof BeginRecord) { + deep++; + } else if (record instanceof EndRecord) { + deep--; + + if (lastSeriesDeep == deep) { + lastSeriesDeep = -1; + + if (removeSeries) { + removeSeries = false; + result = true; + iter.remove(); + } + } + + if (chartDeep == deep) { + break; + } + } + + if (record instanceof ChartRecord) { + if (record == chartRecord) { + chartDeep = deep; + chartEntered = true; + } + } else if (record instanceof SeriesRecord) { + if (chartEntered) { + if (series.series == record) { + lastSeriesDeep = deep; + removeSeries = true; + } else { + seriesIdx++; + } + } + } else if (record instanceof DataFormatRecord) { + if (chartEntered && !removeSeries) { + DataFormatRecord dataFormatRecord = (DataFormatRecord) record; + dataFormatRecord.setSeriesIndex((short) seriesIdx); + dataFormatRecord.setSeriesNumber((short) seriesIdx); + } + } + + if (removeSeries) { + iter.remove(); + } + } + + return result; + } + + public HSSFChartType getType() { + return type; + } }