Trying to add multiple images to XSSFGroupShape. I have tried the following(modified for simplicity):
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
private void addImagesToGroupShape(Sheet sheet, List<XSSFGroupImage> groupImages) throws Exception{
// XSSFGroupShape dimension
int dx1 = 180975;
int dy1 = 409575;
int dx2 = 219075;
int dy2 = 638175;
int row1 = 9;
int col1 = 8;
int row2 = 38;
int col2 = 19;
XSSFDrawing drawing = (XSSFDrawing) sheet.createDrawingPatriarch();
XSSFClientAnchor anchor = drawing.createAnchor(dx1, dy1, dx2, dy2, col1, row1, col2, row2);
XSSFShapeGroup shapeGroup = drawing.createGroup(anchor);
// XSSFGroupImage is a custom class
// Included below
for (XSSFGroupImage groupImage : groupImages) {
byte[] imageData = Base64.getDecoder().decode(groupImage.data().getBytes());
int picType = 1;
switch (groupImage.extension()) {
case "jpeg":
picType = Workbook.PICTURE_TYPE_JPEG;
break;
case "png":
picType = Workbook.PICTURE_TYPE_PNG;
break;
case "emf":
picType = Workbook.PICTURE_TYPE_EMF;
break;
default:
var msg = "Extension not supported";
throw new Exception(msg);
}
int pictureIndex = sheet.getWorkbook().addPicture(imageData, picType);
// shapeGroup.createPicture(anchor, pictureIndex);
CTPicture ctPic = shapeGroup.getCTGroupShape().insertNewPic(pictureIndex);
CTTransform2D ct2D = ctPic.addNewSpPr().addNewXfrm();
ct2D.addNewOff();
ct2D.addNewExt();
ct2D.getOff().setX(groupImage.offsetX());
ct2D.getOff().setY(groupImage.offsetY());
ct2D.getExt().setCx(Long.parseLong(groupImage.extentsCx()));
ct2D.getExt().setCy(Long.parseLong(groupImage.extentsCy()));
XSSFChildAnchor childAnchor = new XSSFChildAnchor(Integer.parseInt(groupImage.offsetX()),
Integer.parseInt(groupImage.offsetY()), Integer.parseInt(groupImage.extentsCx()),
Integer.parseInt(groupImage.extentsCy()));
// shapeGroup.getCTGroupShape().addNewPic();
// shapeGroup.createPicture(childAnchor, picIndex);
// ctPic.addNewBlipFill().addNewBlip().setEmbed("");
}
}
#Accessors(fluent = true)
public class XSSFGroupImage {
#Getter
#Setter
private String data;
#Getter
#Setter
private String extension;
#Getter
#Setter
private String offsetX;
#Getter
#Setter
private String offsetY;
#Getter
#Setter
private String extentsCx;
#Getter
#Setter
private String extentsCy;
}
Added images outside group and retrieved properties and data of embedded images from group shape.
I understand the fact that an image cannot attached without an anchor. However, I couldn't find a way of adding an XSSFChildAnchor inside the XSSFGroupShape that would allow setting of properites of CTTransform2D object.
I also tried to add the image to the Workbook using int pictureIndex = sheet.getWorkbook().addPicture(imageData, picType) and use that index to set the CTPicture using shapeGroup.getCTGroupShape().insertNewPic(pictureIndex). However, by doing this the relationId that would point to that image is not being set resulting in no image being displayed.
Image of desired output
XSSFShapeGroup is not complete until now (apache 5.0.0 May 2021).
But if pictures shall be in that group then those pictures first must be created at workbook level using Workbook.addPicture. There is no way around.
Having the picture ids then one can put the pictures in the group using XSSFShapeGroup.createPicture. There the XSSFClientAnchor is useless until now and can be simply new XSSFClientAnchor(). Nevertheless what else is set for that XSSFClientAnchor it will not have effect, neither for size of picture nor for position in the group. So after XSSFPicture picture = shapeGroup.createPicture(new XSSFClientAnchor(), pictureIdx1); the picture will be at position 0, 0 and will be of size 0, 0.
The size can be set using XSSFPicture.resize. If that is not possible, then it would must be calculated in unit EMU and set using low level ooxml classes. For example along the lines of
...
XSSFPicture picture = shapeGroup.createPicture(new XSSFClientAnchor(), pictureIdx1);
picture.getCTPicture().getSpPr().getXfrm().getExt().setCx(widthInEMU);
picture.getCTPicture().getSpPr().getXfrm().getExt().setCy(heightInEMU);
...
The position must be set using low level ooxml classes. For position X and Y also EMU is the measurement unit. For example:
...
picture.getCTPicture().getSpPr().getXfrm().getOff().setX(xInEMU);
picture.getCTPicture().getSpPr().getXfrm().getOff().setY(yInEMU);
...
So knowing that, let's provide a complete example. The following works (tested) using apache poi 5.0.0. Please note: The native size of the image files image1.jpeg and image2.jpeg should not be bigger than the anchor for the group frame.
import org.apache.poi.xssf.usermodel.*;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.Units;
import java.io.FileInputStream;
import java.io.FileOutputStream;
class CreateExcelXSSFShapeGroup {
public static void main(String[] args) throws Exception{
Workbook workbook = new XSSFWorkbook();
CreationHelper helper = workbook.getCreationHelper();
//add picture data to this workbook.
FileInputStream is = new FileInputStream("./image1.jpeg");
byte[] bytes = IOUtils.toByteArray(is);
int pictureIdx1 = workbook.addPicture(bytes, Workbook.PICTURE_TYPE_JPEG);
is.close();
is = new FileInputStream("./image2.jpeg");
bytes = IOUtils.toByteArray(is);
int pictureIdx2 = workbook.addPicture(bytes, Workbook.PICTURE_TYPE_JPEG);
is.close();
Sheet sheet = workbook.createSheet("Sheet1");
Drawing drawing = sheet.createDrawingPatriarch();
//From here on XSSF only.
XSSFDrawing xssfdrawing = (XSSFDrawing)drawing;
XSSFClientAnchor anchor = (XSSFClientAnchor)helper.createClientAnchor();
int groupShapeStartsAtCol = 1;
int groupShapeStartsAtRow = 1;
anchor.setCol1(groupShapeStartsAtCol);
anchor.setRow1(groupShapeStartsAtRow);
int groupWidthInCols = 8;
anchor.setCol2(groupShapeStartsAtCol + groupWidthInCols);
int groupHeightInRows = 20;
anchor.setRow2(groupShapeStartsAtRow + groupHeightInRows);
XSSFShapeGroup shapeGroup = xssfdrawing.createGroup(anchor); // group shape is now of size from anchor abowe
XSSFPicture picture = shapeGroup.createPicture(new XSSFClientAnchor(), pictureIdx1);
//picture.resize(); // picture is now at pos 0, 0 (top left) of group shape and of it's native size
picture.getCTPicture().getSpPr().getXfrm().getExt().setCx(1234567);
picture.getCTPicture().getSpPr().getXfrm().getExt().setCy(1234567); // picture is now at pos 0, 0 (top left) of group shape and of size 1234567 x 1234567 EMU
picture = shapeGroup.createPicture(new XSSFClientAnchor(), pictureIdx2);
picture.resize(); // picture is now at pos 0, 0 (top left) of group shape and of it's native size
// picture shall be at position bottom right of group shape
// so top left of picture must be at x = group width - picture width and y = group height - picture height
int pictureWidthInPx = (int)Math.round(picture.getImageDimension().getWidth());
int pictureHeightInPx = (int)Math.round(picture.getImageDimension().getHeight());
int defaultColWidthInPx = Math.round(sheet.getColumnWidthInPixels(0));
int defaultRowHeightInPx = Units.pointsToPixel(sheet.getDefaultRowHeightInPoints());
int xInEMU = Units.pixelToEMU(groupWidthInCols * defaultColWidthInPx - pictureWidthInPx);
picture.getCTPicture().getSpPr().getXfrm().getOff().setX(xInEMU);
int yInEMU = Units.pixelToEMU(groupHeightInRows * defaultRowHeightInPx - pictureHeightInPx);
picture.getCTPicture().getSpPr().getXfrm().getOff().setY(yInEMU);
FileOutputStream out = new FileOutputStream("CreateExcelXSSFShapeGroup.xlsx");
workbook.write(out);
out.close();
workbook.close();
}
}
Related
Read pptx template then use new data to replace it, there is a scatter chart
associated excel data
xVal and yVal could replace successfully but how to replace C column (extList) ?
xVal and yVal replace by below manner
final CTScatterSer ser = serList.get(0);
final CTAxDataSource xVal = ser.getXVal();
final CTNumDataSource yVal = ser.getYVal();
final CTExtension ctExtension = ser.getExtLst().getExtList().get(0);
final long ptCount = xVal.getNumRef().getNumCache().getPtCount().getVal();
for (int i = 0; i < scData.size(); i++) {
SCNameDouble data = scData.get(i);
CTNumVal xNumVal = ptCount > i ? xVal.getNumRef().getNumCache().getPtArray(i)
: xVal.getNumRef().getNumCache().addNewPt();
xNumVal.setIdx(i);
xNumVal.setV(String.format("%.2f", data.xValue));
CTNumVal yNumVal = ptCount > i ? yVal.getNumRef().getNumCache().getPtArray(i)
: yVal.getNumRef().getNumCache().addNewPt();
yNumVal.setIdx(i);
yNumVal.setV(String.format("%.2f", data.yValue));
}
final int newSize = scData.size();
xVal.getNumRef().setF(
replaceRowEnd(xVal.getNumRef().getF(),
ptCount,
newSize));
yVal.getNumRef().setF(
replaceRowEnd(yVal.getNumRef().getF(),
ptCount,
newSize));
xVal.getNumRef().getNumCache().getPtCount().setVal(newSize);
yVal.getNumRef().getNumCache().getPtCount().setVal(newSize);
Using current apache poi versions one should not trying manipulating charts using the low level CT... classes. There is XDDF for such cases now.
If it comes to PowerPoint charts, then the need is always updating the data in the embedded workbook and updating the data in the chart. See Java edit bar chart in ppt by using poi for an example using bar chart.
Of course a scatter chart is another case then as it not has a category axis but has two value axes. But this also can be updated using XDDF.
The biggest problem you have is the data labels. There is not full support for chart data labels in XDDF upto now. And since you are talkig about extLst and your Excel table shows the data labels in a cell range, I suspect you have set the data labels comming from a cell range. This is a new feature which was not present when Microsoft had published Office Open XML. So not even the low level CT... classes are able to support that feature.
The only way is to manipulate the XML using pure XML manupulating based on org.apache.xmlbeans.XmlObject.
The following shows this on sample of a template you seems to use according to your question.
ScatterChartSample.pptx:
Code:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import org.apache.poi.xslf.usermodel.*;
import org.apache.poi.xddf.usermodel.chart.*;
import org.apache.poi.xssf.usermodel.*;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.AreaReference;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumns;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumn;
public class PowerPointChangeScatterChartData {
//patched version of XSSFTable.updateHeaders, see https://stackoverflow.com/questions/55532006/renaming-headers-of-xssftable-with-apache-poi-leads-to-corrupt-xlsx-file/55539181#55539181
static void updateHeaders(XSSFTable table) {
XSSFSheet sheet = (XSSFSheet)table.getParent();
CellReference ref = table.getStartCellReference();
if (ref == null) return;
int headerRow = ref.getRow();
int firstHeaderColumn = ref.getCol();
XSSFRow row = sheet.getRow(headerRow);
DataFormatter formatter = new DataFormatter();
if (row != null /*&& row.getCTRow().validate()*/) {
int cellnum = firstHeaderColumn;
CTTableColumns ctTableColumns = table.getCTTable().getTableColumns();
if(ctTableColumns != null) {
for (CTTableColumn col : ctTableColumns.getTableColumnList()) {
XSSFCell cell = row.getCell(cellnum);
if (cell != null) {
col.setName(formatter.formatCellValue(cell));
}
cellnum++;
}
}
}
}
static void updateScatterChart(XSLFChart chart, Object[][] data) throws Exception {
// get chart's data source which is a Excel sheet
XSSFWorkbook chartDataWorkbook = chart.getWorkbook();
String sheetName = chartDataWorkbook.getSheetName(0);
XSSFSheet chartDataSheet = chartDataWorkbook.getSheet(sheetName);
// current Office uses a table as data source
// so get that table if present
XSSFTable chartDataTable = null;
if (chartDataSheet.getTables().size() > 0) {
chartDataTable = chartDataSheet.getTables().get(0);
}
if (chart.getChartSeries().size() == 1) { // we will process only one chart data
XDDFChartData chartData = chart.getChartSeries().get(0);
if (chartData.getSeriesCount() == 1) { // we will process only templates having one series
int rMin = 1; // first row (0) is headers row
int rMax = data.length - 1;
// column 0 is X-Values
int c = 0;
// set new x data
XDDFDataSource xs = null;
for (int r = rMin; r <= rMax; r++) {
XSSFRow row = chartDataSheet.getRow(r); if (row == null) row = chartDataSheet.createRow(r);
XSSFCell cell = row.getCell(c); if (cell == null) cell = row.createCell(c);
cell.setCellValue((Double)data[r][c]); // in sheet
}
xs = XDDFDataSourcesFactory.fromNumericCellRange(chartDataSheet, new CellRangeAddress(rMin,rMax,c,c)); // in chart
// set new x-title in sheet
String xTitle = (String)data[0][c];
chartDataSheet.getRow(0).getCell(c).setCellValue(xTitle); // in sheet
// column 1 is Y-Values
c = 1;
// set new y data in sheet and in chart
XDDFNumericalDataSource<Double> ys = null;
for (int r = rMin; r <= rMax; r++) {
XSSFRow row = chartDataSheet.getRow(r); if (row == null) row = chartDataSheet.createRow(r);
XSSFCell cell = row.getCell(c); if (cell == null) cell = row.createCell(c);
cell.setCellValue((Double)data[r][c]); // in sheet
}
ys = XDDFDataSourcesFactory.fromNumericCellRange(chartDataSheet, new CellRangeAddress(rMin,rMax,c,c));
XDDFChartData.Series series1 = chartData.getSeries(0);
series1.replaceData(xs, ys); // in chart
// set new y-title in sheet and in chart
String yTitle = (String)data[0][c];
chartDataSheet.getRow(0).getCell(c).setCellValue(yTitle); // in sheet
series1.setTitle(yTitle, new CellReference(sheetName, 0, c, true, true)); // in chart
series1.plot();
// column 2 is data-labels-range
c = 2;
// set new data labels data in sheet and in chart
XDDFDataSource dataLabelsRangeSource = null;
for (int r = rMin; r <= rMax; r++) {
XSSFRow row = chartDataSheet.getRow(r); if (row == null) row = chartDataSheet.createRow(r);
XSSFCell cell = row.getCell(c); if (cell == null) cell = row.createCell(c);
cell.setCellValue((String)data[r][c]); // in sheet
}
dataLabelsRangeSource = XDDFDataSourcesFactory.fromStringCellRange(chartDataSheet, new CellRangeAddress(rMin,rMax,c,c)); // in chart
updateDataLabelsRange(chart, dataLabelsRangeSource); // in chart
// set new data-labels-title in sheet
String descrTitle = (String)data[0][c];
chartDataSheet.getRow(0).getCell(c).setCellValue(descrTitle); // in sheet
// update the table if present
if (chartDataTable != null) {
CellReference topLeft = new CellReference(chartDataSheet.getRow(0).getCell(0));
CellReference bottomRight = new CellReference(chartDataSheet.getRow(rMax).getCell(c));
AreaReference tableArea = chartDataWorkbook.getCreationHelper().createAreaReference(topLeft, bottomRight);
chartDataTable.setArea(tableArea);
updateHeaders(chartDataTable);
}
}
}
}
static void updateDataLabelsRange(XDDFChart chart, XDDFDataSource dataLabelsRangeSource) {
String declareNameSpaces = "declare namespace c='http://schemas.openxmlformats.org/drawingml/2006/chart'; "
+ "declare namespace c15='http://schemas.microsoft.com/office/drawing/2012/chart' ";
org.apache.xmlbeans.XmlObject[] selectedObjects = chart.getCTChart().selectPath(
declareNameSpaces
+ ".//c:ext[c15:datalabelsRange]"); // needs net.sf.saxon - Saxon-HE (Saxon-HE-10.6.jar)
if (selectedObjects.length > 0) { // we have at least one ext containing datalabelsRange
org.apache.xmlbeans.XmlObject ext = selectedObjects[0]; // get first ext containing datalabelsRange
// get dataLabelsRange
org.apache.xmlbeans.XmlObject[] datalabelsRanges = ext.selectChildren(new javax.xml.namespace.QName("http://schemas.microsoft.com/office/drawing/2012/chart", "datalabelsRange", "c15"));
org.apache.xmlbeans.XmlObject dataLabelsRange = datalabelsRanges[0];
// set formula
org.apache.xmlbeans.XmlObject[] formulas = dataLabelsRange.selectChildren(new javax.xml.namespace.QName("http://schemas.microsoft.com/office/drawing/2012/chart", "f", "c15"));
org.apache.xmlbeans.XmlObject formula = formulas[0];
((org.apache.xmlbeans.impl.values.XmlObjectBase)formula).setStringValue(dataLabelsRangeSource.getFormula());
// get dlblRangeCache
org.apache.xmlbeans.XmlObject[] dlblRangeCaches = dataLabelsRange.selectChildren(new javax.xml.namespace.QName("http://schemas.microsoft.com/office/drawing/2012/chart", "dlblRangeCache", "c15"));
org.apache.xmlbeans.XmlObject dlblRangeCache = dlblRangeCaches[0];
// empty the cache
dlblRangeCache.newCursor().removeXmlContents();
// create new cache from dataLabelsRangeSource
org.openxmlformats.schemas.drawingml.x2006.chart.CTStrData cache = org.openxmlformats.schemas.drawingml.x2006.chart.CTStrData.Factory.newInstance();
dataLabelsRangeSource.fillStringCache(cache);
// set new cache
dlblRangeCache.set(cache);
}
}
public static void main(String[] args) throws Exception {
String filePath = "ScatterChartSample.pptx"; // has template scatter chart
String filePathNew = "ScatterChartSample_New.pptx";
Object[][] data = new Object[][] { // new data 1 series, 6 x-y-values and data labels
{"X-Values", "Y-Values", "DataLabels"}, // series title
{0.7d, 1.7d, "aa"}, // x1
{1.8d, 3.2d, "bb"}, // x2
{2.6d, 2.8d, "cc"}, // x3
{1.7d, 3.7d, "dd"}, // x4
{2.8d, 4.2d, "ee"}, // x5
{3.6d, 1.8d, "ff"} // x6
};
XMLSlideShow slideShow = new XMLSlideShow(new FileInputStream(filePath));
XSLFChart chart = slideShow.getCharts().get(0);
updateScatterChart(chart, data);
FileOutputStream out = new FileOutputStream(filePathNew);
slideShow.write(out);
out.close();
slideShow.close();
}
}
Resulting ScatterChartSample_New.pptx:
Note: Tested and works using current apache poi 5.2.0.
To be able to use XPath as .//c:ext[c15:datalabelsRange] it needs net.sf.saxon - Saxon-HE (Saxon-HE-10.6.jar in my case).
And it needs poi-ooxml-full-5.2.0.jar and not only the lite version of ooxml-schemas.
I know this question might seem similar but I have different problem. I know how to create a circle or a shape. This is what I want to create
A little circle in the center of the excel cell
The circle that I am able to make, looking at tutorials etc is:
This is the code I am using to create:
CreationHelper helper = workbook.getCreationHelper();
Drawing drawing = worksheet.createDrawingPatriarch();
ClientAnchor anchor = helper.createClientAnchor();
anchor.setCol1(0);
anchor.setRow1(0);
anchor.setCol2(1);
anchor.setRow2(1);
anchor.setDx1(255);
anchor.setDx2(255);
anchor.setDy1(0);
anchor.setDy2(0);
XSSFSimpleShape shape = ((XSSFDrawing)drawing).createSimpleShape((XSSFClientAnchor)anchor);
shape.setShapeType(ShapeTypes.FLOW_CHART_CONNECTOR);
shape.setFillColor(255, 0, 0);
I think there is something to do with the dx1,dx2,dy1,dy2 but setting any value in there has no effect.
I need to make that shape smaller somehow
If the shape shall within one cell only then the anchor must also be only one cell. The positioning is done then with Dx and Dy. But the measurement unit is special. It is EMU English Metric Units.
So the positioning of an ellipse in cell A1 will be like so:
import org.apache.poi.xssf.usermodel.*;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.util.Units;
import java.io.FileOutputStream;
import java.io.IOException;
class CenterShapeInCell {
public static void main(String[] args) {
try {
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("Sheet1");
Row row = sheet.createRow(0);
Cell cell = row.createCell(0);
row.setHeight((short)(20*20));
sheet.setColumnWidth(0, 20*256);
CreationHelper helper = workbook.getCreationHelper();
Drawing drawing = sheet.createDrawingPatriarch();
ClientAnchor anchor = helper.createClientAnchor();
//set anchor to A1 only
anchor.setCol1(0);
anchor.setRow1(0);
anchor.setCol2(0);
anchor.setRow2(0);
//get the cell width of A1
float cellWidthPx = sheet.getColumnWidthInPixels(0);
System.out.println(cellWidthPx);
//set wanted shape size
int shapeWidthPx = 20;
int shapeHeightPx = 20;
//calculate the position of left upper edge
float centerPosPx = cellWidthPx/2f - (float)shapeWidthPx/2f;
System.out.println(centerPosPx);
//set the position of left edge as Dx1 in unit EMU
anchor.setDx1(Math.round(centerPosPx * Units.EMU_PER_PIXEL));
//set the position of right edge as Dx2 in unit EMU
anchor.setDx2(Math.round((centerPosPx + shapeWidthPx) * Units.EMU_PER_PIXEL));
//set upper padding
int upperPaddingPx = 4;
//set upper padding as Dy1 in unit EMU
anchor.setDy1(upperPaddingPx * Units.EMU_PER_PIXEL);
//set upper padding + shape height as Dy2 in unit EMU
anchor.setDy2((upperPaddingPx + shapeHeightPx) * Units.EMU_PER_PIXEL);
XSSFSimpleShape shape = ((XSSFDrawing)drawing).createSimpleShape((XSSFClientAnchor)anchor);
shape.setShapeType(ShapeTypes.ELLIPSE);
shape.setFillColor(255, 0, 0);
FileOutputStream fileOut = new FileOutputStream("CenterShapeInCell.xlsx");
workbook.write(fileOut);
fileOut.close();
} catch (IOException ioex) {
}
}
}
Looks like:
But I suspect what you really want is a conditional formatting with traffic light symbols like so:
import org.apache.poi.xssf.usermodel.*;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import java.io.FileOutputStream;
import java.io.IOException;
class ConditionalFormattingIconSet {
public static void main(String[] args) {
try {
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("Sheet1");
CellStyle cellStyle = workbook.createCellStyle();
//cellStyle.setAlignment(CellStyle.ALIGN_CENTER); // old apache poi versions
cellStyle.setAlignment(HorizontalAlignment.CENTER); // current apache poi version 4.1.0
//cellStyle.setVerticalAlignment(CellStyle.VERTICAL_CENTER); // old apache poi versions
cellStyle.setVerticalAlignment(VerticalAlignment.CENTER); // current apache poi version 4.1.0
Cell cell;
for (int i = 0; i < 3; i++) {
cell = sheet.createRow(i).createCell(0);
cell.setCellValue(1+i);
cell.setCellStyle(cellStyle);
}
SheetConditionalFormatting sheetCF = sheet.getSheetConditionalFormatting();
ConditionalFormattingRule rule = sheetCF.createConditionalFormattingRule(IconMultiStateFormatting.IconSet.GYR_3_TRAFFIC_LIGHTS);
rule.getMultiStateFormatting().setIconOnly(true);
ConditionalFormattingRule [] cfRules = {rule};
CellRangeAddress[] regions = {CellRangeAddress.valueOf("A1:A3")};
sheetCF.addConditionalFormatting(regions, cfRules);
FileOutputStream fileOut = new FileOutputStream("ConditionalFormattingIconSet.xlsx");
workbook.write(fileOut);
fileOut.close();
} catch (IOException ioex) {
}
}
}
Looks like:
I am using Epplus library to generate Excel 2010 and up compatible files in Asp.Net C#.
I am using version 3.1.2 which is the latest at this moment.
I am setting the row height first, before adding any pictures like this:
ExcelPackage pck = new ExcelPackage();
var ws = pck.Workbook.Worksheets.Add("sheet 1");
while (i < dt.Rows.Count + offset)
{
ws.Row(i).Height = 84;
i++;
}
dt is my DataTable with DataRows.
After setting the height, I am looping again through the rows to add the pictures
while (i < dt.Rows.Count + offset)
{
var prodImg = ws.Drawings.AddPicture(dr["code"].ToString(), new FileInfo(path));
prodImg.SetPosition(i - 1, 0, 14, 0);
prodImg.SetSize(75);
}
This works, but this does not:
var prodImg = ws.Drawings.AddPicture(dr["code"].ToString(), new FileInfo(path));
int w = prodImg.Image.Width;
int h = prodImg.Image.Height;
if (h > 140) // because height of 84 is 140 pixels in excel
{
double scale = h / 140.0;
w = (int)Math.Floor(w / scale);
h = 140;
}
int xOff = (150 - w) / 2;
int yOff = (140 - h) / 2;
prodImg.SetPosition(i - 1, xOff, 11, yOff);
prodImg.SetSize(w, h);
This results in off center pictures and unresized images. And this code then which is in the same loop:
var prodImgDm = ws.Drawings.AddPicture("bcdm" + dr["code"].ToString(), new FileInfo(pathDm));
prodImgDm.SetPosition(i - 1, 25, 15, 40);
prodImgDm.SetSize(100);
This does work sometimes. the pictures prodImgDm are datamatrix images with a static width and height and do not need to be resized because they are always small/tiny. So also without the SetSize in some rows, it works and in some other rows, it does not work. Really strange because the code is the same. It might be something in the library and/or Excel. Perhaps I am using it wrong? Any epplus picture expert?
Thanks in advance!!
edit sometimes a picture is worth a thousand words, so here is the screenshot. As you can see the product images are not horizontal and vertical aligned in the cell. And the datamatrix on the far right is sometimes scaled about 120% even when I set SetSize(100) so it is really strange to me. So the last datamatrix has the correct size... I already found this SO thread but that does not help me out, I think.
edit 2013/04/09 Essenpillai gave me a hint to set
pck.DoAdjustDrawings = false;
but that gave me even stranger images:
the datamatrix is still changing on row basis. on row is ok, the other is not. and the ean13 code is too wide.
public static void CreatePicture(ExcelWorksheet worksheet, string name, Image image, int firstColumn, int lastColumn, int firstRow, int lastRow, int defaultOffsetPixels)
{
int columnWidth = GetWidthInPixels(worksheet.Cells[firstRow, firstColumn]);
int rowHeight = GetHeightInPixels(worksheet.Cells[firstRow, firstColumn]);
int totalColumnWidth = columnWidth * (lastColumn - firstColumn + 1);
int totalRowHeight = rowHeight * (lastRow - firstRow + 1);
double cellAspectRatio = Convert.ToDouble(totalColumnWidth) / Convert.ToDouble(totalRowHeight);
int imageWidth = image.Width;
int imageHeight = image.Height;
double imageAspectRatio = Convert.ToDouble(imageWidth) / Convert.ToDouble(imageHeight);
int pixelWidth;
int pixelHeight;
if (imageAspectRatio > cellAspectRatio)
{
pixelWidth = totalColumnWidth - defaultOffsetPixels * 2;
pixelHeight = pixelWidth * imageHeight / imageWidth;
}
else
{
pixelHeight = totalRowHeight - defaultOffsetPixels * 2;
pixelWidth = pixelHeight * imageWidth / imageHeight;
}
int rowOffsetPixels = (totalRowHeight - pixelHeight) / 2;
int columnOffsetPixels = (totalColumnWidth - pixelWidth) / 2;
int rowOffsetCount = 0;
int columnOffsetCount = 0;
if (rowOffsetPixels > rowHeight)
{
rowOffsetCount = (int)Math.Floor(Convert.ToDouble(rowOffsetPixels) / Convert.ToDouble(rowHeight));
rowOffsetPixels -= rowHeight * rowOffsetCount;
}
if (columnOffsetPixels > columnWidth)
{
columnOffsetCount = (int)Math.Floor(Convert.ToDouble(columnOffsetPixels) / Convert.ToDouble(columnWidth));
columnOffsetPixels -= columnWidth * columnOffsetCount;
}
int row = firstRow + rowOffsetCount - 1;
int column = firstColumn + columnOffsetCount - 1;
ExcelPicture pic = worksheet.Drawings.AddPicture(name, image);
pic.SetPosition(row, rowOffsetPixels, column, columnOffsetPixels);
pic.SetSize(pixelWidth, pixelHeight);
}
public static int GetHeightInPixels(ExcelRange cell)
{
using (Graphics graphics = Graphics.FromHwnd(IntPtr.Zero))
{
float dpiY = graphics.DpiY;
return (int)(cell.Worksheet.Row(cell.Start.Row).Height * (1 / 72.0) * dpiY);
}
}
public static float MeasureString(string s, Font font)
{
using (var g = Graphics.FromHwnd(IntPtr.Zero))
{
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
return g.MeasureString(s, font, int.MaxValue, StringFormat.GenericTypographic).Width;
}
}
public static int GetWidthInPixels(ExcelRange cell)
{
double columnWidth = cell.Worksheet.Column(cell.Start.Column).Width;
Font font = new Font(cell.Style.Font.Name, cell.Style.Font.Size, FontStyle.Regular);
double pxBaseline = Math.Round(MeasureString("1234567890", font) / 10);
return (int)(columnWidth * pxBaseline);
}
enter image description here
I have the same problem with Epplus library.
After I find no solution how to solve this in my code, I checked source code of this library.
Epplus create excel picture always as twoCellAnchor drawing. In xlsx files you can find drawingXYZ.xml with this code:
<xdr:twoCellAnchor editAs="oneCell">
<xdr:from> ... </xdr:from>
<xdr:to> ... </xdr:to>
<xdr:pic>
...
</xdr:twoCellAnchor>
So, picture is always connected to two cells, and this is not variable part of Epplus library. You can find this part of code in ExcelDrawing.cs file.
XmlElement drawNode = _drawingsXml.CreateElement(
"xdr", "twoCellAnchor", ExcelPackage.schemaSheetDrawings);
colNode.AppendChild(drawNode);
You can easy create your own copy of this dll. The good news is that you need to modify only two files to fix this problem. So..
Download your copy of source codes for Epplus library from this site and open in Visual Studio.
We need to generate code of drawing as oneCellAnchor, so we must remove <xdr:to> element for pictures and create element <xdr:ext /> with picture dimensions as parameters.
New xml structure will looks like:
<xdr:oneCellAnchor editAs="oneCell">
<xdr:from> ... </xdr:from>
<xdr:ext cx="1234567" cy="7654321" />
<xdr:pic>
...
</xdr:oneCellAnchor>
Ok, so, how to do this?
Changes in Epplus code
ExcelDrawings.cs (link to file here)
At first we modify method CreateDrawingXml() inside ExcelDrawings.cs. Order to preserve the original functionality we add an optional parameter (if create oneCellAnchor) with default value. And in method, based this parameter, we create one or tow cell anchor and create or not <xdr:to> element.
Important part of this method code:
private XmlElement CreateDrawingXml(bool twoCell = true) {
if (DrawingXml.OuterXml == "")
{ ... } // not changed
XmlNode colNode= _drawingsXml.SelectSingleNode("//xdr:wsDr", NameSpaceManager);
//First change in method code
XmlElement drawNode;
if (twoCell)
drawNode = _drawingsXml.CreateElement(
"xdr", "twoCellAnchor", ExcelPackage.schemaSheetDrawings);
else
drawNode = _drawingsXml.CreateElement(
"xdr", "oneCellAnchor", ExcelPackage.schemaSheetDrawings);
colNode.AppendChild(drawNode);
//Add from position Element; // Not changed
XmlElement fromNode = _drawingsXml.CreateElement(
"xdr", "from", ExcelPackage.schemaSheetDrawings);
drawNode.AppendChild(fromNode);
fromNode.InnerXml = "<xdr:col>0</xdr:col><xdr:colOff>0</xdr:colOff>"
+ "<xdr:row>0</xdr:row><xdr:rowOff>0</xdr:rowOff>";
//Add to position Element;
//Second change in method
if (twoCell)
{
XmlElement toNode = _drawingsXml.CreateElement(
"xdr", "to", ExcelPackage.schemaSheetDrawings);
drawNode.AppendChild(toNode);
toNode.InnerXml = "<xdr:col>10</xdr:col><xdr:colOff>0</xdr:colOff>"
+ "<xdr:row>10</xdr:row><xdr:rowOff>0</xdr:rowOff>";
}
return drawNode;
}
Then we modify two methods for AddPicture inside the same file:
public ExcelPicture AddPicture(string Name, Image image, Uri Hyperlink)
{
if (image != null) {
if (_drawingNames.ContainsKey(Name.ToLower())) {
throw new Exception("Name already exists in the drawings collection");
}
XmlElement drawNode = CreateDrawingXml(false);
// Change: we need create element with dimensions
// like: <xdr:ext cx="3857625" cy="1047750" />
XmlElement xdrext = _drawingsXml.CreateElement(
"xdr", "ext", ExcelPackage.schemaSheetDrawings);
xdrext.SetAttribute("cx",
(image.Width * ExcelDrawing.EMU_PER_PIXEL).ToString());
xdrext.SetAttribute("cy",
(image.Height * ExcelDrawing.EMU_PER_PIXEL).ToString());
drawNode.AppendChild(xdrext);
// End of change, next part of method is the same:
drawNode.SetAttribute("editAs", "oneCell");
...
}
}
And this method with FileInfo as input parameter:
public ExcelPicture AddPicture(string Name, FileInfo ImageFile, Uri Hyperlink)
{
if (ImageFile != null) {
if (_drawingNames.ContainsKey(Name.ToLower())) {
throw new Exception("Name already exists in the drawings collection");
}
XmlElement drawNode = CreateDrawingXml(false);
// Change: First create ExcelPicture object and calculate EMU dimensions
ExcelPicture pic = new ExcelPicture(this, drawNode, ImageFile, Hyperlink);
XmlElement xdrext = _drawingsXml.CreateElement(
"xdr", "ext", ExcelPackage.schemaSheetDrawings);
xdrext.SetAttribute("cx",
(pic.Image.Width * ExcelDrawing.EMU_PER_PIXEL).ToString());
xdrext.SetAttribute("cy",
(pic.Image.Height * ExcelDrawing.EMU_PER_PIXEL).ToString());
drawNode.AppendChild(xdrext);
// End of change, next part of method is the same (without create pic object)
drawNode.SetAttribute("editAs", "oneCell");
...
}
}
So, this are all important code. Now we must change code for searching nodes and preserve order in elements.
In private void AddDrawings() we change xpath from:
XmlNodeList list = _drawingsXml.SelectNodes(
"//xdr:twoCellAnchor", NameSpaceManager);
To this:
XmlNodeList list = _drawingsXml.SelectNodes(
"//(xdr:twoCellAnchor or xdr:oneCellAnchor)", NameSpaceManager);
It is all in this file, now we change
ExcelPicture.cs (link to file here)
Original code find node for append next code in constructor like this:
node.SelectSingleNode("xdr:to",NameSpaceManager);
Because we do not create <xdr:to> element always, we change this code:
internal ExcelPicture(ExcelDrawings drawings, XmlNode node
, Image image, Uri hyperlink)
: base(drawings, node, "xdr:pic/xdr:nvPicPr/xdr:cNvPr/#name")
{
XmlElement picNode = node.OwnerDocument.CreateElement(
"xdr", "pic", ExcelPackage.schemaSheetDrawings);
// Edited: find xdr:to, or xdr:ext if xdr:to not exists
XmlNode befor = node.SelectSingleNode("xdr:to",NameSpaceManager);
if (befor != null && befor.Name == "xdr:to")
node.InsertAfter(picNode, befor);
else {
befor = node.SelectSingleNode("xdr:ext", NameSpaceManager);
node.InsertAfter(picNode, befor);
}
// End of change, next part of constructor is unchanged
_hyperlink = hyperlink;
...
}
And the same for second constructor with FileInfo as input parameter:
internal ExcelPicture(ExcelDrawings drawings, XmlNode node
, FileInfo imageFile, Uri hyperlink)
: base(drawings, node, "xdr:pic/xdr:nvPicPr/xdr:cNvPr/#name")
{
XmlElement picNode = node.OwnerDocument.CreateElement(
"xdr", "pic", ExcelPackage.schemaSheetDrawings);
// Edited: find xdr:to, or xdr:ext if xdr:to not exists
XmlNode befor = node.SelectSingleNode("xdr:to", NameSpaceManager);
if (befor != null && befor.Name == "xdr:to")
node.InsertAfter(picNode, befor);
else {
befor = node.SelectSingleNode("xdr:ext", NameSpaceManager);
node.InsertAfter(picNode, befor);
}
// End of change, next part of constructor is unchanged
_hyperlink = hyperlink;
...
Now, pictures are created as oneCellAnchor. If you want, you can create multiple AddPicture methods for booth variants. Last step is build this project and create your own custom EPPlus.dll. Then close your project which use this dll and copy new files EPPlus.dll, EPPlus.pdb, EPPlus.XML inside your project (backup and replace your original dll files) at the same place (so you don't need do any change in your project references or settings).
Then open and rebuild your project and try if this solve your problem.
Maybe I am too late, but here is mine answer..
you can read it on codeplex issue as well
(https://epplus.codeplex.com/workitem/14846)
I got this problem as well.
And after some research I figured out where the bug is.
It's in the ExcelRow class on the 149 line of code (ExcelRow.cs file).
There is a mistake, when row's Height got changed it recalcs all pictures heights but uses pictures widths inplace of heights, so it's easy to fix.
Just change the line
var pos = _worksheet.Drawings.GetDrawingWidths();
to
var pos = _worksheet.Drawings.GetDrawingHeight();
See the code changes on image
P.S. Actual for version 4.0.4
I am Persian and j2me do not have good support for persian font.
I will create a native font library that get bitmap font and paint my persian text in desplay. But I have a problem.
In english each letter is a set consist shap and uncode. Like (a , U+0061)
But in persian a char may have several shape. for example letter 'ب' in persian alphabet can be:
آب --when it is separate letter in a word
به --when it is start letter in a word
...
How can I get other form of a letter from font file?
I am a persian developer and I had the same problem in about 4 years ago.You have some way to solve this problem:
1-using custom fonts.
2-reshape your text before display it.
A good article in about first,is "MIDP Terminal Emulation, Part 3: Custom Fonts for MIDP ".But for arabic letters I think that is not simple.
In about second way,say you would to replace any character in your text with correct character.This means when you have:
String str = "به";
If get str characters they will be look like:
{1576,1607} that is like "ب ه" instead of "به".So you would to replace incorrect Unicode with correct Unicode codes(in this case correct characters are: {65169, 65258}).You can use "Arabic Reshapers" even reshapers that designed for android!I saw 2 link for this reshapers:1-github 2-Arabic Android(I'm persian developer and so I do not try them,instead I create classes with the same idea as they have).
With using a good reshaper also you may have problem with character arranging from left to right instead of right to left.(some phones draw characters from left to right and other from right to left).I use below class to detect that ordering is true(from right to left) or not:
public class DetectOrdering{
public static boolean hasTrueOrdering()
{
boolean b = false;
try {
char[] chArr = {65169, 65258};
String str = new String(chArr);
System.out.println(str);
int width = f1.charWidth(chArr[1]) / 2;
int height = f1.getHeight();
image1 = Image.createImage(width, height);
image2 = Image.createImage(width, height);
Graphics g1 = image1.getGraphics();
Graphics g2 = image2.getGraphics();
g1.drawString(str, 0, 0, 0);
g2.drawChar(chArr[1], 0, 0, 0);
int[] im1 = new int[width * height];
int[] im2 = new int[width * height];
image1.getRGB(im1, 0, width, 0, 0, width, height);
image2.getRGB(im2, 0, width, 0, 0, width, height);
if (areEqualIntArrrays(im1, im2)) {
b = true;
} else {
b = false;
}
} catch (Exception e) {
e.printStackTrace();
}
return b;
}
private static boolean areEqualIntArrrays(int[] i1, int[] i2) {
if (i1.length != i2.length) {
return false;
} else {
for (int i = 0; i < i1.length; i++) {
if (i1[i] != i2[i]) {
return false;
}
}
}
return true;
}
}
If DetectOrdering.hasTrueOrdering() returns true,sure that phone draw Arabic characters from right to left and display your String.If returns false it draws from left to right.If phone draws Arabic character from left to right you would to reverse string after reshape it and then you can display it.
You can use one alphabet.png for the direct unicode mappings (those where the persian char does not change because of the neighbor chars). If your characters are monospaced, you may start with below class, as seen at http://smallandadaptive.blogspot.com.br/2008/12/custom-monospaced-font.html:
public class MonospacedFont {
private Image image;
private char firstChar;
private int numChars;
private int charWidth;
public MonospacedFont(Image image, char firstChar, int numChars) {
if (image == null) {
throw new IllegalArgumentException("image == null");
}
// the first visible Unicode character is '!' (value 33)
if (firstChar <= 33) {
throw new IllegalArgumentException("firstChar <= 33");
}
// there must be at lease one character on the image
if (numChars <= 0) {
throw new IllegalArgumentException("numChars <= 0");
}
this.image = image;
this.firstChar = firstChar;
this.numChars = numChars;
this.charWidth = image.getWidth() / this.numChars;
}
public void drawString(Graphics g, String text, int x, int y) {
// store current Graphics clip area to restore later
int clipX = g.getClipX();
int clipY = g.getClipY();
int clipWidth = g.getClipWidth();
int clipHeight = g.getClipHeight();
char[] chars = text.toCharArray();
for (int i = 0; i < chars.length; i++) {
int charIndex = chars[i] - this.firstChar;
// current char exists on the image
if (charIndex >= 0 && charIndex <= this.numChars) {
g.setClip(x, y, this.charWidth, this.image.getHeight());
g.drawImage(image, x - (charIndex * this.charWidth), y,
Graphics.TOP | Graphics.LEFT);
x += this.charWidth;
}
}
// restore initial clip area
g.setClip(clipX, clipY, clipWidth, clipHeight);
}
}
And change it to use a different char_uxxxx.png file for each persian char that changes because of the neighbor chars.
When parsing your string, before painting, you must check which png file is appropriate to use. Hope this is a good place to start.
In WPF there is the FormattedText in the System.Windows.Media namespace MSDN FormattedText that I can use like so:
private static Size GetTextSize(string txt, string font, int size, bool isBold)
{
Typeface tf = new Typeface(new System.Windows.Media.FontFamily(font),
FontStyles.Normal,
(isBold) ? FontWeights.Bold : FontWeights.Normal,
FontStretches.Normal);
FormattedText ft = new FormattedText(txt, new CultureInfo("en-us"), System.Windows.FlowDirection.LeftToRight, tf, (double)size, System.Windows.Media.Brushes.Black, null, TextFormattingMode.Display);
return new Size { Width = ft.WidthIncludingTrailingWhitespace, Height = ft.Height };
}
Is there a good approach in Silverlight to getting the width in pixels (at the moment height isn't important) besides making a call to the server?
An approach that I've seen used, that may not work in your particular instance, is to throw the text into an unstyled TextBlock, and then get the width of that control, like so:
private double GetTextWidth(string text, int fontSize)
{
TextBlock txtMeasure = new TextBlock();
txtMeasure.FontSize = fontSize;
txtMeasure.Text = text;
double width = txtMeasure.ActualWidth;
return width;
}
It's a hack, no doubt.