ScalaFx Group wrapped nodes data extraction - scalafx

Trying to extract Group wrapped node data seems really tricky. Any pointers getting data inside node objects (which by definition have no children) like in code below I would like to get Rectangle.userData inside Group(VBox(Rectangle))?
def west(): Unit = {
def boxPart(ix: Int): VBox = new VBox {
padding = Insets(2, 5, 8, 5)
userData = "My userData VBox wrap"
children = (
new Rectangle {
width = 250
height = 125
fill = Color.DeepSkyBlue
userData = "My userData Rectangle wrap"+ix.toString
}
)
}
val packet = new Group(boxPart(12345))
val vboxproof = packet.children.get(0).userData
println("TESTER:" + vboxproof)
// getting rectangle userData ???
}
west

Children of Group are generic Nodes. You need to access actual type of a child to access additional properties. Also keep in mind that you are navigating the JavaFX scene graph, so need to "cast" children to JavaFX types. You can create vboxproof using something like this to get children of VBox:
val vboxproof = packet.children.get(0) match {
case v : javafx.scene.layout.VBox => v.children.get(0).userData
case v => "Unexpected type: " + v.getClass.getName
}

Related

AMCharts - Dynamically add children to tree map

I am thoroughly enjoying AMChart's many features but I couldn't find any way to dynamically add some children to a treemap.
I am trying to load additional children on "hit" for each element
for (var i = 0; i < this.maxDepthLevel; i++) {
const series = this.chart.seriesTemplates.create(i);
series.columns.template.events.on("hit", async function(ev) {
const data = ev.target.dataItem.dataContext;
children = await api.getChildrenOf(data.id);
ev.target.dataItem.treeMapDataItem.children.values.push(...children);
});
}
^ this doesn't work and when doing this and then zooming out, I get
I even tried changing the underlying data and then calling
this.chart.invalidateRawData();
but to no avail.
Does anyone have any experience with adding such dynamic children to a tree map?
I cannot simply load everything upfront, there are far too many possible layers of depth unfortunately and the request will be too large!
Rather than directly pushing child items to children values you need to do it like this :
for(var index=0; index < children.length; index++){
var newChildDataItem = new am4charts.TreeMapDataItem();
newChildDataItem.value = children[index].value;
newChildDataItem.name = children[index].name;
newChildDataItem.color = ev.target.dataItem.dataContext.color;
ev.target.dataItem.dataContext.children.insert(newChildDataItem);
}

Dependent dynamically populated dropdown list Google Sheet

I would simply like to autopopulate the Point Segment with all the segments linked to a specific Street, when the Street name is inputted.
When Street name is inputted into column C, Column D should have a dropdown containing only the point segments of that street.
While I realize this can simply be achieved by creating a filter in the Data tab, I am trying to create a form which does not allow this and therefore need to script it.
This is the Google sheet:
https://docs.google.com/spreadsheets/d/1QbTqPegE_GLj9V6x5uCNNXAoi0v12Pmaelhc7uaMknE/edit?usp=sharing
I have written this code, however I am having trouble filtering by Street.
function setDataValid_(range, sourceRange) {
var rule = SpreadsheetApp.newDataValidation().requireValueInRange(sourceRange,
true).build();
range.setDataValidation(rule);
}
function onEdit() {
var aSheet = SpreadsheetApp.getActiveSheet();
var aCell = aSheet.getActiveCell();
var aColumn = aCell.getColumn();
if (aColumn == 3 && aSheet.getName() == 'Sheet1') {
var range = aSheet.getRange(aCell.getRow(), aColumn + 1);
var sourceRange = aSheet.getRange('Sheet1!B2:B5131');
setDataValid_(range, sourceRange)
}
}
Any help would be much appreciated.
You're close, but you should use requireValueInList instead of requireValueInRange. You were passing sourceRange, which is equal to all of the point segments.
To accomplish the filtering, you need to look at all of the street values. If the street value matches the selection, then save the adjacent point segment to a separate list. Once you've saved all those point segments, then pass it to requireValueInList. To do this, you need to take advantage of getValues() to get the range values as an array and loop through it.
I've made a few other modifications:
In onEdit, you should use the an event object
Changed your variable names so they would be easier to understand
Added a check to make sure that the Street cell isn't blank (no need to trigger the action when you delete a value from the cell)
Added a function getPointSegments that does the filtering
Removed the setDataValid_ function as it made your code less readable, and in my opinion, wasn't worthy of being its own function
function onEdit(event) {
var eventSheet = event.range.getSheet();
var eventCell = event.range;
var eventColumn = eventCell.getColumn();
var eventValue = eventCell.getValue();
if (eventColumn == 3 && eventSheet.getName() == "Sheet1" && eventValue != "") {
var pointRange = eventSheet.getRange(eventCell.getRow(), eventColumn + 1);
var pointSegments = getPointSegments_(eventSheet, eventValue);
var rule = SpreadsheetApp.newDataValidation().requireValueInList(pointSegments, true).build();
pointRange.setDataValidation(rule);
}
}
function getPointSegments_(sheet, selectedStreet) {
var streetsAndPoints = sheet.getRange("A2:B").getValues();
var pointSegments = [];
for (var i=0; i<streetsAndPoints.length; i++) {
var street = streetsAndPoints[i][0];
var pointSegment = streetsAndPoints[i][1];
if (street === selectedStreet)
pointSegments.push(pointSegment);
}
return pointSegments;
}
Lastly, be sure that your data validations in the Street field look like this (and I would actually suggest "Reject input" on invalid data).

SceneKit - Stop continuously looping Collada animation

I am using SceneKit to load a Collada (.dae) file.
Whatever I have tried, the animation repeats in a continuous loop whereas I only want it play once and then stop.
Here's the loading code where sceneView is my SCNView:
//Load the scene and play
let scene = SCNScene(named: "Scenes.scnassets/test4.dae")
sceneView.scene = scene
sceneView.autoenablesDefaultLighting = true
sceneView.allowsCameraControl = true
The scene loads correctly and the animation plays on loading as required, but in a continuous loop.
I have tried removing all animations as follows:
sceneView.scene?.rootNode.removeAllAnimations()
but this seems to have no effect.
I have also tried retrieving all animations and setting the repeatCount = 1 or even to 0
let url = NSBundle.mainBundle().URLForResource("Scenes.scnassets/test4", withExtension: "dae")
let sceneSource = SCNSceneSource(URL:url!, options: nil)
let animationIndentifiers = sceneSource?.identifiersOfEntriesWithClass(CAAnimation)
if let ids = animationIndentifiers
{
for id in ids {
let animation: CAAnimation = sceneSource!.entryWithIdentifier(id as! String, withClass: CAAnimation.self)! as! CAAnimation
animation.repeatCount = 1
}
}
I have also tried this another way:
let keys = sceneView.scene!.rootNode.animationKeys() //get all the animation keys
if let theKeys = keys {
for key in theKeys {
let animation = sceneView.scene?.rootNode.animationForKey(key as! String)
animation?.repeatCount = 1
}
}
but again no luck.
I can find nothing in the Collada file itself that is obviously causing the animation to repeat. However, I notice that the .dae file icon on my Mac disk has a play button and clicking this also plays the animation within the icon on a continuous loop.
Update:
I now notice that in the code above I am setting the constant 'animation' attributes and this is not copied back to the actual scene nodes. Also, the only animation in the .dae file is in a child node. So here's my next attempt:
//Get the child nodes
let children = scene?.rootNode.childNodes
//Initialise a childNode counter
var childCount = 0
//Iterate through the child nodes
if let theChildren = children {
for child in theChildren {
let keys = child.animationKeys() //get all the animation keys
//Iterate through the animations
if let theKeys = keys {
for key in theKeys {
let animation = child.animationForKey(key as! String)
println("Initial Repeat count: \(animation.repeatCount)")
animation.repeatCount = 1
//Remove existing animation
scene?.rootNode.childNodes[childCount].removeAnimationForKey(key as! String)
//Add amended animation
scene?.rootNode.childNodes[childCount].addAnimation(animation, forKey: key as! String)
}
}
childCount++
}
}
There is actually only one animation attached to one child node. (I have also tried setting this using the the actual animation id string in place of 'key' above.)
The above shows Initial Repeat count: inf
and on checking afterwards it is indeed set to 1.
However, the animation still runs in an infinite loop :-(
Any help to resolve this would be much appreciated.
Further update
I have now created a new Collada file with simple animation using Maya and for some reason one of the trials attempted above actually works:
func sceneSetup() {
let scene = SCNScene(named: "Scenes.scnassets/test10.dae")
let children = scene?.rootNode.childNodes
var childCount = 0
if let theChildren = children {
for child in theChildren {
let keys = child.animationKeys() //get all the animation keys
if let theKeys = keys {
for key in theKeys {
let animation = child.animationForKey(key as! String)
animation.repeatCount = 1
scene?.rootNode.childNodes[childCount].removeAnimationForKey(key as! String)
scene?.rootNode.childNodes[childCount].addAnimation(animation, forKey: key as! String)
}
}
childCount++
}
}
sceneView.scene = scene
sceneView.autoenablesDefaultLighting = true
}
if anybody can explain that would be great!
Ah, but there's another problem!
Here's the start frame of the animation:
and here's the end frame where we want to end up:
But at the end of the animation the scene jumps back to the start view.
I have 'fixed' this by amending the animation so that frame 1 is copy of the final frame. This works and isn't noticeable but doesn't seem a very elegant solution.
For the animation jumping back to the first frame, the isAppliedOnCompletion value is what you are looking for.
animation.repeatCount = 1
animation.isAppliedOnCompletion = true
This will make sure that the animation pauses on the final frame.
I know this is an old question, but I came across this same problem and found a solution for the jumping back to the beginning issue. If you set the animation to not be removed on completion, the object should stay at the end location:
if let animation = child.animationForKey(child.animationKeys.first!) {
animation.repeatCount = 1
animation.removedOnCompletion = false
...
This works for me. It's based off your answer but will go through the node's entire tree and limit all of their animation counts to one.
Personally I found that if I missed any of the descendant nodes when manually traversing and setting the node animation counts to one, I would have a problem. This ensures that the node passed in itself, and all child nodes will only animate once, and then hold the model in place after finishing.
Use like this animateEntireNodeTreeOnce(mostRootNode: nodeYouImportedFromCollada).
func animateEntireNodeTreeOnce(mostRootNode node: SCNNode){
onlyAnimateThisNodeOnce(node)
for childNode in node.childNodes {
animateEntireNodeTreeOnce(mostRootNode: childNode)
}
}
func onlyAnimateThisNodeOnce(_ node: SCNNode) {
if node.animationKeys.count > 0 {
for key in node.animationKeys {
let animation = node.animation(forKey: key)!
animation.repeatCount = 1
animation.isRemovedOnCompletion = false
node.removeAllAnimations()
node.addAnimation(animation, forKey: key)
}
}
}
I Agree with #Phil Dudas and I like to add also this workaround to Prevent The Animation from Start By Default By Reducing Speed to Zero, When you set Speed to Zero it will remain at First state,
nodeAnimation.speed = 0
animation.repeatCount = 1
nodeAnimation.animation.isRemovedOnCompletion = false

How to perform local search on the map in windows phone 8 app

I want to perform string based search on the map in a windows phone 8 app.
On searching on net I got this on msdn website -
BingMapsTask bingMapsTask = new BingMapsTask();
bingMapsTask.SearchTerm = "coffee";
bingMapsTask.ZoomLevel = 2;
bingMapsTask.Show();
But this opens a new window(or app) showing the search results. I have two questions -
How can I get input from the new window, I mean I want that when user would touch any of the search results my app should get its co-ordinate. How to do it ?
Is there any way to perform the search in the app itself ?
You can use GeocodeQuery to look up the coordinates of a search term.
// Get your current position
var myPosition = await new Geolocator().GetGeopositionAsync(TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(10));
// Define search
var geoQuery = new GeocodeQuery();
geoQuery.SearchTerm = "Seattle, WA";
geoQuery.GeoCoordinate = new GeoCoordinate(myPosition.Coordinate.Latitude, myPosition.Coordinate.Longitude);
geoQuery.QueryCompleted += (s, e) => {
if (e.Error == null && e.Result.Count > 0) {
// e.Result will contain a list of coordinates of matched places.
// You can show them on a map control , e.g.
myMap.Center = e.Result[0].GeoCoordinate;
myMap.ZoomLevel = 2;
}
}
geoQuery.QueryAsync();
Note, you need ID_CAP_MAP added for this to work.

How to set cursor position to end of text in CKEditor?

Is there a way to set the cursor to be at the end of the contents of a CKEditor?
This developer asked too, but received no answers:
http://cksource.com/forums/viewtopic.php?f=11&t=19877&hilit=cursor+end
I would like to set the focus at the end of the text inside a CKEditor. When I use:
ckEditor.focus();
It takes me to the beginning of the text already inside the CKEditor.
Dan's answer got strange results for me, but minor change (in addition to typo fix) made it work:
var range = me.editor.createRange();
range.moveToElementEditEnd( range.root );
me.editor.getSelection().selectRanges( [ range ] );
According to the documentation for CKEditor 4, you can do the following once you have the editor object.
var range = editor.createRange();
range.moveToPosition( range.root, CKEDITOR.POSITION_BEFORE_END );
editor.getSelection().selectRanges( [ range ] );
Link: http://docs.ckeditor.com/#!/api/CKEDITOR.dom.selection (under selectRanges function).
After a bit of fiddling, I've got it to work with the following code:
$(document).ready(function() {
CKEDITOR.on('instanceReady', function(ev) {
ev.editor.focus();
var s = ev.editor.getSelection(); // getting selection
var selected_ranges = s.getRanges(); // getting ranges
var node = selected_ranges[0].startContainer; // selecting the starting node
var parents = node.getParents(true);
node = parents[parents.length - 2].getFirst();
while (true) {
var x = node.getNext();
if (x == null) {
break;
}
node = x;
}
s.selectElement(node);
selected_ranges = s.getRanges();
selected_ranges[0].collapse(false); // false collapses the range to the end of the selected node, true before the node.
s.selectRanges(selected_ranges); // putting the current selection there
}
});
The idea is:
Get the root node (not body)
Advance to next node, until there are no more nodes to advance to.
Select last node.
Collapse it
Set range
Here's a similar answer to #peter-tracey. In my case my plugin is inserting a citation. If the user has made a selection, I needed to disable the selection and place the cursor at the end of the sentence.
// Obtain the current selection & range
var selection = editor.getSelection();
var ranges = selection.getRanges();
var range = ranges[0];
// Create a new range from the editor object
var newRange = editor.createRange();
// assign the newRange to move to the end of the current selection
// using the range.endContainer element.
var moveToEnd = true;
newRange.moveToElementEditablePosition(range.endContainer, moveToEnd);
// change selection
var newRanges = [newRange];
selection.selectRanges(newRanges);
// now I can insert html without erasing the previously selected text.
editor.insertHtml("<span>Hello World!</span>");
CKEditor 3.x:
on : {
'instanceReady': function(ev) {
ev.editor.focus();
var range = new CKEDITOR.dom.range( ev.editor.document );
range.collapse(false);
range.selectNodeContents( ev.editor.document.getBody() );
range.collapse(false);
ev.editor.getSelection().selectRanges( [ range ] );
}
}
based on pseudo-code provided by the developers here:
https://dev.ckeditor.com/ticket/9546#comment:3
You have to focus editor, get document object, put it in range,
collapse range (with false parameter), select body (with
selectNodeContents), collapse it (with false parameter) and finally
select range. It is best to do it all in instanceReady event.
This is the easiest solution provided by the ckeditor API. I have tested it on IE10+, ff, safari and Chrome:
range = editor.createRange();
// the first parameter is the last line text element of the ckeditor instance
range.moveToPosition(new CKEDITOR.dom.node(editor.element.$.children[pos - 1]), CKEDITOR.POSITION_BEFORE_END)
range.collapse()
editor.getSelection().selectRanges([ range ])
This will work for sure.
CKEDITOR.config.startupFocus = 'end';
have you tried ckEditor.Selection.Collapse(false);
According to CKEditor 4 documentation, another option is:
const range = this.ckeditor.createRange()
range.moveToElementEditablePosition(range.root, true)
this.ckeditor.getSelection().selectRanges([range])
Link: https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_dom_range.html#method-moveToElementEditablePosition

Resources