I am trying to find the answer in Google Slides API references for how to set the background color of a shape I have in my Google Slide. I have given it the title (using Alt Text feature) "rectangle1", so my intention is to write the code along the lines of "if shape's property "title" == "rectangle1", then set background color to red."
I can't see a single reference to "SetBackgroundFill" or SetBackgroundColor, or anything of that sort.
Is it possible?
This is another possible answer, using a so-called "container bound script", which is only accessible through the specific Slide's Tools/Script Editor menu (no other way, or else it won't work).
I found that this "container bound script" approach gives me more power over my slide, and it avoids these expensive calls to "batchUpdate", when using "stand alone" scripts as in my other "self-answer".
So, in a way, I recommend it to myself, but, perhaps, to someone else, my other approach would be a better choice.
For one thing, this approach has a much faster response time.
var hex_color = '#54BdeF';
function test1() {
var selection = SlidesApp.getActivePresentation().getSelection();
var currentPage = selection.getCurrentPage();
var selectionType = selection.getSelectionType();
var shapes = currentPage.getShapes();
for (i=0; i < shapes.length; i++) {
if (shapes[i].getTitle() == 'rectangle1') {
shape_fill = shapes[i].getFill();
shape_fill.setSolidFill(hex_color);
}
}
}
Again, as before, I would welcome any comments and suggestions.
To set background color, you need Element Operations.
The Slides API allows you to create and edit a variety of page
elements, including text boxes, images, tables, basic shapes, lines,
and embedded videos. The examples on this page show some common page
element operations that can be achieved with the API.
Following the steps specified here will do the changes in your specified shape or element. Check the example.
Well, here is my solution. If someone sees a way to improve it, I am all ears, but so far, it appears to work for me glitch-free.
First, I find the shape I am after using the following logic:
function ChangeColorMain()
{
ChangeShapeBackgroundColor('title', 'rectangle1', color_to_repl_r, color_to_repl_g, color_to_repl_b, alpha_value );
}
function ChangeShapeBackgroundColor(shape_property_name, shape_property_value, color_to_set_r, color_to_set_g, color_to_set_b) {
Logger.log( 'ChangeShapeBackgroundColor(shape_property_name=%s, shape_property_value=%s, color_to_set_r=%s, color_to_set_g=%s, color_to_set_b=%s) ',
shape_property_name, shape_property_value, color_to_set_r, color_to_set_g, color_to_set_b);
var presentation = Slides.Presentations.get(presentationId);
var slides = presentation.slides;
Logger.log('The presentation contains %s slides:', slides.length);
for (i = 0; i < slides.length; i++) {
for (j = 0; j < slides[i].pageElements.length; j++ ) {
if (shape_property_name == 'title' && shape_property_value == slides[i].pageElements[j].title) {
Logger.log('Found it');
//slides[i].pageElements[j].shape.shapeProperties.shapeBackgroundFill.solidFill.color.rgbColor.red = color_to_set_r;
SubmitRequest(slides[i].pageElements[j].objectId, color_to_set_r, color_to_set_g, color_to_set_b, alpha_value);
}
} //end of for that iterates through every element
}
}
So, you'll notice that I start my process by calling the function "ChangeColorMain" which also gets my global variables color_to_repl_r... which are defined in a different file of my google script project, but that's not important.
Once inside the ChangeShapeBackgroundColor(), I iterate through all "PageElements" on my slide (see the relevant for loops) and use if statements to check if I got to the shape I am looking for. Finally, once I have located it, I call the all important function SubmitRequest(), which is "expensive". You can't make too many calls in one day, or else Google blocks this function until the day ends. But not a problem if you are making less than 500 calls per day (this number might be wrong/might change).
Here are the details of "SubmitRequest()" which I was able to create by finally figuring out how to make sense of this reference page:
https://developers.google.com/slides/reference/rest/v1/presentations/request#UpdateShapePropertiesRequest
function SubmitRequest(shape_id, r, g, b, a) {
var rgb_color = {
red: r,
green: g,
blue: b
};
var opaque_color = {
rgbColor: rgb_color
};
var solid_fill = {
color: opaque_color,
alpha: a
};
var background_fill = {
solidFill: solid_fill
};
var shape_properties = {
shapeBackgroundFill: background_fill
};
var update_request = {
objectId: shape_id,
shapeProperties: shape_properties,
fields: "shapeBackgroundFill.solidFill.color"
};
var requests = [{
updateShapeProperties: update_request
}];
// Execute the request.
var batch_update_return = Slides.Presentations.batchUpdate({
requests: requests
}, presentationId);
Logger.log(
'This is what you get from Google after submitting batchUpdate request:\n%s', batch_update_return);
}
Related
I have points generated one by one, and when a new point is generated, I want to draw a line segment connecting with the previous point. Like this:
var x by remember { mutableStateOf( 0.0f)}
var y by remember { mutableStateOf( 0.5f)}
var pStart by remember { mutableStateOf(Offset(0f, 0.5f))}
Canvas(modifier = Modifier.fillMaxSize()) {
canvasWidth = size.width
canvasHeight = size.height
val pEnd = Offset(x * canvasWidth, (1-y) * canvasHeight)
val col = if (pEnd.y < pStart.y) Color.Green else Color.Red
drawLine(
start = pStart,
end = pEnd,
strokeWidth = 4f,
color = col
)
pStart = pEnd
}
But this only draws the segment in a flash and no segments stay on the screen.
I know I can save the points to a list and redraw all the segments whenever a new point is added. But I just hope to economize. Is it possible?
There's no practical other way. You COULD in fact, keep track of just two points, adding a whole new Canvas (all Transparent and Filling the maximum Size, stacked on top of one another), for each extra point that is added. This does seem a bit impractical, but maybe try it out and do some benchmarking to see which one checks out. This is the only OTHER way I could think of, where you do not have to store all the points and recompose every time a point is added, since all the other lines would technically be frozen in space.
In response to the somewhat (unreasonably) aggressive comment below, here's some sample code. I assume you have a stream of new points coming in so a LiveData object is assumed to be the source of that, which I shall be converting to a MutableState<T> for my use-case.
var latestPoint by liveData.collectAsState()
var recordedPoint by remember { mutableStateOf(latestPoint) }
var triggerDraw by remember { mutableStateOf(false) }
var canvasList = mutableStateListOf<#Composable () -> Unit>Canvas>() // Canvas is the Composable
if(triggerDraw){
canvasList.add(
Canvas(){
/* you have the recordedPoint, and latestPoint, simply draw a line here */
}
)
triggerDraw = false
}
LaunchedEffect(latestPoint){
triggerDraw = true
}
canvasList.forEach {
it() // Invoke the Composable here
}
Thanks Dad!
I have a requirement where i have to show custom points on x-axis instead of dates values. Also same custom data points needs to be shown on navigator as well. In the below Js fiddle, i am converting data (Per13/2016 etc) into equivalent date values and then binding the chart using converted date values.
Below is the link of the JS fiddle:- Fiddle link
In the Js fiddle, i am showing Per1,Per2 etc.on x-axis and same has to be shown on navigator as well.
Now i am facing problem with the navigator,when i changes the range using slider ,the x-axis labels changes but not according to the range selected.Also tool-tip formatting is getting changed.
Can you please let me know how to handle this scenario and best way to do the same.
//few code lines to post fiddle link
xAxis: {
labels: {
formatter: function () {
if(fiscal13){
var perDate = new Date(this.value);
return 'Per' + (perDate.getMonth() + 1);
}
}
}
}
I am not sure if I am right, but I think you are overdoing this.
Let's keep original data, so remove fiscal13Data.Data.forEach(function(item) { .. }); function. And When creating data, use simply index of the point as x-value:
var cost = [],
usage = [],
dataLength = fiscal13Data.Data.length
i = 0;
for (i; i < dataLength; i += 1) {
// need to sum costs
cost.push([
i, // the index
fiscal13Data.Data[i]['Cost'] // cost
]);
usage.push([
i, // the index
fiscal13Data.Data[i]['Usage'] // Usage
]);
}
Now you can get to the "Per13/2016" strings in a simple way in xAxis labels' formatters:
var str = fiscal13Data.Data[this.value].Date;
In tooltip formatter, it is almost exactly the same:
var str = fiscal13Data.Data[this.x].Date;
And here is working demo: http://jsfiddle.net/qneuh4Ld/3/
Note: You data looks a bit strange - don't you want to sort it first? Also, you have twice every date (e.g. "Per13/2016" - once for "water" and once for "electric").
I am trying to implement RGB histogram computation for images in Swift (I am new to iOS).
However the computation time for 1500x1000 image is about 66 sec, which I consider to be too slow.
Are there any ways to speed up image traversal?
P.S. current code is the following:
func calcHistogram(image: UIImage) {
let bins: Int = 20;
let width = Int(image.size.width);
let height = Int(image.size.height);
let binStep: Double = Double(bins-1)/255.0
var hist = Array(count:bins, repeatedValue:Array(count:bins, repeatedValue:Array(count:bins, repeatedValue:Int())))
for i in 0..<bins {
for j in 0..<bins {
for k in 0..<bins {
hist[i][j][k] = 0;
}
}
}
var pixelData = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage))
var data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)
for x in 0..<width {
for y in 0..<height {
var pixelInfo: Int = ((width * y) + x) * 4
var r = Double(data[pixelInfo])
var g = Double(data[pixelInfo+1])
var b = Double(data[pixelInfo+2])
let r_bin: Int = Int(floor(r*binStep));
let g_bin: Int = Int(floor(g*binStep));
let b_bin: Int = Int(floor(b*binStep));
hist[r_bin][g_bin][b_bin] += 1;
}
}
}
As noted in my comment on the question, there are some things you might rethink before you even try to optimize this code.
But even if you do move to a better overall solution like GPU-based histogramming, a library, or both... There are some Swift pitfalls you're falling into here that are good to talk about so you don't run into them elsewhere.
First, this code:
var hist = Array(count:bins, repeatedValue:Array(count:bins, repeatedValue:Array(count:bins, repeatedValue:Int())))
for i in 0..<bins {
for j in 0..<bins {
for k in 0..<bins {
hist[i][j][k] = 0;
}
}
}
... is initializing every member of your 3D array twice, with the same result. Int() produces a value of zero, so you could leave out the triple for loop. (And possibly change Int() to 0 in your innermost repeatedValue: parameter to make it more readable.)
Second, arrays in Swift are copy-on-write, but this optimization can break down in multidimensional arrays: changing an element of a nested array can cause the entire nested array to be rewritten instead of just the one element. Multiply that by the depth of nested arrays and number of element writes you have going on in a double for loop and... it's not pretty.
Unless there's a reason your bins need to be organized this way, I'd recommend finding a different data structure for them. Three separate arrays? One Int array where index i is red, i + 1 is green, and i + 2 is blue? One array of a custom struct you define that has separate r, g, and b members? See what conceptually fits with your tastes or the rest of your app, and profile to make sure it works well.
Finally, some Swift style points:
pixelInfo, r, g, and b in your second loop don't change. Use let, not var, and the optimizer will thank you.
Declaring and initializing something like let foo: Int = Int(whatever) is redundant. Some people like having all their variables/constants explicitly typed, but it does make your code a tad less readable and harder to refactor.
Int(floor(x)) is redundant — conversion to integer always takes the floor.
If you have some issues about performance in your code, first of all, use Time Profiler from Instruments. You can start it via Xcode menu Build->Profile, then, Instruments app opened, where you can choose Time Profiler.
Start recording and do all interactions in the your app.
Stop recording and analyse where is the "tightest" place of your code.
Also check options "Invert call tree", "Hide missing symbols" and "Hide system libraries" for better viewing profile results.
You can also double click at any listed function to view it in code and seeing percents of usage
Background: I'm a dev that knows JS, but is relatively new to Three JS. I've done a few small projects that involve static scenes with basic repeating animation.
I'm currently working on a modified version of Google's Globe project http://workshop.chromeexperiments.com/globe/. Looking back, I probably should have just started from scratch, but it was a good tool to see the approach their dev took. I just wish I could now update ThreeJS w/o the whole thing falling apart (too many unsupported methods and some bugs I never could fix, at least not in the hour I attempted it).
In the original, they are merging all of the geometric points into one object to speed up FPS. For my purposes, I'm updating the points on the globe using JSON, and there will never be more than 100 (probably no more than 60 actually), so they need to remain individual. I've removed the "combine" phase so I can now individually assign data to the points and then TWEEN the height change animation.
My question is, how do I manually select a single point (which is a Cube Geometry) so that I can modify the height value? I've looked through Stack Overflow and Three JS on GitHub and I'm not sure I understand the process. I'm assigning an ID to make it directly relate to the data that is being passed into it (I know WebGL adds an individual name/ID for particles, but I need something that is more directly related to what I'm doing for the sake of simplicity). That seems to work fine. But again, as a JS dev I've tried .getElementById(id) and $('#'+id) in jQuery, and neither works. I realize that Geometry objects don't behave the same way as HTML DOM objects, so I guess that's where I'm having struggles.
Code to add a point of data to the globe:
function addPoint(lat, lng, size, color, server) {
geometry = new THREE.Cube(0.75, 0.75, 1, 1, 1, 1, null, false, { px: true,
nx: true, py: true, ny: true, pz: false, nz: true});
for (var i = 0; i < geometry.vertices.length; i++) {
var vertex = geometry.vertices[i];
vertex.position.z += 0.5;
}
var point = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial ({
vertexColors: THREE.FaceColors
}));
var phi = (90 - lat) * Math.PI / 180;
var theta = (180 - lng) * Math.PI / 180;
point.position.x = 200 * Math.sin(phi) * Math.cos(theta);
point.position.y = 200 * Math.cos(phi);
point.position.z = 200 * Math.sin(phi) * Math.sin(theta);
if($('#'+server).length > 0) {
server = server+'b';
}
point.id = server;
point.lookAt(mesh.position);
point.scale.z = -size;
point.updateMatrix();
for (var i = 0; i < point.geometry.faces.length; i++) {
point.geometry.faces[i].color = color;
}
console.log(point.id);
scene.addObject(point);
}
So now to go back, I know I can't use point.id because obviously that will only reference inside the function. But I've tried 'Globe.id', 'Globe.object.id', 'object.id', and nothing seems to work. I know it is possible, I just can't seem to find a method that works.
Okay, I found a method that works for this by playing with the structure.
Essentially, the scene is labeled "globe" and all objects are its children. So treating the scene as an array, we can successfully pass an object into a var using the following structure:
Globe > Scene > Children > [Object]
Using a matching function, we loop through each item and find the desired geometric object and assign it to a temporary var for animation/adjustment:
function updatePoints(server){
var p, lineObject;
$.getJSON('/JSON/'+server+'.json', function(serverdata) {
/* script that sets p to either 0 or 1 depending on dataset */
var pointId = server+p;
//Cycle through all of the child objects and find a patch in
for(var t = 3; t < globe.scene.children.length; t++) {
if(globe.scene.children[t].name === pointId) {
//set temp var "lineObject" to the matched object
lineObject = globe.scene.children[t];
}
}
/* Manipulation based on data here, using lineObject */
});
}
I don't know if this is something that anyone else has had questions on, but I hope it helps someone else! :)
EDIT: Just realized this isn't a keyed array so I can use .length to get total # of objects
Alright, sorry if this is a pretty easy question to get answered, but I looked around through pages and pages on google and couldn't find anything somewhat related. I got a lot of help, but I still can't seem to get this part of my ActionScript working. I have a program, that when running, allows me to paint random color squares on mouse click. I added a button, that is supposed to be able to change the shape being painted from rectangle to circle. I can't seem to get that button to work. This is what my code looks like so far.
var color:Number;
stage.addEventListener(MouseEvent.MOUSE_DOWN, startDrawing);
stage.addEventListener(MouseEvent.MOUSE_UP, stopDrawing);
function startDrawing(e:MouseEvent):void {
stage.addEventListener(MouseEvent.MOUSE_MOVE, makeShapes);
color = Math.random() * 0xFFFFFF;
}
function stopDrawing(e:MouseEvent):void {
stage.removeEventListener(MouseEvent.MOUSE_MOVE, makeShapes);
}
function makeShapes(e:MouseEvent):void {
var rect:Rect = new Rect(10,10,color);
addChild(rect);
rect.x = mouseX;
rect.y = mouseY;
}
shape_btn.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
function mouseDownHandler(event:MouseEvent):void {
}
At the bottom I left it blank, it seems to be the part I'm stuck on. I've tried simply setting the VAR to my "Ellipse" class I had made, which gets it to work, but only that one time when I click the button. It doesn't stay a circle and allow me to paint with the shape. Again I'm sorry, I felt like I was getting pretty close to the solution, and then I hit a wall.
Hard to understand what the difficulty is, but I'll try to address what I understand.
First, the stage mouse down event will capture your button event, so you might as well get rid of it and stick to one mouse event.
stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
private function onMouseDown(ev:Event):void
{
if (ev.target==shape_btn)
changeShape();
else if (ev.target==stage)
startDrawing();
}
Or something along those lines.
Second, I don't know what Rect is. Is it a class you have an access to? This is how I'd do it without a special class:
private function makeShape():void
{
switch (shapeType)
{
case "rect":
drawRect();
break;
case "circle":
drawCircle();
break;
}
}
private function drawRect():void
{
var rect:Shape = new Shape();
rect.graphics.beginFill(color);
rect.graphics.drawRect(0, 0, 10, 10);
rect.x = mouseX;
rect.y = mouseY;
addChild(rect);
}
private function drawCircle():void
{
var circle:Shape = new Shape();
circle.graphics.beginFill(0xff0000);
circle.graphics.drawCircle(0, 0, 10);
circle.x = mouseX;
circle.y = mouseY;
addChild(circle);
}
and finally, the changeShape function:
private function changeShape():void
{
shapeType = shapeType=="rect"?"circle":"rect";
}
There are better ways to go about it, but when dealing with two shape types only this is acceptable.
Of course you need to have a var shapeType:String = "rect" somewhere in your code, outside the functions.
I also think the color randomization should be in the mouse move handler rather than the mouse click. Is that on purpose?