How to align the header and the column properly in prawn pdf? - pdf

We are generating pdf for invoice with prawn in rails 3.1.4.
header =[[ "Date", "#", "sample name", "model","lease item", "hours", "$/hour", "Total"]]
items = #invoice.invoice_items.map do |item|
[
item.lease_usage_record.lease_usage_items.map do |li|
[
li.lease_date.strftime("%Y-%m-%d"),
item.lease_usage_record_id,
li.sample_name,
li.sample_model_num,
li.lease_item.short_name,
li.total_hour,
li.charge_rate,
li.subtotal
]
end
]
end
items = header + items
pdf.table items, :header => true, :width => 480
t = pdf.make_table([ [" ", "Total: "," #{#invoice.total}"] ])
t.draw
It works with a problem. The problem is that all the data cells are squeezed into the first column which is "Date". Tried to add one one more [] to header and it generated error of "data is 2-dimensional array...". How to align each column properly under its header? Thanks so much.

The problem was solved by reducing the dimension of the data (items) to 2 dimension. The prawn can only take 2 dimensional data. The following line does the trick:
items = items.flatten(2)

Related

Google Docs API for creating invoice containing table of variable number of rows

I have a template file for my invoice with a table with sample row, but I want to add more rows dynamically based on a given array size, and write the cell values from the array...
Template's photo
I've been struggling for almost 3 days now.
Is there any easy way to accomplish that?
Here's the template file: Link to the Docs file(template)
And here's a few sample arrays of input data to be replaced in the Template file:
[
[
"Sample item 1s",
"Sample Quantity 1",
"Sample price 1",
"Sample total 1"
],
[
"Sample item 2",
"Sample Quantity 2",
"Sample price 2",
"Sample total 2"
],
[
"Sample item 3",
"Sample Quantity 3",
"Sample price 3",
"Sample total 3"
],
]
Now, the length of the parent array can vary depending on the number of items in the invoice, and that's the only problem that I'm struggling with.
And... Yeah, this is a duplicate question, I've found another question on the same topic, but looking at the answers and comments, everyone is commenting that they don't understand the question whereas it looks perfectly clear for me.
Google Docs Invoice template with dynamically items row from Google Sheets
I think the person who asked the question have already quit from it. :(
By the way I am using the API for PHP (Google API Client Library for PHP), and code for replacing dummy text a Google Docs Document by the actual data is given below:
public function replaceTexts(array $replacements, string $document_id) {
# code...
$req = new Docs\BatchUpdateDocumentRequest();
// var_dump($replacements);
// die();
foreach ($replacements as $replacement) {
$target = new Docs\SubstringMatchCriteria();
$target->text = "{{" . $replacement["targetText"] . "}}";
$target->setMatchCase(false);
$req->setRequests([
...$req->getRequests(),
new Docs\Request([
"replaceAllText" => [
"replaceText" => $replacement["newText"],
"containsText" => $target
]
]),
]);
}
return $this->docs_service->documents->batchUpdate(
$document_id,
$req
);
}
A possible solution would be the following
First prep the document by removing every row from the table apart from the title.
Get the full document tree from the Google Docs API.
This would be a simple call with the document id
$doc = $service->documents->get($documentId);
Traverse the document object returned to get to the table and then find the location of the right cell. This could be done by looping through the elements in the body object until one with the right table field is found. Note that this may not necessarily be the first one since in your template, the section with the {{CustomerName}} placeholder is also a table. So you may have to find a table that has the first cell with a text value of "Item".
Add a new row to the table. This is done by creating a request with the shape:
[
'insertTableRow' => [
'tableCellLocation' => [
'rowIndex' => 1,
'columnIndex' => 1,
'tableStartLocation' => [
'index' => 177
]
]
]
]
The tableStartLocation->index element is the paragraph index of the cell to be entered, i.e. body->content[i]->table->startIndex. Send the request.
Repeat steps 2 and 3 to get the updated $doc object, and then access the newly created cell i.e. body->content[i]->table->tableRows[j]->tableCells[k]->content->paragraph->elements[l]->startIndex.
Send a request to update the text content of the cell at the location of the startIndex from 5 above, i.e.
[
'insertText' => [
'location' => [
'index' => 206,
]
],
'text' => 'item_1'
]
]
Repeat step 5 but access the next cell. Note that after each update you need to fetch an updated version of the document object because the indexes change after inserts.
To be honest, this approach is pretty cumbersome, and it's probably more efficient to insert all the data into a spreadsheet and then embed the spreadsheet into your word document. Information on that can be found here How to insert an embedded sheet via Google Docs API?.
As a final note, I created a copy of your template and used the "Try this method" feature in the API documentation to validate my approach so some of the PHP syntax may be a bit off, but I hope you get the general idea.

How to read data in a named range?

I've been through the documentation of the Google Sheets API v4 and couldn't find a way to read data from a named range.
I'm using Python, specifically, and looking for something along the lines of:
named_range = 'My Beautiful Range'
service = build('sheets', 'v4', credentials=creds)
sheet = service.spreadsheets()
result = sheet.values().get(spreadsheetId=my_id,
namedRange=named_range).execute()
values = result.get('values', [])
Is there such an API?
Yes, both spreadsheets.values.get and spreadsheets.get accepts named ranges as their range parameter.
Just keep in mind that your named ranges should be valid and existing in the spreadsheet that you are trying to access.
NOTE:
Valid Range Names:
Can contain only letters, numbers, and underscores.
Can't start with a number, or the words "true" or "false."
Can't contain any spaces or punctuation.
Must be 1–250 characters.
Can't be in either A1 or R1C1 syntax. For example, you might get an error if you give your range a name like "A1:B2" or "R1C1:R2C2."
Sample Code:
named_range = 'MyBeautifulRange'
result = sheet.values().get(spreadsheetId=my_id,
range=named_range).execute()
values = result.get('values', [])
Sample Sheet:
Sample spreadsheets.values.get request using API explorer:
Sample Response Body:
{
"range": "Sheet1!A1:B3",
"majorDimension": "ROWS",
"values": [
[
"A1",
"B1"
],
[
"A2",
"B2"
],
[
"A3",
"B3"
]
]
}

elm-ui center elements in wrapped row

I'm using Elm with mdgriffiths/elm-ui, and I've really been enjoying it. Right now, I'm trying to create a centered, wrapped row of elements:
I can get it to this point:
using this code:
button : String -> String -> Element Msg
button label url =
link
[ height (px 150)
, width (px 150)
, Border.width 1
, Background.color (rgb255 255 255 255)
]
{ url = url
, label =
Element.paragraph
[ Font.center ]
[ textEl [] label ]
}
row : Element Msg
row =
Element.wrappedRow
[ Element.spacing 25
, Element.centerX
, Element.centerY
, width (fill |> Element.maximum 600)
, Font.center
]
[ button "A" "/a"
, button "B" "/b"
, button "C" "/c"
, button "D" "/d"
]
But when I try adding Element.centerX to my buttons like this
link
[ Element.centerX
, ...
]
I get this instead:
I've also tried Font.center without success, and I don't know what else I can try.
I'm not sure if I'm missing something I should be using, or if the whole thing needs re-arranging, or if I just need to use the built-in CSS stuff.
Update:
Link to an Ellie with the issues I'm seeing.
https://ellie-app.com/7NpM6SPfhLHa1
This Github issue is useful for this problem. I'm afraid you will have to use some CSS (unless I'm missing something). I've found this before with elm-ui; every now and then it can't quite do what you want and you need a bit of CSS.
This works for me (taken from the post by AlienKevin in the link above). You need to set "marginLeft" and "marginRight" to "auto".
module Main exposing (main)
import Element as E
import Element.Border
import Html.Attributes
box : String -> E.Element msg
box label =
E.paragraph
[ E.width <| E.px 200
, Element.Border.width 1
, E.htmlAttribute (Html.Attributes.style "marginLeft" "auto")
, E.htmlAttribute (Html.Attributes.style "marginRight" "auto")
]
[ E.text label ]
row : E.Element msg
row =
E.wrappedRow []
[ box "A"
, box "B"
, box "C"
]
main =
E.layout [] row
(See here for an Ellie.)
You can also do the following:
Define the following elm-ui classes. I usually setup a UI.elm module for this
centerWrap : Attribute msg
centerWrap =
Html.Attributes.class "centerWrap"
|> htmlAttribute
dontCenterWrap : Attribute msg
dontCenterWrap =
Html.Attributes.class "dontCenterWrap"
|> htmlAttribute
Add the following to your css. Basically says center elements if has centerWrap class but not dontCenterWrap class.
:not(.dontCenterWrap).centerWrap>div.wrp {
justify-content: center !important;
}
Apply the attribute
wrappedRow [ width fill, UI.centerWrap, spaceEvenly ] [...]
Assuming you created a custom element that centerWraps and wanted to disable that you could use UI.dontCenterWrap
centerWrappedRow attr children =
wrappedRow (UI.centerWrap :: attr) children
-- somewhere else
...
centerWrappedRow [UI.dontCenterWrap] [..]
...

rebol/red: Is there an elegant way to know if it is last item in foreach?

My code is clunky:
length: length? items
count: 0
foreach item items [
count: count + 1
if count = length [
print "last item"
]
]
Is there something better ?
Typically people use FORALL (which would be much better named FOR-NEXT) which moves the series position as opposed to giving an item, then you can test it with TAIL?. The downside is you have to pick the item out of the series at the current position:
forall items [
probe items/1 ;-- how to access current item
if tail? next items [ ;-- could use LAST? ITEMS in Rebol 3
print "last item"
]
]
This is approximately equivalent to:
if not tail? items [
original: items
until [
probe items/1
if tail? next items [
print "last item"
]
items: next items
tail? items
]
items: original
]
Be forewarned: FORALL mutates its input series and tries to put it back to the initial position at the end. But it has poorly-defined behavior in the case of errors being raised, so you could leave your input in mid-iteration if there is a problem.

Create a combined bar/line chart with dimplejs and use self-defined colors

I'm trying to create a combined bar/line chart based on a simple data set (columns: countries, index1, index2, index3, ...) using dimplejs. Index1 will be the bar chart and index2 upwards should be dynamically (=adding and removing indices per user interaction) displayed as line charts on top.
I found out that I seemingly needed to create a hidden 2nd x-axis for the line graphics. Is there any other way?
I was also unable to add more than one index as line chart. Is there a way to add more lines where each one is based on a column (index2, index3, ...)?
Defining colors was another problem: The bars are all in one color which is good but if I want to assign a color myself I have to give the data a tag which is displayed in the tooltip (not good)?
Assigning colors to the line charts didn't work at all for me. I would need some advice here.
My code so far:
var svg = dimple.newSvg("#graphic", 550, 700);
// Get data for one year
var filteredData = dimple.filterData(data, 'year', '2010');
var myChart = new dimple.chart(svg, filteredData);
myChart.setBounds(50, 30, 480, 630);
var xAxis = myChart.addMeasureAxis('x', 'index1');
xAxis.title = 'Index Value';
xAxis.overrideMax = 1;
var yAxis = myChart.addCategoryAxis('y', 'countries');
yAxis.title = 'Country';
var xAxis2 = myChart.addMeasureAxis('x', 'index2');
xAxis2.title = null;
xAxis2.overrideMax = 1;
xAxis2.hidden = true;
var bars = myChart.addSeries('bars', dimple.plot.bar, [xAxis,yAxis]);
myChart.assignColor('bars', '#D3D3D3', '#D3D3D3');
var lines = myChart.addSeries('index2', dimple.plot.line, [xAxis2,yAxis]);
yAxis.addOrderRule('index1', false);
myChart.draw();
That data structure is not ideal for your data requirement within dimple. The way dimple would like that data is with your index names as dimension values:
var data = [
{ "Index" : "Index 1", "Country" : "UK", "Index Value": 0.2 },
{ "Index" : "Index 1", "Country" : "Spain", "Index Value": 0.7 },
{ "Index" : "Index 2", "Country" : "UK", "Index Value": 0.5 },
{ "Index" : "Index 2", "Country" : "Spain", "Index Value": 0.6 },
{ "Index" : "Index 3", "Country" : "UK", "Index Value": 0.4 },
{ "Index" : "Index 3", "Country" : "Spain", "Index Value": 0.3 },
...
];
Then in order to draw index 1 as a bar and the rest as lines you would need to split your data set into 2:
var barData = [],
lineData = [],
i,
keyIndex = "Index 1";
for (i = 0; i < data.length; i += 1) {
if (data[i]["Index"] === keyIndex) {
barData.push(data[i]);
} else {
lineData.push(data[i]);
}
}
You could then just define your chart as follows:
var chart = new dimple.chart(svg),
bars,
lines;
chart.addMeasureAxis("x", "Index Value");
chart.addCategoryAxis("y", "Country");
bars = chart.addSeries("Index", dimple.plot.bar);
bars.data = barData;
lines = chart.addSeries("Index", dimple.plot.line);
lines.data = lineData;
chart.draw();
I haven't tested this code but it ought to do what you want (minus the formatting etc).
If you want to continue on the road you have started in your code above (which is still possible) you will find composite axes very helpful to avoid hidden secondary axes and potential problems with differing max/min values. See the relevant section in the version 2 release notes for an example of these.
I don't understand why you don't want to tag the indices with something which appears in the tooltip, there must be some difference between them which you can communicate, but if you want to remove the index name from the tooltip you can define custom tooltips as shown here.
Edit: I should add if you just want to change the set of colours which dimple will arbitrarily ascribe to your data points you can override the default colours in the chart object as discussed here.