I have figured out a way to add a new worksheet to a an existing spreadsheet BUT I can't seem to figure out how to format the added worksheet.
For example, I can color the header row of the first worksheet (that is the default worksheet) in a spreadsheet using the method below:
def color_header_row(file)
spreadsheet_id = file.id
requests = {
requests: [
{
repeat_cell: {
range: {
sheet_id: 0,
start_row_index: 0,
end_row_index: 1
},
cell: {
user_entered_format: {
background_color: { red: 0.0, green: 0.4, blue: 0.0 },
horizontal_alignment: "CENTER",
text_format: {
foreground_color: {
red: 1.0,
green: 1.0,
blue: 1.0
},
font_size: 12,
bold: true
}
}
},
fields: 'userEnteredFormat(backgroundColor,textFormat,horizontalAlignment)'
},
}
]
}
sheet_service.batch_update_spreadsheet(spreadsheet_id, requests, {})
end
Notice that the default worksheet sheet_id is 0 thus my assumption (given how GridRange is defined in the documentation) was that setting sheet_id to 1 will result in a reference to the newly added worksheet (in essence worksheet at position 1). Yet when sheet_id is set to 1, the error Invalid request(Google::Apis::ClientError) is returned.
Any idea on how to format the header row of a non-default (that is a worksheet that isn't the in the first position in a spreadsheet) worksheet?
I believe your goal and your current situation as follows.
You want to use repeat_cell for the worksheet in Google Spreadsheet except for the sheet ID 0.
You want to achieve this using googleapis for ruby.
You have already been able to get and put values for Spreadsheet using Sheets API.
In this case, how about retrieving the sheet ID using the sheet name with the method of spreadsheets.get? In the Spreadsheet, the same sheet name cannot be used. So, in this case, I thought that this direction might be useful for your situation. When this is reflected to your script, it becomes as follows.
Modified script:
In this script, the sheet ID is retrieved using the sheet name of "Sheet2" and the retrieved sheet ID is used for repeat_cell request.
sheet_name = 'Sheet2' # Please set the sheet name.
spreadsheet_id = file.id
response = sheet_service.get_spreadsheet(spreadsheet_id, ranges: [sheet_name], fields: 'sheets(properties)')
sheet_id = response.sheets[0].properties.sheet_id
requests = {
requests: [
{
repeat_cell: {
range: {
sheet_id: sheet_id,
start_row_index: 0,
end_row_index: 1
},
cell: {
user_entered_format: {
background_color: { red: 0.0, green: 0.4, blue: 0.0 },
horizontal_alignment: "CENTER",
text_format: {
foreground_color: {
red: 1.0,
green: 1.0,
blue: 1.0
},
font_size: 12,
bold: true
}
}
},
fields: 'userEnteredFormat(backgroundColor,textFormat,horizontalAlignment)'
},
}
]
}
sheet_service.batch_update_spreadsheet(spreadsheet_id, requests, {})
Note:
If you want to retrieve the sheet ID using the sheet index (For example, the 1st and 2nd sheets are 0 and 1, respectively.), you can also use the following script.
sheet_index = 1
response = sheet_service.get_spreadsheet(spreadsheet_id, fields: 'sheets(properties)')
sheet_id = response.sheets[sheet_index].properties.sheet_id
Reference:
Method: spreadsheets.get
Related
I have an image type field in Sanity (docs) and I need to make sure that the dimensions are within a particular range to avoid breaking the website they're going on. Sanity offers validations, but the image type only has “required” and “custom” rules, and the field information passed into the custom validator doesn't include the image metadata.
How can I work around this limitation and offer in-CMS validation of the dimensions?
While Sanity doesn't pass the image metadata into the validator, you can extract image format and dimension information from the asset ID that is provided. According to this documentation, this is a supported, stable way of accessing this information without loading the image object from Sanity.
For example, here's the first argument passed to the Rule.custom validator:
{
"_type": "image",
"alt": "Example Image",
"asset": {
"_ref": "image-bff149a0b87f5b0e00d9dd364e9ddaa0-700x650-jpg",
"_type": "reference"
}
}
Getting to the image dimensions can be accomplished like this:
{
title: "My Image",
name: "image",
type: "image",
options: {
accept: "image/*",
},
validation: Rule => Rule.custom(image => {
if (!image) return true
const { dimensions } = decodeAssetId(image.asset._ref)
return dimensions.width >= 500 || "Image must be wider"
}
}
const pattern = /^image-([a-f\d]+)-(\d+x\d+)-(\w+)$/
const decodeAssetId = id => {
const [, assetId, dimensions, format] = pattern.exec(id)
const [width, height] = dimensions.split("x").map(v => parseInt(v, 10))
return {
assetId,
dimensions: { width, height },
format,
}
}
I have also rolled this functionality into the sanity-pills library that makes it easier to do like this:
import { createImageField } from "sanity-pills"
createImageField({
name: "image",
validations: {
required: true,
minWidth: 500,
maxHeight: 9000
},
})
I'm trying to update an ellipsis shape using the google slides api in ruby. This is the code:
shape_properties = {
shape_background_fill: {
solid_fill: {
color: {
rgb_color: {
red: 1.0,
green: 0,
blue: 0
}
}
}
}
}
requests = [{
update_shape_properties: {
object_id: ellipse.object_id,
fields: 'shapeBackgroundFill',
shape_properties: shape_properties,
},
}]
# Execute the request.
req = Google::Apis::SlidesV1::BatchUpdatePresentationRequest.new(requests: requests)
response = #slides.batch_update_presentation(presentation_id,req)
Another code that I've tried with the same error is this one:
rgb_color = Google::Apis::SlidesV1::RgbColor.new(red: 1.0, green: 0, blue: 0)
color = Google::Apis::SlidesV1::OpaqueColor.new(rgb_color: rgb_color)
solid_fill = Google::Apis::SlidesV1::SolidFill.new(color: color)
shape_background_fill = Google::Apis::SlidesV1::ShapeBackgroundFill.new(solid_fill: solid_fill)
shape_properties = Google::Apis::SlidesV1::ShapeProperties.new(shape_background_fill: shape_background_fill)
requests = [{
update_shape_properties: {
object_id: ellipse.object_id,
fields: 'shapeBackgroundFill',
shape_properties: shape_properties,
},
}]
req = Google::Apis::SlidesV1::BatchUpdatePresentationRequest.new(requests: requests)
response = #slides.batch_update_presentation(presentation_id, req)
I get this error:
`check_status': badRequest: Invalid requests[0].updateShapeProperties: The object () could not be found. (Google::Apis::ClientError)
Any idea why is it fails?
try using object_id_prop instead of object_id in update_shape_properties.
shape_properties = {
shape_background_fill: {
solid_fill: {
color: {
rgb_color: {
red: 1.0,
green: 0,
blue: 0
}
}
}
}
}
requests = [{
update_shape_properties: {
object_id_prop: ellipse.object_id,
fields: 'shapeBackgroundFill',
shape_properties: shape_properties,
},
}]
# Execute the request.
req = Google::Apis::SlidesV1::BatchUpdatePresentationRequest.new(requests:
requests)
response = #slides.batch_update_presentation(pres`entation_id,req)
Because UpdateShapePropertiesRequest has object_id_prop accessor instead of object_id.
That's why object_id name is already used in Object.
I've been trying for 3 days to get this chart to display the way I want it to. Everything was working 100% until I realized the grouped bar chart numbers were off.
Example: When the bottom bar value equals 10 and the top bar value equals 20, the top of the grouped bar read 30. This is the default behavior, but not how I want to represent my data. I want the top of the grouped bar to read whatever the highest number is, which lead me to this fiddle representing the data exactly how I wanted to.
After refactoring my logic, this is what I have so far. As you can see the timeseries line is broken up and the tooltip is not rendering the group of data being hovered over.
My questions:
1) How to get the tooltip to render all three data points (qty, price, searches)
2) How to solidify the timeseries line so it's not disconnected
Any help would be greatly appreciated so I can move on from this 3 day headache!
Below is most of my code - excluding the JSON array for brevity, which is obtainable at my jsfiddle link above. Thank you in advance for your time.
var chart = c3.generate({
bindto: '#chart',
data: {
x: 'x-axis',
type: 'bar',
json: json,
xFormat: '%Y-%m-%d',
keys: {
x: 'x-axis',
y: 'searches',
value: ['qty', 'searches', 'price']
},
types: {
searches: 'line'
},
groups: [
['qty', 'price']
],
axes: {
qty: 'y',
searches: 'y2'
},
names: {
qty: 'Quantity',
searches: 'Searches',
price: 'Price ($)'
},
colors: {
price: 'rgb(153, 153, 153)',
qty: 'rgb(217, 217, 217)',
searches: 'rgb(255, 127, 14)'
}
},
bar: {
width: {
ratio: 0.60
}
},
axis: {
x: {
type: 'timeseries',
label: { text: 'Timeline', position: 'outer-right' },
tick: {
format: '%Y-%m-%d'
}
},
y: {
type: 'bar',
label: {
text: 'Quantity / Price',
position: 'outer-middle'
}
},
y2: {
show: true,
label: {
text: 'Searches',
position: 'outer-middle'
}
}
},
tooltip: {
grouped: true,
contents: function(d, defaultTitleFormat, defaultValueFormat, color) {
var data = this.api.data.shown().map(function(series) {
var matchArr = series.values.filter(function(datum) {
return datum.value != undefined && datum.x === d[0].x;
});
if (matchArr.length > 0) {
matchArr[0].name = series.id;
return matchArr[0];
}
});
return this.getTooltipContent(data, defaultTitleFormat, defaultValueFormat, color);
}
}
});
1) If I got it right, you want tooltip to show all values, even if some of them are null.
Null values are hidden by default. You can replace them with zero (if it is suitable for your task) and thus make them visible.
Also, it seems to me that there is a shorter way to get grouped values:
var data = chart.internal.api.data().map(function(item) {
var row = item.values[d[0].index]; // get data for selected index
if (row.value === null) row.value = 0; // make null visible
return row;
});
2) I think you are talking about line.connectNull option:
line: {
connectNull: true
}
UPDATE
Looks like having duplicate keys breaks work of api.data() method.
You need to change json structure to make keys unique:
Before:
var json = [
{"x-axis":"2017-07-17","qty":100},
{"x-axis":"2017-07-17","price":111},
{"x-axis":"2017-07-17","searches":1},
{"x-axis":"2017-07-18","qty":200},
{"x-axis":"2017-07-18","price":222},
{"x-axis":"2017-07-18","searches":2}
];
After:
var json = [
{"x-axis":"2017-07-17","qty":100,"price":111,"searches":1},
{"x-axis":"2017-07-18","qty":200,"price":222,"searches":2}
];
See fiddle.
I've been trying to get a telerik kendo ui column chart to display grouped data but where the groups might not have entries for all possible values and I don't want to show space/empty columns in these empty cases.
Telerik dojo of problem
Is anyone aware of anyway to get this to work more like the screenshot below
Excel has grouped the data but doesn't display a column at all if the data is null/zero
I couldn't find a built-in way to do this, so I ended up just placing the bars manually by overriding the visual function. In my case, I only needed to move one bar and that bar will always be the same category, which made it a whole lot easier in that I only had to identify it by matching the category. I could then move it with a transform. You cannot move it by setting the coordinates because the visual has already been created.
It would be more complex to do this dynamically, but it's certainly possible. This may give someone a start in the right direction.
One downside of this method is that you must also place the labels manually, which I have also done below. You can override the visual function of the labels, as well, but no references to any other elements are passed with the event data. Note how the documentation says the sender field may be undefined; in my experience, this is always the case.
It also does not move the tooltip or the highlight. You could use the same method to move the highlight (override the visual function, though on the series instead of the seriesDefaults) and draw the tooltip manually while moving the highlight -- similar to how the method below draws the label while moving the column.
Telerik Dojo Example
$(document).ready(function () {
$("#chart").kendoChart({
legend: { visible: false },
tooltip: { visible: false },
categoryAxis: {
name: "categoryAxis",
categories: ["1", "2", "3"],
},
series: [
{
data: [1, 2, 3],
highlight: { visible: false },
},
{
data: [1.5, null, 3.5],
highlight: { visible: false },
}
],
seriesDefaults: {
type: "column",
labels: { visible: false },
visual: function (e) {
if (e.value === null) return;
var visual = e.createVisual();
var axisRect = e.sender.getAxis("categoryAxis").slot("2");
var group = new kendo.drawing.Group();
var label = new kendo.drawing.Text(e.value, [0, 0], {
font: "20px sans-serif",
fill: { color: "black" }
});
var lbox = label.clippedBBox();
label.position([
e.rect.origin.x + e.rect.size.width / 2 - lbox.size.width / 2,
e.rect.origin.y - label.bbox().size.height * 1.5
]);
group.append(visual, label);
if (e.category === "2") {
var x = (axisRect.origin.x + axisRect.size.width / 2) - e.rect.size.width / 2;
group.transform(kendo.geometry.transform().translate(x - e.rect.origin.x, 0));
}
return group;
},
}
});
});
I have a c3js line graph of type timeseries.
My axis tables are currently like this:
11/10.....05/11.....11/11.....05/12.....11/12.....05/13.....11/13
and it want it to be
.12/10.....06/11.....12/11.....06/12.....12/12.....06/13.....
where each dot indicates a month, note that the datapoint at 11/10 is still kept and displayed, only the axis labelling has changed. FYI, The increment is not set directly but hacked using culling.
Is there any way to alter the starting label to 12/10 (with all subsequent labels increasing in 6-months intervals)?
UPDATE (14NOV14): I have added a working jsFiddle for you to tinker with. Any help is appreciated!
UPDATE (14NOV14): I have tried using functions for this. But I want the 'pop-up' box to show the grey heading all the time.
This was a painfully long learning process, for a very badly-documented library. But I had to use a function for the format option for the tick, and then format the title again for the tooltip. Here's the final, working copy.
HTML (Include the d3.js, c3.js, and c3.css scripts too)
<body>
<div id="chartContainer"></div>
</body>
JS
var chart = c3.generate({
bindto: '#chartContainer',
data: {
x: 'x',
columns: [
['x', '2013-04-01', '2013-05-02', '2013-06-03', '2013-07-04', '2013-08-05', '2013-09-06'],
['data1', 30, 200, 100, 400, 150, 250],
['data2', 130, 340, 200, 500, 250, 350]
]
},
axis: {
x: {
type: 'timeseries',
tick: {
format: function (x) {
if((x.getMonth()+1) % 6 === 0) {
return ('0' + (x.getMonth()+1)).slice(-2) + '/' + x.getFullYear().toString().substr(2,2);
}
}
}
}
},
tooltip: {
format: {
title: function (d) {
var format = d3.time.format('%m/%y');
return format(d)
}
}
}
});
N.B. Also be aware that culling might interfere with this. I had to set culling: {max: 100} to ensure any built in incrementations are not applied.