I found 3 videos from SnorklTV.
https://www.youtube.com/watch?v=ZUM0i0DLKk0
https://www.youtube.com/watch?v=_44H68-QciU
https://www.youtube.com/watch?v=Z9NUkmQDB1k
There is a problem: GreenSock still works to use it, but it is no longer officially supported.
Error 1172: Definition com.greensock could not be found.
Code:
var bubbleMax: Number = 50;
var tl: TimelineMax = new TimelineMax();
function createBubble() {
var Bubble: Bubble = new Bubble();
Bubble.y = 380;
Bubble.x = randomRange(25, 610);
Bubble.alpha = 1;
addChild(Bubble);
var nestedTL: TimelineMax = new TimelineMax();
var speed: Number = randomRange(1, 3);
var wiggle: Number = randomRange(25, 50);
wiggle = Math.random() > .5 ? wiggle : -wiggle;
nestedTL.insert(TweenMax.to(Bubble, speed, {
y: -40,
ease:Quad.easeIn
}));
nestedTL.insert(TweenMax.to(Bubble, .5, {
scaleX: speed,
scaleY: speed,
alpha: randomRange(.5, 1)
}));
nestedTL.insert(TweenMax.to(Bubble, speed * .25, {
x: String(wiggle),
repeat: randomRange(1, 4),
yoyo: true
}));
tl.append(nestedTL, speed * -.89);
}
function randomRange(min: Number, max: Number): Number {
return min + (Math.random() * (max - min));
}
function init() {
for (var count: Number = 0; count < bubbleMax; count++) {
createBubble();
}
}
init();
Any ideas for the codes without importing Greensock?
import com.greensock.*;
import com.greensock.easing.*;
var bubbleMax: Number = 200;
var tl: TimelineMax = new TimelineMax();
function createBubble() {
var Bubble: bubble = new bubble();
Bubble.y = 430;
Bubble.x = randomRange(25, 610);
Bubble.alpha = 0;
addChild(Bubble);
Bubble.visible = true;
var nestedTL: TimelineMax = new TimelineMax();
var speed: Number = randomRange(1, 3);
var wiggle: Number = randomRange(25, 50);
wiggle = Math.random() > .5 ? wiggle : -wiggle;
nestedTL.insert(TweenMax.to(Bubble, speed, {
y: -40,
ease:Quad.easeIn
}));
nestedTL.insert(TweenMax.to(Bubble, .5, {
scaleX: speed,
scaleY: speed,
alpha: randomRange(.5, 1)
}));
nestedTL.insert(TweenMax.to(Bubble, speed * .25, {
x: String(wiggle),
repeat: randomRange(1, 4),
yoyo: true
}));
tl.append(nestedTL, speed * -.89);
}
function randomRange(min: Number, max: Number): Number {
return min + (Math.random() * (max - min));
}
function init() {
for (var count: Number = 0; count < bubbleMax; count++) {
createBubble();
}
}
init();
Related
I am trying to build a little world with a Third Person Controller.
For the character, I followed this tutorial by SimonDev and just changed the model from a FBX to GLTF.
Now, I try to implement a Physics World with Cannon.js.
I got it to the point where the collider body is positioned at the starting point of the model. But it stays there after I move the model. I need the collider body to be attached at the model.
I know that I should move the collider body and update the character model to that position but I cannot get it to work. This is my current code. Maybe it is just a simple solution but I am new to Cannon.js, so any help is appreciated. Thank you!
class BasicCharacterController {
constructor(experience, params) {
this.experience = experience;
this._Init(params);
}
_Init(params) {
this._params = params;
this._decceleration = new THREE.Vector3(-0.0005, -0.0001, -5.0);
this._acceleration = new THREE.Vector3(1, 0.25, 50.0);
this._velocity = new THREE.Vector3(0, 0, 0);
this._position = new THREE.Vector3();
this._animations = {};
this._input = new BasicCharacterControllerInput();
this._stateMachine = new CharacterFSM(
new BasicCharacterControllerProxy(this._animations));
this._LoadModels();
}
_LoadModels() {
this.physicsCharacterShape = new CANNON.Box(new CANNON.Vec3(0.5, 1, 0.5));
this.physicsCharacterBody = new CANNON.Body({
mass: 0,
shape: this.physicsCharacterShape,
position: new CANNON.Vec3(0, 0, 0)
});
this.experience.physicsWorld.addBody(this.physicsCharacterBody);
this.gltfLoader = new GLTFLoader();
this.gltfLoader.setPath('./sources/assets/');
this.gltfLoader.load('VSLBINA_TPOSE_GLTF.gltf', (gltf) => {
gltf.scene.traverse(c => {
c.castShadow = true;
});
this._target = gltf.scene;
this._params.scene.add(this._target);
this._target.position.copy(this.physicsCharacterBody.position);
this._target.quaternion.copy(this.physicsCharacterBody.quaternion);
this._mixer = new THREE.AnimationMixer(this._target);
this._manager = new THREE.LoadingManager();
this._manager.onLoad = () => {
this._stateMachine.SetState('idle');
};
const _OnLoad = (animName, anim) => {
const clip = anim.animations[0];
const action = this._mixer.clipAction(clip);
this._animations[animName] = {
clip: clip,
action: action,
};
};
const loader = new GLTFLoader(this._manager);
loader.setPath('./sources/assets/');
loader.load('VSLBINA_WALKING_GLTF.gltf', (a) => { _OnLoad('walk', a); });
loader.load('VSLBINA_IDLE_GLTF.gltf', (a) => { _OnLoad('idle', a); });
});
}
get Position() {
return this._position;
}
get Rotation() {
if (!this._target) {
return new THREE.Quaternion();
}
return this._target.quaternion;
}
Update(timeInSeconds) {
if (!this._stateMachine._currentState) {
return;
}
this._stateMachine.Update(timeInSeconds, this._input);
const velocity = this._velocity;
const frameDecceleration = new THREE.Vector3(
velocity.x * this._decceleration.x,
velocity.y * this._decceleration.y,
velocity.z * this._decceleration.z
);
frameDecceleration.multiplyScalar(timeInSeconds);
frameDecceleration.z = Math.sign(frameDecceleration.z) * Math.min(
Math.abs(frameDecceleration.z), Math.abs(velocity.z));
velocity.add(frameDecceleration);
const controlObject = this._target;
const _Q = new THREE.Quaternion();
const _A = new THREE.Vector3();
const _R = controlObject.quaternion.clone();
const acc = this._acceleration.clone();
if (this._input._keys.shift) {
acc.multiplyScalar(2.0);
}
if (this._input._keys.forward) {
velocity.z += acc.z * timeInSeconds;
}
if (this._input._keys.backward) {
velocity.z -= acc.z * timeInSeconds;
}
if (this._input._keys.left) {
_A.set(0, 1, 0);
_Q.setFromAxisAngle(_A, 4.0 * Math.PI * timeInSeconds * this._acceleration.y);
_R.multiply(_Q);
}
if (this._input._keys.right) {
_A.set(0, 1, 0);
_Q.setFromAxisAngle(_A, 4.0 * -Math.PI * timeInSeconds * this._acceleration.y);
_R.multiply(_Q);
}
controlObject.quaternion.copy(_R);
const oldPosition = new THREE.Vector3();
oldPosition.copy(controlObject.position);
const forward = new THREE.Vector3(0, 0, 1);
forward.applyQuaternion(controlObject.quaternion);
forward.normalize();
const sideways = new THREE.Vector3(1, 0, 0);
sideways.applyQuaternion(controlObject.quaternion);
sideways.normalize();
sideways.multiplyScalar(velocity.x * timeInSeconds);
forward.multiplyScalar(velocity.z * timeInSeconds);
controlObject.position.add(forward);
controlObject.position.add(sideways);
this._position.copy(controlObject.position);
if (this._mixer) {
this._mixer.update(timeInSeconds);
};
// Physics Collider Body
// if (this._target) {
// this._target.position.copy(this.physicsCharacterBody.position);
// this._target.quaternion.copy(this.physicsCharacterBody.quaternion);
// }
}
};
I'm trying to create a morphing object based on a number of particles, I have 4 objects, 2 normal three js shapes (cube and sphere) and 2 OBJ objects.
When I hover the related name of the object it changes to that one.
The problem here is that when I try to hover the name of the obj object the console.log returns the following error at the last line where I try to get newParticles.vertices[i].x, etc:
Uncaught TypeError: Cannot read property 'x' of undefined
Code:
// Particle Vars
var particleCount = numberOfParticles;
let spherePoints,
cubePoints,
rocketPoints,
spacemanPoints;
var particles = new Geometry(),
sphereParticles = new Geometry(),
cubeParticles = new Geometry(),
rocketParticles = new Geometry(),
spacemanParticles = new Geometry();
var pMaterial = new PointsMaterial({
color: particleColor,
size: particleSize,
map: new TextureLoader().load(particleImage),
blending: AdditiveBlending,
transparent: true
});
// Objects
var geometry = new SphereGeometry( 5, 30, 30 );
spherePoints = GeometryUtils.randomPointsInGeometry(geometry, particleCount)
var geometry = new BoxGeometry( 9, 9, 9 );
cubePoints = GeometryUtils.randomPointsInGeometry(geometry, particleCount)
// Custom (OGJ) Objects
const codepenAssetUrl = 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/605067/';
var objLoader = new OBJLoader();
objLoader.setPath('./upload/');
objLoader.load( 'Nymph.obj', function ( object ) {
object.traverse( function ( child ) {
if ( child instanceof Mesh ) {
let scale = 0.2; //era 2.1 per il razzo
let area = new Box3();
area.setFromObject( child );
let yOffset = (area.max.y * scale) / 2;
child.geometry.scale(scale,scale,scale);
rocketPoints = GeometryUtils.randomPointsInBufferGeometry(child.geometry, particleCount);
createVertices(rocketParticles, rocketPoints, yOffset, 2);
}
});
});
var objLoader = new OBJLoader();
objLoader.setPath(codepenAssetUrl);
objLoader.load( 'Astronaut.obj', function ( object ) {
object.traverse( function ( child ) {
if ( child instanceof Mesh ) {
let scale = 4.6;
let area = new Box3();
area.setFromObject( child );
let yOffset = (area.max.y * scale) / 2;
child.geometry.scale(scale,scale,scale);
spacemanPoints = GeometryUtils.randomPointsInBufferGeometry(child.geometry, particleCount);
createVertices(spacemanParticles, spacemanPoints, yOffset, 3);
}
});
});
// Particles
for (var p = 0; p < particleCount; p++) {
var vertex = new Vector3();
vertex.x = 0;
vertex.y = 0;
vertex.z = 0;
particles.vertices.push(vertex);
}
createVertices (sphereParticles, spherePoints, null, null)
createVertices (cubeParticles, cubePoints, null, 1)
function createVertices (emptyArray, points, yOffset = 0, trigger = null) {
for (var p = 0; p < particleCount; p++) {
var vertex = new Vector3();
vertex.x = points[p]['x'];
vertex.y = points[p]['y'] - yOffset;
vertex.z = points[p]['z'];
emptyArray.vertices.push(vertex);
}
if (trigger !== null) {
triggers[trigger].setAttribute('data-disabled', false)
}
}
var particleSystem = new Points(
particles,
pMaterial
);
particleSystem.sortParticles = true;
// Add the particles to the scene
scene.add(particleSystem);
// Animate
const normalSpeed = (defaultAnimationSpeed/100),
fullSpeed = (morphAnimationSpeed/100)
let animationVars = {
speed: normalSpeed
}
function animate() {
particleSystem.rotation.y += animationVars.speed;
particles.verticesNeedUpdate = true;
window.requestAnimationFrame( animate );
renderer.render( scene, camera );
}
animate();
setTimeout(toSphere, 500);
function toSphere () {
handleTriggers(0);
morphTo(sphereParticles);
}
function toCube () {
handleTriggers(1);
morphTo(cubeParticles);
}
function toRocket () {
handleTriggers(2);
morphTo(rocketParticles);
}
function toSpaceman () {
handleTriggers(3);
morphTo(spacemanParticles);
}
function morphTo (newParticles, color = '0xffffff') {
TweenMax.to(animationVars, .3, {ease:
Power4.easeIn, speed: fullSpeed, onComplete: slowDown});
particleSystem.material.color.setHex(color);
for (var i = 0; i < particles.vertices.length; i++){
TweenMax.to(particles.vertices[i], 4, {ease:
Elastic.easeOut.config( 1, 0.75), x: newParticles.vertices[i].x, y: newParticles.vertices[i].y, z: newParticles.vertices[i].z})
}
}
P.S. note that I'm using webpack.
Using the WebGL API, is there a way to count the number of vertices rendered within a given canvas? I've seen some tools that attempt to accomplish this task but some are giving strange results (e.g. Three.js' renderer.info.render is reporting my scene has 10,134.3 triangles).
Any help with using the raw WebGL API to count the number of rendered vertices (and, ideally, points and lines) would be greatly appreciated.
WebGL can't do this for you but you could can add your own augmentation.
The most obvious way is just to track your own usage. Instead of calling gl.drawXXX call functionThatTracksDrawingCountsXXX and track the values yourself.
You can also augment the WebGL context itself. Example
// copy this part into a file like `augmented-webgl.js`
// and include it in your page
(function() {
// NOTE: since WebGL constants are um, constant
// we could statically init this.
let primMap;
function addCount(ctx, type, count) {
const ctxInfo = ctx.info;
const primInfo = primMap[type];
ctxInfo.vertCount += count;
ctxInfo.primCount[primInfo.ndx] += primInfo.fn(count);
}
WebGLRenderingContext.prototype.drawArrays = (function(oldFn) {
return function(type, offset, count) {
addCount(this, type, count);
oldFn.call(this, type, offset, count);
};
}(WebGLRenderingContext.prototype.drawArrays));
WebGLRenderingContext.prototype.drawElements = (function(oldFn) {
return function(type, count, indexType, offset) {
addCount(this, type, count);
oldFn.call(this, type, count, indexType, offset);
};
}(WebGLRenderingContext.prototype.drawElements));
HTMLCanvasElement.prototype.getContext = (function(oldFn) {
return function(type, ...args) {
const ctx = oldFn.call(this, type, args);
if (ctx && type === "webgl") {
if (!primMap) {
primMap = {};
primMap[ctx.POINTS] = { ndx: 0, fn: count => count, };
primMap[ctx.LINE_LOOP] = { ndx: 1, fn: count => count, };
primMap[ctx.LINE_STRIP]= { ndx: 1, fn: count => count - 1, };
primMap[ctx.LINES] = { ndx: 1, fn: count => count / 2 | 0, };
primMap[ctx.TRIANGLE_STRIP] = { ndx: 2, fn: count => count - 2, };
primMap[ctx.TRIANGLE_FAN] = { ndx: 2, fn: count => count - 2, };
primMap[ctx.TRIANGLES] = { ndx: 2, fn: count => count / 3 | 0, };
};
ctx.info = {
vertCount: 0,
primCount: [0, 0, 0],
};
}
return ctx;
}
}(HTMLCanvasElement.prototype.getContext));
}());
// ---- cut above ----
const $ = document.querySelector.bind(document);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({canvas: $('canvas')});
const geometry = new THREE.BoxGeometry(1, 1, 1);
const items = [];
for (let i = 0; i < 50; ++i) {
let item;
switch(rand(0, 3) | 0) {
case 0:
case 1:
const material = new THREE.MeshBasicMaterial({
color: rand(0xFFFFFF) | 0,
wireframe: rand(0, 3) > 2,
});
item = new THREE.Mesh(geometry, material);
break;
case 2:
const pmat = new THREE.PointsMaterial({
color: rand(0xFFFFFF) | 0,
});
item = new THREE.Points(geometry, pmat);
break;
default:
throw "oops";
}
item.position.x = rand(-10, 10);
item.position.y = rand(-10, 10);
item.position.z = rand( 0, -50);
scene.add(item);
items.push(item);
}
camera.position.z = 5;
const countElem = $('#count');
function render(time) {
time *= 0.001;
resize();
// animate the items
items.forEach((items, ndx) => {
items.rotation.x = time * 1.2 + ndx * 0.01;
items.rotation.y = time * 1.1;
});
// turn on/off a random items
items[rand(items.length) | 0].visible = Math.random() > .5;
renderer.render(scene, camera);
// get the current counts
const info = renderer.context.info;
countElem.textContent = ` VERTS: ${info.vertCount}
POINTS: ${info.primCount[0]}
LINES: ${info.primCount[1]}
TRIANGLES: ${info.primCount[2]}`;
// zero out the count
renderer.context.info.vertCount = 0;
renderer.context.info.primCount = [0, 0, 0];
requestAnimationFrame(render);
}
requestAnimationFrame(render);
function rand(min, max) {
if (max === undefined) {
max = min;
min = 0;
}
return Math.random() * (max - min) + min;
}
function resize() {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
if (canvas.width !== width || canvas.height !== height) {
renderer.setSize(width, height, false);
camera.aspectRatio = width / height;
camera.updateProjectionMatrix();
}
}
body { border: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
#ui { position: absolute; left: 1em; top: 1em; background: rgba(0,0,0,.5); color: white; padding: .5em; width: 10em; }
<canvas></canvas>
<div id="ui">
<pre id="count"></pre>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/92/three.min.js"></script>
Of course you might want to add support for drawArraysInstanced etc... and support for WebGL2.
We removed the amount of processed vertices from renderer.info.render since the important measurement is the amount or rendered primitives (so triangles, points, lines). Please read https://github.com/mrdoob/three.js/pull/13404 and the related issues/PRs for more information. If you still want to know how many vertices were processed, you need to count manually. WebGL can't do this for you.
I am creating animations of migrating sea animals across the Pacific using OpenLayers. I would like each "animal" to trace a track as it goes over time. At the head of the track will be an icon/marker/overlay representing that animal. I have gotten this to work for one track, but although I am able to grow the track of each animal as a linestring constructed segment by segment, I am unable to specifically assign an icon/marker/overlay to each track. Instead, I am only able to animate one icon on one track. The rest of the linestring tracks proceed, but they do not have an icon at the head of the track as it is traced out. Here is my code. Any help appreciated.
// draw tracks
function makeLineString(id, species, multipointCoords, tracksTime) {
// layer structure and style assignment
var trackSource = new ol.source.Vector();
var trackLayer = new ol.layer.Vector({
source: trackSource,
style: BWtrackStyle
});
map.addLayer(trackLayer);
var lineString = new ol.geom.LineString([
ol.proj.fromLonLat(multipointCoords[0][0])
]);
var trackFeature = new ol.Feature({
geometry: lineString
});
if (species === "Blue Whale") {
trackFeature.setStyle([BWtrackStyle, shadowStyle]);
};
trackSource.addFeature(trackFeature);
// icon-marker-overlay styling
var BW2205005icon = document.getElementById('BW2205005icon');
var BW2205005marker = new ol.Overlay({
positioning: 'center-center',
offset: [0, 0],
element: BW2205005icon,
stopEvent: false
});
map.addOverlay(BW2205005marker);
var BW2205012icon = document.getElementById('BW2205012icon');
var BW2205012marker = new ol.Overlay({
positioning: 'center-center',
offset: [0, 0],
element: BW2205012icon,
stopEvent: false
});
map.addOverlay(BW2205012marker);
var coordinate, i = 1,
length = multipointCoords[0].length;
var currentTime = tracksTime[0][0];
var nextTime = tracksTime[0][1];
speedOption = 100; // the highter this value, the faster the tracks, see next line
var transitionTime = (nextTime - currentTime) / speedOption;
console.log(transitionTime);
var timer;
timer = setInterval(function() {
segmentConstruction(id, multipointCoords, tracksTime);
}, transitionTime);
function segmentConstruction(id, multipointCoords, tracksTime) {
coordinate = ol.proj.fromLonLat(multipointCoords[0][i]);
lineString.appendCoordinate(coordinate);
console.log(id);
if (id === "BW2205005") {
BW2205005marker.setPosition(coordinate);
} else {
BW2205012marker.setPosition(coordinate);
};
if (i >= length - 1) {
clearInterval(timer);
} else {
i++;
clearInterval(timer);
currentTime = tracksTime[0][i];
nextTime = tracksTime[0][i + 1];
transitionTime = (nextTime - currentTime) / speedOption;
timer = setInterval(function() {
segmentConstruction(id, multipointCoords, tracksTime);
}, transitionTime);
};
};
};
I would suggest this for animation of multiple markers with smooth movement, but it requires a delay for other styles:
var vehicles= [{ "curlon":77.654397, "curlat":12.959898, "prevlon":77.651951, "prevlat":12.951074 },{ "curlon":77.672936, "curlat":12.958100, "prevlon":77.649290, "prevlat":12.960024 }];
function push_data_for_multimarker(map,gps_obj)
{
destruct=false;
var getz = gps_obj;
var data_arry = [];
for (var i = 0, length = getz.length; i < length; i++)
{
gps_smooth_coordinates_generator(parseFloat(getz[i].prevlon),parseFloat(getz[i].prevlat),parseFloat(getz[i].curlon),parseFloat(getz[i].curlat));
}
}
function gps_smooth_coordinates_generator(map,sourcelon,sourcelat,destinationlon,destinationlat)
{
var vehicle_data=[];
var beginz=ol.proj.transform([sourcelon,sourcelat], 'EPSG:4326', 'EPSG:3857');
var endz=ol.proj.transform([destinationlon,destinationlat], 'EPSG:4326', 'EPSG:3857');
vehicle_data.push(beginz);
vehicle_data.push(endz);
var path= vehicle_data;
var genrated_positions = [];
for(var k = 1;k<path.length;k++)
{
var pointsNo = 1000;
var startPos = {};
startPos.lat = path[k-1][1];
startPos.lng = path[k-1][0];
var endPos = {};
endPos.lat = path[k][1];
endPos.lng = path[k][0];
var latDelta = (endPos.lat - startPos.lat) / pointsNo;
var lngDelta = (endPos.lng - startPos.lng) / pointsNo;
for (var i = 0; i < pointsNo; i++)
{
var curLat = startPos.lat + i * latDelta;
var curLng = startPos.lng + i * lngDelta;
var arr = [];
arr.push(curLng);
arr.push(curLat);
genrated_positions.push(arr);
}
}
animate_multiple_marker(genrated_positions);
}
function animate_multiple_marker(map,veh_croods)
{
var routeCoords = veh_croods;
var routeLength = routeCoords.length;
console.log("routeCoords"+routeCoords);
console.log("routeLength"+routeLength);
var geoMarker = new ol.Feature({
type: 'geoMarker',
geometry: new ol.geom.Point(routeCoords[0])
});
var off_style =new ol.style.Style({
image: new ol.style.Circle({
radius: 7,
snapToPixel: false,
fill: new ol.style.Fill({color: 'black'}),
stroke: new ol.style.Stroke({
color: 'white', width: 2
})
})
});
var now;
var animating = false;
var speed=20;
var moveFeature = function(event) {
var vectorContext = event.vectorContext;
var frameState = event.frameState;
if (animating) {
var elapsedTime = frameState.time - now;
var index = Math.round(speed * elapsedTime / 1000);
if (index >= routeLength) {
console.log("index>>"+index);
stopAnimation();
animating = false;
console.log("animation ends");
}
if(animating)
{
var currentPoint = new ol.geom.Point(routeCoords[index]);
var feature = new ol.Feature(currentPoint);
vectorContext.drawFeature(feature,off_style);
}
else
{}
if(destruct)
{
console.log("termination initiated");
stopAnimation();
//destruct=false;
}
else
{ }
}
else
{
console.log("Not amnimating!!");
}
// tell OL3 to continue the postcompose animation
map.render();
};
triggerz_animation();
function stopAnimation() {
animating = false;
caller=false;
//remove listener
map.un('postcompose', moveFeature);
}
function start_vehicles()
{
animating = true;
now = new Date().getTime();
map.on('postcompose', moveFeature);
map.render();
}
if(caller)
{
start_vehicles();
}
else
{
}
}
var caller=false;
var drive_vehicle;
function triggerz_animation()
{
caller=true;
}
var destruct=false;
function collapse_animation()
{
destruct=true;
}
with reference to
http://openlayers.org/en/latest/examples/feature-move-animation.html?q=animation
Let this be helpfull..Thank You
pass data as json to the function:
push_data_for_multimarker(map,vehicles);
I think your overlay is being reused for each line. In any case, it would probably be simpler and more performant to use a point feature instead of the overlay, as follows:
add a new style to your layer for points (circle, image, etc.)
add a point feature representing the head of each line
update the coordinates of the point feature when necessary
In other words, each animal would have a linestring and a point feature. The style on the layer would include the stroke style for the line and the icon style for the point.
You could also style the points independently by giving each point feature it's own style, or using a style function on the layer.
I am using google API to create charts. I am able to create OHLC(Candlestick) charts. But I want to add an overlay of Moving Average to it. Can anyone please guide me as to how I can do it?
Thanks in advance.
Here's an example of how to add a moving average line to a CandlestickChart:
function drawChart() {
var data = new google.visualization.DataTable();
data.addColumn('date', 'Date');
data.addColumn('number', 'Low');
data.addColumn('number', 'Open');
data.addColumn('number', 'Close');
data.addColumn('number', 'High');
var low, open, close = 45, high;
for (var i = 0; i < 30; i++) {
open = close;
close += ~~(Math.random() * 10) * Math.pow(-1, ~~(Math.random() * 2));
high = Math.max(open, close) + ~~(Math.random() * 10);
low = Math.min(open, close) - ~~(Math.random() * 10);
data.addRow([new Date(2014, 0, i + 1), low, open, close, high]);
}
// use a DataView to calculate an x-day moving average
var days = 5;
var view = new google.visualization.DataView(data);
view.setColumns([0, 1, 2, 3, 4, {
type: 'number',
label: days + '-day Moving Average',
calc: function (dt, row) {
// calculate average of closing value for last x days,
// if we are x or more days into the data set
if (row >= days - 1) {
var total = 0;
for (var i = 0; i < days; i++) {
total += dt.getValue(row - i, 3);
}
var avg = total / days;
return {v: avg, f: avg.toFixed(2)};
}
else {
// return null for < x days
return null;
}
}
}]);
var chart = new google.visualization.ComboChart(document.querySelector('#chart_div'));
chart.draw(view, {
height: 400,
width: 600,
chartArea: {
left: '7%',
width: '70%'
},
series: {
0: {
type: 'candlesticks'
},
1: {
type: 'line'
}
}
});
}
google.load("visualization", "1", {packages:["corechart"], callback: drawChart});
see it working here: http://jsfiddle.net/asgallant/74u6ox8b/