I use javascript (not jQuery).
In short
Filled circle
Transparent behind the circle
Number centered in the middle
These values need to be easy to change:
Radius
Background color
Font size
Font family
It should not care if the number will break the circle.
Reason
The reason for me needing this is I have a Google map. It has a lot of markers with different numbers and colors. I found a script that works (in Chrome) but it needed D3 in order to work.
It seems like this is a simple task. That's why I hope I could leave D3 in this case.
Update
Here is my current code. I've read some more and maybe it would be possible to do it with SVG. What ever works.
var karta = (function () {
var fn = {};
var map;
var latitude;
var longitude;
var zoom;
var cache = {};
var colors = [];
var height = 75;
var width = 75;
// Init
fn.init = function(options) {
console.log(options);
markers = options.markers;
latitude = options.latitude;
longitude = options.longitude;
zoom = options.zoom;
colors = fn.setColors();
fn.setMap();
fn.setMarkers();
console.log(options);
};
// Set map
fn.setMap = function() {
map = new google.maps.Map(document.getElementById('map_canvas'), {
zoom: zoom,
center: {
lat: latitude,
lng: longitude
},
mapTypeId: 'satellite',
mapTypeControl: false,
zoomControl: false,
streetViewControl: false,
});
};
// Set markers
fn.setMarkers = function() {
var mytest = '<svg height="32" width="32"><foreignObject width="32" height="32" x="16" y="16" transform="translate(-16,-16)"><div class="circle" style="background: blue; border-radius: 100%; text-align: center; line-height: 32px; font-size: 12px;"><span style="display: inline-block; vertical-align: middle; color: #fff; font-weight: bold;">180</span></div></foreignObject></svg>';
var myurl = 'data:image/svg+xml;charset=UTF-8;base64,' + btoa(mytest);
console.log(myurl);
markers.forEach( function( point ) {
fn.icon( point[0], function(src) {
new google.maps.Marker({
position: new google.maps.LatLng( point[1], point[2] ),
map: map,
icon: {
url: myurl,
anchor: new google.maps.Point(25, 25),
origin: new google.maps.Point(0, 0),
scaledSize: new google.maps.Size(50, 50)
}
});
});
});
};
// Set colors
fn.setColors = function() {
colors[65] = "E50000";
colors[80] = "E52500";
colors[95] = "E54A00";
colors[110] = "E56F00";
colors[125] = "E59400";
colors[140] = "B79B00";
colors[155] = "89A200";
colors[170] = "5CA900";
colors[185] = "2EB000";
colors[250] = "00B700";
return colors;
};
// Set circle
fn.setCircle = function(svg, number) {
var circles = svg.append('circle')
.attr('cx', '27.2')
.attr('cy', '27.2')
.attr('r', '12')
.style('fill', colors[number]);
return circles;
};
// Set label
fn.setLabel = function(svg, number) {
var label = svg.append('text')
.attr('dx', 27)
.attr('dy', 32)
.attr('text-anchor', 'middle')
.attr('style', 'font-size: 12px; fill: #FFFFFF; font-family: Arial, Verdana; font-weight: bold')
.text(number);
return label;
};
// Set svg
fn.setSvg = function(number) {
var svg = d3.select(document.createElement('div')).append('svg')
.attr('viewBox', '0 0 54.4 54.4')
.append('g')
fn.setCircle(svg, number);
fn.setLabel(svg, number);
return svg;
};
// Set image
fn.setImage = function(number, node, callback) {
var image = new Image();
image.onload = (function(width, height) {
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
var dataURL;
d3.select(canvas)
.attr('width', width)
.attr('height', height);
context.drawImage(image, 0, 0, width, height);
dataURL = canvas.toDataURL();
generateIconCache[number] = dataURL;
callback(dataURL);
}).bind(this, width, height);
var xmlSource = (new XMLSerializer()).serializeToString(node);
image.src = 'data:image/svg+xml;base64,' + btoa(encodeURIComponent(xmlSource).replace(/%([0-9A-F]{2})/g, function(match, p1) {
return String.fromCharCode('0x' + p1);
}));
};
// Icon
fn.icon = function( number, callback ) {
if( cache[number] !== undefined ) {
callback( cache[number] );
}
var svg = fn.setSvg(number);
var node = svg.node().parentNode.cloneNode(true);
d3.select(node).select('clippath').remove();
fn.setImage(number, node, callback);
}
return fn;
})();
Scaling text to fit a circle.
To scale text to fit a circle you need to find the angle of the diagonal line across the text. To get that you need the font height and the font width. The font height is in the font attribute of the 2D context. Eg "18px Arial" and the font width can be calculated using the 2D context textWidth = ctx.measureText(text).width.
once you have the font width and the font height you can get the diagonal angle with textDiag = Math.atan(fontheight / fontWidth) . This will be the angle where on the circle the corner of the text will touch the side.
We can draw a line from that point on the circle circumference to the horizontal line through the middle of the circle to give us the location of right edge of the text. You get that with halfWidth = Math.cos(textDiag) * radius The total text width should be twice that.
As it is a real pain to find a point size to match a given text width, it is easier just to scale the text using the context transformation matrix.
The scale is simply the required text width divided by then known text width. fontScale = halfWidth * 2) / textWidth then set the transform ctx.setTransform(fontScale,0,0,fontScale,r,r) (r is the radius or center of circle) and draw the text ctx.filltext(text,0,0) with the textAlign and textBaseline 2d context attributes set to "center" and "middle" respectively
The example show this in action by creating a simple text circle img that can be drawn on another canvas, or just added to the DOM as an image. The two properties number and radius set the number to display and the size in pixels. The demo shows it being created and drawn onto a canvas with the number increasing to show how it scales.
// creates a circle image object
// Function arguments.
// radius is the you know ....
// number is the value to display in the circle
// style is this style
// style.color the circle colour. Defaults to "red";
// style.font the font. Defaults to '18px arial';
// style.fontColor is the font colour. Defaults to "white";
// style.fit if true then the number is made to fit te circle as best it can. Defaults to true
// style.decimals number of decimal places to display. Defaults to 0
//
// returns
// Html canvas element with the following custom attributes
// ctx the 2D context
// number the value to display
// radius the radius
// displayStyle the referance to the style arrgument passed when createCircleNumberImage
// was called
// draw the function used to render the image. This function has no arguments
// use the displayStyle atrribute to set the style
function createCircleNumberImage(radius,number,style){
// create HTML 5 image element
img = document.createElement("canvas");
// size it
img.width = Math.ceil(radius * 2);
img.height = Math.ceil(radius * 2);
// get a drawing context
img.ctx = img.getContext("2d");
// set custom attributes
img.radius = radius;
img.number = number;
img.displayStyle = style;
// set defaults
style.color = style.color ? style.color : "red";
style.font = style.font ? style.font : '18px arial';
style.fontColor = style.fontColor ? style.fontColor : "white";
style.fit = style.fit === undefined ? true : style.fit;
style.decimals = style.decimals === undefined || isNaN(style.decimals) ? 0 : style.decimals;
// add draw function
img.draw = function(){
var fontScale, fontWidth, fontSize, number;
// resize
this.width = Math.ceil(this.radius * 2);
this.height = Math.ceil(this.radius * 2);
// clear (incase resize did not do it)
this.ctx.clearRect(0,0,this.width,this.height);
// draw the circle
this.ctx.fillStyle = this.displayStyle.color;
this.ctx.beginPath();
this.ctx.arc(radius,radius,radius,0,Math.PI * 2);
this.ctx.fill();
// setup the font styles
this.ctx.font = this.displayStyle.font;
this.ctx.textAlign = "center";
this.ctx.textBaseline = "middle";
this.ctx.fillStyle = this.displayStyle.fontColor;
// get the value to display
number = this.number.toFixed(this.displayStyle.decimals);
// get the font size
fontSize = Number(/[0-9\.]+/.exec(this.ctx.font)[0]);
if(!this.displayStyle.fit || isNaN(fontSize)){ // Dont fit text or font height unknown
this.ctx.fillText(number,radius,radius);
}else{
// fit font as based on the angle from text center to bottom right
fontWidth = this.ctx.measureText(number).width;
fontScale = Math.cos(Math.atan(fontSize/fontWidth)) * this.radius * 2 / fontWidth;
this.ctx.setTransform(fontScale,0,0,fontScale,this.radius,this.radius);
this.ctx.fillText(number,0,0);
this.ctx.setTransform(1,0,0,1,0,0); // restor the transform
}
if(!this.displayStyle.fit || isNaN(fontSize)){ // Dont fit text or font height unknown
this.ctx.fillText(number,radius,radius);
}else{
fontScale = Math.cos(Math.atan(fontSize/fontWidth)) * this.radius * 2 / fontWidth;
this.ctx.setTransform(fontScale,0,0,fontScale,this.radius,this.radius);
this.ctx.fillText(number,0,0);
this.ctx.setTransform(1,0,0,1,0,0); // restor the transform
}
// return this so you can call the draw function from within a canvas drawImage function
return this;
}
// draw first time
img.draw()
// return new image
return img;
}
var canvas = document.createElement("canvas");
canvas.width = 320;
canvas.height = 200;
var ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
// set comments above the function declaration for help
var sun = createCircleNumberImage (
60,1,{
fontColor : "white",
font : "24px arial",
color : "#EE0",
fit : true,
decimals : 0,
}
)
function doAgain(){
ctx.fillStyle = "black";
ctx.fillRect(0,0,canvas.width,canvas.height/2)
ctx.fillStyle = "Red";
ctx.fillRect(0,canvas.height /2 ,canvas.width,canvas.height/2)
if(sun.number > 5000000000){
sun.number = 0;
}
sun.number = sun.number + Math.floor(sun.number/10) + 1;
ctx.drawImage(sun.draw(),ctx.canvas.width / 2 - sun.radius,ctx.canvas.height / 2 - sun.radius);
setTimeout(doAgain,200);
}
doAgain();
Related
I'm using the Pixi.js v4 graphics library to make a game with JavaScript. I know that I can draw a black + rounded rectangle like so:
const rectangle = new pixi.Graphics();
rectangle.beginFill(0); // Color it black
rectangle.drawRoundedRect(
0,
0,
100, // Make it 100x100
100,
5, // Make the rounded corners have a radius of 5
);
rectangle.endFill();
stage.addChild(rectangle);
How do I draw a rounded rectangle with a gradient from white to black?
How do I draw a rounded rectangle that has gradual opacity such that it fades in from left to right?
It looks like it's not possible to implement what you need with pixi.js without additional code, but we can do some magic to make it happen. Here's the result of what I've got: https://jsfiddle.net/exkf3zfo/21/
The bottom color is a pure red with 0.2 alpha.
I would split the whole process to the next steps:
Drawing the gradient
Masking the gradient with the rounded mask
Here is the code itself:
var app = new PIXI.Application(800, 600, {
antialias: true
});
document.body.appendChild(app.view);
// Functions
// param color is a number (e.g. 255)
// return value is a string (e.g. ff)
var prepareRGBChannelColor = function(channelColor) {
var colorText = channelColor.toString(16);
if (colorText.length < 2) {
while (colorText.length < 2) {
colorText = "0" + colorText;
}
}
return colorText;
}
// Getting RGB channels from a number color
// param color is a number
// return an RGB channels object {red: number, green: number, blue: number}
var getRGBChannels = function(color) {
var colorText = color.toString(16);
if (colorText.length < 6) {
while (colorText.length < 6) {
colorText = "0" + colorText;
}
}
var result = {
red: parseInt(colorText.slice(0, 2), 16),
green: parseInt(colorText.slice(2, 4), 16),
blue: parseInt(colorText.slice(4, 6), 16)
};
return result;
}
// Preparaiton of a color data object
// param color is a number [0-255]
// param alpha is a number [0-1]
// return the color data object {color: number, alpha: number, channels: {red: number, green: number, blue: number}}
var prepareColorData = function(color, alpha) {
return {
color: color,
alpha: alpha,
channels: getRGBChannels(color)
}
}
// Getting the color of a gradient for a very specific gradient coef
// param from is a color data object
// param to is a color data object
// return value is of the same type
var getColorOfGradient = function(from, to, coef) {
if (!from.alpha && from.alpha !== 0) {
from.alpha = 1;
}
if (!from.alpha && from.alpha !== 0) {
to.alpha = 1;
}
var colorRed = Math.floor(from.channels.red + coef * (to.channels.red - from.channels.red));
colorRed = Math.min(colorRed, 255);
var colorGreen = Math.floor(from.channels.green + coef * (to.channels.green - from.channels.green));
colorGreen = Math.min(colorGreen, 255);
var colorBlue = Math.floor(from.channels.blue + coef * (to.channels.blue - from.channels.blue));
colorBlue = Math.min(colorBlue, 255);
var rgb = prepareRGBChannelColor(colorRed) + prepareRGBChannelColor(colorGreen) + prepareRGBChannelColor(colorBlue);
return {
color: parseInt(rgb, 16),
alpha: from.alpha + coef * (to.alpha - from.alpha)
};
}
var startTime = Date.now();
console.log("start: " + startTime);
// Drawing the gradient
//
var gradient = new PIXI.Graphics();
app.stage.addChild(gradient);
//
var rect = {
width: 200,
height: 200
};
var round = 20;
//
var colorFromData = prepareColorData(0xFF00FF, 1);
var colorToData = prepareColorData(0xFF0000, 0.2);
//
var stepCoef;
var stepColor;
var stepAlpha;
var stepsCount = 100;
var stepHeight = rect.height / stepsCount;
for (var stepIndex = 0; stepIndex < stepsCount; stepIndex++) {
stepCoef = stepIndex / stepsCount;
stepColor = getColorOfGradient(colorFromData, colorToData, stepCoef);
gradient.beginFill(stepColor.color, stepColor.alpha);
gradient.drawRect(
0,
rect.height * stepCoef,
rect.width,
stepHeight
);
}
// Applying a mask with round corners to the gradient
var roundMask = new PIXI.Graphics();
roundMask.beginFill(0x000000);
roundMask.drawRoundedRect(0, 0, rect.width, rect.height, round);
app.stage.addChild(roundMask);
gradient.mask = roundMask;
var endTime = Date.now();
console.log("end: " + endTime);
console.log("total: " + (endTime - startTime));
The interesting thing is that it takes only about 2-5 ms for the whole process!
If you wan't to change colors of the gradient to white>black (as described in the question), just change the next params:
var colorFromData = prepareColorData(0xFF00FF, 1);
var colorToData = prepareColorData(0xFF0000, 0.2);
To:
var colorFromData = prepareColorData(0xFFFFFF, 1);
var colorToData = prepareColorData(0x000000, 0.2);
Not full answer but some extra information
As far I know, you can't use gradient for PIXI.Graphics even for sprites you need extra canvas
Just draw the gradient you want to a canvas:
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/createLinearGradient
Then use that canvas as a texture: Texture.fromCanvas(canvas);
Look at this article.
For gradual opacity, Alpha Mask can help
http://pixijs.io/examples/#/demos/alpha-mask.js
P.S Maybe phaser.js can do more
Did you ever figure this out? I couldn't find a solution online either, so I implemented it myself using a filter. Have a look: https://codepen.io/Lancer611/pen/KodabK.
Some of the pixi code:
function newGradientPoly(poly, fill, fillSize){
var container = new PIXI.Sprite();
app.stage.addChild(container);
var shape = new PIXI.Graphics();
shape.beginFill(0xffffff)
.lineStyle(1, 0x333333)
.drawPolygon(poly);
var mask = new PIXI.Graphics();
mask.beginFill(0xffffff, 1)
.drawPolygon(poly);
container.mask = mask;
container.addChild(shape);
var fshaderCode = document.getElementById("fragShader").innerHTML;
fogShader = new PIXI.Filter(null, fshaderCode);
fogShader.uniforms.resolution = [width, height];
fogShader.uniforms.segments = poly.slice();
fogShader.uniforms.count = poly.length/2;
fogShader.uniforms.gSize = fillSize;
fogShader.uniforms.fill = fill;
shape.filters=[fogShader];
}
I've created a pixi plugin for displaying vector drawings in Pixi. The main limitation is that you need to draw your rectangle in the vector art program Omber first, so you need to know the size of your rectangle beforehand (since everything is vector-based, you could theoretically scale things later, but then the rounded corners would end up being a little uneven). The workflow is similar to using sprites: 1. draw your rectangles in Omber 2. export them to gltf 3. load the gltf files in your Pixi program 4. position the rectangles where you want them.
Another possibility is that you could create the gradient as a separate object, and then you can mask it out with a polygon. Here's an example. In that example, I'm using a vector drawing for the gradient, but since gradients don't become blurry when resized, you could probably use a sprite for that as well. I'm not sure if masks have good performance, but if you just need a few of them, then it's probably fine.
I had drawn an animation in canvas like this and rendered a map using openlayers4. I want to add this canvas to the map[openlayers canvas] in next step.
I had used ol.source.ImageCanvas add a boundary to openlayers, so I try to add the canvas with animation using ImageCanvas, but failed.
What's more, openlayers API said ol.source.ImageCanvas method only the image canvas can be added. I didn't know whether the animate canvas so does.
Should I insit on using ImageCanvas method or try others?
Can someone give me an example if I abandon the ImageCanvas method?
After some tries, I got a solution! Haha!
First: the ol.source.ImageCanvas can still use, but you will get a stopped animate just like a screenshot.
Second: must know the ol.map.render() in openlayers3 or openlayers4, whose description is:
Request a map rendering (at the next animation frame).
Thus, you can use it to refresh the map and get the next animation of canvas.
The following is snippets of my code:
var topoCanvas = function(extent, resolution, pixelRatio, size, projection) {
// topo features;
var features = topojson.feature(tokyo, tokyo.objects.counties);
var canvasWidth = size[0];
var canvasHeight = size[1];
var canvas = d3.select(document.createElement('canvas'));
canvas.attr('width', canvasWidth).attr('height', canvasHeight);
var context = canvas.node().getContext('2d');
var d3Projection = d3.geo.mercator().scale(1).translate([0, 0]);
var d3Path = d3.geo.path().projection(d3Projection);
var pixelBounds = d3Path.bounds(features);
var pixelBoundsWidth = pixelBounds[1][0] - pixelBounds[0][0];
var pixelBoundsHeight = pixelBounds[1][1] - pixelBounds[0][1];
var geoBounds = d3.geo.bounds(features);
var geoBoundsLeftBottom = ol.proj.transform(geoBounds[0], 'EPSG:4326', projection);
var geoBoundsRightTop = ol.proj.transform(geoBounds[1], 'EPSG:4326', projection);
var geoBoundsWidth = geoBoundsRightTop[0] - geoBoundsLeftBottom[0];
if (geoBoundsWidth < 0) {
geoBoundsWidth += ol.extent.getWidth(projection.getExtent());
}
var geoBoundsHeight = geoBoundsRightTop[1] - geoBoundsLeftBottom[1];
var widthResolution = geoBoundsWidth / pixelBoundsWidth;
var heightResolution = geoBoundsHeight / pixelBoundsHeight;
var r = Math.max(widthResolution, heightResolution);
var scale = r / (resolution / pixelRatio);
var center = ol.proj.transform(ol.extent.getCenter(extent), projection, 'EPSG:4326');
d3Projection.scale(scale).center(center).translate([canvasWidth / 2, canvasHeight / 2]);
d3Path = d3Path.projection(d3Projection).context(context);
d3Path(features);
context.stroke();
// above code is add a topoJson boundary to canvas
// below code is add an animation to canvas
var settings = createSettings(tokyo, {
width: canvasWidth,
height: canvasHeight
});
// reset the projection and bounds for animation canvas
settings.projection = d3Projection;
settings.bounds = geoBounds;
var mesh = buildMeshes(tokyo, settings);
when(render(settings, mesh, {
width: canvasWidth,
height: canvasHeight
})).then(function(masks) {
when(interpolateField(stations, data, settings, masks)).then(function(field) {
// wind moving animation
animate(settings, field, canvas);
// refresh the map to get animation
window.setInterval(function() {
map.render();
}, 50);
});
});
return canvas[0][0];
}
I'm looking for a way to zoom from place to place on a globe in D3 v4 (v4 is important). What I'm looking for is basically exactly this: https://www.jasondavies.com/maps/zoom/ My problem is that Jason Davies obfuscated his code, so I can't read it, and I can't find a bl.ock containing that project or anything similar to it. I'll provide a link to what I've got here: http://plnkr.co/edit/0mjyR3ovTfkDXB8FTG0j?p=preview
The relevant is probably inside the .tween():
.tween("rotate", function () {
var p = d3.geoCentroid(points[i]),
r = d3.geoInterpolate(projection.rotate(), [-p[0], -p[1]]);
return function (t) {
projection.rotate(r(t));
convertedLongLats = [projection(points[0].coordinates), projection(points[1].coordinates)]
c.clearRect(0, 0, width, height);
c.fillStyle = colorGlobe, c.beginPath(), path(sphere), c.fill();
c.fillStyle = colorLand, c.beginPath(), path(land), c.fill();
for (var j = 0; j < points.length; j++) {
var textCoords = projection(points[j].coordinates);
c.fillStyle = textColors, c.textAlign = "center", c.font = "18px FontAwesome", c.fillText(points[j].icon, textCoords[0], textCoords[1]);
textCoords[0] += 15;
c.textAlign = "left", c.font = " 12px Roboto", c.fillText(points[j].location, textCoords[0], textCoords[1]);
}
c.strokeStyle = textColors, c.lineWidth = 4, c.setLineDash([10, 10]), c.beginPath(), c.moveTo(convertedLongLats[0][0], convertedLongLats[0][1]), c.lineTo(convertedLongLats[1][0], convertedLongLats[1][1]), c.stroke();
};
})
Basically, I want what I've got now but I want it to zoom in, pretty much exactly like it is in the Animated World Zoom example I provided above. I'm not really looking for code, I'd rather someone point me in the right direction with an example or something (it's worth noting that I'm fairly new to d3 and that this project is heavily based on World Tour by mbostock, so it uses canvas). Thank you all in advance!
Based on your plunker and comment, a challenge in zooming out between two points in a transition is that the interpolator will only interpolate between two values. The solution in your plunker relies on two interpolators, one for zooming in and zooming out. This method has added un-needed complexity and somewhere along the line, as you note, it jumps to an incorrect scale. You could simplify this:
Take an interpolator that interpolates between -1 and 1, and weight each scale according to the absolute value of the interpolator. At zero, the zoom should be out all the way, while at -1,1, you should be zoomed in:
var s = d3.interpolate(-1,1);
// get the appropriate scale:
scale = Math.abs(0-s(t))*startEndScale + (1-Mat.abs(0-s(t)))*middleScale
This is a little clunky as it goes from zooming out to zooming in rather abruptly, so you could ease it with a sine type easing:
var s = d3.interpolate(0.0000001,Math.PI);
// get the appropriate scale:
scale = (1-Math.abs(Math.sin(s(t))))*startEndScale + Math.abs(Math.sin(s(t)))*middleScale
I've applied this to your plunker here.
For a simple and minimal example using the example that I suggested and your two points and path (and using your plunkr as a base), stripping out the animated line and icons, I would probably put together something like (plunker, snippet below best viewed on full screen):
var width = 600,
height = 600;
var points = [{
type: "Point",
coordinates: [-74.2582011, 40.7058316],
location: "Your Location",
icon: "\uF015"
}, {
type: "Point",
coordinates: [34.8887969, 32.4406351],
location: "Caribe Royale Orlando",
icon: "\uF236"
}];
var canvas = d3.select("body").append("canvas")
.attr("width", width)
.attr("height", height);
var c = canvas.node().getContext("2d");
var point = points[0].coordinates;
var projection = d3.geoOrthographic()
.translate([width / 2, height / 2])
.scale(width / 2)
.clipAngle(90)
.precision(0.6)
.rotate([-point[0], -point[1]]);
var path = d3.geoPath()
.projection(projection)
.context(c);
var colorLand = "#4d4f51",
colorGlobe = "#2e3133",
textColors = "#fff";
d3.json("https://unpkg.com/world-atlas#1/world/110m.json", function (error, world) {
if (error) throw error;
var sphere = { type: "Sphere" };
var land = topojson.feature(world, world.objects.land);
var i = 0;
var scaleMiddle = width/2;
var scaleStartEnd = width * 2;
loop();
function loop() {
d3.transition()
.tween("rotate",function() {
var flightPath = {
type: 'Feature',
geometry: {
type: "LineString",
coordinates: [points[i++%2].coordinates, points[i%2].coordinates]
}
};
// next point:
var p = points[i%2].coordinates;
// current rotation:
var currentRotation = projection.rotate();
// next rotation:
var nextRotation = projection.rotate([-p[0],-p[1]]).rotate();
// Interpolaters:
var r = d3.geoInterpolate(currentRotation,nextRotation);
var s = d3.interpolate(0.0000001,Math.PI);
return function(t) {
// apply interpolated values
projection.rotate(r(t))
.scale( (1-Math.abs(Math.sin(s(t))))*scaleStartEnd + Math.abs(Math.sin(s(t)))*scaleMiddle ) ;
c.clearRect(0, 0, width, height);
c.fillStyle = colorGlobe, c.beginPath(), path(sphere), c.fill();
c.fillStyle = colorLand, c.beginPath(), path(land), c.fill();
c.beginPath(), path(flightPath), c.globalAlpha = 0.5, c.shadowColor = "#fff", c.shadowBlur = 5, c.lineWidth = 0.5, c.strokeStyle = "#fff", c.stroke(), c.shadowBlur = 0, c.globalAlpha = 1;
}
})
.duration(3000)
.on("end", function() { loop(); })
}
});
<script src="http://d3js.org/d3.v4.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
I have been trying to draw gaussin-like function using bezierCurveTo
find the code below
<canvas id="thisCan" width="0px" height="0px" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.
</canvas>
<script>
(function() {
var
// Obtain a reference to the canvas element
// using its id.
htmlCanvas = document.getElementById('thisCan'),
// Obtain a graphics context on the
// canvas element for drawing.
ctx = htmlCanvas.getContext('2d');
var width = 0;
var height = 0;
// Start listening to resize events and
// draw canvas.
initialize();
function initialize()
{
// Register an event listener to
// call the resizeCanvas() function each time
// the window is resized.
window.addEventListener('resize', resizeCanvas, false);
// Draw canvas border for the first time.
resizeCanvas();
}
// Display custom canvas.
// In this case it's a blue, 5 pixel border that
// resizes along with the browser window.
function redraw()
{
ctx.beginPath();
ctx.moveTo(width/2, 0);
ctx.bezierCurveTo(150, 119, 186, 121, 66, 185);
ctx.moveTo(width/2 + width * 0.08 , 0);
ctx.bezierCurveTo(344, 119, 344, 121, 504, 185);
ctx.stroke();
}
// Runs each time the DOM window resize event fires.
// Resets the canvas dimensions to match window,
// then draws the new borders accordingly.
function resizeCanvas()
{
var contentElement = document.getElementsByClassName("content-box post")[0]
width = htmlCanvas.width = (contentElement.clientWidth * 0.75)
height = htmlCanvas.height = (contentElement.offsetWidth*0.75 * 0.5);
redraw();
}
})();
</script>
I am planning to draw many curves in between as well. But how do I make it parametric, based on width and height variables?
I need to specify the control points using width and height parameters, so that it becomes window-size invariant.
Is there a way?
Don't use Bezier curves if you want an exponent curve, they're different functions. Instead, just plot the function you actually need, with something like http://jsbin.com/nubutodosu/edit?js,output, where you define a Gaussian object:
// Gaussian distribution generator
var Gaussian = function(mean, std) {
this.mean = mean;
this.std = std;
this.a = 1/Math.sqrt(2*Math.PI);
};
Gaussian.prototype = {
addStd: function(v) {
this.std += v;
},
get: function(x) {
var f = this.a / this.std;
var p = -1/2;
var c = (x-this.mean)/this.std;
c *= c;
p *= c;
return f * Math.pow(Math.E, p);
},
generateValues: function(start, end) {
var LUT = [];
var step = (Math.abs(start)+Math.abs(end)) / 100;
for(var i=start; i<end; i+=step) {
LUT.push(this.get(i));
}
return LUT;
}
};
And then you can give that a draw routine so that it can plot itself over the interval that you need:
...
draw: function(ctx) {
var points = this.generateValues(-10,10);
var len = points.length;
ctx.strokeStyle = "black";
ctx.beginPath();
var p0 = points[0];
ctx.moveTo(0, height - (height*p0));
points.forEach(function(p,i) {
if(i===0) {
return;
}
ctx.lineTo(width * i/len, height - (height*p));
p0 = p;
});
ctx.stroke();
}
...
So you build your array of values over the interval, then draw them on the canvas by "connecting the dots".
I managed to resolve it.
To be specific, I was looking out for half-gaussian curve.
I managed to figure out that guassian function has a very special property with respect to bezier curves. It might be elementary, but I could not find it on google. So this might be informative finding.
If each control points of the cubic bezier curve reside on a "line parallel to X axis and passing through start point/end point", the resulting curve will be of a half guassian shape.
e.g.
On top of this finding, I have written the following code:
<canvas id="thisCan" width="0px" height="0px">
Your browser does not support the HTML5 canvas tag.
</canvas>
<script>
(function() {
var
// Obtain a reference to the canvas element
// using its id.
htmlCanvas = document.getElementById('thisCan'),
// Obtain a graphics context on the
// canvas element for drawing.
ctx = htmlCanvas.getContext('2d');
// Start listening to resize events and
// draw canvas.
initialize();
function initialize()
{
// Register an event listener to
// call the resizeCanvas() function each time
// the window is resized.
window.addEventListener('resize', resizeCanvas, false);
// Draw canvas border for the first time.
resizeCanvas();
}
// Display custom canvas.
// In this case it's a blue, 5 pixel border that
// resizes along with the browser window.
function redraw(width, height)
{
var start = width/2;
var margin = width * 0.01;
var height = (width / 3) - (margin * 4);
var end_step = width/4
ctx.beginPath();
ctx.moveTo(start - margin, 0);
ctx.bezierCurveTo((start - height/3), 0 , (start - height/3), height , end_step*1, height);
ctx.moveTo(start + margin, 0);
ctx.bezierCurveTo((start + height/3), 0 , (start + height/3), height , end_step*3, height);
ctx.moveTo(start - margin, 0);
ctx.bezierCurveTo((start - height*0.33), 0 , (start - height*0.16), height , end_step*1.5, height);
ctx.moveTo(start + margin, 0);
ctx.bezierCurveTo((start + height*0.33), 0 , (start + height*0.16), height , end_step*2.5, height);
ctx.moveTo(start, 0);
ctx.bezierCurveTo((start ), 0 , (start ), height , end_step*2, height);
ctx.stroke();
}
// Runs each time the DOM window resize event fires.
// Resets the canvas dimensions to match window,
// then draws the new borders accordingly.
function resizeCanvas()
{
var width = 0;
var height = 0;
var contentElement = document.getElementsByClassName("content-box post")[0]
width = htmlCanvas.width = (contentElement.clientWidth * 0.85)
height = htmlCanvas.height = (contentElement.offsetWidth*0.85 * 0.33);
redraw(width, height);
}
})();
</script>
and the output:
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 years ago.
Improve this question
I have been doing a little experimenting with canvas, creating drawings using lines shapes text etc, and inserting .png files. The inserting .png files is the bit that I cannot get to work.
Edit:
Undesired behaviour of this code: I load shapes to the graphics context, then load an image file to the graphics context, however when the graphics context is drawn, the image is at behind the shapes, despite being drawn last.
I wanted the image file to be at the top, in front of the shapes.
Desired behaviour: To bring image file to the front of the canvas, so it is not hidden by shapes drawn in the graphics context.
function loadImage(name) {
images[name] = new Image();
images[name].src = "DogWalking/" + name + ".png";
images[name].onload = function() {
graphics.drawImage(this, 0, 300);
canvas.bringToFront(this);
};
}
the function for drawing is called here:
function draw() {
graphics.save(); // to make sure changes don't carry over from one call to the next
graphics.fillStyle = "transparent"; // background color
graphics.fillRect(0,0,wWidth, wHeight);
graphics.fillStyle = "black";
applyLimits(graphics,xleft,xright,ytop,ybottom,true);
graphics.lineWidth = pixelSize;
world.draw(graphics);
graphics.drawImage(images["dog-walking11"],200,200);
graphics.restore();
}
code for the whole page is
<!DOCTYPE html>
<html>
<meta charset="UTF-8">
<head>
<title>Hierarchical Modeling 2D</title>
<style>
#messagediv {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 0;
background-color: indigo;
}
#canvasdiv {
position: absolute;
left: 0;
top: 0;
z-index: 10;
background-color: transparent;
}
</style>
<script type="text/javascript" src="rgbcolor.js"></script>
<script type="text/javascript">
"use strict";
var totalResources = 17;
var numResourcesLoaded = 0;
var images = {};
function loadImage(name) {
images[name] = new Image();
images[name].src = "DogWalking/" + name + ".png";
images[name].onload = function() {
//
graphics.drawImage(this, 0, 300);
canvas.bringToFront(this);
}
}
var canvas; // DOM object corresponding to the canvas
var graphics; // 2D graphics context for drawing on the canvas
var ctx; // 2D graphics context for drawing on the canvas
var myNumber = 0, myNumber2 = 0, myInterval, myInterval2, myelement, thisdiv, printx;
var mycoords = new Array();
var pcoords = new Array(); //coordinates of the portal.
//var pcoords = [[0,0], [50,300], [250,150]]; //coordinates of the portal.
var nocoords = 2;
var frameNumber = 0; // Current frame number.
var frameNumber2 = 0;
var sun;
var sun2;
var ground;
var world;
var pixelSize;
var wWidth;
var wHeight;
var portals = new Array("calendar1","alternativsearch","art1", "directory1");
var portalsval = new Array();
var portalsobj;
var leftj = new Array(3,1,4,2);
var forwards = "http://www.alternativworld.com";
// ---------------- Set Page Layout ----------------
// function to set size of canvas and location of portals
function pageLayout() {
var w = window, d = document, e = d.documentElement, g = d.getElementsByTagName('body')[0];
wWidth = w.innerWidth || e.clientWidth || g.clientWidth;
wHeight = w.innerHeight|| e.clientHeight|| g.clientHeight;
// Adjust wWidth and wHeight if ratio does not match scenary 7 by 5.
if (wWidth/wHeight != 7/5)
if (wWidth/wHeight > 7/5) {
var widthPortion = 5 * wWidth/wHeight;
wWidth = wWidth * 7 / widthPortion;
} else {
var heightPortion = 7 * wHeight/wWidth;
wHeight = wHeight * 5 / heightPortion;
}
var widthheight, localerror = false;
widthheight = Math.min(wWidth, wHeight);
if(widthheight < 400){
var localerror = true;
}
if (localerror == true)
alert("Warning, the page size of your browser or your screen resolution may be too small to correctly view this web page.");
var theCanvas = d.getElementById("theCanvas");
theCanvas.height = wHeight;
theCanvas.width = wWidth;
}
//Function to listen to the mouse events and see if a link is selected.
function doMouseDown(evt) {
var r = canvas.getBoundingClientRect();
var x = Math.round(evt.clientX - r.left);
var y = Math.round(evt.clientY - r.top);
alert(evt.clientX+ " " + evt.clientY);
for (var i = portals.length+1; i >= 0; i--) {
var p = pcoords[i];
if (Math.abs(p[0] - x) <= 50 && Math.abs(p[1] - y) <= 50) {
document.location.href = forwards;
return;
} else if (Math.abs(0 - x) <= 50 && Math.abs(0 - y) <= 50){
document.location.href = "http://www.alternativeuk.co.uk";
return;
}
}
}
// ---------------- The object-oriented scene graph API ------------------
/**
* The base class for all nodes in the scene graph data structure.
*/
function SceneGraphNode() {
this.fillColor = null; // If non-null, the default fillStyle for this node.
this.strokeColor = null; // If non-null, the default strokeStyle for this node.
}
SceneGraphNode.prototype.doDraw = function(g) {
// This method is meant to be abstract and must be
// OVERRIDDEN in any actual object in the scene graph.
// It is not meant to be called; it is called by draw().
throw "doDraw not implemented in SceneGraphNode"
}
SceneGraphNode.prototype.draw = function(g) {
// This method should be CALLED to draw the object
// represented by this SceneGraphNode. It should NOT
// ordinarily be overridden in subclasses.
graphics.save();
if (this.fillColor) {
g.fillStyle = this.fillColor;
}
if (this.strokeColor) {
g.strokeStyle = this.strokeColor;
}
this.doDraw(g);
graphics.restore();
}
SceneGraphNode.prototype.setFillColor = function(color) {
// Sets fillColor for this node to color.
// Color should be a legal CSS color string, or null.
this.fillColor = color;
return this;
}
SceneGraphNode.prototype.setStrokeColor = function(color) {
// Sets strokeColor for this node to color.
// Color should be a legal CSS color string, or null.
this.strokeColor = color;
return this;
}
SceneGraphNode.prototype.setColor = function(color) {
// Sets both the fillColor and strokeColor to color.
// Color should be a legal CSS color string, or null.
this.fillColor = color;
this.strokeColor = color;
return this;
}
/**
* Defines a subclass, CompoundObject, of SceneGraphNode to represent
* an object that is made up of sub-objects. Initially, there are no
* sub-objects.
*/
function CompoundObject() {
SceneGraphNode.call(this); // do superclass initialization
this.subobjects = []; // the list of sub-objects of this object
}
CompoundObject.prototype = new SceneGraphNode(); // (makes it a subclass!)
CompoundObject.prototype.add = function(node) {
// Add node a subobject of this object. Note that the
// return value is a reference to this node, to allow chaining
// of method calls.
this.subobjects.push(node);
return this;
}
CompoundObject.prototype.doDraw = function(g) {
// Just call the sub-objects' draw() methods.
for (var i = 0; i < this.subobjects.length; i++)
this.subobjects[i].draw(g);
}
/**
* Define a subclass, TransformedObject, of SceneGraphNode that
* represents an object along with a modeling transformation to
* be applied to that object. The object must be specified in
* the constructor. The transformation is specified by calling
* the setScale(), setRotate() and setTranslate() methods. Note that
* each of these methods returns a reference to the TransformedObject
* as its return value, to allow for chaining of method calls.
* The modeling transformations are always applied to the object
* in the order scale, then rotate, then translate.
*/
function TransformedObject(object) {
SceneGraphNode.call(this); // do superclass initialization
this.object = object;
this.rotationInDegrees = 0;
this.scaleX = 1;
this.scaleY = 1;
this.translateX = 0;
this.translateY = 0;
}
TransformedObject.prototype = new SceneGraphNode(); // (makes it a subclass!)
TransformedObject.prototype.setRotation = function(angle) {
// Set the angle of rotation, measured in DEGREES. The rotation
// is always about the origin.
this.rotationInDegrees = angle;
return this;
}
TransformedObject.prototype.setScale = function(sx, sy) {
// Sets scaling factors.
this.scaleX = sx;
this.scaleY = sy;
return this;
}
TransformedObject.prototype.setTranslation = function(dx,dy) {
// Set translation mounts.
this.translateX = dx;
this.translateY = dy;
return this;
}
TransformedObject.prototype.doDraw = function(g) {
// Draws the object, with its modeling transformation.
g.save();
if (this.translateX != 0 || this.translateY != 0) {
g.translate(this.translateX, this.translateY);
}
if (this.rotationInDegrees != 0) {
g.rotate(this.rotationInDegrees/180*Math.PI);
}
if (this.scaleX != 1 || this.scaleY != 1) {
g.scale(this.scaleX, this.scaleY);
}
this.object.draw(g);
g.restore();
}
/**
* A subclass of SceneGraphNode representing filled triangles.
* The constructor specifies the vertices of the triangle:
* (x1,y1), (x2,y2), and (x3,y3).
*/
function Triangle(x1,y1,x2,y2,x3,y3) {
SceneGraphNode.call(this);
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
this.x3 = x3;
this.y3 = y3;
}
Triangle.prototype = new SceneGraphNode();
Triangle.prototype.doDraw = function(g) {
g.beginPath();
g.moveTo(this.x1,this.y1);
g.lineTo(this.x2,this.y2);
g.lineTo(this.x3,this.y3);
g.closePath();
g.fill();
}
/**
* Directly create a line object as a SceneGraphNode with a
* custom doDraw() method. line is of length 1 and
* extends along the x-axis from (0,0) to (1,0).
*/
var line = new SceneGraphNode();
line.doDraw = function(g) {
g.beginPath();
g.moveTo(0,0);
g.lineTo(1,0);
g.stroke();
}
/**
* Directly create a filled rectangle object as a SceneGraphNode with a
* custom doDraw() method. filledRect is a square with side 1, centered
* at (0,0), with corners at (-0.5,-0.5) and (0.5,0.5).
*/
var filledRect = new SceneGraphNode();
filledRect.doDraw = function(g) {
g.fillRect(-0.5,-0.5,1,1);
}
/**
* Directly create a rectangle object as a SceneGraphNode with a
* custom doDraw() method. rect is a square with side 1, centered
* at (0,0), with corners at (-0.5,-0.5) and (0.5,0.5). Only the
* outline of the square is drawn.
*/
var rect = new SceneGraphNode();
rect.doDraw = function(g) {
g.strokeRect(-0.5,-0.5,1,1);
}
/**
* Directly create a filled circle object as a SceneGraphNode with a
* custom doDraw() method. filledCircle is a circle with radius 0.5
* (diameter 1), centered at (0,0).
*/
var filledCircle = new SceneGraphNode();
filledCircle.doDraw = function(g) {
g.beginPath();
g.arc(0,0,0.5,0,2*Math.PI);
g.fill();
}
var clickHere = new SceneGraphNode();
clickHere.doDraw = function(g) {
g.fillText("click here :)",0,0)
}
/**
* Directly create a circle object as a SceneGraphNode with a
* custom doDraw() method. filledCircle is a circle with radius 0.5
* (diameter 1), centered at (0,0). Only the outline of the circle
* is drawn.
*/
var circle = new SceneGraphNode();
circle.doDraw = function(g) {
g.beginPath();
g.arc(0,0,0.5,0,2*Math.PI);
g.stroke();
}
var dog = new SceneGraphNode();
dog.doDraw = function(g) {
g.drawImage(images["dog-walking11"],-2, 2);
alert(images["dog-walking11"].name);
}
// -------------------- Specific to this application ----------------------------
/*
* Define two extra basic objects as SceneGraphNodes with custom doDraw() methods.
* One represents the ground, the other a vane for a windmill.
*/
var ground = new SceneGraphNode();
ground.doDraw = function(g) {
g.beginPath();
g.moveTo(0,-1);
g.lineTo(0,0.8);
g.lineTo(1.5,1.65);
g.lineTo(1.8,1.3);
g.lineTo(3,2.1);
g.lineTo(4.7,0.7);
g.lineTo(6.1,1.2);
g.lineTo(7,0.8);
g.lineTo(7,-1);
g.closePath();
g.fill();
}
var windmillVane = new SceneGraphNode();
windmillVane.doDraw = function(g) {
g.beginPath();
g.moveTo(0,0);
g.lineTo(0.5,0.1);
g.lineTo(1.5,0);
g.lineTo(0.5,-0.1);
g.closePath();
g.fill();
}
var world; // A SceneGraphNode representing the entire picture. This should
// be created in the createWorld() method.
var pixelSize; // The size of one pixel, in the transformed coordinates.
// This is used as the default width of a stroke.
var background = "#C8C8FF"; // A CSS color string giving the background color.
// the draw() function fills the canvas with this color.
var xleft = 0; // The requested xy-limits on the canvas, after the
var xright = 7; // coordinate transformation has been applied.
var ybottom = -1; // The transformation is applied in the draw() function.
var ytop = 4;
var frameNumber = 0; // Current frame number.
var cart; // TransformedObjects that are animated.
var wheel;
var sun;
var clickText1;
var clickText2;
var rotor;
/**
* Create the scene graph data structure. The global variable world must
* refer to the root node of the scene graph. This function is called in
* the init() function.
*/
function createWorld() {
pageLayout();
var i;
var sunTemp = new CompoundObject();
sunTemp.setColor("yellow"); // color for filled circle and light rays
for (i = 0; i < 12; i++) { // add the 12 light rays, with different rotations
sunTemp.add( new TransformedObject(line).setScale(0.75,0.75).setRotation(i*30) );
}
sunTemp.add( filledCircle ); // the face of the sun
sunTemp.add( new TransformedObject(circle).setColor("#B40000") ); // outlines the face
sun = new TransformedObject(sunTemp);
clickText1 = new TransformedObject(clickHere).setColor("#B40000").setScale(0.01,-0.01);
var wheelTemp = new CompoundObject();
wheelTemp.setColor("black"); // color for all but one of the subobjects
wheelTemp.add( new TransformedObject(filledCircle).setScale(2,2) );
wheelTemp.add( new TransformedObject(filledCircle).setScale(1.6,1.6).setColor("#CCCCCC") );
wheelTemp.add( new TransformedObject(filledCircle).setScale(0.4,0.4) );
for (i = 0; i < 12; i++) { // add the 12 spokes
wheelTemp.add( new TransformedObject(line).setRotation(i*30) );
}
wheel = new TransformedObject(wheelTemp);
var cartTemp = new CompoundObject();
cartTemp.setColor("red"); // color for the rectangular body of the cart
cartTemp.add( new TransformedObject(wheel).setScale(0.8,0.8).setTranslation(1.5,-0.1) );
cartTemp.add( new TransformedObject(wheel).setScale(0.8,0.8).setTranslation(-1.5,-0.1) );
cartTemp.add( new TransformedObject(filledRect).setScale(5,2).setTranslation(0,1) ); // the body of the cart
cart = new TransformedObject(cartTemp).setScale(0.3,0.3);
clickText2 = new TransformedObject(clickHere).setColor("yellow").setScale(0.01,-0.01);
var rotorTemp = new CompoundObject(); // a "rotor" consisting of three vanes
rotorTemp.setColor( "#C86464" ); // color for all of the vanes
rotorTemp.add( windmillVane );
rotorTemp.add( new TransformedObject(windmillVane).setRotation(120) );
rotorTemp.add( new TransformedObject(windmillVane).setRotation(240) );
rotor = new TransformedObject(rotorTemp);
var windmill = new CompoundObject();
windmill.setColor("#E0C8C8"); // color for the pole
windmill.add( new TransformedObject(filledRect).setScale(0.1,3).setTranslation(0,1.5) ); // the pole
windmill.add( new TransformedObject(rotor).setTranslation(0,3) ); // the rotating vanes
world = new CompoundObject();
world.setColor("#00961E"); // color used for the ground only
world.add(ground);
//world.add( new TransformedObject(filledRect).setScale(7,0.8).setTranslation(3.5,0).setColor("#646496") ); // road
//world.add( new TransformedObject(filledRect).setScale(7,0.06).setTranslation(3.5,0).setColor("white") ); // line in road
world.add( new TransformedObject(windmill).setScale(0.6,0.6).setTranslation(0.75,1) );
world.add( new TransformedObject(windmill).setScale(0.4,0.4).setTranslation(2.2,1.3) );
world.add( new TransformedObject(windmill).setScale(0.7,0.7).setTranslation(3.7,0.8) );
world.add( new TransformedObject(sun).setTranslation(5.5,3.3) );
world.add( new TransformedObject(clickText1).setTranslation(5.25,3.3) );
world.add( cart );
world.add( clickText2 );
//alert(2);
}
/**
* This will be called before each frame is drawn.
*/
function updateFrame() {
frameNumber++;
if (frameNumber>= 312){
frameNumber = 0;
frameNumber2 = 1;
}
cart.setTranslation(-3 + 13*(frameNumber % 300) / 300.0, 0);
clickText2.setTranslation(-3.3 + 13*(frameNumber % 300) / 300.0, 0.25);
if (typeof(pcoords[5]) != 'undefined') {
pcoords[5][0] = (-3.3 + 13*(frameNumber % 300) / 300.0-xleft)*canvas.width / (xright-xleft);
pcoords[5][1] = (0.25-ytop)*canvas.height / (ybottom-ytop);
}
wheel.setRotation(-frameNumber*3.1);
sun.setRotation(-frameNumber);
rotor.setRotation(frameNumber * 2.7);
}
// ------------------------------- graphics support functions --------------------------
/**
* Draw one frame of the animation. Probably doesn't need to be changed,
* except maybe to change the setting of preserveAspect in applyLimits().
*/
function draw() {
graphics.save(); // to make sure changes don't carry over from one call to the next
graphics.fillStyle = "transparent"; // background color
graphics.fillRect(0,0,wWidth, wHeight);
graphics.fillStyle = "black";
applyLimits(graphics,xleft,xright,ytop,ybottom,true);
graphics.lineWidth = pixelSize;
world.draw(graphics);
graphics.drawImage(images["dog-walking11"],200,200);
graphics.restore();
}
/**
* Applies a coordinate transformation to the graphics context, to map
* xleft,xright,ytop,ybottom to the edges of the canvas. This is called
* by draw(). This does not need to be changed.
*/
//pcoords[0][0] =
//pcoords[0][1]=
function applyLimits(g, xleft, xright, ytop, ybottom, preserveAspect) {
var width = canvas.width; // The width of this drawing area, in pixels.
var height = canvas.height; // The height of this drawing area, in pixels.
var k = portals.length;
var j;
var i = 0, widthheight, myradius;
var localerror = false;
if (pcoords.length < k) {
while (portals[i]){
j = i + 1;
if (width > 100){
var rWidth = width/(k + 1);
rWidth= Math.floor(rWidth);
} else {
var lWidth = 0;
var rWidth = 0;
}
if (height > 100){
var bHeight = height/(k + 1);
bHeight= Math.floor(bHeight);
} else {
var tHeight = 0;
var bHeight = 0;
}
var myleft = leftj[i] * rWidth - 50;
var mytop = j * bHeight - 50;
pcoords[i]= new Array;
pcoords[i][0] = myleft;
pcoords[i][1] = mytop;
i = i + 1;
}
}
if (preserveAspect) {
// Adjust the limits to match the aspect ratio of the drawing area.
var displayAspect = Math.abs(height / width);
var requestedAspect = Math.abs(( ybottom-ytop ) / ( xright-xleft ));
var excess;
if (displayAspect > requestedAspect) {
excess = (ybottom-ytop) * (displayAspect/requestedAspect - 1);
ybottom += excess/2;
ytop -= excess/2;
}
else if (displayAspect < requestedAspect) {
excess = (xright-xleft) * (requestedAspect/displayAspect - 1);
xright += excess/2;
xleft -= excess/2;
}
}
var pixelWidth = Math.abs(( xright - xleft ) / width);
var pixelHeight = Math.abs(( ybottom - ytop ) / height);
pixelSize = Math.min(pixelWidth,pixelHeight);
if (frameNumber == 4 || frameNumber == 5){
pcoords.push([(5.25-xleft)*width / (xright-xleft),(3.3-ytop)*height / (ybottom-ytop)]);
pcoords.push([(-3.3 + 13*(frameNumber % 300) / 300.0-xleft)*width / (xright-xleft), (0.25-ytop)*height / (ybottom-ytop)]);
}
g.scale( width / (xright-xleft), height / (ybottom-ytop) );
g.translate( -xleft, -ytop );
// if (frameNumber < 3)
}
//------------------ Animation framework ------------------------------
var animationTimeout = null; // A null value means the animation is off.
// Otherwise, this is the timeout ID.
function frame() {
// Draw one frame of the animation, and schedule the next frame.
updateFrame();
draw();
canvas.addEventListener("mousedown", doMouseDown, false);
animationTimeout = setTimeout(frame, 33);
}
function setAnimationRunning(run) {
if ( run ) {
if (animationTimeout == null) {
// If the animation is not already running, start
// it by scheduling a call to frame().
animationTimeout = setTimeout(frame, 33);
}
}
else {
if (animationTimeout != null) {
// If the animation is running, stop it by
// canceling the next scheduled call to frame().
clearTimeout(animationTimeout);
}
animationTimeout = null; // Indicates that animation is off.
}
}
//----------------------- initialization -------------------------------
function init() {
try {
canvas = document.getElementById("theCanvas");
if(typeof G_vmlCanvasManager != 'undefined') {
canvas = G_vmlCanvasManager.initElement(canvas);
}
graphics = canvas.getContext("2d");
}
catch (e) {
document.getElementById("message").innerHTML =
"Sorry, this page requires canvas graphics, but<br>" +
"it looks like your browser does not support it<br>" +
"Reported error: " + e;
return;
}
// add any other necessary initialization
document.getElementById("animateCheck").checked = true; // Make sure box is checked!
loadImage("dog-walking11");
createWorld();
setAnimationRunning(true); // start the animation
}
</script>
</head>
<body onload="init()" style="background-color: rgb(220,220,220)">
<div id="messagediv">
<h2>Hierarchical Modeling Example</h2>
<!-- For error reporting: the contents of the noscript tag are
shown only if JavaScript is not available. The paragraph with
id="message" is for reporting errors using JavaScript.-->
<noscript><b>This page requires JavaScript, which is<br>
not enabled in your browser.</b></noscript>
<p id="message" style="color:red"></p>
<p><input type="checkbox" id="animateCheck" onchange="setAnimationRunning(this.checked)">
<label for="animateCheck">Run Animation</label>
</p>
</div>
<div id="canvasdiv">
<canvas id="theCanvas" width= "400" height= "300"
style="background-color: transparent"></canvas>
</div>
</body>
</html>
It seems that I had to use graphics.drawImage() after using graphics.restore().
Though I was trying to draw the image in the correct order (after), compared to drawing the rectangles, circles etc, the shapes did not come out of the buffer onto the page until after the restore function().
When I was calling drawImage, I assumed that it was loading it onto the buffered canvas with the rest of the things, ready to be drawn in the correct order, when in fact it was putting it straight onto the page.
It seems strange since it was being called with the graphics context, so I thought it would be added to the rest of the canvas graphics, but i appear to be wrong.