I'm working on translating this ActionScript tutorial on binary space partitioning into Swift so I can use it in my rogue-like game. I came across a hitch.
In the article, the writer initializes his class like so:
public function Leaf(X:int, Y:int, Width:int, Height:int)
{
// initialize our leaf
x = X;
y = Y;
width = Width;
height = Height;
}
When I translated this into Swift, I ran into an error. The code above doesn't initialize all its declared values. This leads me into an impossible error that I can't seem to fix. Somehow, the writer of the article initializes his leftChild and rightChild variables with this function that is outside the initialization scope.
public function split():Boolean
{
// begin splitting the leaf into two children
if (leftChild != null || rightChild != null)
return false; // we're already split! Abort!
// determine direction of split
// if the width is >25% larger than height, we split vertically
// if the height is >25% larger than the width, we split horizontally
// otherwise we split randomly
var splitH:Boolean = FlxG.random() > 0.5;
if (width > height && width / height >= 1.25)
splitH = false;
else if (height > width && height / width >= 1.25)
splitH = true;
var max:int = (splitH ? height : width) - MIN_LEAF_SIZE; // determine the maximum height or width
if (max <= MIN_LEAF_SIZE)
return false; // the area is too small to split any more...
var split:int = Registry.randomNumber(MIN_LEAF_SIZE, max); // determine where we're going to split
// create our left and right children based on the direction of the split
if (splitH)
{
leftChild = new Leaf(x, y, width, split);
rightChild = new Leaf(x, y + split, width, height - split);
}
else
{
leftChild = new Leaf(x, y, split, height);
rightChild = new Leaf(x + split, y, width - split, height);
}
return true; // split successful!
}
Which is somehow ok in ActionScript, but in Swift it leads me to my problem.
Here is my translated code (Swift):
private let mapWidth:Int = 50
private let mapHeight:Int = 50
class Leaf {
var leftLeaf = [Leaf]()
var rightLeaf = [Leaf]()
var minLeafSize:Int = 6
var x, y, width, height: Int
var leftChild:Leaf
var rightChild:Leaf
init (X:Int, Y:Int, W:Int, H:Int) {
x = Y
y = Y
width = W
height = H
let maxLeafSize:UInt = 20
var leaves = [Leaf]()
// first, create a Leaf to be the 'root' of all Leafs.
let root = Leaf(X: 0, Y: 0, W: mapWidth, H: mapHeight)
leaves.append(root)
var didSplit:Bool = true
// we loop through every Leaf in our Vector over and over again, until no more Leafs can be split.
while (didSplit) {
didSplit = false
for l in leaves {
if l.leftLeaf.isEmpty == true && l.rightLeaf.isEmpty == true {
// if this Leaf is too big, or 75% chance...
if l.width > maxLeafSize || l.height > maxLeafSize || Int(arc4random_uniform(100)) > 25 {
if (l.split()) {
// if we did split, push the child leafs to the Vector so we can loop into them next
leaves.append(l.leftChild)
leaves.append(l.rightChild)
didSplit = true
}
}
}
}
}
}
func split() -> Bool {
if leftLeaf.isEmpty == true || rightLeaf.isEmpty == true {
return false
}
var splitH = arc4random_uniform(100) > 50 ? true : false
if width > height && Double(width / height) >= 1.25 {
splitH = false
}
if height > width && Double(height / width) >= 1.25 {
splitH = true
}
let max:Int = (splitH ? height : width) - minLeafSize // determine the maximum height or width
if max <= minLeafSize { return false }
let split:Int = Int(arc4random_uniform(UInt32(minLeafSize - max) + UInt32(max)))
if (splitH) {
leftChild = Leaf(X: x, Y: y, W: width, H: split)
rightChild = Leaf(X: x, Y: y + split, W: width, H: height - split)
leftLeaf.append(leftChild)
rightLeaf.append(rightChild)
} else {
leftChild = Leaf(X: x, Y: y, W: split, H: height)
rightChild = Leaf(X: x + split, Y: y, W: width - split, H: height);
leftLeaf.append(leftChild)
rightLeaf.append(rightChild)
}
return true
}
}
It is identical (as far as I can figure) to the ActionScript code in the article. But it is giving me an error. The leftChild and rightChild variables aren't initialized in my init method. When I move the split() -> Bool function into the init method it won't let me use the function, giving me an error "Value of type Leaf has no member split()". Removing the l from the if (l.spit()) line gives me a second error "Use of local variable 'split' before its declaration". The split() function has to be outside the initialization scope.
If I attempt to initialize leftChild and rightChild like so:
init (X:Int, Y:Int, W:Int, H:Int) {
x = Y
y = Y
width = W
height = H
leftChild = Leaf(X: x, Y: y, W: width, H: height)
rightChild = Leaf(X: x, Y: y, W: width, H: height)
}
It creates an infinite loop that eventually causes a crash.
The code should be initializing leftChild and rightChild in the split() -> Bool function but I don't think that's how it works in Swift. You should be able to copy/paste it into a Swift file and get the same errors.
Why is this happening? Is my code poorly written? How can I fix this?
In ActionScript, uninitialised variables are automatically evaluated with the special value undefined; also, in ActionScript, undefined == null, which is why if (leftChild != null || rightChild != null) works.
In Swift, you need to explicitly allow your variables to be nilable. The variables you are worried about need to start off as nil (which they automatically will, if you allow them to, by setting their type to Optional - note the question mark):
var leftChild:Leaf?
var rightChild:Leaf?
Related
ok so the reason for this question is that i am trying to deal with multiple konva shapes at a time. in the original project the shapes are being selected by drawing a momentary rectangle around the shapes that you want selected (rectangular selection). I have seen some of the other post about this, but they only seem to deal with the selection itself, i have that working.
Here is a codepen example that illustrates the problem.
link
Instructions:
click the select button to have the two shapes put in a group and a transformer applied
Rotate and scale the selected shapes.
click the deselect button to have the shapes moved back onto the layer.
The parts that is interresting is after line 92, where i am exploring different methods of moving the shapes back onto the layer.
children.toArray().forEach(e => {
// Need to apply transformations correctly before putting back on layer
//Method 1
if (method === 1) {
let newTransforms = e.getAbsoluteTransform();
let localTransforms = e.getTransform();
let m = newTransforms.getMatrix();
let matrices = getMatrix(e);
console.log("matrix before : ");
console.log(matrices);
e.rotation(selectionGroupRotation);
e.skew({ x: m[1], y: m[2] });
e.scale({ x: m[0], y: m[3] });
e.position({ x: m[4], y: m[5] })
m = newTransforms.getMatrix();
matrices = getMatrix(e);
console.log("matrix after : ");
// console.log(m);
console.log(matrices);
}
//Method 2
if (method === 2) {
let groupPos = selectionGroup.position();
let point = { x: groupPos.x, y: groupPos.y };
let groupScale = selectionGroup.scale();
let groupRotation = selectionGroup.rotation();
let configGroupMatrix = selectionGroup.getTransform();
let newpos = configGroupMatrix.point(point);
e.rotation(selectionGroupRotation + e.rotation());
e.scaleX(groupScale.x * e.scaleX());
e.scaleY(groupScale.y * e.scaleY());
let finalpos = {
x: groupPos.x + e.x(),
y: groupPos.y + e.y()
}
e.x(finalpos.x);
e.y(finalpos.y);
}
e.moveTo(layer);
})
The frustrating part is that the function getAbsoluteTransform() seem to give a transformed matrix, but you can't set the transformation matrix of a shape directly. But the solution might be as simple as setting the shapes matrix to the one returned from getAbsoluteTransform()
Currently, there are no methods to in Konva core to calculate attributes from the matrix. But you can easily find them online.
https://math.stackexchange.com/questions/13150/extracting-rotation-scale-values-from-2d-transformation-matrix
extract rotation, scale values from 2d transformation matrix
From the answers, I made this function to get attrs:
function decompose(mat) {
var a = mat[0];
var b = mat[1];
var c = mat[2];
var d = mat[3];
var e = mat[4];
var f = mat[5];
var delta = a * d - b * c;
let result = {
x: e,
y: f,
rotation: 0,
scaleX: 0,
scaleY: 0,
skewX: 0,
skewY: 0,
};
// Apply the QR-like decomposition.
if (a != 0 || b != 0) {
var r = Math.sqrt(a * a + b * b);
result.rotation = b > 0 ? Math.acos(a / r) : -Math.acos(a / r);
result.scaleX = r;
result.scaleY = delta / r;
result.skewX = Math.atan((a * c + b * d) / (r * r));
result.scleY = 0;
} else if (c != 0 || d != 0) {
var s = Math.sqrt(c * c + d * d);
result.rotation =
Math.PI / 2 - (d > 0 ? Math.acos(-c / s) : -Math.acos(c / s));
result.scaleX = delta / s
result.scaleY = s;
result.skewX = 0
result.skewY = Math.atan((a * c + b * d) / (s * s));
} else {
// a = b = c = d = 0
}
result.rotation *= 180 / Math.PI;
return result;
}
Then you can use that function to calculate attributes from the absolute transform.
Demo: https://codepen.io/lavrton/pen/dwGPBz?editors=1010
I need to change size of bubbles(points) by supplying 4th value in data points in Highcharts' 3D scatter chart. I couldn't find any way how to do this. Can anyone help ?
It seems that it is not supported out of the box. Although in this Thread in the Highcharts-Forum, a wrapper is shown that allows a 4th w value to be used as the size of the bubble (see http://jsfiddle.net/uqLfm1k6/1/):
(function (H) {
H.wrap(H.seriesTypes.bubble.prototype, 'getRadii', function (proceed, zMin, zMax, minSize, maxSize) {
var math = Math,
len,
i,
pos,
zData = this.zData,
wData = this.userOptions.data.map( function(e){ return e.w }), // ADDED
radii = [],
options = this.options,
sizeByArea = options.sizeBy !== 'width',
zThreshold = options.zThreshold,
zRange = zMax - zMin,
value,
radius;
// Set the shape type and arguments to be picked up in drawPoints
for (i = 0, len = zData.length; i < len; i++) {
// value = zData[i]; // DELETED
value = this.chart.is3d()? wData[i] : zData[i]; // ADDED
// When sizing by threshold, the absolute value of z determines the size
// of the bubble.
if (options.sizeByAbsoluteValue && value !== null) {
value = Math.abs(value - zThreshold);
zMax = Math.max(zMax - zThreshold, Math.abs(zMin - zThreshold));
zMin = 0;
}
if (value === null) {
radius = null;
// Issue #4419 - if value is less than zMin, push a radius that's always smaller than the minimum size
} else if (value < zMin) {
radius = minSize / 2 - 1;
} else {
// Relative size, a number between 0 and 1
pos = zRange > 0 ? (value - zMin) / zRange : 0.5;
if (sizeByArea && pos >= 0) {
pos = Math.sqrt(pos);
}
radius = math.ceil(minSize + pos * (maxSize - minSize)) / 2;
}
radii.push(radius);
}
this.radii = radii;
});
}(Highcharts));
I have this code:
var w = 0
var h = 0
for i in 1...am
{
if w > Int(screenSize.width)
{
w = 0
h += CHeight
}
//some other code
w += CWidth
so the value W is a part of the screen width, and it can be not "perfectly" equal to the screen width while adding them together.
the IF doing it's job only when the part value(W) larger then screen width. but how to make it so the IF will work when value W will be just before the end of screen width (doesn't go over it)?
if w > Int(screenSize.width) - CWidth
{
w = 0
h += CHeight
}
I have seen many posts on this topic, but it doesn't seem the issue has ever been properly addressed.
We have a large scatter with about 30 points on it (nothing overwhelming). But in certain cases, the dots will be very close together or overlapping (not much we can really do about that, I guess).
The main problem is that we want the data labels visible at all times, and these data labels are overlapping when the points are close to each other.
We have tried allowOverlap: false, but that's not really what we need/want. Our ideal outcome is allowing all datalabels to be displayed on screen inside the scatter while still being able to read each one at all times.
Do we fix this by adjusting the separation of the dots or by adjusting the separation/padding of the datalabels? Any suggestions? Thank you.
I haven't found a working configuration solution of this problem from Highcharts (although I cannot guarantee there isn't one in latest version). However there are some algorithms for acceptable randomization of the labels coordinates that split data labels.
Here are some useful links that could help you with the algorithm:
wordcloud package in R (cloud.R is the file containing the algorithm)
direct labels package in R
And some dummy pseudo code translation in JavaScript of the R code would be:
splitLabels: function() {
// Create an array of x-es and y-es that indicate where your data lie
var xArr = getAllDataX();
var yArr = getAllDataY();
var labelsInfo = {};
this.chartSeries.forEach(function(el) {
var text = el.data.name;
labelsInfo[el.data.id] = {
height: getHeight(text),
width: getWidth(text),
text: text
};
}, this);
var sdx = getStandardDeviation(xArr);
var sdy = getStandardDeviation(yArr);
if(sdx === 0) sdx = 1;
if(sdy === 0) sdy = 1;
var boxes = [];
var xlim = [], ylim = [];
xlim[0] = this.chart.xAxis[0].getExtremes().min;
xlim[1] = this.chart.xAxis[0].getExtremes().max;
ylim[0] = this.chart.yAxis[0].getExtremes().min;
ylim[1] = this.chart.yAxis[0].getExtremes().max;
for (var i = 0; i < data.length; i++) {
var pointX = data[i].x;
var pointY = data[i].y;
if (pointX<xlim[0] || pointY<ylim[0] || pointX>xlim[1] || pointY>ylim[1]) continue;
var theta = Math.random() * 2 * Math.PI,
x1 = data[i].x,
x0 = data[i].x,
y1 = data[i].y,
y0 = data[i].y,
width = labelsInfo[data[i].id].width,
height = labelsInfo[data[i].id].height ,
tstep = Math.abs(xlim[1] - xlim[0]) > Math.abs(ylim[1] - ylim[0]) ? Math.abs(ylim[1] - ylim[0]) / 100 : Math.abs(xlim[1] - xlim[0]) / 100,
rstep = Math.abs(xlim[1] - xlim[0]) > Math.abs(ylim[1] - ylim[0]) ? Math.abs(ylim[1] - ylim[0]) / 100 : Math.abs(xlim[1] - xlim[0]) / 100,
r = 0;
var isOverlapped = true;
while(isOverlapped) {
if((!hasOverlapped(x1-0.5*width, y1-0.5*height, width, height, boxes)
&& x1-0.5*width>xlim[0] && y1-0.5*height>ylim[0] && x1+0.5*width<xlim[1] && y1+0.5*height<ylim[1]) )
{
boxes.push({
leftX: x1-0.5*width,
bottomY: y1-0.5*height,
width: width,
height: height,
icon: false,
id: data[i].id,
name: labelsInfo[data[i].id].text
});
data[i].update({
name: labelsInfo[data[i].id].text,
dataLabels: {
x: (x1 - data[i].x),
y: (data[i].y - y1)
}
}, false);
isOverlapped = false;
} else {
theta = theta+tstep;
r = r + rstep*tstep/(2*Math.PI);
x1 = x0+sdx*r*Math.cos(theta);
y1 = y0+sdy*r*Math.sin(theta);
}
}
}
// You may have to redraw the chart here
},
You can call this function on redraw or optimized to call it less often.
Please note that if you have some big points or shapes or icons indicating where your data items lie you will have to check if any of the proposed solutions does not interfere(overlap) with the icons as well.
You can try to adapt this algorithm:
function StaggerDataLabels(series) {
sc = series.length;
if (sc < 2) return;
for (s = 1; s < sc; s++) {
var s1 = series[s - 1].points,
s2 = series[s].points,
l = s1.length,
diff, h;
for (i = 0; i < l; i++) {
if (s1[i].dataLabel && s2[i].dataLabel) {
diff = s1[i].dataLabel.y - s2[i].dataLabel.y;
h = s1[i].dataLabel.height + 2;
if (isLabelOnLabel(s1[i].dataLabel, s2[i].dataLabel)) {
if (diff < 0) s1[i].dataLabel.translate(s1[i].dataLabel.translateX, s1[i].dataLabel.translateY - (h + diff));
else s2[i].dataLabel.translate(s2[i].dataLabel.translateX, s2[i].dataLabel.translateY - (h - diff));
}
}
}
}
}
//compares two datalabels and returns true if they overlap
function isLabelOnLabel(a, b) {
var al = a.x - (a.width / 2);
var ar = a.x + (a.width / 2);
var bl = b.x - (b.width / 2);
var br = b.x + (b.width / 2);
var at = a.y;
var ab = a.y + a.height;
var bt = b.y;
var bb = b.y + b.height;
if (bl > ar || br < al) {
return false;
} //overlap not possible
if (bt > ab || bb < at) {
return false;
} //overlap not possible
if (bl > al && bl < ar) {
return true;
}
if (br > al && br < ar) {
return true;
}
if (bt > at && bt < ab) {
return true;
}
if (bb > at && bb < ab) {
return true;
}
return false;
}
http://jsfiddle.net/menXU/6/
When using UIPinchGestureRecognizer what is the best way to detect/read the pinch scale in horizontal and vertical directions individually? I saw this post
UIPinchGestureRecognizer Scale view in different x and y directions
but I noticed there were so many going back and forth for such a seemingly routine task that I am not sure that is the best answer/way.
If not using UIPinchGestureRecognizer altogether for this purpose is the answer, what's the best way to detect pinch scale in two different directions?
Basically do this,
func _mode(_ sender: UIPinchGestureRecognizer)->String {
// very important:
if sender.numberOfTouches < 2 {
print("avoided an obscure crash!!")
return ""
}
let A = sender.location(ofTouch: 0, in: self.view)
let B = sender.location(ofTouch: 1, in: self.view)
let xD = fabs( A.x - B.x )
let yD = fabs( A.y - B.y )
if (xD == 0) { return "V" }
if (yD == 0) { return "H" }
let ratio = xD / yD
// print(ratio)
if (ratio > 2) { return "H" }
if (ratio < 0.5) { return "V" }
return "D"
}
That function will return H, V, D for you .. horizontal, vertical, diagonal.
You would use it something like this ...
func yourSelector(_ sender: UIPinchGestureRecognizer) {
// your usual code such as ..
// if sender.state == .ended { return } .. etc
let mode = _mode(sender)
print("the mode is \(mode) !!!")
// in this example, we only care about horizontal pinches...
if mode != "H" { return }
let vel = sender.velocity
if vel < 0 {
print("you're squeezing the screen!")
}
}
In my C# I do the following
private double _firstDistance = 0;
private int _firstScaling = 0;
private void PinchHandler(UIPinchGestureRecognizer pinchRecognizer)
{
nfloat x1, y1, x2, y2 = 0;
var t1 = pinchRecognizer.LocationOfTouch(0, _previewView);
x1 = t1.X;
y1 = t1.Y;
var t2 = pinchRecognizer.LocationOfTouch(1, _previewView);
x2 = t2.X;
y2 = t2.Y;
if (pinchRecognizer.State == UIGestureRecognizerState.Began)
{
_firstDistance = Math.Sqrt(Math.Pow((x2 - x1), 2) + Math.Pow((y2 - y1), 2));
_firstScaling = _task.TextTemplates[_selectedTextTemplate].FontScaling;
}
if (pinchRecognizer.State == UIGestureRecognizerState.Changed)
{
var distance = Math.Sqrt(Math.Pow((x2 - x1), 2) + Math.Pow((y2 - y1), 2));
var fontScaling = Convert.ToInt32((distance - _firstDistance) / _previewView.Frame.Height * 100);
fontScaling += _firstScaling;
_task.TextTemplates[_selectedTextTemplate].FontScaling = fontScaling;
UpdateBitmapPreview();
}
}
I calculate the distance between the two points when pinch "began" and hold that value in two privates. Then I calculate a scaling (fontScaling) based on the first measured distance and the second one (in "changed").
I use my own view (_previewView) to set as base (100%), but you can use View.Bounds.height instead or the width for that matter. in my case, I always have a square view, so height == width in my app.