Patch from Jens Gotze from bug #49581 - Ability to add, modify and remove series from HSSF Charts

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@964855 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Nick Burch 2010-07-16 16:46:42 +00:00
parent 7732426887
commit bea0801c24
7 changed files with 494 additions and 21 deletions

View File

@ -34,6 +34,7 @@
<changes> <changes>
<release version="3.7-beta2" date="2010-??-??"> <release version="3.7-beta2" date="2010-??-??">
<action dev="POI-DEVELOPERS" type="add">49581 - Ability to add, modify and remove series from HSSF Charts</action>
<action dev="POI-DEVELOPERS" type="add">49185 - Support for HSSFNames where the comment is stored in a NameCommentRecord</action> <action dev="POI-DEVELOPERS" type="add">49185 - Support for HSSFNames where the comment is stored in a NameCommentRecord</action>
<action dev="POI-DEVELOPERS" type="fix">49599 - Correct writing of NoteRecord author text when switching between ASCII and Unicode</action> <action dev="POI-DEVELOPERS" type="fix">49599 - Correct writing of NoteRecord author text when switching between ASCII and Unicode</action>
<action dev="POI-DEVELOPERS" type="fix">HWPF: Improve reading of auto-saved ("complex") documents</action> <action dev="POI-DEVELOPERS" type="fix">HWPF: Improve reading of auto-saved ("complex") documents</action>

View File

@ -232,6 +232,7 @@ public final class RecordFactory {
ChartStartObjectRecord.class, ChartStartObjectRecord.class,
ChartEndObjectRecord.class, ChartEndObjectRecord.class,
CatLabRecord.class, CatLabRecord.class,
DataFormatRecord.class,
EndRecord.class, EndRecord.class,
LinkedDataRecord.class, LinkedDataRecord.class,
SeriesToChartGroupRecord.class, SeriesToChartGroupRecord.class,

View File

@ -35,6 +35,9 @@ public final class ChartEndBlockRecord extends StandardRecord {
private short iObjectKind; private short iObjectKind;
private byte[] unused; private byte[] unused;
public ChartEndBlockRecord() {
}
public ChartEndBlockRecord(RecordInputStream in) { public ChartEndBlockRecord(RecordInputStream in) {
rt = in.readShort(); rt = in.readShort();
grbitFrt = in.readShort(); grbitFrt = in.readShort();
@ -80,4 +83,16 @@ public final class ChartEndBlockRecord extends StandardRecord {
buffer.append("[/ENDBLOCK]\n"); buffer.append("[/ENDBLOCK]\n");
return buffer.toString(); 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;
}
} }

View File

@ -37,6 +37,9 @@ public final class ChartStartBlockRecord extends StandardRecord {
private short iObjectInstance1; private short iObjectInstance1;
private short iObjectInstance2; private short iObjectInstance2;
public ChartStartBlockRecord() {
}
public ChartStartBlockRecord(RecordInputStream in) { public ChartStartBlockRecord(RecordInputStream in) {
rt = in.readShort(); rt = in.readShort();
grbitFrt = in.readShort(); grbitFrt = in.readShort();
@ -80,4 +83,18 @@ public final class ChartStartBlockRecord extends StandardRecord {
buffer.append("[/STARTBLOCK]\n"); buffer.append("[/STARTBLOCK]\n");
return buffer.toString(); 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;
}
} }

View File

@ -18,6 +18,7 @@
package org.apache.poi.hssf.usermodel; package org.apache.poi.hssf.usermodel;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; import java.util.List;
import org.apache.poi.hssf.record.chart.*; import org.apache.poi.hssf.record.chart.*;
@ -35,8 +36,10 @@ import org.apache.poi.hssf.record.SCLRecord;
import org.apache.poi.hssf.record.UnknownRecord; import org.apache.poi.hssf.record.UnknownRecord;
import org.apache.poi.hssf.record.VCenterRecord; import org.apache.poi.hssf.record.VCenterRecord;
import org.apache.poi.hssf.record.formula.Area3DPtg; 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.formula.Ptg;
import org.apache.poi.hssf.record.chart.LinkedDataRecord; import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellRangeAddressBase;
/** /**
* Has methods for construction of a chart object. * Has methods for construction of a chart object.
@ -44,6 +47,7 @@ import org.apache.poi.hssf.record.chart.LinkedDataRecord;
* @author Glen Stampoultzis (glens at apache.org) * @author Glen Stampoultzis (glens at apache.org)
*/ */
public final class HSSFChart { public final class HSSFChart {
private HSSFSheet sheet;
private ChartRecord chartRecord; private ChartRecord chartRecord;
private LegendRecord legendRecord; private LegendRecord legendRecord;
@ -51,10 +55,54 @@ public final class HSSFChart {
private SeriesTextRecord chartTitleText; private SeriesTextRecord chartTitleText;
private List<ValueRangeRecord> valueRanges = new ArrayList<ValueRangeRecord>(); private List<ValueRangeRecord> valueRanges = new ArrayList<ValueRangeRecord>();
private HSSFChartType type = HSSFChartType.Unknown;
private List<HSSFSeries> series = new ArrayList<HSSFSeries>(); private List<HSSFSeries> series = new ArrayList<HSSFSeries>();
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.chartRecord = chartRecord;
this.sheet = sheet;
} }
/** /**
@ -146,22 +194,20 @@ public final class HSSFChart {
for(RecordBase r : records) { for(RecordBase r : records) {
if(r instanceof ChartRecord) { if(r instanceof ChartRecord) {
lastChart = new HSSFChart((ChartRecord)r); lastSeries = null;
lastChart = new HSSFChart(sheet,(ChartRecord)r);
charts.add(lastChart); charts.add(lastChart);
} } else if(r instanceof LegendRecord) {
if(r instanceof LegendRecord) {
lastChart.legendRecord = (LegendRecord)r; lastChart.legendRecord = (LegendRecord)r;
} } else if(r instanceof SeriesRecord) {
if(r instanceof SeriesRecord) {
HSSFSeries series = lastChart.new HSSFSeries( (SeriesRecord)r ); HSSFSeries series = lastChart.new HSSFSeries( (SeriesRecord)r );
lastChart.series.add(series); lastChart.series.add(series);
lastSeries = series; lastSeries = series;
} } else if(r instanceof ChartTitleFormatRecord) {
if(r instanceof ChartTitleFormatRecord) {
lastChart.chartTitleFormat = lastChart.chartTitleFormat =
(ChartTitleFormatRecord)r; (ChartTitleFormatRecord)r;
} } else if(r instanceof SeriesTextRecord) {
if(r instanceof SeriesTextRecord) {
// Applies to a series, unless we've seen // Applies to a series, unless we've seen
// a legend already // a legend already
SeriesTextRecord str = (SeriesTextRecord)r; SeriesTextRecord str = (SeriesTextRecord)r;
@ -173,13 +219,28 @@ public final class HSSFChart {
} else { } else {
lastChart.chartTitleText = str; lastChart.chartTitleText = str;
} }
} } else if (r instanceof LinkedDataRecord) {
if(r instanceof LinkedDataRecord) { LinkedDataRecord linkedDataRecord = (LinkedDataRecord) r;
LinkedDataRecord data = (LinkedDataRecord)r; if (lastSeries != null) {
lastSeries.insertData( data ); lastSeries.insertData(linkedDataRecord);
} }
if(r instanceof ValueRangeRecord){ } else if(r instanceof ValueRangeRecord){
lastChart.valueRanges.add((ValueRangeRecord)r); 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 +969,13 @@ public final class HSSFChart {
private LinkedDataRecord dataValues; private LinkedDataRecord dataValues;
private LinkedDataRecord dataCategoryLabels; private LinkedDataRecord dataCategoryLabels;
private LinkedDataRecord dataSecondaryCategoryLabels; private LinkedDataRecord dataSecondaryCategoryLabels;
private int dataReaded = 0;
/* package */ HSSFSeries(SeriesRecord series) { /* package */ HSSFSeries(SeriesRecord series) {
this.series = series; this.series = series;
} }
public void insertData(LinkedDataRecord data){ /* package */ void insertData(LinkedDataRecord data){
switch(dataReaded){ switch(data.getLinkType()){
case 0: dataName = data; case 0: dataName = data;
break; break;
case 1: dataValues = data; case 1: dataValues = data;
@ -925,7 +985,11 @@ public final class HSSFChart {
case 3: dataSecondaryCategoryLabels = data; case 3: dataSecondaryCategoryLabels = data;
break; break;
} }
dataReaded++; }
/* package */ void setSeriesTitleText(SeriesTextRecord seriesTitleText)
{
this.seriesTitleText = seriesTitleText;
} }
public short getNumValues() { public short getNumValues() {
@ -996,5 +1060,281 @@ public final class HSSFChart {
public SeriesRecord getSeries() { public SeriesRecord getSeries() {
return series; 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 CellRangeAddress(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<Ptg> ptgList = new ArrayList<Ptg>();
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<RecordBase> seriesTemplate = new ArrayList<RecordBase>();
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<RecordBase> 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<RecordBase> clonedRecords = new ArrayList<RecordBase>();
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<RecordBase> records = sheet.getSheet().getRecords();
/* store first series as template and find last series index */
Iterator<RecordBase> 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;
} }
} }

View File

@ -19,8 +19,12 @@ package org.apache.poi.hssf.usermodel;
import junit.framework.TestCase; import junit.framework.TestCase;
import org.apache.poi.hssf.HSSFITestDataProvider;
import org.apache.poi.hssf.HSSFTestDataSamples; import org.apache.poi.hssf.HSSFTestDataSamples;
import org.apache.poi.hssf.record.chart.SeriesRecord; import org.apache.poi.hssf.record.chart.SeriesRecord;
import org.apache.poi.hssf.usermodel.HSSFChart.HSSFSeries;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellRangeAddressBase;
/** /**
* Tests for {@link HSSFChart} * Tests for {@link HSSFChart}
@ -127,4 +131,99 @@ public final class TestHSSFChart extends TestCase {
assertEquals("Base Numbers", charts[0].getSeries()[1].getSeriesTitle()); assertEquals("Base Numbers", charts[0].getSeries()[1].getSeriesTitle());
assertEquals("Sheet 3 Chart with Title", charts[0].getChartTitle()); assertEquals("Sheet 3 Chart with Title", charts[0].getChartTitle());
} }
public void testExistingSheet3() throws Exception {
HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("49581.xls");
HSSFSheet sheet = wb.getSheetAt( 2 ) ;
HSSFChart[] charts = HSSFChart.getSheetCharts( sheet ) ;
assertEquals(1, charts.length);
for ( HSSFChart chart : charts ) {
for ( HSSFSeries series : chart.getSeries() ) {
chart.removeSeries( series ) ;
}
}
// Save and re-check
wb = HSSFITestDataProvider.instance.writeOutAndReadBack(wb);
sheet = wb.getSheetAt( 2 ) ;
assertEquals(1, HSSFChart.getSheetCharts(sheet).length);
HSSFChart c = HSSFChart.getSheetCharts(sheet)[0];
assertEquals(0, c.getSeries().length);
}
public void testExistingSheet2() throws Exception {
HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("49581.xls");
HSSFSheet sheet = wb.getSheetAt( 1 ) ;
HSSFChart[] charts = HSSFChart.getSheetCharts( sheet ) ;
assertEquals(1, charts.length);
for ( HSSFChart chart : charts ) {
HSSFSeries series ;
// Starts with one
assertEquals(1, chart.getSeries().length);
// Add two more
series = chart.createSeries() ;
series.setCategoryLabelsCellRange( new CellRangeAddress( 3, 4, 0, 0 ) ) ;
series.setValuesCellRange( new CellRangeAddress( 3, 4, 1, 1 ) ) ;
series = chart.createSeries() ;
series.setCategoryLabelsCellRange( new CellRangeAddress( 6, 7, 0, 0 ) ) ;
series.setValuesCellRange( new CellRangeAddress( 6, 7, 1, 1 ) ) ;
}
// Save and re-check
wb = HSSFITestDataProvider.instance.writeOutAndReadBack(wb);
sheet = wb.getSheetAt( 1 ) ;
assertEquals(1, HSSFChart.getSheetCharts(sheet).length);
HSSFChart c = HSSFChart.getSheetCharts(sheet)[0];
assertEquals(3, c.getSeries().length);
}
public void testExistingSheet1() throws Exception {
HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("49581.xls");
HSSFSheet sheet = wb.getSheetAt( 0 ) ;
HSSFChart[] charts = HSSFChart.getSheetCharts( sheet ) ;
for ( HSSFChart chart : charts ) {
//System.out.println( chart.getType() ) ;
HSSFSeries[] seriesArray = chart.getSeries() ;
//System.out.println( "seriesArray.length=" + seriesArray.length ) ;
for ( HSSFSeries series : seriesArray )
{
//System.out.println( "serie.getNumValues()=" + series.getNumValues() ) ;
CellRangeAddressBase range ;
range = series.getValuesCellRange() ;
//System.out.println( range.toString() ) ;
range.setLastRow( range.getLastRow() + 1 ) ;
series.setValuesCellRange( range ) ;
range = series.getCategoryLabelsCellRange() ;
//System.out.println( range.toString() ) ;
range.setLastRow( range.getLastRow() + 1 ) ;
series.setCategoryLabelsCellRange( range ) ;
}
for ( int id = 0 ; id < 2 ; id++ )
{
HSSFSeries newSeries = chart.createSeries() ;
newSeries.setValuesCellRange( new CellRangeAddress( 1 + id, 4, 3, 3 ) ) ;
String oldSeriesTitle = newSeries.getSeriesTitle() ;
if ( oldSeriesTitle != null )
{
//System.out.println( "old series title: " + oldSeriesTitle ) ;
newSeries.setSeriesTitle( "new series" ) ;
}
}
}
HSSFChart chart = charts[ 2 ] ;
chart.removeSeries( chart.getSeries()[ 0 ] ) ;
}
} }

Binary file not shown.