PdfPageEventHelper in iTextSharp - itext

sorry if this was posted earlier
I need to print list of expenses in a pdf document. the list may extend to any number of pages.
i'm writing the list by iterating the datarow object. One important thing is whenever the current page is going to end need to print the running total at the end of the page. i've wrote a class that implements PdfPageEventHandler as below
public class PaymentPageEventHandler : iTextSharp.text.pdf.PdfPageEventHelper
{
PdfContentByte cb;
private string subTotal = "";
public string GetSubtotal
{
set{
value = subTotal;
}
get
{
return subTotal;
}
}
public override void OnEndPage(PdfWriter writer, Document document)
{
// base.OnEndPage(writer, document);
cb = writer.DirectContent;
float[] iOuterTblWidth = { 10, 40F, 8, 12, 10 }; column widths of the table.
PdfPTable pdftbl = new PdfPTable(iOuterTblWidth);
PdfPCell cell = new PdfPCell();
cell.Colspan = 4;
cell.AddElement(new Chunk("Sub-Total"));
cell.HorizontalAlignment = 0;
pdftbl.AddCell(cell);
cell = new PdfPCell();
cell.AddElement(new Chunk(GetSubtotal));
cell.HorizontalAlignment = 2;
pdftbl.AddCell(cell);
}
}
the onEndPAge event above tries to write two columns. the above code gets called but the rows are not appearing in the pdf page.
I'm calling the above class like below
PaymentPageEventHandler ppem = new PaymentPageEventHandler();
ppem.GetSubtotal = "123"; // test value to print as running total
writer.PageEvent = ppem; // assigning event to writer object
Should i call explicity the PdfContentByte variable cb to write. if so how should i write the cells to the pdf.
can any one help me out on this.

You need to add the table using the WriteSelectedRows method. Please read the documentation and take a look at this example (or the C# ports of the examples)
Look for the line:
table.WriteSelectedRows(0, -1, 34, 803, writer.DirectContent);
Obviously, you'll need to adapt the coordinates.

Related

In iText 7 java how do you update Link text after it's already been added to the document

I am using iText7 to build a table of contents for my document. I know all the section names before I start, but don't know what the page numbers will be. My current process is to create a table on the first page and create all the Link objects with generic text "GO!". Then as I add sections I add through the link objects and update the text with the page numbers that I figured out as I created the document.
However, at the end, what gets written out for the link is "GO!", not the updated page number values I set as I was creating the rest of the document.
I did set the immediateFlush flag to false when I created the Document.
public class UpdateLinkTest {
PdfDocument pdfDocument = null;
List<Link>links = null;
Color hyperlinkColor = new DeviceRgb(0, 102, 204);
public static void main(String[] args) throws Exception {
List<String[]>notes = new ArrayList<>();
notes.add(new String[] {"me", "title", "this is my text" });
notes.add(new String[] {"me2", "title2", "this is my text 2" });
new UpdateLinkTest().exportPdf(notes, new File("./test2.pdf"));
}
public void exportPdf(List<String[]> notes, File selectedFile) throws Exception {
PdfWriter pdfWriter = new PdfWriter(selectedFile);
pdfDocument = new PdfDocument(pdfWriter);
Document document = new Document(pdfDocument, PageSize.A4, false);
// add the table of contents table
addSummaryTable(notes, document);
// add a page break
document.add(new AreaBreak(AreaBreakType.NEXT_PAGE));
// add the body of the document
addNotesText(notes, document);
document.close();
}
private void addSummaryTable(List<String[]> notes, Document document) {
links = new ArrayList<>();
Table table = new Table(3);
float pageWidth = PageSize.A4.getWidth();
table.setWidth(pageWidth-document.getLeftMargin()*2);
// add header
addCell("Author", table, true);
addCell("Title", table, true);
addCell("Page", table, true);
int count = 0;
for (String[] note : notes) {
addCell(note[0], table, false);
addCell(note[1], table, false);
Link link = new Link("Go!", PdfAction.createGoTo(""+ (count+1)));
links.add(link);
addCell(link, hyperlinkColor, table, false);
count++;
}
document.add(table);
}
private void addNotesText(List<String[]> notes, Document document)
throws Exception {
int count = 0;
for (String[] note : notes) {
int numberOfPages = pdfDocument.getNumberOfPages();
Link link = links.get(count);
link.setText(""+(numberOfPages+1));
Paragraph noteText = new Paragraph(note[2]);
document.add(noteText);
noteText.setDestination(++count+"");
if (note != notes.get(notes.size()-1))
document.add(new AreaBreak(AreaBreakType.NEXT_PAGE));
}
}
private static void addCell(String text, Table table, boolean b) {
Cell c1 = new Cell().add(new Paragraph(text));
table.addCell(c1);
}
private static void addCell(Link text, Color backgroundColor, Table table, boolean b) {
Cell c1 = new Cell().add(new Paragraph(text));
text.setUnderline();
text.setFontColor(backgroundColor);
table.addCell(c1);
}
}
Quite more work needs to be done compared to the code you have now because the changes to the elements don't take any effect once you've added them to the document. Immediate flush set to false allows you to relayout the elements, but that does not happen automatically. The way you calculate the current page the paragraph will be placed on (int numberOfPages = pdfDocument.getNumberOfPages();) is not bulletproof because in some cases pages might be added in advance, even if the content is not going to be placed on them immediately.
There is a very low level way to achieve your goal but with the recent version of iText (7.1.15) there is a simpler way as well, which still requires some work though. Basically your use case is very similar to target-counter concept in CSS, with page counter being the target one in your case. To support target counters in pdfHTML add-on we added new capabilities to our layout module which are possible to use directly as well.
To start off, we are going to tie our Link elements to the corresponding Paragraph elements that they will point to. We are going to do it with ID property in layout:
link.setProperty(Property.ID, String.valueOf(count));
noteText.setProperty(Property.ID, String.valueOf(count));
Next up, we are going to create custom renderers for our Link elements and Paragraph elements. Those custom renderers will interact with TargetCounterHandler which is the new capability in layout module I mentioned in the introduction. The idea is that during layout operation the paragraph will remember the page on which it was placed and then the corresponding link element (remember, link elements are connected to paragraph elements) will ask TargetCounterHandler during layout process of that link element which page the corresponding paragraph was planed on. So in a way, TargetCounterHandler is a connector.
Code for custom renderers:
private static class CustomParagraphRenderer extends ParagraphRenderer {
public CustomParagraphRenderer(Paragraph modelElement) {
super(modelElement);
}
#Override
public IRenderer getNextRenderer() {
return new CustomParagraphRenderer((Paragraph) modelElement);
}
#Override
public LayoutResult layout(LayoutContext layoutContext) {
LayoutResult result = super.layout(layoutContext);
TargetCounterHandler.addPageByID(this);
return result;
}
}
private static class CustomLinkRenderer extends LinkRenderer {
public CustomLinkRenderer(Link link) {
super(link);
}
#Override
public LayoutResult layout(LayoutContext layoutContext) {
Integer targetPageNumber = TargetCounterHandler.getPageByID(this, getProperty(Property.ID));
if (targetPageNumber != null) {
setText(String.valueOf(targetPageNumber));
}
return super.layout(layoutContext);
}
#Override
public IRenderer getNextRenderer() {
return new CustomLinkRenderer((Link) getModelElement());
}
}
Don't forget to assign the custom renderers to their elements:
link.setNextRenderer(new CustomLinkRenderer(link));
noteText.setNextRenderer(new CustomParagraphRenderer(noteText));
Now, the other thing we need to do it relayout. You already set immediateFlush to false and this is needed for relayout to work. Relayout is needed because on the first layout loop we will not know all the positions of the paragraphs, but we will already have placed the links on the pages by the time we know those positions. So we need the second pass to use the information about page numbers the paragraphs will reside on and set that information to the links.
Relayout is pretty straightforward - once you've put all the content you just need to call a single dedicated method:
// For now we have to prepare the handler for relayout manually, this is going to be improved
// in future iText versions
((DocumentRenderer)document.getRenderer()).getTargetCounterHandler().prepareHandlerToRelayout();
document.relayout();
One caveat is that for now you also need to subclass the DocumentRenderer since there is an additional operation that needs to be done that is not performed under the hood - propagation of the target counter handler to the root renderer we will be using for the second layout operation:
// For now we have to create a custom renderer for the root document to propagate the
// target counter handler to the renderer that will be used on the second layout process
// This is going to be improved in future iText versions
private static class CustomDocumentRenderer extends DocumentRenderer {
public CustomDocumentRenderer(Document document, boolean immediateFlush) {
super(document, immediateFlush);
}
#Override
public IRenderer getNextRenderer() {
CustomDocumentRenderer renderer = new CustomDocumentRenderer(document, immediateFlush);
renderer.targetCounterHandler = new TargetCounterHandler(targetCounterHandler);
return renderer;
}
}
document.setRenderer(new CustomDocumentRenderer(document, false));
And now we are done. Here is our visual result:
Complete code looks as follows:
public class UpdateLinkTest {
PdfDocument pdfDocument = null;
Color hyperlinkColor = new DeviceRgb(0, 102, 204);
public static void main(String[] args) throws Exception {
List<String[]> notes = new ArrayList<>();
notes.add(new String[] {"me", "title", "this is my text" });
notes.add(new String[] {"me2", "title2", "this is my text 2" });
new UpdateLinkTest().exportPdf(notes, new File("./test2.pdf"));
}
public void exportPdf(List<String[]> notes, File selectedFile) throws Exception {
PdfWriter pdfWriter = new PdfWriter(selectedFile);
pdfDocument = new PdfDocument(pdfWriter);
Document document = new Document(pdfDocument, PageSize.A4, false);
document.setRenderer(new CustomDocumentRenderer(document, false));
// add the table of contents table
addSummaryTable(notes, document);
// add a page break
document.add(new AreaBreak(AreaBreakType.NEXT_PAGE));
// add the body of the document
addNotesText(notes, document);
// For now we have to prepare the handler for relayout manually, this is going to be improved
// in future iText versions
((DocumentRenderer)document.getRenderer()).getTargetCounterHandler().prepareHandlerToRelayout();
document.relayout();
document.close();
}
private void addSummaryTable(List<String[]> notes, Document document) {
Table table = new Table(3);
float pageWidth = PageSize.A4.getWidth();
table.setWidth(pageWidth-document.getLeftMargin()*2);
// add header
addCell("Author", table, true);
addCell("Title", table, true);
addCell("Page", table, true);
int count = 0;
for (String[] note : notes) {
addCell(note[0], table, false);
addCell(note[1], table, false);
Link link = new Link("Go!", PdfAction.createGoTo(""+ (count+1)));
link.setProperty(Property.ID, String.valueOf(count));
link.setNextRenderer(new CustomLinkRenderer(link));
addCell(link, hyperlinkColor, table, false);
count++;
}
document.add(table);
}
private void addNotesText(List<String[]> notes, Document document) {
int count = 0;
for (String[] note : notes) {
Paragraph noteText = new Paragraph(note[2]);
noteText.setProperty(Property.ID, String.valueOf(count));
noteText.setNextRenderer(new CustomParagraphRenderer(noteText));
document.add(noteText);
noteText.setDestination(++count+"");
if (note != notes.get(notes.size()-1))
document.add(new AreaBreak(AreaBreakType.NEXT_PAGE));
}
}
private static void addCell(String text, Table table, boolean b) {
Cell c1 = new Cell().add(new Paragraph(text));
table.addCell(c1);
}
private static void addCell(Link text, Color backgroundColor, Table table, boolean b) {
Cell c1 = new Cell().add(new Paragraph(text));
text.setUnderline();
text.setFontColor(backgroundColor);
table.addCell(c1);
}
private static class CustomLinkRenderer extends LinkRenderer {
public CustomLinkRenderer(Link link) {
super(link);
}
#Override
public LayoutResult layout(LayoutContext layoutContext) {
Integer targetPageNumber = TargetCounterHandler.getPageByID(this, getProperty(Property.ID));
if (targetPageNumber != null) {
setText(String.valueOf(targetPageNumber));
}
return super.layout(layoutContext);
}
#Override
public IRenderer getNextRenderer() {
return new CustomLinkRenderer((Link) getModelElement());
}
}
private static class CustomParagraphRenderer extends ParagraphRenderer {
public CustomParagraphRenderer(Paragraph modelElement) {
super(modelElement);
}
#Override
public IRenderer getNextRenderer() {
return new CustomParagraphRenderer((Paragraph) modelElement);
}
#Override
public LayoutResult layout(LayoutContext layoutContext) {
LayoutResult result = super.layout(layoutContext);
TargetCounterHandler.addPageByID(this);
return result;
}
}
// For now we have to create a custom renderer for the root document to propagate the
// target counter handler to the renderer that will be used on the second layout process
// This is going to be improved in future iText versions
private static class CustomDocumentRenderer extends DocumentRenderer {
public CustomDocumentRenderer(Document document, boolean immediateFlush) {
super(document, immediateFlush);
}
#Override
public IRenderer getNextRenderer() {
CustomDocumentRenderer renderer = new CustomDocumentRenderer(document, immediateFlush);
renderer.targetCounterHandler = new TargetCounterHandler(targetCounterHandler);
return renderer;
}
}
}

It is possible to issue java.lang.reflect.Field to javafx.scene.control.TextField?

It is possible to issue java.lang.reflect.Field to javafx.scene.control.TextField?
For example:
Field[] nodes;
nodes = clase.getDeclaredFields();
for (Field n : nodes)
if (n.getType().getSimpleName().equals("TextField"))
((TextField)((Object) n)).setText("Text");
If you want to modify the TextFields, you need to retrieve the value from those fields (and cast this value to TextField).
The following example should demonstrate the approach:
private TextField t1 = new TextField();
private TextField t2 = new TextField();
#Override
public void start(Stage primaryStage) {
Button btn = new Button("Say 'Hello World'");
btn.setOnAction((ActionEvent event) -> {
Object object = this;
Class clazz = object.getClass();
for (Field field : clazz.getDeclaredFields()) {
if (field.getType().getName().equals("javafx.scene.control.TextField")) {
try {
// get field value here
TextField textField = (TextField) field.get(object);
if (textField != null) {
textField.setText("Hello World");
}
} catch (IllegalArgumentException | IllegalAccessException ex) {
Logger.getLogger(ReflectTest.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
});
VBox root = new VBox();
root.getChildren().addAll(btn, t1, t2);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
Reflection is probably a really bad approach to this. Among many problems is that you make the functionality dependent on how the code is written. Specifically, you assume that each text field is stored in a specific instance field in some class. If you change the implementation, e.g. so that you keep the text fields in a data structure instead of maintaining references to them yourself, then your functionality will break. It is bad practice to write code that is so tightly coupled to the actual implementation of the code, for obvious reasons.
One better approach would simply to be to put all the text fields in a list (or other data structure), so you can do whatever you need with them easily. E.g.
public class MyForm {
private GridPane view ;
private String[] messages = {"First name:", "Last name", "Email"} ;
private List<TextField> textFields ;
public MyForm {
view = new GridPane();
textFields = new ArrayList<>();
for (int r = 0; r < messages.length ; r++) {
view.addRow(r, new Label(messages[r]), createTextField(messages[r]));
}
}
private TextField createTextField(String text) {
TextField textField = new TextField();
textField.setPromptText(text);
textFields.add(textField);
return textField ;
}
public void processTextFields() {
textField.forEach(tf -> tf.setText("Hello"));
}
}
Another approach would be to use a CSS lookup. If myForm is some node that is an ancestor of all the text fields:
myForm.lookupAll(".text-field").forEach(node -> {
TextField textField = (TextField)node ;
textField.setText("Hello");
});
but note that CSS lookups will not work until after CSS has been applied (by default, this means after the scene has been rendered for the first time).
Another way, if all the text fields are all contained in a single direct parent (such as the grid pane in the first example), would be to iterate through the child nodes and filter the text fields:
textFieldParentNode.getChildrenUnmodifiable().stream()
.filter(TextField.class::isInstance)
.map(TextField.class::cast)
.forEach(tf -> tf.setText("Hello"));

Add table row counts to every page

How can I get the total number of rows written to a PdfPTable for every page using iTextSharp?
I want to put all of my code onStartPage
public class MyPdfPageEventHelpPageNo : iTextSharp.text.pdf.PdfPageEventHelper
{
public override void OnStartPage(PdfWriter writer, Document document)
{
/// my code
}
}
As Bruno said you'll want to look into IPdfPTableEvent. The downside of that specific interface is that it gets called on every page with only a copy of the table that's being printed to that specific page. So if you have a 200 row table, on page one you might only see a table with 50 rows and you never know the total row count. However, there's a more interesting interface that derives from it called IPdfPTableEventSplit which receives a copy of the original table, too, which is what you're looking for.
Below is a basic implementation of that interface. You might need to apply some additional logic to account for headers but it should be relatively easy. I'm also writing to the bottom of the page but you'll probably want to adjust that.
One very important note is that if a table doesn't split then the SplitTable is never called. I account for this in the TableLayout method by checking to see if our default row count of -1 has been changed to something else.
public class TableRowCounter : IPdfPTableEventSplit {
/// <summary>
/// Will hold the total number of rows in the table if a split occurs,
/// or negative one if no split happened.
/// </summary>
private int totalRowCount = -1;
/// <summary>
/// Holds the number of rows previously processed
/// </summary>
private int currentRowIndex = 0;
/// <summary>
/// Called if/when a table split happens
/// </summary>
/// <param name="table"></param>
public void SplitTable(PdfPTable table) {
this.totalRowCount = table.Rows.Count;
}
public void TableLayout(PdfPTable table, float[][] widths, float[] heights, int headerRows, int rowStart, PdfContentByte[] canvases) {
//Count the number of rows processed in this iteration
var thisRowCount = table.Rows.Count;
//Get one of the canvases to draw on. You could also use one of these
//PdfPTable.BACKGROUNDCANVAS or PdfPTable.LINECANVAS or PdfPTable.TEXTCANVAS
var writer = canvases[PdfPTable.BASECANVAS].PdfWriter;
//Create our text
var txt = String.Format(
"Showing rows {0} through {1} of {2} total rows",
(currentRowIndex + 1), //Zero-based index convert to one-based
(currentRowIndex + thisRowCount),
( -1 == totalRowCount ? thisRowCount : totalRowCount) //If a table split doesn't occur then our class variable won't be reset, just use the local count
);
//Draw our text
ColumnText.ShowTextAligned(writer.DirectContent, Element.ALIGN_LEFT, new Phrase(txt), 10, 10, 0);
//Increment our total row count
currentRowIndex += table.Rows.Count;
}
}
A very simple implementation of this code would be:
var testFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "test.pdf");
using (var fs = new FileStream(testFile, FileMode.Create, FileAccess.Write, FileShare.None)) {
using (var doc = new Document()) {
using (var writer = PdfWriter.GetInstance(doc, fs)) {
doc.Open();
var t = new PdfPTable(1);
//Bind an instance of our table counter to the table event
t.TableEvent = new TableRowCounter();
for (var i = 1; i < 500; i++) {
t.AddCell(i.ToString());
}
doc.Add(t);
doc.Close();
}
}
}

Hyperlink at footer using itextSharp

I need to put a hyperlink at the footer of my PDF generated using iTextSharp.
I know how to use PdfPageEventHelper to print some text in the footer but not putting a hyperlink.
public class PdfHandlerEvents: PdfPageEventHelper
{
private PdfContentByte _cb;
private BaseFont _bf;
public override void OnOpenDocument(PdfWriter writer, Document document)
{
_cb = writer.DirectContent;
}
public override void OnEndPage(PdfWriter writer, Document document)
{
base.OnEndPage(writer, document);
_bf = BaseFont.CreateFont(BaseFont.HELVETICA, BaseFont.CP1252, BaseFont.NOT_EMBEDDED);
Rectangle pageSize = document.PageSize;
_cb.SetRGBColorFill(100, 100, 100);
_cb.BeginText();
_cb.SetFontAndSize(_bf, 10);
_cb.ShowTextAligned(PdfContentByte.ALIGN_CENTER, "More information", pageSize.GetRight(200), pageSize.GetBottom(30), 0);
_cb.EndText();
}
}
How do I make the text "More information" a hyperlink?
Edited:
After the answer from Chris below, I have also figure out how to print image at the footer, here is the code:
Image pic = Image.GetInstance(#"C:\someimage.jpg");
pic.SetAbsolutePosition(0, 0);
pic.ScalePercent(25);
PdfTemplate tpl = _cb.CreateTemplate(pic.Width, pic.Height);
tpl.AddImage(pic);
_cb.AddTemplate(tpl, 0, 0);
The Document object generally lets you work with abstract things like Paragraph and Chunk but in doing so you lose absolute positioning. The PdfWriter and PdfContentByte objects give you absolute positioning but you need to work with lower level objects like raw text.
Luckily there is a happy middle-ground object called ColumnText that should do what you're looking for. You can think of the ColumnText as basically a table and most people use it as a single column table so you can actually just think of it as a rectangle that you add objects to. See the comments in the code below for any questions.
public class PdfHandlerEvents : PdfPageEventHelper {
private PdfContentByte _cb;
private BaseFont _bf;
public override void OnOpenDocument(PdfWriter writer, Document document) {
_cb = writer.DirectContent;
}
public override void OnEndPage(PdfWriter writer, Document document) {
base.OnEndPage(writer, document);
_bf = BaseFont.CreateFont(BaseFont.HELVETICA, BaseFont.CP1252, BaseFont.NOT_EMBEDDED);
iTextSharp.text.Rectangle pageSize = document.PageSize;
//Create our ColumnText bound to the canvas
var ct = new ColumnText(_cb);
//Set the dimensions of our "box"
ct.SetSimpleColumn(pageSize.GetRight(200), pageSize.GetBottom(30), pageSize.Right, pageSize.Bottom);
//Create a new chunk with our text and font
var c = new Chunk("More Information", new iTextSharp.text.Font(_bf, 10));
//Set the chunk's action to a remote URL
c.SetAction(new PdfAction("http://www.aol.com"));
//Add the chunk to the ColumnText
ct.AddElement(c);
//Tell the ColumnText to draw itself
ct.Go();
}
}

How to clean an AnnotatedTimeline gwt visualization chart

I am dealing with a problem with annotatedtimelines.
I have to draw some charts depending on the tab that the user is, so when the tab is changed I clean the current chart and draw the new data. But how to do that?
For now I am removing all the rows, but it is not working.
Can someone help me?
here is the code:
...
//Creating Columns
dataTable.addColumn(ColumnType.DATETIME, "Time");
dataTable.addColumn(ColumnType.NUMBER, "Realtime Consumption");
dataTable.addColumn(ColumnType.NUMBER, "Historical Consumption");
//Create options
options.setDisplayAnnotations(false);
options.setDisplayZoomButtons(false);
options.setScaleType(AnnotatedTimeLine.ScaleType.FIXED);
options.setLegendPosition(AnnotatedTimeLine.AnnotatedLegendPosition.SAME_ROW);
options.setAllowRedraw(true);
options.setDisplayRangeSelector(false);
options.setFill(30);
//to parse the time
DateTimeFormat dtf = DateTimeFormat.getFormat("hh:mm:ss");
//For each item of the list
for(int i = 0; i < list.size(); i++){
//get date
Date date = new Date(list.getTimeAt(i));
//get hh:mm:ss
String time = date.getHours()+":"+date.getMinutes()+":"+date.getSeconds();
//add row
dataTable.addRow();
dataTable.setValue(dataTable.getNumberOfRows() - 1, 0, dtf.parse(time));
dataTable.setValue(dataTable.getNumberOfRows() - 1, 2, list.getDataAt(i));
}
/**
* To clean the chart
*/
public void cleanChart(){
//Remove all rows
this.dataTable.removeRows(0, dataTable.getNumberOfRows());
//Redraw the chart
this.draw(this.dataTable, this.options);
}
Thanks,
MaurĂ­cio
It is very strange, but I did it (that seems ok, because I'd create another datatable), and it still not working. The old data continues there.
I have a class that extends AnnotatedTimeLine, and the clean method is now:
/**
* Method to clean the annotated time line
*/
public void clean() {
//Create new table
this.dataTable = DataTable.create();
//Create columns
this.dataTable.addColumn(ColumnType.DATETIME, "Time");
this.dataTable.addColumn(ColumnType.NUMBER, "Data 1");
this.dataTable.addColumn(ColumnType.NUMBER, "Data 2");
//Redraw the chart with the same options
this.draw(this.dataTable, this.options);
}
Any idea?
Thanks!
You need to recreate DataTable for your new data and don't need to do
this.dataTable.removeRows(0, dataTable.getNumberOfRows());
I have something like this
public void reloadChart(List<SharedBean> list, String titleX) {
viz.draw(getChartData(), getOptions(titleX));
}
private DataTable getChartData() {
DataTable data = DataTable.create();
data.addColumn(ColumnType.STRING, "column");
data.addColumn(ColumnType.NUMBER, "value");
data.addRows(list.size());
int row = 0;
int i = 0;
for(SharedBean bean: list){
......
}
return data;
}