How can I get the canvas context to follow a body in Box2dWeb? - box2dweb

I am uing Box2D.Dynamics.b2DebugDraw to render my Box2dWeb world. How can I get the canvas to remain centered on a moving body?
var debugDraw = new Box2D.Dynamics.b2DebugDraw();
debugDraw.SetSprite(document.getElementById("canvasElement").getContext("2d"));
debugDraw.SetDrawScale(30.0);
debugDraw.SetFillAlpha(0.3);
debugDraw.SetLineThickness(1.0);
debugDraw.SetFlags(Box2D.Dynamics.b2DebugDraw.e_shapeBit | Box2D.Dynamics.b2DebugDraw.e_jointBit);
world.SetDebugDraw(debugDraw);
And here in the loop that runs the simluation:
window.setInterval( function(){
_this.world.Step(
1 / 60 //frame-rate
, 10 //velocity iterations
, 10 //position iterations
);
_this.world.DrawDebugData();
_this.world.ClearForces();
}, 1000 / 60);

Translate the canvas element in the run loop:
var renderingContext = document.getElementById("canvasElement").getContext("2d");
debugDraw.SetSprite(renderingContext);
// .... snip .....
window.setInterval( function(){
// ...
renderingContext.translate(someX, someY); // <--- interesting line
_this.world.DrawDebugData();
_this.world.ClearForces();
}, 1000 / 60);

Related

How to get Node Coordinates Data in Sankey Graph

I'm trying to create something similar to the image below. Where each column has a heading with it.
I know that chart.renderer.text can be used to create & place custom text on chart. However, I'm unable to find a way to fetch the column/node coordinates data(x,y) which would help me place them.
Also is there a programmatic way to do this task. For example, a function that fetches all the columns coordinates and populates all the headings from an existing list.
To summarize:
How to fetch a columns (x,y) Coordinates?
How to dynamically place headings for all columns from a list?
Image
You can get the required coordinates and place the headers in render event, for example:
events: {
render: function() {
var chart = this,
series = chart.series[0],
columns = series.nodeColumns,
isFirst,
isLast,
xPos;
if (!series.customHeaders) {
series.customHeaders = [];
}
columns.forEach(function(column, i) {
xPos = column[0].nodeX + chart.plotLeft;
isFirst = i === 0;
isLast = i === columns.length - 1;
if (!series.customHeaders[i]) {
series.customHeaders.push(
chart.renderer.text(
headers[i],
xPos,
80
).attr({
translateX: isFirst ? 0 : (
isLast ?
series.options.nodeWidth :
series.options.nodeWidth / 2
),
align: isFirst ? 'left' : (
isLast ? 'right' : 'center'
)
}).add()
)
} else {
series.customHeaders[i].attr({
x: xPos
});
}
});
}
}
Live demo: https://jsfiddle.net/BlackLabel/6Lvdufbp/
API Reference:
https://api.highcharts.com/highcharts/chart.events.render
https://api.highcharts.com/class-reference/Highcharts.SVGRenderer#text

Highcharts: plotbands in gauge charts

I'm using plotbands in a gauge chart to represent angle ranges. But I'm facing a problem with the plotband angles when the "from" value is higher than the "to" value.
JSFiddle
As you can see, the plotband is set as from: 270, to: 45 but it really is rendered as if it was set as from: 45, to: 270. That renders exactly the oposite angle range that I need.
The only way that I can find to do that is setting two plotbands, one from 270 to 360 and another one from 0 to 45, but that seems very unconvenient.
Is there any easy way to achieve what I'm trying to do?
As I have mentioned in my comment, I think that you should be able to override getPlotBand method in your code for enabling plotBands with bigger from value than to value:
(function(H) {
H.wrap(H.Axis.prototype, 'init', function(proceed, chart, userOptions) {
this.getPlotBandPath = function(from, to, options) {
var center = this.center,
startAngleRad = this.startAngleRad,
pick = H.pick,
map = H.map,
pInt = H.pInt,
fullRadius = center[2] / 2,
radii = [
pick(options.outerRadius, '100%'),
options.innerRadius,
pick(options.thickness, 10)
],
offset = Math.min(this.offset, 0),
percentRegex = /%$/,
start,
end,
open,
isCircular = this.isCircular, // X axis in a polar chart
ret;
// Polygonal plot bands
if (this.options.gridLineInterpolation === 'polygon') {
ret = this.getPlotLinePath(from).concat(this.getPlotLinePath(to, true));
// Circular grid bands
} else {
// Keep within bounds
from = Math.max(from, this.min);
to = Math.min(to, this.max);
// Plot bands on Y axis (radial axis) - inner and outer radius depend on to and from
if (!isCircular) {
radii[0] = this.translate(from);
radii[1] = this.translate(to);
}
// Convert percentages to pixel values
radii = map(radii, function(radius) {
if (percentRegex.test(radius)) {
radius = (pInt(radius, 10) * fullRadius) / 100;
}
return radius;
});
// Handle full circle
if (options.shape === 'circle' || !isCircular) {
start = -Math.PI / 2;
end = Math.PI * 1.5;
open = true;
} else {
start = startAngleRad + this.translate(from);
end = startAngleRad + this.translate(to);
}
radii[0] -= offset; // #5283
radii[2] -= offset; // #5283
ret = this.chart.renderer.symbols.arc(
this.left + center[0],
this.top + center[1],
radii[0],
radii[0], {
start: start, // Math is for reversed yAxis (#3606)
end: end,
innerR: pick(radii[1], radii[0] - radii[2]),
open: open
}
);
}
return ret;
}
proceed.call(this, chart, userOptions);
});
}(Highcharts))
Live example:
http://jsfiddle.net/2Ljk7usL/9/

Why d3.scaleLinear domain impacts bars offset in V4?

I am drawing my first bar chart in d3.js, and quite surprised with the scaleLinear() impact on the bars position.
First I define the scale as:
var y = d3.scaleLinear()
.domain([0,100])
.range([0,300]);
Here is the result:
Actually, the domain is too large, 20 is sufficient. so I set:
var y = d3.scaleLinear()
.domain([0,20])
.range([0,300]);
Here is the result:
The bars are longer, which is correct, but they were pushed down, which is not. Here after is the complete script embedded in a Ruby on Rails 5.0 partial.
Can someone explain this behaviour and help me to solve the issue?
Thanks a lot.
The complete script:
<script>
// Progression data
var errors = <%= d3_chart_series_for(this_object).map { |measure| {index: measure.idx, count: measure.score}}.to_json.html_safe %>;
// Returns an array of hashes
// var errors = [{"index":"2017-01-14","count":"5.35"},{"index":"2017-01-15","count":"2.24"},{"index":"2017-01-16","count":"1.55"},{"index":"2017-01-17","count":"5.11"},{"index":"2017-01-18","count":"2.96"},{"index":"2017-01-19","count":"4.62"},{"index":"2017-01-20","count":"6.71"},{"index":"2017-01-21","count":"9.47"},{"index":"2017-01-22","count":"8.15"},{"index":"2017-01-23","count":"9.25"},{"index":"2017-01-24","count":"5.35"}];
var x = d3.scaleTime()
.domain([
new Date(Date.parse('<%=history_date.strftime("%Y-%m-%d")%>')),
new Date()
])
.range([0,500]);
var y = d3.scaleLinear()
.domain([0,100])
.range([0,300]);
var xAxis = d3.axisTop(x)
.ticks(10);
var yAxis = d3.axisRight(y)
.ticks(5);
var graph = d3.select('#progression')
.append('svg')
.attr('width', 650)
.attr('height', 400);
var bars = graph.selectAll('rect')
.data(errors);
var newBars = bars.enter();
graph.append('g')
.attr('class', 'x axis')
.attr("transform", "translate(100,50)")
.call(xAxis);
graph.append('g')
.attr('class', 'y axis')
.attr("transform", "translate(600,50)")
.call(yAxis);
newBars.append('rect')
.attr("transform", "translate(100,0)")
.attr('x',function(d, i) {
return x(Date.parse(d.index));})
.attr('y',y(20))
.attr('height', function(d,i) {
return y(d.count);})
.attr('width', 20 )
.attr('fill', d3.scale.category20());
</script>
This is the problem:
.attr('y',y(20))
When you do this for setting the y position of the bars you're not defining a fixed position, but a position that varies according to the domain. So, every time you change your domain, y(20) is mapped to a different value in the range.
Check this:
var y = d3.scaleLinear()
.domain([0, 100])
.range([50, 300]);
console.log(y(20));//returns 100
var y = d3.scaleLinear()
.domain([0, 20])
.range([50, 300]);
console.log(y(20));//returns 300
Solution: set the top of the bars to same y position of the x axis, which is 50 in your code:
.attr('y', 50)
If you want a padding, just add something to that value.
Here is a demo, with the y domain going from 0 to 20, as you requested:
var errors = [{"index":"2017-01-14","count":"5.35"},{"index":"2017-01-15","count":"2.24"},{"index":"2017-01-16","count":"1.55"},{"index":"2017-01-17","count":"5.11"},{"index":"2017-01-18","count":"2.96"},{"index":"2017-01-19","count":"4.62"},{"index":"2017-01-20","count":"6.71"},{"index":"2017-01-21","count":"9.47"},{"index":"2017-01-22","count":"8.15"},{"index":"2017-01-23","count":"9.25"},{"index":"2017-01-24","count":"5.35"}];
var parse = d3.timeParse("%Y-%m-%d");
errors.forEach(function(d){
d.index = parse(d.index);
});
var x = d3.scaleTime()
.domain(d3.extent(errors, function(d){ return d.index}))
.range([0,500]);
var y = d3.scaleLinear()
.domain([0,20])
.range([50,300]);
var xAxis = d3.axisTop(x)
.ticks(10);
var yAxis = d3.axisRight(y)
.ticks(5);
var graph = d3.select('body')
.append('svg')
.attr('width', 650)
.attr('height', 400);
var bars = graph.selectAll('rect')
.data(errors);
var newBars = bars.enter();
graph.append('g')
.attr('class', 'x axis')
.attr("transform", "translate(100,50)")
.call(xAxis);
graph.append('g')
.attr('class', 'y axis')
.attr("transform", "translate(600,50)")
.call(yAxis);
newBars.append('rect')
.attr("transform", "translate(100,0)")
.attr('x',function(d) {
return x(d.index);})
.attr('y', 50)
.attr('height', function(d,i) {
return y(d.count);})
.attr('width', 20 )
.attr('fill', "teal");
<script src="https://d3js.org/d3.v4.min.js"></script>
PS: bars and time scales don't mix very well... you can see that the bars start at the precise date, but their width makes them finishing at a wrong date. Move them to the left or, a better alternative, use another scale (as a band scale).

Printing in Openlayers 3 (pdf)

I have made a printing tools for openlayers 3 which prints in PDF format. Here is my code to print in pdf.
var dims = {
a0: [1189, 841],
a1: [841, 594],
a2: [594, 420],
a3: [420, 297],
a4: [297, 210],
a5: [210, 148]
};
var exportElement = document.getElementById('export-pdf');
exportElement.addEventListener('click', function(e) {
if (exportElement.className.indexOf('disabled') > -1) {
return;
}
exportElement.className += ' disabled';
var format = document.getElementById('format').value;
var resolution = document.getElementById('resolution').value;
var buttonLabelElement = document.getElementById('button-label');
var label = buttonLabelElement.innerText;
var dim = dims[format];
var width = Math.round(dim[0] * resolution / 25.4);
var height = Math.round(dim[1] * resolution / 25.4);
var size = /** #type {ol.Size} */ (map.getSize());
var extent = map.getView().calculateExtent(size);
map.once('postcompose', function(event) {
//var tileQueue = map.getTileQueue();
// To prevent potential unexpected division-by-zero
// behaviour, tileTotalCount must be larger than 0.
//var tileTotalCount = tileQueue.getCount() || 1;
var interval;
interval = setInterval(function() {
//var tileCount = tileQueue.getCount();
//var ratio = 1 - tileCount / tileTotalCount;
//buttonLabelElement.innerText = ' ' + (100 * ratio).toFixed(1) + '%';
//if (ratio == 1 && !tileQueue.getTilesLoading()) {
clearInterval(interval);
buttonLabelElement.innerText = label;
var canvas = event.context.canvas;
var data = canvas.toDataURL('image/jpeg');
var pdf = new jsPDF('landscape', undefined, format);
pdf.addImage(data, 'JPEG', 0, 0, dim[0], dim[1]);
pdf.save('map.pdf');
map.setSize(size);
map.getView().fitExtent(extent, size);
map.renderSync();
exportElement.className =
exportElement.className.replace(' disabled', '');
// }
}, 100);
});
map.setSize([width, height]);
map.getView().fitExtent(extent, /** #type {ol.Size} */ (map.getSize()));
map.renderSync();
}, false);
I can print in PDF when I have only OSM Layer but when I add local layers from my geoserver I can't print anything and the whole application is freezed.
Can anyone tell me what am I doing wrong here?
I am using jspdf to print pdf.
AJ
Your problem is that you load imagery from other domains, and haven't configured them for CORS. See https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image for a description on cross origin image use.
In order to get data out of the canvas, all images put into it must be from the same domain or transmitted with the appropriate Access-Control-Allow-Origin header.
I would investigate how to set up your server to serve the map imagery with those headers. You should also take a look at the crossOrigin option on your ol3 sources.
There is few solutions for CORS.
Very simple solution is to proxy OSM requests through your backend server (user <-> backend <-> OSM), but then we have little more server load.

Highcharts - Dyanmic graph with no initial data

If you open this JSFiddle with the dynamic spline update it loads the series with 20 points before it starts updating every second.
Example
I don't want to display any initial data and let the interval add the points as they come in.
So I change:
series: [{
name: 'Random data',
data: (function() {
// generate an array of random data
var data = [],
time = (new Date()).getTime(),
i;
for (i = -19; i <= 0; i++) {
data.push({
x: time + i * 1000,
y: Math.random()
});
}
return data;
})()
}]
to
series: [{
name: 'Random data',
data: []
}]
But it doesnt add the points. Is there something I am missing?
Change your load function so that the shift parameter doesn't apply before you've added your 20 values, see this jsfiddle
load: function() {
// set up the updating of the chart each second
var series = this.series[0],
maxSamples = 20,
count = 0;
setInterval(function() {
var x = (new Date()).getTime(), // current time
y = Math.random();
series.addPoint(
[x,y]
, true
, (++count >= maxSamples)
);
}, 1000);
}
The third parameter of addPoint is set if you what to shift a point after add this one.
So, what is happening ?
You're adding a point and then removing it.
Change:
series.addPoint([x, y], true, true);
To:
series.addPoint([x, y], true);
Demo
Reference
http://api.highcharts.com/highstock#Series.addPoint()

Resources