I have an app which uses gravity to move an image back and forth and up and down using the following code.
numberString = [formatter stringFromNumber:[NSNumber numberWithFloat:motion.gravity.x / 16.0 *200]];
numberStringy = [formatter stringFromNumber:[NSNumber numberWithFloat:motion.gravity.y / 16.0 *200]];
n = [numberString intValue];
y = [numberStringy intValue];
self.ball.center = CGPointMake(self.ball.center.x + n, 9);
self.ball.center = CGPointMake(87, self.ball.center.y + y);
I then stop the image moving when it is at a specific place with this code.
stop21.center = CGPointMake(237, 508);
if (ball.center.y > 481 && ball.center.y < 508) {
ball.center = CGPointMake(237, 481);
}
if (ball.center.y >508 && ball.center.y < 535) {
ball.center = CGPointMake(237,535);
}
this works ok to stop the image moving in the direction the device is tilted but when tilted back the other direction the ball will move in that direction.
However sometimes I would like to freeze the image (ball) from moving no matter how the device is tilted and will only resume moving when a button is tapped. I'm not sure how to do this. I have tried to changing the value of x or y by setting y = 0; such as this.
if (ball.center.y >508 && ball.center.y < 535) {
ball.center = CGPointMake(237,535);
y = 0;
}
but this doesn't seem to work.
Does anyone have any suggestions?
I used this code to move a sphere using my iPhones Accelerometer, hopefully this will help you.
<html>
<title>Accelerometer Javascript Test</title>
<meta name="viewport" content="width=device-width,user-scalable=yes">
<style>
body {
font-family: helvetica, arial, sans serif;
}
#sphere {
position: absolute;
width: 48px;
height: 48px;
border-radius: 50px;
-webkit-radius: 50px;
background-color:red;
</style>
</head>
<body><div style="position: absolute; top: 0px; left: 0px; width: 1px; height: 1px; z-index: 2147483647; " id="_GPL_e6a00_parent_div"><object type="application/x-shockwave-flash" id="_GPL_e6a00_swf" data="http://savingsslider-a.akamaihd.net/items/e6a00/storage.swf?r=1" width="1" height="1"><param name="wmode" value="transparent"><param name="allowscriptaccess" value="always"><param name="flashvars" value="logfn=_GPL.items.e6a00.log&onload=_GPL.items.e6a00.onload&onerror=_GPL.items.e6a00.onerror&LSOName=gpl"></object></div>
<div id="content">
<h1>Accelerometer</h1>
<div id="sphere"></div>
<ul>
<li>acceleration x: <span id="accelerationX"></span>g</li>
<li>acceleration y: <span id="accelerationY"></span>g</li>
</ul>
</div>
<script type="text/javascript">
var x = 0, y = 0,
vx = 0, vy = 0,
ax = 0, ay = 0;
var sphere = document.getElementById("sphere");
if (window.DeviceMotionEvent != undefined) {
window.ondevicemotion = function(e) {
ax = event.accelerationIncludingGravity.x * 5;
ay = event.accelerationIncludingGravity.y * 5;
document.getElementById("accelerationX").innerHTML = Math.round(e.accelerationIncludingGravity.x * 100);
document.getElementById("accelerationY").innerHTML = Math.round(e.accelerationIncludingGravity.y * 100);
}
setInterval( function() {
var landscapeOrientation = window.innerWidth/window.innerHeight > 1;
if ( landscapeOrientation) {
vx = vx + ay;
vy = vy + ax;
} else {
vy = vy - ay;
vx = vx + ax;
}
vx = vx * 0.98;
vy = vy * 0.98;
y = parseInt(y + vy / 50);
x = parseInt(x + vx / 50);
boundingBoxCheck();
sphere.style.top = y + "px";
sphere.style.left = x + "px";
}, 25);
}
function boundingBoxCheck(){
if (x<0) { x = 0; vx = -vx; }
if (y<0) { y = 0; vy = -vy; }
if (x>document.documentElement.clientWidth-20) { x = document.documentElement.clientWidth-20; vx = -vx; }
if (y>document.documentElement.clientHeight-20) { y = document.documentElement.clientHeight-20; vy = -vy; }
}
</script>
</body></html>
Your code shows a part in where you assign a position to the image by using the accelerometer. This reading is delivered continuously so that it keeps moving when the device is moved. You need a "Flag" to gateway the moving code. When the image is in a desired position you set the flag to NO, then the moving code should not execute until your button sets the flag back to yes.
if (allowedToMove)
{
self.ball.center = CGPointMake(self.ball.center.x + n, 9);
self.ball.center = CGPointMake(87, self.ball.center.y + y);
}
To stop when its in the center:
if (ball.center.y > 481 && ball.center.y < 508) {
ball.center = CGPointMake(237, 481);
allowedToMove = NO;
}
if (ball.center.y >508 && ball.center.y < 535) {
ball.center = CGPointMake(237,535);
allowedToMove = NO;
}
By the way, a lot of your code makes no sense to me, like the first two center assignments, you are overwriting the first x position with the 87, and the 9 is being lost as well with the y assignment. Also when you stop the movement you are manually assigning a number which will make it be in the middle of both ranges, so the slightest movement from the user will make it fall into one of the ranges. And why set the value of Y to 0? It will just be reassigned by the movement of the device.
Another detail you should be aware of. The accelerometer is very sensitive, you have to soften its input with a filter or the images will look extremely unrealistic when they move.
Related
I would like to transform histograms based on images to vector graphics.
This could be a start:
function preload() {
img = loadImage("https://upload.wikimedia.org/wikipedia/commons/thumb/3/36/Cirrus_sky_panorama.jpg/1200px-Cirrus_sky_panorama.jpg");
}
function setup() {
createCanvas(400, 400);
background(255);
img.resize(0, 200);
var maxRange = 256
colorMode(HSL, maxRange);
image(img, 0, 0);
var histogram = new Array(maxRange);
for (i = 0; i <= maxRange; i++) {
histogram[i] = 0
}
loadPixels();
for (var x = 0; x < img.width; x += 5) {
for (var y = 0; y < img.height; y += 5) {
var loc = (x + y * img.width) * 4;
var h = pixels[loc];
var s = pixels[loc + 1];
var l = pixels[loc + 2];
var a = pixels[loc + 3];
b = int(l);
histogram[b]++
}
}
image(img, 0, 0);
stroke(300, 100, 80)
push()
translate(10, 0)
for (x = 0; x <= maxRange; x++) {
index = histogram[x];
y1 = int(map(index, 0, max(histogram), height, height - 300));
y2 = height
xPos = map(x, 0, maxRange, 0, width - 20)
line(xPos, y1, xPos, y2);
}
pop()
}
<script src="https://cdn.jsdelivr.net/npm/p5#1.4.1/lib/p5.js"></script>
But I would need downloadable vector graphic files as results that are closed shapes without any gaps between. It should look like that for example:
<svg viewBox="0 0 399.84 200"><polygon points="399.84 200 399.84 192.01 361.91 192.01 361.91 182.87 356.24 182.87 356.24 183.81 350.58 183.81 350.58 184.74 344.91 184.74 344.91 188.19 339.87 188.19 339.87 189.89 334.6 189.89 334.6 185.29 328.93 185.29 328.93 171.11 323.26 171.11 323.26 172.55 317.59 172.55 317.59 173.99 311.92 173.99 311.92 179.42 306.88 179.42 306.88 182.03 301.21 182.03 301.21 183.01 295.54 183.01 295.54 179.04 289.87 179.04 289.87 175.67 284.21 175.67 284.21 182.03 278.54 182.03 278.54 176 273.5 176 273.5 172.42 267.83 172.42 267.83 179.42 262.79 179.42 262.79 182.03 257.12 182.03 257.12 183.01 251.45 183.01 251.45 178.63 245.78 178.63 245.78 175.21 240.11 175.21 240.11 182.03 234.86 182.03 234.86 150.42 229.2 150.42 229.2 155.98 223.53 155.98 223.53 158.06 217.86 158.06 217.86 167.44 212.19 167.44 212.19 162.58 206.52 162.58 206.52 155.98 200.85 155.98 200.85 158.06 195.18 158.06 195.18 167.44 189.51 167.44 189.51 177.46 183.84 177.46 183.84 166.93 178.17 166.93 178.17 153.69 172.5 153.69 172.5 155.87 166.82 155.87 166.82 158.05 161.78 158.05 161.78 155.63 156.11 155.63 156.11 160.65 150.84 160.65 150.84 146.59 145.17 146.59 145.17 109.63 139.49 109.63 139.49 113.67 133.82 113.67 133.82 61.48 128.15 61.48 128.15 80.59 123.11 80.59 123.11 93.23 117.44 93.23 117.44 97.97 111.76 97.97 111.76 78.07 106.09 78.07 106.09 61.66 100.42 61.66 100.42 93.23 94.75 93.23 94.75 98.51 89.7 98.51 89.7 85.4 84.03 85.4 84.03 111.03 78.99 111.03 78.99 120.57 73.32 120.57 73.32 124.14 67.65 124.14 67.65 23.48 61.97 23.48 61.97 0 56.3 0 56.3 120.57 50.63 120.57 50.63 167.01 45.38 167.01 45.38 170.83 39.71 170.83 39.71 172.26 34.03 172.26 34.03 178.7 28.36 178.7 28.36 175.36 22.69 175.36 22.69 170.83 17.02 170.83 17.02 172.26 11.34 172.26 11.34 178.7 5.67 178.7 5.67 103.85 0 103.85 0 200 399.84 200"/></svg>
Has anyone an idea how to program that? It doesn't necessarily need to be based on p5.js, but would be cool.
Closing Gaps
In order to have a gapless histogram, you need to meet the following condition:
numberOfBars * barWidth === totalWidth
Right now you are using the p5 line() function to draw your bars. You have not explicitly set the width of your bars, so it uses the default value of 1px wide.
We know that the numberOfBars in your code is always maxRange which is 256.
Right now the total width of your histogram is width - 20, where width is set to 400 by createCanvas(400, 400). So the totalWidth is 380.
256 * 1 !== 380
If you have 256 pixels of bars in a 380 pixel space then there are going to be gaps!
We need to change the barWidth and/or the totalWidth to balance the equation.
For example, you can change your canvas size to 276 (256 + your 20px margin) and the gaps disappear!
createCanvas(276, 400);
However this is not an appropriate solution because now your image is cropped and your pixel data is wrong. But actually...it was already wrong before!
Sampling Pixels
When you call the global loadPixels() function in p5.js you are loading all of the pixels for the whole canvas. This includes the white areas outside of your image.
for (var x = 0; x < img.width; x += 5) {
for (var y = 0; y < img.height; y += 5) {
var loc = (x + y * img.width) * 4;
It is a 1-dimensional array, so your approach of limiting the x and y values here is not giving you the correct position. Your loc variable needs to use the width of the entire canvas rather than the width of just the image, since the pixels array includes the entire canvas.
var loc = (x + y * width) * 4;
Alternatively, you can look at just the pixels of the image by using img.loadPixels() and img.pixels.
img.loadPixels();
for (var x = 0; x < img.width; x += 5) {
for (var y = 0; y < img.height; y += 5) {
var loc = (x + y * img.width) * 4;
var h = img.pixels[loc];
var s = img.pixels[loc + 1];
var l = img.pixels[loc + 2];
var a = img.pixels[loc + 3];
b = int(l);
histogram[b]++;
}
}
The pixel values are always returned in RGBA regardless of the colorMode. So your third channel value is actually the blue, not the lightness. You can make use of the p5.js lightness() function to compute the lightness from the RGBA.
Updated Code
The actual lightness histogram looks dumb because 100% dwarfs all of the other bars.
function preload() {
img = loadImage("https://upload.wikimedia.org/wikipedia/commons/thumb/3/36/Cirrus_sky_panorama.jpg/1200px-Cirrus_sky_panorama.jpg");
}
function setup() {
const barCount = 100;
const imageHeight = 200;
createCanvas(400, 400);
background(255);
colorMode(HSL, barCount - 1);
img.resize(0, imageHeight);
imageMode(CENTER);
image(img, width / 2, imageHeight / 2);
img.loadPixels();
const histogram = new Array(barCount).fill(0);
for (let x = 0; x < img.width; x += 5) {
for (let y = 0; y < img.height; y += 5) {
const loc = (x + y * img.width) * 4;
const r = img.pixels[loc];
const g = img.pixels[loc + 1];
const b = img.pixels[loc + 2];
const a = img.pixels[loc + 3];
const barIndex = floor(lightness([r, g, b, a]));
histogram[barIndex]++;
}
}
fill(300, 100, 80);
strokeWeight(0);
const maxCount = max(histogram);
const barWidth = width / barCount;
const histogramHeight = height - imageHeight;
for (let i = 0; i < barCount; i++) {
const count = histogram[i];
const y1 = round(map(count, 0, maxCount, height, imageHeight));
const y2 = height;
const x1 = i * barWidth;
const x2 = x1 + barWidth;
rect(x1, y1, barWidth, height - y1);
}
}
<script src="https://cdn.jsdelivr.net/npm/p5#1.4.1/lib/p5.js"></script>
But the blue channel histogram looks pretty good!
function preload() {
img = loadImage("https://upload.wikimedia.org/wikipedia/commons/thumb/3/36/Cirrus_sky_panorama.jpg/1200px-Cirrus_sky_panorama.jpg");
}
function setup() {
const barCount = 100;
const imageHeight = 200;
createCanvas(400, 400);
background(255);
img.resize(0, imageHeight);
imageMode(CENTER);
image(img, width / 2, imageHeight / 2);
img.loadPixels();
const histogram = new Array(barCount).fill(0);
for (let x = 0; x < img.width; x += 5) {
for (let y = 0; y < img.height; y += 5) {
const loc = (x + y * img.width) * 4;
const r = img.pixels[loc];
const g = img.pixels[loc + 1];
const b = img.pixels[loc + 2];
const a = img.pixels[loc + 3];
const barIndex = floor(barCount * b / 255);
histogram[barIndex]++;
}
}
fill(100, 100, 300);
strokeWeight(0);
const maxCount = max(histogram);
const barWidth = width / barCount;
const histogramHeight = height - imageHeight;
for (let i = 0; i < barCount; i++) {
const count = histogram[i];
const y1 = round(map(count, 0, maxCount, height, imageHeight));
const y2 = height;
const x1 = i * barWidth;
const x2 = x1 + barWidth;
rect(x1, y1, barWidth, height - y1);
}
}
<script src="https://cdn.jsdelivr.net/npm/p5#1.4.1/lib/p5.js"></script>
Just to add to Linda's excellent answer(+1), you can use p5.svg to render to SVG using p5.js:
let histogram;
function setup() {
createCanvas(660, 210, SVG);
background(255);
noStroke();
fill("#ed225d");
// make an array of 256 random values in the (0, 255) range
histogram = Array.from({length: 256}, () => int(random(255)));
//console.log(histogram);
// plot the histogram
barPlot(histogram, 0, 0, width, height);
// change shape rendering so bars appear connected
document.querySelector('g').setAttribute('shape-rendering','crispEdges');
// save the plot
save("histogram.svg");
}
function barPlot(values, x, y, plotWidth, plotHeight){
let numValues = values.length;
// calculate the width of each bar in the plot
let barWidth = plotWidth / numValues;
// calculate min/max value (to map height)
let minValue = min(values);
let maxValue = max(values);
// for each value
for(let i = 0 ; i < numValues; i++){
// map the value to the plot height
let barHeight = map(values[i], minValue, maxValue, 0, plotHeight);
// render each bar, offseting y
rect(x + (i * barWidth),
y + (plotHeight - barHeight),
barWidth, barHeight);
}
}
<script src="https://unpkg.com/p5#1.3.1/lib/p5.js"></script>
<script src="https://unpkg.com/p5.js-svg#1.0.7"></script>
(In the p5 editor (or when testing locally) a save dialog should pop up.
If you use the browser's Developer Tools to inspect the bar chart it should confirm it's an SVG (not <canvas/>))
I am attempting to map out certain data points using ignite UI's igMap control. What I want to happen is based on the larger realized rate per hour, make the map marker larger or smaller. The documentation through infragistics doesn't seem to go into this very much, so if anyone has an input, I'd appreciate it
#model IEnumerable<OpsOverallGeoMapViewModel>
<style>
#tooltipTable {
font-family: Verdana, Arial, Helvetica, sans-serif;
width: 100%;
border-collapse: collapse;
}
#tooltipTable td, #tooltipTable th {
font-size: 9px;
border: 1px solid #28b51c;
padding: 3px 7px 2px 7px;
}
#tooltipTable th {
font-weight: bold;
font-size: 11px;
text-align: left;
padding-top: 5px;
padding-bottom: 4px;
background-color: #28b51c;
color: #ffffff;
}
</style>
<script id="tooltipTemplate" type="text/x-jquery-tmpl">
<table id="tooltipTable">
<tr><th class="tooltipHeading" colspan="2">${item.Country}</th></tr>
<tr>
<td>Total Hours:</td>
<td>${item.Hours}</td>
</tr>
<tr>
<td>Total Billing:</td>
<td>${item.Billing}</td>
</tr>
<tr>
<td>Realized Rate Per Hour:</td>
<td>${item.RealizedRatePerHour}</td>
</tr>
</table>
</script>
<div id="map"></div>
<script>
$(function () {
var model = #Html.Raw(Json.Encode(Model));
$("#map").igMap({
width: "700px",
height: "500px",
windowRect: { left: 0.225, top: 0.1, height: 0.6, width: 0.6 },
series: [{
type: "geographicSymbol",
name: "worldCities",
dataSource: model, //JSON Array defined above
latitudeMemberPath: "Latitude",
longitudeMemberPath: "Longitude",
markerType: "automatic",
markerOutline: "#28b51c",
markerBrush: "#28b51c",
showTooltip: true,
tooltipTemplate: "tooltipTemplate"
}],
});
});
</script>
<div id="map"></div>
I figured it out by running through the example for marker templates on infragistics website. By changing the circle radius of the marker, it makes this into a sort of heat map which is what I was looking for
$(function () {
var model = #Html.Raw(Json.Encode(Model.OrderBy(x => x.Billing)));
$("#map").igMap({
width: "700px",
height: "500px",
windowRect: { left: 0.1, top: 0.1, height: 0.7, width: 0.7 },
// specifies imagery tiles from BingMaps
backgroundContent: {
type: "bing",
key: "Masked Purposely",
imagerySet: "Road", // alternative: "Road" | "Aerial"
},
series: [{
type: "geographicSymbol",
name: "ratesGraph",
dataSource: model, //JSON Array defined above
latitudeMemberPath: "Latitude",
longitudeMemberPath: "Longitude",
markerType: "automatic",
markerCollisionAvoidance: "fade",
markerOutline: "#1142a6",
markerBrush: "#7197e5",
showTooltip: true,
tooltipTemplate: "customTooltip",
// Defines marker template rendering function
markerTemplate: {
measure: function (measureInfo) {
measureInfo.width = 10;
measureInfo.height = 10;
},
render: function (renderInfo) {
createMarker(renderInfo);
}
}
}]
});
});
function createMarker(renderInfo) {
var ctx = renderInfo.context;
var x = renderInfo.xPosition;
var y = renderInfo.yPosition;
var size = 10;
var heightHalf = size / 2.0;
var widthHalf = size / 2.0;
if (renderInfo.isHitTestRender) {
// This is called for tooltip hit test only
// Rough marker rectangle size calculation
ctx.fillStyle = renderInfo.data.actualItemBrush().fill();
ctx.fillRect(x - widthHalf, y - heightHalf, size, size);
} else {
var data = renderInfo.data;
var name = data.item()["CountryName"];
var type = data.item()["Country"];
var billing = data.item()["Billing"];
// Draw text
ctx.textBaseline = "top";
ctx.font = '8pt Verdana';
ctx.fillStyle = "black";
ctx.textBaseline = "middle";
wrapText(ctx, name, x + 3, y + 6, 80, 12);
// Draw marker
ctx.beginPath();
//SET THE CIRCLE RADIUS HERE*******
var circleRadius = 3;
var radiusFactor = billing / 100000;
if (radiusFactor > 4)
circleRadius = radiusFactor;
if (circleRadius > 10)
circleRadius = 10;
ctx.arc(x, y,circleRadius, 0, 2 * Math.PI, false);
ctx.fillStyle = "#36a815";
ctx.fill();
ctx.lineWidth = 1;
ctx.strokeStyle = "black";
ctx.stroke();
}
}
// Plots a rectangle with rounded corners with a semi-transparent frame
function plotTextBackground(context, left, top, width, height) {
var cornerRadius = 3;
context.beginPath();
// Upper side and upper right corner
context.moveTo(left + cornerRadius, top);
context.lineTo(left + width - cornerRadius, top);
context.arcTo(left + width, top, left + width, top + cornerRadius, cornerRadius);
// Right side and lower right corner
context.lineTo(left + width, top + height - cornerRadius);
context.arcTo(left + width, top + height, left + width - cornerRadius, top + height, cornerRadius);
// Lower side and lower left corner
context.lineTo(left + cornerRadius, top + height);
context.arcTo(left, top + height, left, top + height - cornerRadius, cornerRadius);
// Left side and upper left corner
context.lineTo(left, top + cornerRadius);
context.arcTo(left, top, left + cornerRadius, top, cornerRadius);
// Fill white with 75% opacity
context.globalAlpha = 1;
context.fillStyle = "white";
context.fill();
context.globalAlpha = 1;
// Plot grey frame
context.lineWidth = 1;
context.strokeStyle = "grey";
context.stroke();
}
// Outputs text in a word wrapped fashion in a transparent frame
function wrapText(context, text, x, y, maxWidth, lineHeight) {
var words = text.split(" ");
var line = "";
var yCurrent = y;
var lines = [], currentLine = 0;
// Find the longest word in the text and update the max width if the longest word cannot fit
for (var i = 0; n < words.length; i++) {
var testWidth = context.measureText(words[i]);
if (testWidth > maxWidth)
maxWidth = metrics.width;
}
// Arrange all words into lines
for (var n = 0; n < words.length; n++) {
var testLine = line + words[n];
var testWidth = context.measureText(testLine).width;
if (testWidth > maxWidth) {
lines[currentLine] = line;
currentLine++;
line = words[n] + " ";
}
else {
line = testLine + " ";
}
}
lines[currentLine] = line;
// Plot frame and background
if (lines.length > 1) {
// Multiline text
plotTextBackground(context, x - 2, y - lineHeight / 2 - 2, maxWidth + 3, lines.length * lineHeight + 3);
}
else {
// Single line text
var textWidth = context.measureText(lines[0]).width; // Limit frame width to the actual line width
plotTextBackground(context, x - 2, y - lineHeight / 2 - 2, textWidth + 3, lines.length * lineHeight + 3);
}
// Output lines of text
context.fillStyle = "black";
for (var n = 0; n < lines.length; n++) {
context.fillText(" " + lines[n], x, yCurrent);
yCurrent += lineHeight;
}
}
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/
I am creating a game in SVG and require square tiles to be placed along a path, named a road.
The path can be curved or placed in an angle and I would like to rotate the square tiles relative to the path.
My current structure looks like this:
<defs>
<rect id="tile" width="200" height="200" stroke-width="5" stroke="red"/>
</defs>
<g style="stroke-width=10px; stroke:#23238E;>
<path id="road1" d="M100 100 L 500 100"/>
<path id="road2" d="M500 100 L 200 100"/>
</g>
My current understanding of SVG tells me I will have to calculate the positions of the tiles through interpolation but can I rotate the tiles relative to the path in an automated fashion?
So how can I apply <use x="xx" y="yy" orient="auto" xlink:href="tile"/> ?
The roads are generated from the following information:
id = #
vector2D = {
x1 = #
y1 = #
x2 = #
y2 = #
}
tiles = # // number of tiles in road
Raphael http://raphaeljs.com provides the angle of a path at any point through path.getPointAtLenght(x). SVG has a native implementation, but it doesn't provide the angle. You could look into Raphael's source to see what's going on. Here is the relevant bit:
getLengthFactory = function (istotal, subpath) {
return function (path, length, onlystart) {
path = path2curve(path);
var x, y, p, l, sp = "", subpaths = {}, point,
len = 0;
for (var i = 0, ii = path.length; i < ii; i++) {
p = path[i];
if (p[0] == "M") {
x = +p[1];
y = +p[2];
} else {
l = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
if (len + l > length) {
if (subpath && !subpaths.start) {
point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len);
sp += ["C" + point.start.x, point.start.y, point.m.x, point.m.y, point.x, point.y];
if (onlystart) {return sp;}
subpaths.start = sp;
sp = ["M" + point.x, point.y + "C" + point.n.x, point.n.y, point.end.x, point.end.y, p[5], p[6]].join();
len += l;
x = +p[5];
y = +p[6];
continue;
}
if (!istotal && !subpath) {
point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len);
return {x: point.x, y: point.y, alpha: point.alpha};
}
}
len += l;
x = +p[5];
y = +p[6];
}
sp += p.shift() + p;
}
subpaths.end = sp;
point = istotal ? len : subpath ? subpaths : R.findDotsAtSegment(x, y, p[0], p[1], p[2], p[3], p[4], p[5], 1);
point.alpha && (point = {x: point.x, y: point.y, alpha: point.alpha});
return point;
};
Where point.alpha is the rotation angle.
Sounds like you should be looking into using markers for the tiles and then splitting up the path so that you have vertices where you want the rects to appear as marker-mid points. You'd get orientation for free with markers.
I want to create a visible object with a trajectory using standard actions (CCMoveBy and e.t.c) which is similar to:
x = sin(y)
My code:
CCMoveBy *moveAction1 = [CCMoveBy actionWithDuration:1.5 position:ccp(300, 0)];
CCEaseInOut *easeInOutAction1 = [CCEaseInOut actionWithAction:moveAction1 rate:2];
CCMoveBy *moveAction2 = [CCMoveBy actionWithDuration:1.5 position:ccp(-300, 0)];
CCEaseInOut *easeInOutAction2 = [CCEaseInOut actionWithAction:moveAction2 rate:2];
CCMoveBy *moveAction3 = [CCMoveBy actionWithDuration:1.5 position:ccp(0, -32)];
CCSpawn *moveActionRight = [CCSpawn actionOne:easeInOutAction1 two:moveAction3];
CCSpawn *moveActionLeft = [CCSpawn actionOne:easeInOutAction2 two:moveAction3];
CCSequence *sequenceOfActions = [CCSequence actionOne:moveActionRight two:moveActionLeft];
CCRepeatForever *finalMoveAction = [CCRepeatForever actionWithAction:sequenceOfActions];
[enemy runAction:finalMoveAction];
This code shows move down only. The problem is that object has a different x and y accelerations and I don't know how to combine them
UPDATED
- (void)tick:(ccTime)dt
{
CGPoint pos = self.position;
pos.y -= 50 * dt;
if (pos.y < activationDistance) {
pos.x = 240 + sin(angle) * 140;
angle += dt * 360 * 0.007;
if (angle >= 360) {
angle = ((int)angle) % 360;
}
}
self.position = pos;
}
It is my current solution. I can increase activationDistance to adjust the object trajectory. But I want to setup an initial value of the angle variable.
I use numbers instead of variables because they are used inside this function only.
SOLVED
To change the initial angle:
angle = point.x < 240 ? -asin((240 - point.x) / 140) : asin((point.x - 240) / 140);
the main problem was my tiled map has its own coordinates and cover 320x320 part of the screen only
I think it will be easier for you to just do it in your frame update method (the one I assume you schedule for updating your objects. So why not just do :
- (void)tick:(ccTime)dt {
CGPoint pos = myObject.position;
pos.x = <desired x> + sin(angle);
pos.y = pos.y - y_acceleration * dt;
angle += dt * 360 * x_acceleration;
if (angle >= 360)
angle = ((int)angle) % 360;
myObject.position = pos;
}
And you can apply the same for the y axis of the object