I am currently building a Vue application utilizing a map. The functionality is basically this. I have a position on the map and an algorythm to determine weather that position is over water or over land. If it is over water I want to reiterate over random positions nearby until my position is over land and return those coordinates. So basically my (very rudimentary for testing it out first) code looks similar to this
snippet from template
<mapbox-marker :lngLat="getPositionOverLand([0,5])" />
corresponding code
const getPositonOverLand = (coords: any, tries=100) => {
const newCoords = [coords[0], coords[1]] as [number, number];
let thisTry = 0;
while(isOverWater(newCoords) && thisTry < tries){
newCoords[0] = coords[0] + (Math.random() - 0.5)*0.5
newCoords[1] = coords[1] + (Math.random() - 0.5)*0.5
thisTry++;
}
if(thisTry === tries)
throw new Error('No position on land nearby could be found');
coords[0] = newCoords[0];
coords[1] = newCoords[1];
}
Now I debugged the algorythm and it works just fine. Usually after 5 or so attempts a position on land has been found and is returned. I also implemented a parameter for maximum tries so if a position on the open ocean e.g. is beeing put in an error is thrown after a certain number of attempts.
Now my problem in vue is this. If I change the coordinates in my function (it does not matter if I use a return statement or change the coordinates in the array) the function will be triggered again leaving me with an infinite loop.
Is there any possibility to prevent that behaviour and make sure the algorythm will only be run once or did I miss something else.
Strangly I also tried to just return the given coords just for testing and this does not cause an infinite loop.
Not sure what's triggering the reactivity. From the code posted, there is no way to tell since there are no reactive variables in sight, so it might be caused by something further upstream.
However, I see this: <mapbox-marker :lngLat="getPositionOverLand([0,5])" /> as a code smell when it comes to vue, because it has the potential to cause the type of issues you are seeing.
When you are passing a function result into as a parameter the function will re-run every time any of the parents triggers a re-render. And if you have anything in that function that triggers a re-render (which doesn't seem to be the case here, but I'm not seeing a return so I know it's not shown in entirety).
The better way to do this is to use a computed, or a data that gets updated through a watch that way the value is stored and a change is only made as needed.
Alright,
as pointed out already the problem is that every mutation on the position will trigger another reender (which still does not explain though why a direct return of the parameter itsself in the function didn't result in the same behaviour).
However I was able to solve my problem. Now I will post a little more code than included in my original question since another component with asynchronous behavior is involved. So my html looks like this
<mapbox-map :accessToken="myToken" #loaded="updateMyPosition">
<mapbox-marker :lngLat="myPosition" />
</mapbox-map>
Now the position needs to be a ref which will be updated once the map is loaded and available. This will ensure the data is only mutated once instead of the function beeing run over and over again.
I also changed the getPositionOverLand function a little bit but the functionality is basically the same as before. I just preferred the recursive variant over the while loop.
const myPosition = ref([10,10]);
const getPositionOverLand = (coords, currentTry=0, maxTries=100) => {
if(!isOverWater(coords))
return coords;
let newCoords = [
coords[0] + (Math.random() - 0.5)*0.5,
coords[1] + (Math.random() - 0.5)*0.5
];
if(currentTry >= maxTries)
throw new Error('No position on land could be found');
return (isOverWater(newCoords) && currentTry < maxTries)
? getPositonOverLand(coords, currentTry+1, maxTries)
: newCoords;
}
const updateMyPosition = map => {
myPosition.value = getPositionOverLand(myPosition.value);
}
Now while this is working I still think it is not pretty. I would prefer to use the function instead of mutation the ref because in that way working with a v-for for example would be significantly cleaner as it would be to iterate over that array in code and change each value.
For now I will make this the answer but if someone comes up with a cleaner way to do this I'd be more than happy to change the accepted answer ;)
Related
I'm a beginner on here, so apologies in advance for naivety. I've made a simple image on Brackets using Javascript, trying to generate circles with random x and y values, and random colours. There are no issues showing when I open the browser console in Developer Tools, and when I save and refresh, it works. But I was expecting the refresh to happen on a loop through the draw function. Any clues as to where I've gone wrong?
Thanks so much
var r_x
var r_y
var r_width
var r_height
var x
var y
var z
function setup()
{
r_x = random()*500;
r_y = random()*500;
r_width = random()*200;
r_height = r_width;
x = random(1,255);
y= random(1,255);
z= random(1,255);
createCanvas(512,512);
background(255);
}
function draw()
{
ellipse(r_x, r_y, r_width, r_height);
fill(x, y, z);
}
Brackets.io is just your text editor (or IDE if you want to be technical) - so we can remove that from the equation. The next thing that baffles me is that something has to explicitly call your draw() method as well as the setup() method -
I'm thinking that you're working in some sort of library created to simplify working with the Canvas API because in the setup() method you're calling createCanvas(xcord,ycord) and that doesn't exist on it's own. If you want to rabbit hole on that task check out this medium article, it walks you thru all the requirements for creating a canvas element and then drawing on that canvas
Your also confirming that you're drawing at least 1 circle on browser refresh so i think all you need to focus on is 1)initiating your code on load and 2)a loop, and we'll just accept there is magic running in the background that will handle everything else.
At the bottom of the file you're working in add this:
// when the page loads call drawCircles(),
// i changed the name to be more descriptive and i'm passing in the number of circles i want to draw,
// the Boolean pertains to event bubbling
window.addEventListener("load", drawCircles(73), false);
In your drawCircles() method you're going to need to add the loop:
// im using a basic for loop that requires 3 things:
// initialization, condition, evaluation
// also adding a parameter that will let you determine how many circles you want to draw
function drawCircles(numCircles) {
for (let i = 0; i < numCircles; i++) {
ellipse(r_x, r_y, r_width, r_height);
fill(x, y, z);
}
}
here's a link to a codepen that i was tinkering with a while back that does a lot of the same things you are
I hope that helps - good luck on your new learning venture, it's well worth the climb!
Thank you so much for your help! What you say makes sense - I basically deleted the equivalent amount of code from a little training exercise downloaded through coursera, thinking that I could then essentially use it as an empty sandpit to play in. But there's clearly far more going on under the hood!
Thanks again!
I'm working on a data visualization that has an odd little bug:
It's a little tricky to see, but essentially, when I click on a point in the line chart, that point corresponds to a specific issue of a magazine. The choropleth updates to reflect geodata for that issue, but, critically, the geodata is for a sampled period that corresponds to the issue. Essentially, the choropleth will look the same for any issue between January-June or July-December of a given year.
As you can see, I have a key called Sampled Issue Date (for Geodata), and the value should be the date of the issue for which the geodata is based on (basically, they would get geographical distribution for one specific issue and call it representative of ALL data in a six month period.) Yet, when I initially click on an issue, I'm always getting the last sampled date in my data. All of the geodata is correct, and, annoyingly, all subsequent clicks display the correct information. So it's only that first click (after refreshing the page OR clearing an issue) that I have a problem.
Honestly, my code is a nightmare right now because I'm focused on debugging, but you can see my reducer for the remove function on GitHub which is also copy/pasted below:
// Reducer function for raw geodata
function geoReducerAdd(p, v) {
// console.log(p.sampled_issue_date, v.sampled_issue_date, state.periodEnding, state.periodStart)
++p.count
p.sampled_mail_subscriptions += v.sampled_mail_subscriptions
p.sampled_single_copy_sales += v.sampled_single_copy_sales
p.sampled_total_sales += v.sampled_total_sales
p.state_population = v.state_population // only valid for population viz
p.sampled_issue_date = v.sampled_issue_date
return p
}
function geoReducerRemove(p, v) {
const currDate = new Date(v.sampled_issue_date)
// if(currDate.getFullYear() === 1921) {
// console.log(currDate)
// }
currDate <= state.periodEnding && currDate >= state.periodStart ? console.log(v.sampled_issue_date, p.sampled_issue_date) : null
const dateToRender = currDate <= state.periodEnding && currDate >= state.periodStart ? v.sampled_issue_date : p.sampled_issue_date
--p.count
p.sampled_mail_subscriptions -= v.sampled_mail_subscriptions
p.sampled_single_copy_sales -= v.sampled_single_copy_sales
p.sampled_total_sales -= v.sampled_total_sales
p.state_population = v.state_population // only valid for population viz
p.sampled_issue_date = dateToRender
return p
}
// generic georeducer
function geoReducerDefault() {
return {
count: 0,
sampled_mail_subscriptions: 0,
sampled_single_copy_sales: 0,
sampled_total_sales: 0,
state_population: 0,
sampled_issue_date: ""
}
}
The problem could be somewhere else, but I don't think it's a crossfilter issue (I'm not running into the "two groups from the same dimension" problem for sure) and adding additional logic to the add reducer makes things even less predictable (understandably - I don't ever really need to render the sample date for all values anyway.) The point of this is that I'm completely lost about where the flaw in my logic is, and I'd love some help!
EDIT: Note that the reducers are for the reduce method on a dc.js dimension, not the native javascript reducer! :D
Two crossfilters! Always fun to see that... but it can be tricky because nothing in dc.js directly supports that, except for the chart registry. You're on your own for filtering between different chart groups, and it can be tricky to map between data sets with different time resolutions and so on.
The problem
As I understand your app, when a date is selected in the line chart, the choropleth and accompanying text should have exactly one row from the geodata dataset selected per state.
The essential problem is that Crossfilter is not great at telling you which rows are in any given bin. So even though there's just one row selected, you don't know what it is!
This is the same problem that makes minimum, maximum, and median reductions surprisingly complicated. You often end up building new data structures to capture what crossfilter throws away in the name of efficiency.
A general solution
I'll go with a general solution that's more that you need, but can be helpful in similar situations. The only alternative that I know is to go completely outside crossfilter and look in the original dataset. That's fine too, and maybe more efficient. But it can be buggy and it's nice to work within the system.
So let's keep track of which dates we've seen per bin. When we start out, every bin will have all the dates. Once a date is selected, there will be only one date (but not exactly the one that was selected, because of your two-crossfilter setup).
Instead of the sampled_issue_date stuff, we'll keep track of an object called date_counts now:
// Reducer function for raw geodata
function geoReducerAdd(p, v) {
// ...
const canonDate = new Date(v.sampled_issue_date).getTime()
p.date_counts[canonDate] = (p.date_counts[canonDate] || 0) + 1
return p
}
function geoReducerRemove(p, v) {
// ...
const canonDate = new Date(v.sampled_issue_date).getTime()
if(!--p.date_counts[canonDate])
delete p.date_counts[canonDate]
return p
}
// generic georeducer
function geoReducerDefault() {
return {
// ...
date_counts: {}
}
}
What does it do?
Line by line
const canonDate = new Date(v.sampled_issue_date).getTime()
Maybe this is paranoid, but this canonicalizes the input dates by converting them to the number of milliseconds since 1970. I'm sure you'd be safe using the string dates directly, but who knows there could be a space or a zero or something.
You can't index an object with a date object, you have to convert it to an integer.
p.date_counts[canonDate] = (p.date_counts[canonDate] || 0) + 1
When we add a row, we'll check if we currently have a count for the row's date. If so, we'll use the count we have. Otherwise we'll default to zero. Then we'll add one.
if(!--p.date_counts[canonDate])
delete p.date_counts[canonDate]
When we remove a row, we know that we have a count for the date for that row (because crossfilter won't tell us it's removing the row unless it was added earlier). So we can go ahead and decrement the count. Then if it hits zero we can remove the entry.
Like I said, it's overkill. In your case, the count will only go to 1 and then drop to 0. But it's not much more expensive to this rather than just keep
Rendering the side panel
When we render the side panel, there should only be one date left in date_counts for that selected item.
console.assert(Object.keys(date_counts).length === 1) // only one entry
console.assert(Object.entries(date_counts)[0][1] === 1) // with count 1
document.getElementById('geo-issue-date').textContent = new Date(+Object.keys(date_counts)[0]).format('mmm dd, yyyy')
Usability notes
From a usability perspective, I would recommend not to filter(null) on mouseleave, or if you really want to, then put it on a timeout which gets cancelled when you see a mouseenter. One should be able to "scrub" over the line chart and see the changes over time in the choropleth without accidentally switching back to the unfiltered colors.
I also noticed (and filed) an issue because I noticed that dots to the right of the mouse pointer are shown, making them difficult to click. The reason is that the dots are overlapping, so only a little sliver of a crescent is hoverable. At least with my trackpad, the click causes the pointer to travel leftward. (I can see the date go back a week in the tooltip and then return.) It's not as much of a problem when you're zoomed in.
I have some nested React components, the inner of which is a large svg graph with hundreds if not thousands line and rect elements. To enable some application-wide behaviour and appearance changes I thought to change the className of the uppermost component. The problem is, that the whole application re-renders if I do that.
I understand that this behaviour is somewhat intended, in the sense of a unidirectional render flow, but I thought React would be smarter about what it can reuse and keep DOM changes to a minimum.
Minimal example here: https://jsbin.com/rabofewawu/1/edit?html,js,output
As you can see, the line pattern in the SVG changes every time you press 'here' though I only want the background color to change.
A similar, but more extreme, example arises when I try to zoom and pan the svg by changing the transform property of an inner g element. Using d3, I simply change the attribute. With react, my render function gets called, the updated state results in an updated transform property, and the whole group is re-renderd from scratch instead of changing the DOM attribute.
Am I missing something? Whats the React way to achieve what I'm trying to do?
Take this simple harmless looking render function:
render(){
return <div>{Date.now()}</div>;
}
In React's mental model this would always display the current number every millisecond. React conceptually updates infinite times per second. The inputs of render here are everything in the world, but we happen to just use the clock. Given the same world, we get the same output from render and thus it's idempotent.
Well crap... we don't have infinitely fast computers so we need to compromise. Instead of render's input being everything we restrict it to state and props (and context).
In this restricted setting use of Math.random or Date.now breaks the rules. If you need to use the output of these, it must go through state or props first. How would this look? Well we can use a deterministic random number generator and store the seed in state. Here's a modified version of your component doing that:
var MyComponent = React.createClass({
displayName:"MyComponent",
getInitialState(){
return {
seed: Math.floor(Math.random()*0xffffff)
};
},
render: function() {
// make a random number generator with the given seed
var rng = new Chance(this.state.seed);
function random(x){
return rng.floating({min: 0, max: x, fixed: 7})
}
var s=100, lines = [];
for (var i=0; i<100; i++) {
var line = { x1: random(s), y1: random(s), x2: random(s), y2: random(s) };
lines.push(React.createElement("line", line));
}
return React.createElement("svg", { height: s, width: s}, lines);
}
});
Rendering less than infinite times per second, and rendering only certain components is an optimization. Optimizations should not affect the behavior of the program.
If you wanted different random numbers, you could set the seed to a different random number. Using real Math.random() here is okay because state is the result or i/o operations in your code, and you're invoking this setState in response to some other i/o (e.g. a click handler).
Time is similar; if you want the current time to be rendered, use a setTimeout and setState with the current time. You can then display it how you like in render, including passing it down to other components and doing any kind of math on it you like.
You can control this with the component's shouldComponentUpdate function. By default this always returns true (so the component will always re-render).
There's a little bit of documentation for this function here https://facebook.github.io/react/docs/component-specs.html#updating-shouldcomponentupdate
All examples uses Ramda as _ (it's clear what methods do in examples contexts) and kefir as frp (almost same API as in bacon.js)
I have a stream, that describes change of position.
var xDelta = frp
.merge([
up.map(_.multiply(1)),
down.map(_.multiply(-1))
])
.sampledBy(frp.interval(10, 0))
.filter();
It emits +1 when I press UP key, and -1 on DOWN.
To get position I scan this delta
var x = xDelta
.scan(_.add)
.toProperty(0);
That's work as expected. But I want to limit value of x from 0 to 1000.
To solve this problem I found two solution:
Change function in scan
var x = xDelta.scan(function (prev, next) {
var newPosition = prev + next;
if (newPosition < 0 && next < 0) {
return prev;
}
if (newPosition > 1000 && next > 0) {
return prev;
}
return newPosition;
}, 0);
It looks Ok, but later, as new rules will be introduced, this method will grow. So I mean it doesn't look composable and FRPy.
I have current position. And delta. I want to apply delta to current, only if current after applying will not be out of limits.
current depends on delta
delta depends on current after applying
current after applying depends on current
So it looks like circular dependency. But I solved it using flatMap.
var xDelta = frp
.merge([
up.map(_.multiply(1)),
down.map(_.multiply(-1))
])
.sampledBy(frp.interval(10, 0))
.filter();
var possibleNewPlace = xDelta
.flatMap(function (delta) {
return x
.take(1)
.map(_.add(delta));
});
var outOfLeftBoundFilter = possibleNewPlace
.map(_.lte(0))
.combine(xDelta.map(_.lte(0)), _.or);
var outOfRightBoundFilter = possibleNewPlace
.map(_.gte(1000))
.combine(xDelta.map(_.gte(0)), _.or);
var outOfBoundFilter = frp
.combine([
outOfLeftBoundFilter,
outOfRightBoundFilter
], _.and);
var x = xDelta
.filterBy(outOfBoundFilter)
.scan(_.add)
.toProperty(0);
You can see full code example at iofjuupasli/capture-the-sheep-frp
And it's working demo gh-pages
It works, but using circular dependencies is probably anti-pattern.
Is there a better way to solve circular dependency in FRP?
The second more general question
With Controller it's possible to read some values from two Model and depending on it's values update both of them.
So dependencies looks like:
---> Model
Controller ---|
---> Model
With FRP there is no Controller. So Model value should be declaratively calculated from other Model. But what if Model1 calculating from another Model2 which is the same, so Model2 calculates from Model1?
Model ----->
<----- Model
For example two players with collision detection: both players have position and movement. And movement of first player depends on position of second, and vice versa.
I'm still newbie in all this stuff. It's not easy to start think in declarative FRP style after years of imperative coding. Probably I'm missing something.
using circular dependencies is probably anti-pattern
Yes and no. From the difficulties you had with implementing this, you can see that it's hard to create a circular dependency. Especially in a declarative way. However, if we want to use pure declarative style, we can see that circular dependencies are invalid. E.g. in Haskell you can declare let x = x + 1 - but it will evaluate to an exception.
current depends on delta, delta depends on current after applying,
current after applying depends on current
If you look closely, it doesn't. If this were a true circular dependency, current never had any value. Or threw an exception.
Instead, current does depend on its previous state. This is a well-known pattern in FRP, the stepper. Taking from this answer:
e = ((+) <$> b) <#> einput
b = stepper 0 e
Without knowing what <$> and <#> exactly do, you can probably tell how the events e and the behaviour ("property") b depend on the events einput. And much better, we can declaratively extend them:
e = ((+) <$> bound) <#> einput
bound = (min 0) <$> (max 1000) <$> b
b = stepper 0 e
This is basically what Bacon does in scan. Unfortunately it forces you to do all of this in a single callback function.
I haven't seen a stepper function in any JS FRP library1. In Bacon and Kefir, you'll probably have to use a Bus if you want to implement this pattern. I'd be happy to be proven wrong :-)
[1]: Well, except in the one I have implemented myself because of this (it's not presentable yet). But using Stepper still felt like jumping through hoops, as JavaScript doesn't support recursive declarations.
There is a new framework/library called cyclejs that works off exactly the circular mechanism you describe, but in that case for a webfrontend library similar to Facebook's new React.
The basic idea is to have a Model that is a stream of "state" values, a view stream that renders those, a user-interaction stream that emits the user interactions coming from the sideeffect of the view (the browser DOM) and an "intent" stream that creates high level events from the user and feeds into the model which creates new values.
It's still in early development, but it's a pretty neat idea and works well so far.
I'm fairly new to javascript and box2d, i was wondering if someone knows how i can call a custom function when two objects collide. I tried using some examples that uses the b2ContactListener without any succes. I've placed an object above another and let the standard Box2d physics do it's thing.
I recieve two console outputs, the first is null and the second is Ball with the following code:
var listener = new Box2D.Dynamics.b2ContactListener;
listener.BeginContact = function(contact) {
console.log(contact.GetFixtureA().GetBody().GetUserData());
console.log(contact.GetFixtureB().GetBody().GetUserData());
};.
The two objects that need to collide are a b2_dynamicbody (ball) and a b2PolygonShape. (rectangle). Using bodyDef.userData = "Ball"; in my Ball.js and bodyDef.userData = "Mouse"; in my Mouse.js i try to identify if they are hit. Instead only the ball is displayed.
Next to that i'm sure this is not the correct way for detecting collision :P I hope i've explained it well enough, could somebody steer me in the right direction?
Ok I solved it myself, apparently I had to add my custom event to the world I create with box2d. So, the issue was solved by me reading big chunks of box2d documentation/manual which can be found here:
I started with adding a String as UserData() to every object which can collide and has to do something else next to just colliding. Using the following code:
bodyDef.userData = "Car";
note: every object has to have it's own unique string.
Then I created a new contact listener (formulated in my question above) and listened for fixtures colliding with each other. When that happens, I 'save' the fixtures UserData() in variables which I can then use to look what objects collide with each other.
var contactListener = new Box2D.Dynamics.b2ContactListener;
contactListener.BeginContact = function(contact) {
var fixA = contact.GetFixtureA().GetBody().GetUserData();
var fixB = contact.GetFixtureB().GetBody().GetUserData();
// if else statement here...
};
world.SetContactListener(contactListener);
Finally, I added the last statement world.SetContactListener(contactListener); to add the event to the world, making it possible for it to listen to collisions, which I forgot to add and thus was my problem.
Hope someone finds this usefull!