Related
I'm trying to add a satellite image on my map using OpenLayers 5.
The problem is that I'm not able to do this, because I've just found an option to add an image on the map passing the image extent (xmin, ymin, xmax, ymax) and not the bounding box. The image should fit inside the bounding box. For that reason, the image was distorted.
The image is in JPG file (attribute feature.properties.icon). Example: http://exampleserver.com/220/063/353LGN00/353LGN00_thumb_large.jpg
The result that I would like is something like this:
The result that I've got was that:
My code that adds this image on the map is the following:
import ImageLayer from 'ol/layer/Image'
import Static from 'ol/source/ImageStatic'
...
this.olmap = new Map({
target: 'map',
layers: [
baseLayerGroup, rasterLayerGroup, vectorLayer
],
view: new View({
projection: 'EPSG:4326',
center: [ -45.8392, -3.65286 ],
zoom: 8
})
})
...
this.rasterLayerGroup.getLayers().push(
new ImageLayer({
source: new Static({
url: feature.properties.icon,
projection: 'EPSG:4326',
imageExtent: [
feature.properties.bl_longitude, feature.properties.bl_latitude,
feature.properties.tr_longitude, feature.properties.tr_latitude
]
})
})
)
Would someone know how to pass the image bounding box instead of just the image extent?
Thank you in advance.
EDIT 1: Mike's solution
Through Mike's solution I was able to fix a bug that some images have (near to the equator line). For that reason, his answer solved my problem and it inserted the image in a better position that I was expecting in the moment that I created the question.
However, this solution worked to me with images near to the equator line. Images next to the poles stay distorted (Edit 2).
I send below a picture illustrating the final result:
EDIT 2: New problem?
I was testing some images and I have discovered a new bug. Now I have discovered that the image should fit inside the bounding box. If the image does not fit inside the bbox, it stays distorted, such as the print that I send below illustrating.
The image should fit inside the bbox like in the image below [PS 1]:
I believe that it can be a problem of reprojection, but I don't know, because both view projection and image projection is EPSG:4326.
I tried to follow the explanation about Raster Reprojection[1.] on Openlayers site, however I was not able to reproduce it, because, as I said, both projections (view and image) are the same (or they should be).
I send below the GeoJSON that contains the information related to the image above. The image can be found in "properties.icon" (http://www.dpi.inpe.br/newcatalog/tmp/MOD13Q1/2018/MOD13Q1.A2018017.h13v14.jpg). The bbox coordinates can be found in "geometry.coordinates" or in "properties.bl_latitude", "properties.bl_longitude", "properties.br_latitude" and so on.
"bl" means "bottom left", "br" means "bottom right", "tl" means "top left" and "tr" means "top right". These coordinates inside "properties" are the same inside "geometry.coordinates".
{
"geometry": {
"coordinates": [
[
[
-77.7862,
-50
],
[
-100,
-60
],
[
-80,
-60
],
[
-62.229,
-50
],
[
-77.7862,
-50
]
]
],
"type": "Polygon"
},
"properties": {
"alternate": "http://www.dpi.inpe.br/opensearch/v2/granule.json?uid=MOD13Q1.A2018017.h13v14",
"auxpath": null,
"bitslips": null,
"bl_latitude": -60,
"bl_longitude": -100,
"br_latitude": -60,
"br_longitude": -80,
"centerlatitude": -55,
"centerlongitude": -80.0038,
"centertime": null,
"cloud": 0,
"cloudcovermethod": "M",
"dataset": "MOD13Q1",
"date": "2018-01-17T00:00:00",
"enclosure": [
{
"band": "evi",
"radiometric_processing": "SR",
"type": "MOSAIC",
"url": "http://www.dpi.inpe.br/newcatalog/tmp/MOD13Q1/2018/MOD13Q1.A2018017.h13v14.006.2018033223827.hdf"
},
{
"band": "ndvi",
"radiometric_processing": "SR",
"type": "MOSAIC",
"url": "http://www.dpi.inpe.br/newcatalog/tmp/MOD13Q1/2018/MOD13Q1.A2018017.h13v14.006.2018033223827.hdf"
},
...
],
"icon": "http://www.dpi.inpe.br/newcatalog/tmp/MOD13Q1/2018/MOD13Q1.A2018017.h13v14.jpg",
"id": "http://www.dpi.inpe.br/opensearch/v2/granule.json?uid=MOD13Q1.A2018017.h13v14",
"orbit": 0,
"path": 14,
"provider": "OP_CBERS1",
"row": 13,
"satellite": "T1",
"sensor": "MODIS",
"title": "MOD13Q1.A2018017.h13v14",
"tl_latitude": -50,
"tl_longitude": -77.7862,
"tr_latitude": -50,
"tr_longitude": -62.229,
"type": "IMAGES",
"updated": "2018-03-01T18:51:56",
"via": "http://www.dpi.inpe.br/opensearch/v2/metadata/MOD13Q1.A2018017.h13v14"
},
"type": "Feature"
}
Would someone have a new idea?
[PS 1]: The original code that does the image fits inside the bbox is a Leaflet code [2.] and I send it below:
var map = L.map('map').setView([-15.22, -53.23], 5)
...
var anchor = [
[feature.properties.tl_latitude, feature.properties.tl_longitude],
[feature.properties.tr_latitude, feature.properties.tr_longitude],
[feature.properties.br_latitude, feature.properties.br_longitude],
[feature.properties.bl_latitude, feature.properties.bl_longitude]
]
layer._quicklook = L.imageTransform(feature.properties.icon, anchor).addTo(map)
[1.] https://openlayers.org/en/latest/doc/tutorials/raster-reprojection.html
[2.] https://github.com/ScanEx/Leaflet.imageTransform
If the coordinates are those of the photo and the jpg which contains the rotated photo is in EPSG:4326 (i.e. aligned to meridians and parallels) then you need a bounding box containing all of the corners of the photo
import {boundingExtent} from 'ol/extent';
....
this.rasterLayerGroup.getLayers().push(
new ImageLayer({
source: new Static({
url: feature.properties.icon,
projection: 'EPSG:4326',
imageExtent: boundingExtent([
[feature.properties.bl_longitude, feature.properties.bl_latitude],
[feature.properties.br_longitude, feature.properties.br_latitude],
[feature.properties.tl_longitude, feature.properties.tl_latitude],
[feature.properties.tr_longitude, feature.properties.tr_latitude]
])
})
})
)
However your top screenshot has the jpg itself rotated. If that is desired the projection isn't EPSG:4326 and you would need to define a custom projection to handle the rotation.
I've managed to get something close, but simply stretching the image to fit the polygon doesn't give the exact alignment at the side that the leaflet method does
var properties = {
"bl_latitude": -60,
"bl_longitude": -100,
"br_latitude": -60,
"br_longitude": -80,
"centerlatitude": -55,
"centerlongitude": -80.0038,
"icon": "https://www.mikenunn.net/demo/MOD13Q1.A2018017.h13v14.jpg",
"tl_latitude": -50,
"tl_longitude": -77.7862,
"tr_latitude": -50,
"tr_longitude": -62.229,
};
function overlaySource ( properties ) {
var projection = ol.proj.get('EPSG:3857'); // leaflet projection
var extentSize = [0, 0, 4096, 4096]; // arbitary extent for the projection transforms
var size0 = extentSize[2];
var size1 = extentSize[3];
var url = properties.icon;
var bl = ol.proj.transform([properties.bl_longitude, properties.bl_latitude], 'EPSG:4326', projection);
var tl = ol.proj.transform([properties.tl_longitude, properties.tl_latitude], 'EPSG:4326', projection);
var br = ol.proj.transform([properties.br_longitude, properties.br_latitude], 'EPSG:4326', projection);
var tr = ol.proj.transform([properties.tr_longitude, properties.tr_latitude], 'EPSG:4326', projection);
function normalTransform(coordinates, output, dimensions) {
var dims = dimensions || 2;
for (var i=0; i<coordinates.length; i+=dims) {
var left = bl[0] + (tl[0]-bl[0]) * coordinates[i+1]/size1;
var right = br[0] + (tr[0]-br[0]) * coordinates[i+1]/size1;
var top = tl[1] + (tr[1]-tl[1]) * coordinates[i]/size0;
var bottom = bl[1] + (br[1]-bl[1]) * coordinates[i]/size0;
var newCoordinates0 = left + (right-left) * coordinates[i]/size0;
var newCoordinates1 = bottom + (top-bottom) * coordinates[i+1]/size1;
c = ol.proj.transform([newCoordinates0, newCoordinates1], projection, 'EPSG:3857');
//console.log(coordinates[i] + ' ' + coordinates[i+1] + ' ' + c[0] + ' ' + c[1]);
coordinates[i] = c[0];
coordinates[i+1] = c[1];
}
return coordinates;
}
function rotateTransform(coordinates, output, dimensions) {
var dims = dimensions || 2;
for (var i=0; i<coordinates.length; i+=dims) {
c = ol.proj.transform([coordinates[i], coordinates[i+1]], 'EPSG:3857', projection);
var left = bl[0] + (tl[0]-bl[0]) * (c[1]-bl[1]) /(tl[1]-bl[1]);
var right = br[0] + (tr[0]-br[0]) * (c[1]-br[1]) /(tr[1]-br[1]);
var top = tl[1] + (tr[1]-tl[1]) * (c[0]-tl[0])/(tr[0]-tl[0]);
var bottom = bl[1] + (br[1]-bl[1]) * (c[0]-bl[0])/(br[0]-bl[0]);
var newCoordinates0 = (c[0]-left)*size0/(right-left);
var newCoordinates1 = (c[1]-bottom)*size1/(top-bottom);
//console.log(coordinates[i] + ' ' + coordinates[i+1] + ' ' + newCoordinates0 + ' ' + newCoordinates1);
coordinates[i] = newCoordinates0;
coordinates[i+1] = newCoordinates1;
}
return coordinates;
}
var rotatedProjection = new ol.proj.Projection({
code: 'EPSG:' + url + 'rotated',
units: 'm',
extent: extentSize
});
ol.proj.addProjection(rotatedProjection);
ol.proj.addCoordinateTransforms('EPSG:3857', rotatedProjection,
function(coordinate) {
return rotateTransform(coordinate);
},
function(coordinate) {
return normalTransform(coordinate);
}
);
ol.proj.addCoordinateTransforms('EPSG:4326', rotatedProjection,
function(coordinate) {
return rotateTransform(ol.proj.transform(coordinate, "EPSG:4326", "EPSG:3857"));
},
function(coordinate) {
return ol.proj.transform(normalTransform(coordinate), "EPSG:3857", "EPSG:4326");
}
);
return new ol.source.ImageStatic({
projection: rotatedProjection,
imageExtent: extentSize,
url: url
});
}
var tileLayer = new ol.layer.Tile({
source: new ol.source.XYZ({
attributions: [
'Powered by Esri',
'Source: Esri, DigitalGlobe, GeoEye, Earthstar Geographics, CNES/Airbus DS, USDA, USGS, AeroGRID, IGN, and the GIS User Community'
],
//attributionsCollapsible: false,
url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
maxZoom: 23
})
});
var imageLayer = new ol.layer.Image({
source: overlaySource( properties ),
opacity: 0.7
})
var map = new ol.Map({
layers: [tileLayer, imageLayer],
target: 'map',
logo: false,
view: new ol.View()
});
var imageProj = imageLayer.getSource().getProjection();
map.getView().fit(ol.proj.transformExtent(imageProj.getExtent(), imageProj, map.getView().getProjection()), {constrainResolution: false});
html, body, .map {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
<link href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css" rel="stylesheet" />
<script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.5.0/proj4.js"></script>
<div id="map" class="map"></div>
This is my KML method, but that is a simple rotation of a rectangle by a specified angle, not warping it into a quadrilateral where only two of the sides are parallel.
function kmlOverlaySource ( kmlExtent, // KMLs specify the extent the unrotated image would occupy
url,
rotation,
imageSize,
) {
// calculate latitude of true scale of equidistant cylindrical projection based on pixels per degree on each axis
proj4.defs('EPSG:' + url, '+proj=eqc +lat_ts=' +
(Math.acos((ol.extent.getHeight(kmlExtent)/imageSize[1])
/(ol.extent.getWidth(kmlExtent)/imageSize[0]))*180/Math.PI) +
' +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs');
if (ol.proj.proj4 && ol.proj.proj4.register) { ol.proj.proj4.register(proj4); } // if OL5 register proj4
// convert the extents to source projection coordinates
var projection = ol.proj.get('EPSG:' + url);
var projExtent = ol.proj.transformExtent(kmlExtent, 'EPSG:4326', projection);
var angle = -rotation * Math.PI/180;
function rotateTransform(coordinates, output, dimensions) {
var dims = dimensions || 2;
for (var i=0; i<coordinates.length; i+=dims) {
var point = new ol.geom.Point([coordinates[i],coordinates[i+1]]);
point.rotate(angle, ol.extent.getCenter(projExtent));
var newCoordinates = point.getCoordinates();
coordinates[i] = newCoordinates[0];
coordinates[i+1] = newCoordinates[1];
}
return coordinates;
}
function normalTransform(coordinates, output, dimensions) {
var dims = dimensions || 2;
for (var i=0; i<coordinates.length; i+=dims) {
var point = new ol.geom.Point([coordinates[i],coordinates[i+1]]);
point.rotate(-angle, ol.extent.getCenter(projExtent));
var newCoordinates = point.getCoordinates();
coordinates[i] = newCoordinates[0];
coordinates[i+1] = newCoordinates[1];
}
return coordinates;
}
var rotatedProjection = new ol.proj.Projection({
code: 'EPSG:' + url + 'rotated',
units: 'm',
extent: projExtent
});
ol.proj.addProjection(rotatedProjection);
ol.proj.addCoordinateTransforms('EPSG:4326', rotatedProjection,
function(coordinate) {
return rotateTransform(ol.proj.transform(coordinate, 'EPSG:4326', projection));
},
function(coordinate) {
return ol.proj.transform(normalTransform(coordinate), projection, 'EPSG:4326');
}
);
ol.proj.addCoordinateTransforms('EPSG:3857', rotatedProjection,
function(coordinate) {
return rotateTransform(ol.proj.transform(coordinate, 'EPSG:3857', projection));
},
function(coordinate) {
return ol.proj.transform(normalTransform(coordinate), projection, 'EPSG:3857');
}
);
return new ol.source.ImageStatic({
projection: rotatedProjection,
url: url,
imageExtent: projExtent
});
}
var tileLayer = new ol.layer.Tile({
source: new ol.source.XYZ({
attributions: [
'Powered by Esri',
'Source: Esri, DigitalGlobe, GeoEye, Earthstar Geographics, CNES/Airbus DS, USDA, USGS, AeroGRID, IGN, and the GIS User Community'
],
//attributionsCollapsible: false,
url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
maxZoom: 23
})
});
// these would normally be parsed from a KML file
var kmlExtent = [8.433995415151397, 46.65804355828784, 9.144871415151389, 46.77980155828784];
var url = 'https://raw.githubusercontent.com/ReneNyffenegger/about-GoogleEarth/master/kml/the_png_says.png'
var rotation = 30;
var imageSize = [];
var img = document.createElement('img');
img.onload = imageLoaded;
img.src = url;
function imageLoaded() {
imageSize[0] = img.width;
imageSize[1] = img.height;
var imageLayer = new ol.layer.Image({
source: kmlOverlaySource(kmlExtent, url, rotation, imageSize),
});
var map = new ol.Map({
layers: [tileLayer, imageLayer],
target: 'map',
logo: false,
view: new ol.View()
});
var imageProj = imageLayer.getSource().getProjection();
map.getView().fit(ol.proj.transformExtent(imageProj.getExtent(), imageProj, map.getView().getProjection()), {constrainResolution: false});
}
html, body, .map {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
<link href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css" rel="stylesheet" />
<script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.5.0/proj4.js"></script>
<div id="map" class="map"></div>
I need some help with my code. I want to make the str sprite invisible after 10 seconds.
I'm using this link for help: http://phaser.io/examples/v2/time/basic-timed-event
var game = new Phaser.Game(500, 550, Phaser.AUTO);
//var picture;
var Pacman = function (game) {
this.map = null;
this.layer = null;
this.pacman = null;
this.safetile = 14;
this.gridsize = 16;
this.speed = 100;
this.threshold = 3;
this.turnSpeed = 200;
this.marker = new Phaser.Point();
this.turnPoint = new Phaser.Point();
this.directions = [ null, null, null, null, null ];
this.opposites = [ Phaser.NONE, Phaser.RIGHT, Phaser.LEFT, Phaser.DOWN, Phaser.UP ];
this.current = Phaser.NONE;
this.turning = Phaser.NONE;
this.score=0;
this.scoreText='';
this.bonus=0;
this.bonusText='';
};
Pacman.prototype = {
init: function () {
this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
this.scale.pageAlignHorizontally = true;
this.scale.pageAlignVertically = true;
Phaser.Canvas.setImageRenderingCrisp(this.game.canvas);
this.physics.startSystem(Phaser.Physics.ARCADE);
},
preload: function () {
// We need this because the assets are on github pages
// Remove the next 2 lines if running locally
this.load.baseURL = 'https://nikosdaskalos.github.io/pacman/';
this.load.crossOrigin = 'anonymous';
this.load.image('str', 'assets/str.png');
this.load.image('dot', 'assets/dot.jpg');
this.load.image('coin', 'assets/coin.jpg');
this.load.image('tiles', 'assets/pacman-tiles.png');
this.load.spritesheet('pacman', 'assets/pacman.png');
this.load.tilemap('map', 'assets/pacman-map.json', null, Phaser.Tilemap.TILED_JSON);
// Needless to say, graphics (C)opyright Namco
},
create: function () {
this.map = this.add.tilemap('map');
this.map.addTilesetImage('pacman-tiles', 'tiles');
this.layer = this.map.createLayer('Pacman');
//picture = game.add.sprite(game.world.centerX, game.world.centerY, 'str');
//picture.anchor.setTo(0.5, 0.5);
//game.time.events.add(Phaser.Timer.SECOND * 4, fadePicture, this);
this.dots = this.add.physicsGroup();
this.map.createFromTiles(36, this.safetile, 'str', this.layer, this.dots);
this.map.createFromTiles(7, this.safetile, 'dot', this.layer, this.dots);
this.map.createFromTiles(35, this.safetile, 'coin', this.layer, this.dots);
// The dots will need to be offset by 6px to put them back in the middle of the grid
this.dots.setAll('x', 6, false, false, 1);
this.dots.setAll('y', 6, false, false, 1);
// Pacman should collide with everything except the safe tile
this.map.setCollisionByExclusion([this.safetile], true, this.layer);
// Position Pacman at grid location 14x17 (the +8 accounts for his anchor)
this.pacman = this.add.sprite((14 * 16) + 8, (17 * 16) + 8, 'pacman', 0);
this.pacman.anchor.set(0.5);
//this.pacman.animations.add('munch', [0, 1, 2, 1], 20, true);
this.physics.arcade.enable(this.pacman);
this.pacman.body.setSize(16, 16, 0, 0);
this.cursors = this.input.keyboard.createCursorKeys();
//this.pacman.play('munch');
this.move(Phaser.LEFT);
this.scoreText = game.add.text(0, 500, 'Score: 0', { fontSize: '34px Arial', fill: 'white' });
lives = game.add.group();
game.add.text(game.world.width - 340, 500, 'Lives : ', { fontSize: '34px Arial', fill: 'white' });
this.bonusText = game.add.text(360, 500, 'Bonus: 0', { fontSize: '20px Arial', fill: 'white' });
for (var i = 0; i < 3; i++)
{
var pacman = lives.create(game.world.width - 235 + (30 * i), 517, 'pacman');
pacman.anchor.setTo(0.5, 0.5);
pacman.angle = 90;
pacman.alpha = 0.4;
}
},
//function fadePicture() {
//game.add.tween(picture).to( { alpha: 0 }, 2000, Phaser.Easing.Linear.None, true);
//},
checkKeys: function () {
if (this.cursors.left.isDown && this.current !== Phaser.LEFT)
{
this.checkDirection(Phaser.LEFT);
}
else if (this.cursors.right.isDown && this.current !== Phaser.RIGHT)
{
this.checkDirection(Phaser.RIGHT);
}
else if (this.cursors.up.isDown && this.current !== Phaser.UP)
{
this.checkDirection(Phaser.UP);
}
else if (this.cursors.down.isDown && this.current !== Phaser.DOWN)
{
this.checkDirection(Phaser.DOWN);
}
else
{
// This forces them to hold the key down to turn the corner
this.turning = Phaser.NONE;
}
},
checkDirection: function (turnTo) {
if (this.turning === turnTo || this.directions[turnTo] === null || this.directions[turnTo].index !== this.safetile)
{
// Invalid direction if they're already set to turn that way
// Or there is no tile there, or the tile isn't index 1 (a floor tile)
return;
}
// Check if they want to turn around and can
if (this.current === this.opposites[turnTo])
{
this.move(turnTo);
}
else
{
this.turning = turnTo;
this.turnPoint.x = (this.marker.x * this.gridsize) + (this.gridsize / 2);
this.turnPoint.y = (this.marker.y * this.gridsize) + (this.gridsize / 2);
}
},
turn: function () {
var cx = Math.floor(this.pacman.x);
var cy = Math.floor(this.pacman.y);
// This needs a threshold, because at high speeds you can't turn because the coordinates skip past
if (!this.math.fuzzyEqual(cx, this.turnPoint.x, this.threshold) || !this.math.fuzzyEqual(cy, this.turnPoint.y, this.threshold))
{
return false;
}
// Grid align before turning
this.pacman.x = this.turnPoint.x;
this.pacman.y = this.turnPoint.y;
this.pacman.body.reset(this.turnPoint.x, this.turnPoint.y);
this.move(this.turning);
this.turning = Phaser.NONE;
return true;
},
move: function (direction) {
var speed = this.speed;
if (direction === Phaser.LEFT || direction === Phaser.UP)
{
speed = -speed;
}
if (direction === Phaser.LEFT || direction === Phaser.RIGHT)
{
this.pacman.body.velocity.x = speed;
}
else
{
this.pacman.body.velocity.y = speed;
}
// Reset the scale and angle (Pacman is facing to the right in the sprite sheet)
/* this.pacman.scale.x = 1;
this.pacman.angle = 0;
if (direction === Phaser.LEFT)
{
this.pacman.scale.x = -1;
}
else if (direction === Phaser.UP)
{
this.pacman.angle = 270;
}
else if (direction === Phaser.DOWN)
{
this.pacman.angle = 90;
}
this.current = direction;
},*/
this.add.tween(this.pacman).to( { angle: this.getAngle(direction) }, this.turnSpeed, "Linear", true);
this.current = direction;
},
getAngle: function (to) {
// About-face?
if (this.current === this.opposites[to])
{
return "180";
}
if ((this.current === Phaser.UP && to === Phaser.LEFT) ||
(this.current === Phaser.DOWN && to === Phaser.RIGHT) ||
(this.current === Phaser.LEFT && to === Phaser.DOWN) ||
(this.current === Phaser.RIGHT && to === Phaser.UP))
{
return "-90";
}
return "90";
},
eatDot: function (pacman, dot) {
dot.kill();
var audio = new Audio('assets/pacman_chomp.wav');
audio.play()
this.score+=10;
this.scoreText.text= 'Score: ' + this.score;
//setTimeout(audio.play(),2000);
if (this.dots.total === 0)
{
this.dots.callAll('revive');
}
},
/*function muteAudio() {
var audio = document.getElementById('audioPlayer');
if (audio.mute = false) {
document.getElementById('audioPlayer').muted = true;
}
else {
audio.mute = true
document.getElementById('audioPlayer').muted = false;
}
}*/
eatCoin: function(pacman,coin){//pente
coin.kill();
this.score+=20;
this.scoreText.text= 'Score: ' + this.score;
var audio = new Audio('assets/pacman_eatfruit.wav');
audio.play()
if(this.coins.total===0)
{
this.coins.callAll('revive');
}
},
eatStr: function(pacman,str){//pente
str.kill();
this.bonus+=100;
this.bonusText.text= 'Bonus: ' + this.bonus;
var audio = new Audio('assets/pacman_eatfruit.wav');
audio.play()
if(this.strs.total===0)
{
this.strs.callAll('revive');
}
},
update: function () {
this.physics.arcade.collide(this.pacman, this.layer);
this.physics.arcade.overlap(this.pacman, this.dots, this.eatDot, null, this);
this.physics.arcade.overlap(this.pacman, this.dots, this.eatCoin, null, this);
this.physics.arcade.overlap(this.pacman, this.dots, this.eatStr, null, this);
this.marker.x = this.math.snapToFloor(Math.floor(this.pacman.x), this.gridsize) / this.gridsize;
this.marker.y = this.math.snapToFloor(Math.floor(this.pacman.y), this.gridsize) / this.gridsize;
// Update our grid sensors
this.directions[1] = this.map.getTileLeft(this.layer.index, this.marker.x, this.marker.y);
this.directions[2] = this.map.getTileRight(this.layer.index, this.marker.x, this.marker.y);
this.directions[3] = this.map.getTileAbove(this.layer.index, this.marker.x, this.marker.y);
this.directions[4] = this.map.getTileBelow(this.layer.index, this.marker.x, this.marker.y);
this.checkKeys();
if (this.turning !== Phaser.NONE)
{
this.turn();
}
}
};
game.state.add('Game', Pacman, true);
</script>
</body>
</html>
So, can anyone help me with making the str invisible after 10 seconds?
You were pretty close.
I'm going to use pacman as an example, since I'm not 100% sure how you were going to fade out the coins. When Pacman eats them?
First, you want the following fadePicture function:
fadePicture: function() {
game.add.tween(this.pacman).to( { alpha: 0 }, 2000, Phaser.Easing.Linear.None, true);
},
Note how that was changed from function fadePicture().
Next, in your create function you can refer to it:
this.game.time.events.add(Phaser.Timer.SECOND * 4, this.fadePicture, this);
That replaces the following:
//game.time.events.add(Phaser.Timer.SECOND * 4, fadePicture, this);
I've created a JSFiddle with the relevant changes.
If you do want your coins to fade then I would try updating fadePicture to accept a dot/str parameter and then game.add.tween on that.
I am displaying data in graphical format and I am using Morris.Bar function in my cshtml page. The Y axis has categories namely: Performance, Maintainability, Others, Portability, Reliability and Security.
I am using the following function:
Morris.Bar({
element: 'category-bar-chart',
data: JSON.parse(''[{"y":"Performance","a":23},{"y":"Maintainability","a":106},{"y":"Others","a":98},{"y":"Portability","a":27},{"y":"Reliability","a":87},{"y":"Security","a":14}]'),'),
xkey: 'y',
ykeys: ['a'],
labels: ['Violation'],
xLabelAngle: 43,
});
But currently it is not displaying the value for each category at the top of each bar. May I know what property I can add to get the values at the top of each bar?
There's no built-in parameter to display the value on top of each Bar.
But you can extend Morris to add this parameter. I've extended Morris, adding a labelTop property for Bar charts. If set to true, a label with the value is added on top of each Bar (I restricted this property for non stacked Bar, as there's multiple values with stacked Bar).
Usage:
labelTop: true
Please try the snippet below to see a working example:
(function() {
var $, MyMorris;
MyMorris = window.MyMorris = {};
$ = jQuery;
MyMorris = Object.create(Morris);
MyMorris.Bar.prototype.defaults["labelTop"] = false;
MyMorris.Bar.prototype.drawLabelTop = function(xPos, yPos, text) {
var label;
return label = this.raphael.text(xPos, yPos, text).attr('font-size', this.options.gridTextSize).attr('font-family', this.options.gridTextFamily).attr('font-weight', this.options.gridTextWeight).attr('fill', this.options.gridTextColor);
};
MyMorris.Bar.prototype.drawSeries = function() {
var barWidth, bottom, groupWidth, idx, lastTop, left, leftPadding, numBars, row, sidx, size, spaceLeft, top, ypos, zeroPos;
groupWidth = this.width / this.options.data.length;
numBars = this.options.stacked ? 1 : this.options.ykeys.length;
barWidth = (groupWidth * this.options.barSizeRatio - this.options.barGap * (numBars - 1)) / numBars;
if (this.options.barSize) {
barWidth = Math.min(barWidth, this.options.barSize);
}
spaceLeft = groupWidth - barWidth * numBars - this.options.barGap * (numBars - 1);
leftPadding = spaceLeft / 2;
zeroPos = this.ymin <= 0 && this.ymax >= 0 ? this.transY(0) : null;
return this.bars = (function() {
var _i, _len, _ref, _results;
_ref = this.data;
_results = [];
for (idx = _i = 0, _len = _ref.length; _i < _len; idx = ++_i) {
row = _ref[idx];
lastTop = 0;
_results.push((function() {
var _j, _len1, _ref1, _results1;
_ref1 = row._y;
_results1 = [];
for (sidx = _j = 0, _len1 = _ref1.length; _j < _len1; sidx = ++_j) {
ypos = _ref1[sidx];
if (ypos !== null) {
if (zeroPos) {
top = Math.min(ypos, zeroPos);
bottom = Math.max(ypos, zeroPos);
} else {
top = ypos;
bottom = this.bottom;
}
left = this.left + idx * groupWidth + leftPadding;
if (!this.options.stacked) {
left += sidx * (barWidth + this.options.barGap);
}
size = bottom - top;
if (this.options.verticalGridCondition && this.options.verticalGridCondition(row.x)) {
this.drawBar(this.left + idx * groupWidth, this.top, groupWidth, Math.abs(this.top - this.bottom), this.options.verticalGridColor, this.options.verticalGridOpacity, this.options.barRadius, row.y[sidx]);
}
if (this.options.stacked) {
top -= lastTop;
}
this.drawBar(left, top, barWidth, size, this.colorFor(row, sidx, 'bar'), this.options.barOpacity, this.options.barRadius);
_results1.push(lastTop += size);
if (this.options.labelTop && !this.options.stacked) {
label = this.drawLabelTop((left + (barWidth / 2)), top - 10, row.y[sidx]);
textBox = label.getBBox();
_results.push(textBox);
}
} else {
_results1.push(null);
}
}
return _results1;
}).call(this));
}
return _results;
}).call(this);
};
}).call(this);
Morris.Bar({
element: 'category-bar-chart',
data: [
{ "y": "Performance", "a": 23 },
{ "y": "Maintainability", "a": 106 },
{ "y": "Others", "a": 98 },
{ "y": "Portability", "a": 27 },
{ "y": "Reliability", "a": 87 },
{ "y": "Security", "a": 14 }],
xkey: 'y',
ykeys: ['a'],
labels: ['Violation'],
xLabelAngle: 43,
labelTop: true
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.min.js"></script>
<link href="//cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.css" rel="stylesheet" />
<div id="category-bar-chart"></div>
I'm using KendoGrid to display some data fetched from my service.
The user selects some parameters (company and date) and cliks on a load button.
The user selects a month on a datePicker and the server will return data from that date plus 11 months.
I only display the grid after the user click on the load button.
Load function:
function loadGrid(e) {
var companyIds = [1, 3, 7]; // user select it
var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
var rowHeaders = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K"];
var _dataSource = function () {
var dataSource = new kendo.data.DataSource({
transport: {
read: {
url: URL,
dataType: "json",
data: {
companyIds: companyIds,
date: kendo.toString(picker.value(), "yyyy-MM-dd") // user select it
}
}
},
schema: {
data: function (data) {
// function to handle data returned from server
var dataArray = [];
var index = 0;
for (var key in data[0]) {
if (Object.prototype.hasOwnProperty.call(data[0], key)) {
var property = key;
if (property == "date") {
continue;
}
key = {};
key["X"] = rowHeaders[index];
index++;
for (var i = 0; i < data.length; i++) {
var date = data[i].date;
var dateSplit = date.split("-");
var year = dateSplit[0];
var month = months[dateSplit[1] - 1];
var header = month + "_" + year;
key[header] = data[i][property];
}
dataArray.push(key);
}
}
return dataArray;
}
}
});
return dataSource;
};
$("#grid").kendoGrid({
scrollable: false,
editable: false,
dataSource: _dataSource()
});
}
When I click on the load button for the first time, the datasource is loaded and the grid is displayed correctly.
But, for instance, if I change the date on the datePicker and click on the load button again, the datasource is loaded with the correct data (new records for other months), but the grid is not refreshed.
If the first time I select the month Jan/2015, it loads and displays from Jan/2015 until Dec/2015, which is correct.
But if than I select the month Feb/2015, the datasource loads from Feb/2015 until Jan/2016 (correct), but the grid display the columns from Jan/2015 until Dec/2015, which is wrong. In this case, the column Jan/2015 is shown empty and the column Jan/2016 is not displayed.
Can someone point me to the right direction?
Thanks!
You should use a function for your dataSource -> transport -> read -> data:
data: function() {
return {
companyIds: companyIds,
date: kendo.toString(picker.value(), "yyyy-MM-dd") // user select it
};
}
UPDATE:
Here is how I would do it:
function loadGrid(e) {
$("#grid").data("kendoGrid").dataSource.fetch();
}
function getData() {
var companyIds = ...
var picker = ...
return {
companyIds: companyIds,
date: kendo.toString(picker.value(), "yyyy-MM-dd") // user select it
};
}
var dataSource = new kendo.data.DataSource({
transport: {
read: {
url: URL,
dataType: "json",
data: getData
}
},
schema: {
data: function (data) {
// function to handle data returned from server
var dataArray = [];
var index = 0;
for (var key in data[0]) {
if (Object.prototype.hasOwnProperty.call(data[0], key)) {
var property = key;
if (property == "date") {
continue;
}
key = {};
key["X"] = rowHeaders[index];
index++;
for (var i = 0; i < data.length; i++) {
var date = data[i].date;
var dateSplit = date.split("-");
var year = dateSplit[0];
var month = months[dateSplit[1] - 1];
var header = month + "_" + year;
key[header] = data[i][property];
}
dataArray.push(key);
}
}
return dataArray;
}
}
});
$("#grid").kendoGrid({
scrollable: false,
editable: false,
dataSource: dataSource
});
I ended up destroying and recreating the grid when the user clicks on load button.
$("#loadButton").kendoButton({
click: loadGrid
});
var loaded = false;
function loadGrid(e) {
if (value) {
if (loaded) {
var grid = $("#grid").data("kendoGrid");
grid.wrapper.empty();
grid.destroy();
}
$("#grid").kendoGrid({
scrollable: false,
editable: false,
autoBind: false,
dataSource: _dataSource()
});
$("#grid").data("kendoGrid").dataSource.read();
loaded = true;
} else {
e.preventDefault();
alert("aaaa");
}
}
I have a requirement to render a set of time series data of contiguous blocks.
I need to describe a series of bars which could span many hours, or just minutes, with their own Y value.
I'm not sure if ChartJS is what I should be using for this, but I have looked at extending the Bar type, but it seems very hard coded for each bar to be the same width. The Scale Class internally is used for labels, chart width etc, not just the bars themselves.
I am trying to achieve something like this that works in Excel: http://peltiertech.com/variable-width-column-charts/
Has anyone else had to come up with something similar?
I found I needed to do this and the answer by #potatopeelings was great, but out of date for version 2 of Chartjs. I did something similar by creating my own controller/chart type via extending bar:
//controller.barw.js
module.exports = function(Chart) {
var helpers = Chart.helpers;
Chart.defaults.barw = {
hover: {
mode: 'label'
},
scales: {
xAxes: [{
type: 'category',
// Specific to Bar Controller
categoryPercentage: 0.8,
barPercentage: 0.9,
// grid line settings
gridLines: {
offsetGridLines: true
}
}],
yAxes: [{
type: 'linear'
}]
}
};
Chart.controllers.barw = Chart.controllers.bar.extend({
/**
* #private
*/
getRuler: function() {
var me = this;
var scale = me.getIndexScale();
var options = scale.options;
var stackCount = me.getStackCount();
var fullSize = scale.isHorizontal()? scale.width : scale.height;
var tickSize = fullSize / scale.ticks.length;
var categorySize = tickSize * options.categoryPercentage;
var fullBarSize = categorySize / stackCount;
var barSize = fullBarSize * options.barPercentage;
barSize = Math.min(
helpers.getValueOrDefault(options.barThickness, barSize),
helpers.getValueOrDefault(options.maxBarThickness, Infinity));
return {
fullSize: fullSize,
stackCount: stackCount,
tickSize: tickSize,
categorySize: categorySize,
categorySpacing: tickSize - categorySize,
fullBarSize: fullBarSize,
barSize: barSize,
barSpacing: fullBarSize - barSize,
scale: scale
};
},
/**
* #private
*/
calculateBarIndexPixels: function(datasetIndex, index, ruler) {
var me = this;
var scale = ruler.scale;
var options = scale.options;
var isCombo = me.chart.isCombo;
var stackIndex = me.getStackIndex(datasetIndex);
var base = scale.getPixelForValue(null, index, datasetIndex, isCombo);
var size = ruler.barSize;
var dataset = me.chart.data.datasets[datasetIndex];
if(dataset.weights) {
var total = dataset.weights.reduce((m, x) => m + x, 0);
var perc = dataset.weights[index] / total;
var offset = 0;
for(var i = 0; i < index; i++) {
offset += dataset.weights[i] / total;
}
var pixelOffset = Math.round(ruler.fullSize * offset);
var base = scale.isHorizontal() ? scale.left : scale.top;
base += pixelOffset;
size = Math.round(ruler.fullSize * perc);
size -= ruler.categorySpacing;
size -= ruler.barSpacing;
}
base -= isCombo? ruler.tickSize / 2 : 0;
base += ruler.fullBarSize * stackIndex;
base += ruler.categorySpacing / 2;
base += ruler.barSpacing / 2;
return {
size: size,
base: base,
head: base + size,
center: base + size / 2
};
},
});
};
Then you need to add it to your chartjs instance like this:
import Chart from 'chart.js'
import barw from 'controller.barw'
barw(Chart); //add plugin to chartjs
and finally, similar to the other answer, the weights of the bar widths need to be added to the data set:
var data = {
labels: ['A', 'B', 'C', 'D', 'E', 'F', 'G'],
datasets: [
{
label: "My First dataset",
fillColor: "rgba(220,220,220,0.5)",
strokeColor: "rgba(220,220,220,0.8)",
highlightFill: "rgba(220,220,220,0.7)",
highlightStroke: "rgba(220,220,220,1)",
data: [65, 59, 80, 30, 56, 65, 40],
weights: [1, 0.9, 1, 2, 1, 4, 0.3]
},
]
};
This will hopefully get someone onto the right track. What I have certainly isn't perfect, but if you make sure you have the right number of weight to data points, you should be right.
Best of luck.
This is based on the #Shane's code, I just posted to help, since is a common question.
calculateBarIndexPixels: function (datasetIndex, index, ruler) {
const options = ruler.scale.options;
const range = options.barThickness === 'flex' ? computeFlexCategoryTraits(index, ruler, options) : computeFitCategoryTraits(index, ruler, options);
const barSize = range.chunk;
const stackIndex = this.getStackIndex(datasetIndex, this.getMeta().stack);
let center = range.start + range.chunk * stackIndex + range.chunk / 2;
let size = range.chunk * range.ratio;
let start = range.start;
const dataset = this.chart.data.datasets[datasetIndex];
if (dataset.weights) {
//the max weight should be one
size = barSize * dataset.weights[index];
const meta = this.chart.controller.getDatasetMeta(0);
const lastModel = index > 0 ? meta.data[index - 1]._model : null;
//last column takes the full bar
if (lastModel) {
//start could be last center plus half of last column width
start = lastModel.x + lastModel.width / 2;
}
center = start + size * stackIndex + size / 2;
}
return {
size: size,
base: center - size / 2,
head: center + size / 2,
center: center
};
}
For Chart.js you can create a new extension based on the bar class to do this. It's a bit involved though - however most of it is a copy paste of the bar type library code
Chart.types.Bar.extend({
name: "BarAlt",
// all blocks that don't have a comment are a direct copy paste of the Chart.js library code
initialize: function (data) {
// the sum of all widths
var widthSum = data.datasets[0].data2.reduce(function (a, b) { return a + b }, 0);
// cumulative sum of all preceding widths
var cumulativeSum = [ 0 ];
data.datasets[0].data2.forEach(function (e, i, arr) {
cumulativeSum.push(cumulativeSum[i] + e);
})
var options = this.options;
// completely rewrite this class to calculate the x position and bar width's based on data2
this.ScaleClass = Chart.Scale.extend({
offsetGridLines: true,
calculateBarX: function (barIndex) {
var xSpan = this.width - this.xScalePaddingLeft;
var x = this.xScalePaddingLeft + (cumulativeSum[barIndex] / widthSum * xSpan) - this.calculateBarWidth(barIndex) / 2;
return x + this.calculateBarWidth(barIndex);
},
calculateBarWidth: function (index) {
var xSpan = this.width - this.xScalePaddingLeft;
return (xSpan * data.datasets[0].data2[index] / widthSum);
}
});
this.datasets = [];
if (this.options.showTooltips) {
Chart.helpers.bindEvents(this, this.options.tooltipEvents, function (evt) {
var activeBars = (evt.type !== 'mouseout') ? this.getBarsAtEvent(evt) : [];
this.eachBars(function (bar) {
bar.restore(['fillColor', 'strokeColor']);
});
Chart.helpers.each(activeBars, function (activeBar) {
activeBar.fillColor = activeBar.highlightFill;
activeBar.strokeColor = activeBar.highlightStroke;
});
this.showTooltip(activeBars);
});
}
this.BarClass = Chart.Rectangle.extend({
strokeWidth: this.options.barStrokeWidth,
showStroke: this.options.barShowStroke,
ctx: this.chart.ctx
});
Chart.helpers.each(data.datasets, function (dataset, datasetIndex) {
var datasetObject = {
label: dataset.label || null,
fillColor: dataset.fillColor,
strokeColor: dataset.strokeColor,
bars: []
};
this.datasets.push(datasetObject);
Chart.helpers.each(dataset.data, function (dataPoint, index) {
datasetObject.bars.push(new this.BarClass({
value: dataPoint,
label: data.labels[index],
datasetLabel: dataset.label,
strokeColor: dataset.strokeColor,
fillColor: dataset.fillColor,
highlightFill: dataset.highlightFill || dataset.fillColor,
highlightStroke: dataset.highlightStroke || dataset.strokeColor
}));
}, this);
}, this);
this.buildScale(data.labels);
// remove the labels - they won't be positioned correctly anyway
this.scale.xLabels.forEach(function (e, i, arr) {
arr[i] = '';
})
this.BarClass.prototype.base = this.scale.endPoint;
this.eachBars(function (bar, index, datasetIndex) {
// change the way the x and width functions are called
Chart.helpers.extend(bar, {
width: this.scale.calculateBarWidth(index),
x: this.scale.calculateBarX(index),
y: this.scale.endPoint
});
bar.save();
}, this);
this.render();
},
draw: function (ease) {
var easingDecimal = ease || 1;
this.clear();
var ctx = this.chart.ctx;
this.scale.draw(1);
Chart.helpers.each(this.datasets, function (dataset, datasetIndex) {
Chart.helpers.each(dataset.bars, function (bar, index) {
if (bar.hasValue()) {
bar.base = this.scale.endPoint;
// change the way the x and width functions are called
bar.transition({
x: this.scale.calculateBarX(index),
y: this.scale.calculateY(bar.value),
width: this.scale.calculateBarWidth(index)
}, easingDecimal).draw();
}
}, this);
}, this);
}
});
You pass in the widths like below
var data = {
labels: ['A', 'B', 'C', 'D', 'E', 'F', 'G'],
datasets: [
{
label: "My First dataset",
fillColor: "rgba(220,220,220,0.5)",
strokeColor: "rgba(220,220,220,0.8)",
highlightFill: "rgba(220,220,220,0.7)",
highlightStroke: "rgba(220,220,220,1)",
data: [65, 59, 80, 30, 56, 65, 40],
data2: [10, 20, 30, 20, 10, 40, 10]
},
]
};
and you call it like so
var ctx = document.getElementById('canvas').getContext('2d');
var myLineChart = new Chart(ctx).BarAlt(data);
Fiddle - http://jsfiddle.net/moye0cp4/