Open curtain animation on a famo.us Scrollview - animation

I'm trying to create an open curtain animation on a Scrollview, so when an item in a Scrollview is clicked, it and item to its left move left, and all items after it move to the right. when the item is clicked again, the curtain closes. Thinking of doing it by moving items out from the Scrollview to 2 SequentialLayouts and once the curtain closes, move them back into the Scollview. Can this be done? Can you move nodes / views around in the render tree from one node to another?
Any other design approaches I should consider?

Here is my version of the curtains you described. It was hard to know exactly what you wanted, but I took a stab at it.
define('main', function(require, exports, module) {
var Engine = require("famous/core/Engine");
var Surface = require("famous/core/Surface");
var RenderNode = require("famous/core/RenderNode");
var Modifier = require("famous/core/Modifier");
var Utility = require("famous/utilities/Utility");
var Scrollview = require("famous/views/Scrollview");
var Transitionable = require("famous/transitions/Transitionable");
var SnapTransition = require("famous/transitions/SnapTransition");
Transitionable.registerMethod('snap', SnapTransition);
var snap = {
method: 'snap',
period: 600,
dampingRatio: 0.6
};
var mainContext = Engine.createContext();
var scrollview = new Scrollview({
direction: Utility.Direction.X
});
var views = [];
scrollview.sequenceFrom(views);
function _resize(index, views, event) {
console.log(index, event);
var itsMe = (index === event.index);
if (itsMe) {
this.trans.halt();
if (this.open)
this.trans.set(100, snap);
else
this.trans.set(400, snap);
this.open = !this.open;
scrollview.goToPage(index);
} else {
if (event.isOpen) {
this.trans.halt();
this.trans.set(100, snap);
} else {
this.trans.halt();
this.trans.set(20, snap);
}
this.open = false;
}
}
function _resizeChosen(index, views, event) {
scrollview._eventOutput.emit('itemChosen', {
index: index,
isOpen: views[index].surface.open
});
}
function _surfaceSize() {
return [this.trans.get(), undefined];
}
for (var i = 0; i < 20; i++) {
var node = new RenderNode();
node.surface = new Surface({
content: (i + 1),
size: [undefined, undefined],
properties: {
backgroundColor: "hsl(" + (i * 360 / 20) + ", 90%, 50%)",
lineHeight: "50px",
textAlign: "center"
}
});
node.surface._index = i;
node.surface.open = false;
node.surface.state = new Modifier();
node.surface.trans = new Transitionable(50);
node.surface.state.sizeFrom(_surfaceSize.bind(node.surface));
node.add(node.surface.state).add(node.surface);
node.surface.pipe(scrollview);
node.surface._eventOutput.subscribe(scrollview._eventOutput);
node.surface.on('click', _resizeChosen.bind(node.surface, i, views));
node.surface.on('itemChosen', _resize.bind(node.surface, i, views));
views.push(node);
}
mainContext.add(scrollview);
});
require(['main']);
<script src="http://requirejs.org/docs/release/2.1.16/minified/require.js"></script>
<script src="http://code.famo.us/lib/requestAnimationFrame.js"></script>
<script src="http://code.famo.us/lib/classList.js"></script>
<script src="http://code.famo.us/lib/functionPrototypeBind.js"></script>
<link rel="stylesheet" type="text/css" href="http://code.famo.us/famous/0.3.5/famous.css" />
<script src="http://code.famo.us/famous/0.3.5/famous.min.js"></script>

Related

Forge viewer: How to disable transparancy at Materials

In order to set colors upon many objects in the forge viewer, I made an extension which sets the color upon material and connect the objects to it. This way I only update the viewer once instead of multiple times. However I would like to have no transparancy, but I keep on getting it, even I have set the material transparancy to false. How can I fix this?
void setColorMaterial(material, viewer)
{
var mesh_material = this.addMaterial(material, viewer)
for (var i=0; i< material.dbIds.length; i++) {
var dbid = material.dbIds[i]
var it = viewer.model.getData().instanceTree
it.enumNodeFragments(dbid, function (fragId) {
var renderProxy = viewer.impl.getRenderProxy(viewer.model, fragId)
renderProxy.meshProxy = new THREE.Mesh(renderProxy.geometry, renderProxy.material)
renderProxy.meshProxy.matrix.copy(renderProxy.matrixWorld)
renderProxy.meshProxy.matrixWorldNeedsUpdate = true
renderProxy.meshProxy.matrixAutoUpdate = false
renderProxy.meshProxy.frustumCulled = false
viewer.impl.addOverlay(material.name, renderProxy.meshProxy)
}, false)
}
this.materialHolder.push(material)
viewer.impl.invalidate(true)
}
void addMaterial(material, viewer)
{
var mesh_material = new THREE.MeshPhongMaterial({
color: #007dfa,
opacity: 1,
transparent: false,
name: 'materialName'
})
viewer.impl.matman().addMaterial(this.newGuid(), mesh_material)
viewer.impl.createOverlayScene(material.name, mesh_material, mesh_material)
}
First, thank you for your replies, unfortunately the transparancy is still there.
Alex: I removed the opacity.
Bryan: I fixed the code in according to your suggestions (as I understood them). Below is updated code.
As you can see from the picture in the link that the transparancy is still there. Any other suggestions would be greatly appreciated.
setColorMaterial(material, viewer) {
var mesh_material = this.addMaterial(material, viewer)
for (var i=0; i< material.dbIds.length; i++) {
var dbid = material.dbIds[i]
//from dbid to node, to fragid
var it = viewer.model.getData().instanceTree
it.enumNodeFragments(dbid, function (fragId) {
var renderProxy = viewer.impl.getRenderProxy(viewer.model, fragId)
renderProxy.meshProxy = new THREE.Mesh(renderProxy.geometry, mesh_material)
renderProxy.meshProxy.matrix.copy(renderProxy.matrixWorld)
renderProxy.meshProxy.matrixWorldNeedsUpdate = true
renderProxy.meshProxy.matrixAutoUpdate = false
renderProxy.meshProxy.frustumCulled = false
viewer.impl.addOverlay(material.name, renderProxy.meshProxy)
}, false)
}
viewer.impl.invalidate(true)
}
addMaterial(material, viewer) {
var mesh_material = new THREE.MeshPhongMaterial({
color: material.color,
transparent: false,
blendEquationAlpha: false,
name: material.name
})
viewer.impl.matman().addMaterial(this.newGuid(), mesh_material)
viewer.impl.createOverlayScene(material.name, mesh_material)
return mesh_material
}
Transparency still there image

Referring to a child view from the parent view on click or tap event in TI

In appcelerator TI code - I have a month scrollable view to which I have added week views and to which I have added days as views.
The expectation on click event of a day I should be able to retrieve properties of the date. However on singletap event I am getting reference to the week view and not able to get the child view "days view. how can I get a reference to the days view on single tap to click event?
Code -Widget.js
var args = arguments[0] || {};
var Moment = require('alloy/moment');
var ROWS = 6;
var COLUMNS = 7;
_.defaults(args, {
// Data
current_date: Moment(),
active_dates: [],
min_date: Moment().subtract(6, 'months'),
max_date: Moment().add(6, 'months'),
// Style
backgroundColor: 'transparent',
dateBackgroundColor: 'transparent',
todayBackgroundColor: '#af80',
dateTextColor: '#fff',
todayTextColor: '#000',
activePinColor: '#f39911',
inactivePinColor: 'transparent',
selectedBackgroundColor: '#60f39911',
fontFamily: '',
// Behaviour
allowInactiveSelection: false,
fillMonth: false,
enablePastDays: false
});
var active_dates = args.active_dates ? getMomentDates(args.active_dates) : [];
var current_page = 0;
/////////////
// Methods //
/////////////
function refreshArrows() {
$.leftBtn.opacity = current_page <= 0 ? 0.4 : 1;
$.rightBtn.opacity = current_page >= $.monthScroll.views.length - 1 ? 0.4 : 1;
}
function getDayLabels() {
var days = Moment.weekdaysMin();
days.push(days.shift()); // Moment week has Sunday at index 0
_.each(days, function(day, i) {
var width = Math.floor($.calendar.rect.width / COLUMNS);
var $label = $.UI.create('Label', {
classes: ['dayLabel'],
width: width,
text: day.charAt(0),
left: i * width,
font: {
fontFamily: args.fontFamily
}
});
$.dayLabels.add($label);
});
}
function getMomentDates(dates) {
return _.map(dates, function(date) {
return Moment(date);
});
}
function isInMomentsList(date, dates) {
return _.find(dates, function(day) {
return date.isSame(day, 'day');
});
}
function getDayContainer(number) {
var $this = $.UI.create('View', {
classes: ['day'],
width: Math.floor($.monthScroll.rect.width / COLUMNS),
height: Math.floor($.monthScroll.rect.height / ROWS),
backgroundColor: args.dateBackgroundColor,
opacity: 1,
date: null,
active: null,
});
$this.add($.UI.create('Label', {
classes: ['dayNumber'],
color: '#fff',
text: number,
font: {
fontFamily: args.fontFamily
}
}));
$this.add($.UI.create('View', {
classes: ['dayDot'],
backgroundColor: 'transparent'
}));
return $this;
}
function setItemDate($item, date) {
$item.date = date;
$item.children[0].text = date.date();
}
function setItemActive($item, active) {
$item.active = active;
$item.children[1].backgroundColor = active ? args.activePinColor : args.inactivePinColor;
}
function setItemToday($item, is_today) {
$item.backgroundColor = is_today ? args.todayBackgroundColor : args.dateBackgroundColor;
$item.children[0].color = is_today ? args.todayTextColor : args.dateTextColor;
}
function setItemCurrent($item, current) {
$item.opacity = current ? 1 : 0.5;
}
function getMonthView(month, year) {
var month_rows = [];
var start_date = Moment().month(month).year(year).startOf('month').startOf('week');
var end_date = Moment().month(month).year(year).endOf('month').endOf('week');
// Month skeleton
var $month_view = $.UI.create('View', {
classes: ['month'],
month: month,
year: year,
backgroundColor: args.backgroundColor,
ready: false
});
// Month activity indicator
var $loader = Ti.UI.createActivityIndicator({
style: OS_IOS ? Ti.UI.iPhone.ActivityIndicatorStyle.BIG : Ti.UI.ActivityIndicatorStyle.BIG,
center: {
x: '50%',
y: '50%'
}
});
$month_view.add($loader);
$month_view.__loader = $loader;
$loader.show();
return $month_view;
}
function buildMonth($month_view, dates) {
if (!$month_view || $month_view.ready) return;
var start_date = Moment().month($month_view.month).year($month_view.year).startOf('month').startOf('week');
var end_date = Moment().month($month_view.month).year($month_view.year).endOf('month').endOf('week');
var $days_container = Ti.UI.createView({
height: Ti.UI.FILL,
width: Ti.UI.FILL
});
// Separators
for (var i = 0; i < ROWS; i++) {
$days_container.add($.UI.create('View', {
classes: ['hr'],
top: (i+1) * Math.floor($.monthScroll.rect.height / ROWS)
}));
}
// Add day containers
for (var d = 0; d < ROWS*COLUMNS; d++) {
var curday = Moment(start_date).add(d, 'days');
// If fillMonth is disabled, add only this month's days
if (curday.month() === $month_view.month || args.fillMonth == true) {
var $curview = getDayContainer(curday.date());
var row = Math.floor(d/COLUMNS);
var col = d % COLUMNS;
setItemDate($curview, curday);
setItemActive($curview, isInMomentsList(curday, dates));
setItemCurrent($curview, !curday.isBefore(Moment(), 'day') || (args.enablePastDays == true && (curday.month() === $month_view.month)));
setItemToday($curview, curday.isSame(Moment(), 'day'));
$curview.top = row * ($curview.height);
$curview.left = col * ($curview.width);
$days_container.add($curview);
}
}
$month_view.add($days_container);
$month_view.ready = true;
$month_view.__loader.hide();
}
function buildCalendar() {
$.main.removeEventListener('postlayout', buildCalendar);
// Add top labels
getDayLabels();
// Create the calendar views
var curmonth_index = -1; var i = 0;
for (var m = Moment(args.min_date); m.diff(Moment(args.max_date)) <= 0; m.add(1, 'months')) {
if (m.isSame(Moment(), 'month')) curmonth_index = i;
var monthview = getMonthView(m.month(), m.year());
$.monthScroll.addView(monthview);
i++;
}
$.monthScroll.currentPage = current_page = curmonth_index > 0 ? curmonth_index : 0;
refreshCalendarMonth(current_page);
refreshArrows();
}
function refreshCalendarMonth(m) {
var month_date = Moment().month($.monthScroll.views[m].month).year($.monthScroll.views[m].year);
$.monthName.text = month_date.format('MMMM').toUpperCase();
$.monthYear.text = month_date.format('YYYY');
buildMonth($.monthScroll.views[m], args.active_dates);
if (current_page - 1 > -1) buildMonth($.monthScroll.views[m-1], args.active_dates);
if (current_page + 1 < 12) buildMonth($.monthScroll.views[m+1], args.active_dates);
}
///////////////
// Listeners //
///////////////
$.main.addEventListener('postlayout', buildCalendar);
$.monthScroll.addEventListener('scroll', function(e) {
if (e.currentPage === current_page) return;
current_page = e.currentPage;
refreshArrows();
refreshCalendarMonth(current_page);
});
$.monthScroll.addEventListener('click', function(e) {
if (!e.source.date || (!e.source.active && !args.allowInactiveSelection) || (args.enablePastDays == false && e.source.date.isBefore(Moment(), 'day'))) return;
e.source.animate({ backgroundColor: args.selectedBackgroundColor, duration: 150, autoreverse: true });
$.trigger('selected', {
date: e.source.date,
active: e.source.active
});
});
$.leftBtn.addEventListener('click', function() {
$.monthScroll.movePrevious();
});
$.rightBtn.addEventListener('click', function() {
$.monthScroll.moveNext();
});
//////////
// Init //
//////////
$.monthName.font = {
fontFamily: args.fontFamily,
fontWeight: 'bold'
};
$.monthYear.font = {
fontFamily: args.fontFamily,
fontWeight: 'light'
};
Widget.xml
<Alloy>
<Window backgroundColor="#110ee1" class="container" exitOnClose="true" id="widget" title="DailyRead" top="0">
<View id="main">
<View class="bar" id="header">
<View class="hr" top="0"/>
<View class="ctrlBtn" id="leftBtn">
<ImageView id="leftArrow"/>
</View>
<View class="headerText">
<Label id="monthName"/>
<Label id="monthYear"/>
</View>
<View class="ctrlBtn" id="rightBtn">
<ImageView id="rightArrow"/>
</View>
<View bottom="0" class="hr"/>
</View>
<View class="sp1/2"/>
<View id="calendar">
<View id="dayLabels"/>
<View backgroundColor="#fff" class="hr" height="2"/>
<ScrollableView id="monthScroll"/>
</View>
</View>
</Window>
</Alloy>
As far as I understood the question I would add more information to the day view. E.g. if you create everything through a loop just add month, weeks to the day view as a property. Then set all views but the day view to touchEnabled:false and just add the click event to the day view. Then you can read event.source.day/event.source.week/event.source.month inside the click-event.
If that doesn't help please add some example code to your question.

animating multiple markers in OpenLayers 3

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.

Performance issue when zooming (Leaflet 0.7.3)

I'm facing a performance problem with Leaflet (version 0.7.3). I'm working with an OSM map that I use to display a bunch of CircleMarkers linked by decorated Polylines (with arrow pattern every 25px). Loading take a little time but the main problem is that when I zoom the map I start facing severe lag (from the zoom level 16) and, beyond a certain limit (say 18 most of the time), browser just freeze and eventually crash (tested with chrome and firefox). I tried with a bunch of 1,000 linked markers, then I dropped to a set of around 100, but still the same concern... Of course, with 10 markers or less I don't have any problem.
Did you already face a similar trouble? How can I optimize Leaflet performances so that I can use an accurate zoom (beyond level 16) with more than 100 linked CircleMarkers ? I also wonder why performances are dropping so badly when zooming, while marker amount stay the same...
Thank you in advance for your answers,
Lenalys.
Cannot get the PolylineDecorator plugin to work on jsfiddle.
But here is the code that generate markers :
Map initialization :
var map;
function initializeMap(){
"use strict";
var layer;
var layer2;
function layerUrl(key, layer) {
return "http://wxs.ign.fr/" + key
+ "/geoportail/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&"
+ "LAYER=" + layer + "&STYLE=normal&TILEMATRIXSET=PM&"
+ "TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&FORMAT=image%2Fjpeg";
}
layer = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png',
{
attribution: '© OpenStreetMap contributors',
maxZoom: 18
});
layer2 = L.tileLayer(
layerUrl(IGN_AMBIENTE_KEY, "GEOGRAPHICALGRIDSYSTEMS.MAPS"),
{attribution: '© IGN'}
);
var baseMaps = {
"Terrestre": layer,
"Bathymetrique": layer2
};
map = L.map('map', {
layers: [layer],
zoom: 8,
center: [42.152796, 9.139150],
zoomControl: false
});
L.control.layers(baseMaps).addTo(map);
//add zoom control with your options
L.control.zoom({
position:'topright' //topleft
}).addTo(map);
L.control.scale({
position:'bottomleft',
imperial : false
}).addTo(map);
}
Data Sample :
var jsonData ={"12":[{"id_stm_device":"7","individual_name":"cerf3","latitude":"42.657283333333","longitude":"9.42362","temperature":null,"pulse":null,"battery":"20","date_time":"2015-03-17 15:37:12"},
{"id_stm_device":"7","individual_name":"cerf3","latitude":"42.657381666667","longitude":"9.42365","temperature":null,"pulse":null,"battery":"20","date_time":"2015-03-17 16:42:16"},
{"id_stm_device":"7","individual_name":"cerf3","latitude":"42.657381666667","longitude":"9.4236933333333","temperature":null,"pulse":null,"battery":"20","date_time":"2015-03-17 17:47:21"},
{"id_stm_device":"7","individual_name":"cerf3","latitude":"42.657283333333","longitude":"9.4237383333333","temperature":null,"pulse":null,"battery":"20","date_time":"2015-03-17 19:57:23"}],
"13":[{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.61683","longitude":"9.4804633333333","temperature":"17.45","pulse":null,"battery":"80","date_time":"2015-04-08 07:45:20"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.538858333333","longitude":"9.48169","temperature":"14.37","pulse":null,"battery":"80","date_time":"2015-04-08 08:00:29"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.458748333333","longitude":"9.500225","temperature":"14.46","pulse":null,"battery":"80","date_time":"2015-04-08 08:15:49"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.3302","longitude":"9.5374583333333","temperature":"15.19","pulse":null,"battery":"80","date_time":"2015-04-08 08:31:05"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.170133333333","longitude":"9.5272116666667","temperature":"15.48","pulse":null,"battery":"80","date_time":"2015-04-08 08:46:20"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.07959","longitude":"9.47688","temperature":"15.97","pulse":null,"battery":"80","date_time":"2015-04-08 09:01:31"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.076163333333","longitude":"9.4828633333333","temperature":"20.42","pulse":null,"battery":"80","date_time":"2015-04-08 09:16:59"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.07194","longitude":"9.4908866666667","temperature":"17.36","pulse":null,"battery":"80","date_time":"2015-04-08 09:32:17"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.072583333333","longitude":"9.4901516666667","temperature":"17.36","pulse":null,"battery":"80","date_time":"2015-04-08 09:47:32"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.07238","longitude":"9.4904266666667","temperature":"19.38","pulse":null,"battery":"80","date_time":"2015-04-08 10:02:42"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.072298333333","longitude":"9.4904983333333","temperature":"17.46","pulse":null,"battery":"80","date_time":"2015-04-08 10:17:55"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.095093333333","longitude":"9.5148383333333","temperature":"17.47","pulse":null,"battery":"80","date_time":"2015-04-08 10:33:12"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.112881666667","longitude":"9.5133133333333","temperature":"19.3","pulse":null,"battery":"80","date_time":"2015-04-08 10:48:23"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.112875","longitude":"9.513285","temperature":"22.71","pulse":null,"battery":"80","date_time":"2015-04-08 11:03:57"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.141096666667","longitude":"9.5078216666667","temperature":"23.73","pulse":null,"battery":"80","date_time":"2015-04-08 11:19:12"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.282186666667","longitude":"9.5505183333333","temperature":"18.97","pulse":null,"battery":"80","date_time":"2015-04-08 11:34:28"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.405126666667","longitude":"9.531145","temperature":"20.71","pulse":null,"battery":"80","date_time":"2015-04-08 11:49:42"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.482063333333","longitude":"9.480665","temperature":"21.7","pulse":null,"battery":"80","date_time":"2015-04-08 12:05:07"}]}
var oJSON = JSON.parse(jsonData);
var colors = [
"#400080",
"#008000",
"#EC7600",
"#E40341",
"#0D5E5E",
"#919191",
"#FF3C9D",
"#A70A0E",
"#00BFBF",
"#7171FF"
];
var classes = [
"color1",
"color2",
"color3",
"color4",
"color5",
"color6",
"color7",
"color8",
"color9",
"color10"
];
var lastMarkers = [];
var layers = new Array();
var polyline;
var decorator;
window.graphicsDevices = [];
var offsetLatitude = 0.003333;
var offsetLongitude = 0.011666;
Marker instanciation :
function printGPSOnMap(oJSON){
var nbKeys = 0;
for (var key in oJSON) {
nbKeys++;
var classe = classes[(key-1)%classes.length];
var color = colors[(key-1)%colors.length];
var positionInfo = [];
if (oJSON.hasOwnProperty(key)) {
var aInfo = oJSON[key];
var marker;
var latlngs = Array();
var startMarker = lastMarkers[key];
if(startMarker !== undefined && startMarker != null) {
var myIcon = L.divIcon({className: "myCircle "+classe, iconSize : [ 20, 20 ] });
startMarker.setIcon(myIcon);
latlngs.push(startMarker.getLatLng());
}
for(var i = 0; i < aInfo.length; i++) {
var oInfos = aInfo[i];
var sIdIndividual = oInfos["id_individual"];
var sLongitude = oInfos["longitude"];
var sLatitude = oInfos["latitude"];
var sTemperature = oInfos["temperature"];
var sPulse = oInfos["pulse"];
var sBattery = oInfos["battery"];
var sDatetime = oInfos["date_time"];
var sIndividualName = oInfos["individual_name"];
var id_device = oInfos["id_stm_device"];
var popupMsg = "...";
latlngs.push(L.marker([sLatitude,sLongitude]).getLatLng());
marker = new MyCustomMarker([sLatitude,sLongitude], {
icon : L.divIcon({
className : "myCircle "+classe + ((i == aInfo.length-1) ? ' myCircleEnd' : ''),
iconSize : [ 20, 20 ]
})
});
marker.bindPopup(popupMsg, {
showOnMouseOver: true
});
marker.bindLabel(key, {
noHide: true,
direction: 'middle',
offset: [offset[0], offset[1]]
});
positionInfo.push(marker);
}
lastMarkers[key] = marker;
}
if(latlngs.length > 1)
{
polyline = L.polyline(latlngs, {className: classe, weight: 2,opacity: 0.4}).addTo(map);
decorator = L.polylineDecorator(polyline, {
patterns: [
// define a pattern of 10px-wide arrows, repeated every 20px on the line
{offset: 0, repeat: '25px', symbol: new L.Symbol.arrowHead({pixelSize: 10, pathOptions: {fillOpacity:
0.76, color: color, weight: 1}})}
]}).addTo(map);
}
if(!window.graphicsDevices.hasOwnProperty(key))
window.graphicsDevices[key] = [];
for(var i = 0; i < positionInfo.length; i++) {
window.graphicsDevices[key].push(positionInfo[i]);
positionInfo[i].addTo(map);
if(latlngs.length > 1){
window.graphicsDevices[key].push(polyline);
polyline.addTo(map);
window.graphicsDevices[key].push(decorator);
decorator.addTo(map);
}
}
}//foreach key
}
Code for the custom marker :
var MyCustomMarker = L.Marker.extend({
bindPopup: function(htmlContent, options) {
if (options && options.showOnMouseOver) {
// call the super method
L.Marker.prototype.bindPopup.apply(this, [htmlContent, options]);
// unbind the click event
this.off("click", this.openPopup, this);
// bind to mouse over
this.on("mouseover", function(e) {
// get the element that the mouse hovered onto
var target = e.originalEvent.fromElement || e.originalEvent.relatedTarget;
var parent = this._getParent(target, "leaflet-popup");
// check to see if the element is a popup, and if it is this marker's popup
if (parent == this._popup._container)
return true;
// show the popup
this.openPopup();
}, this);
// and mouse out
this.on("mouseout", function(e) {
// get the element that the mouse hovered onto
var target = e.originalEvent.toElement || e.originalEvent.relatedTarget;
// check to see if the element is a popup
if (this._getParent(target, "leaflet-popup")) {
L.DomEvent.on(this._popup._container, "mouseout", this._popupMouseOut, this);
return true;
}
// hide the popup
this.closePopup();
}, this);
}
},
_popupMouseOut: function(e) {
// detach the event
L.DomEvent.off(this._popup, "mouseout", this._popupMouseOut, this);
// get the element that the mouse hovered onto
var target = e.toElement || e.relatedTarget;
// check to see if the element is a popup
if (this._getParent(target, "leaflet-popup"))
return true;
// check to see if the marker was hovered back onto
if (target == this._icon)
return true;
// hide the popup
this.closePopup();
},
_getParent: function(element, className) {
var parent = null;
if(element != null) parent = element.parentNode;
while (parent != null) {
if (parent.className && L.DomUtil.hasClass(parent, className))
return parent;
parent = parent.parentNode;
}
return false;
}
});
Have you gauged performance in canvas mode?
Use L_PREFER_CANVAS = true before initializing your leaflet map container.Might be able to help you possibly.

Cloning objects in Fabric.js

I am cloning a selected object on a canvas in Fabric.js using a simple function.
function duplicateObj() {
var obj = canvas.getActiveObject();
var clone = fabric.util.object.clone(obj);
clone.set({left: 100,top: 100});
canvas.add(clone);
}
That works absolutely fine. Now if I work with the object and the clone is not required anymore and I select and delete it, both objects, the clone and the original object are deleted. The delete function is:
function deleteObj() {
var obj = canvas.getActiveObject();
canvas.fxRemove(obj);
}
The objects are the same. Is there are way to clone objects and make the clone independent of the of the original? I tried this:
function duplicateObj() {
var obj = canvas.getActiveObject();
var clone = fabric.util.object.clone(obj);
clone.initialize();
$.extend(clone, obj);
fabric.util.object.extend(clone, obj);
clone.set({left: 100,top: 100});
canvas.add(clone);
}
It works, however the objects are the same again and if I only use initialize I am ending up with an object that has now properties set.
here is the solution
var object = fabric.util.object.clone(canvas.getActiveObject());
object.set("top", object.top+5);
object.set("left", object.left+5);
canvas.add(object);
This worked very well for me, and the cloned object is totally unlinked from the original:
var object = canvas.getActiveObject();
object.clone(function(clone) {
canvas.add(clone.set({
left: object.left + 10,
top: object.top + 10
}));
});
And you can do it to clone all selected objects:
var activeObjects = canvas.getActiveObjects();
if (activeObjects) {
activeObjects.forEach(function(object) {
object.clone(function(clone) {
canvas.add(clone.set({
left: object.left + 10,
top: object.top + 10
}));
})
});
}
I hope it can help you!
I was having a similar issue where actions on the clone would affect the original object. I opted to just serialize the object and deserialize it into a new object:
var copyData = canvas.getActiveObject().toObject();
fabric.util.enlivenObjects([copyData], function(objects) {
objects.forEach(function(o) {
o.set('top', o.top + 15);
o.set('left', o.left + 15);
canvas.add(o);
});
canvas.renderAll();
});
for fabricjs 2.0
$(".copy").on("click", function () {
var activeObject = canvas.getActiveObject();
activeObject.clone(function (cloned) {
canvas.discardActiveObject();
cloned.set({
top: cloned.top + 20,
evented: true
});
if (cloned.type === 'activeSelection') {
// active selection needs a reference to the canvas.
cloned.canvas = canvas;
cloned.forEachObject(function (obj) {
canvas.add(obj);
});
cloned.setCoords();
} else {
canvas.add(cloned);
}
canvas.setActiveObject(cloned);
canvas.requestRenderAll();
});
});
Here is my implementation of cloning selected object or group.
https://jsfiddle.net/milanhlinak/rxtjm7w0/1/
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="lib/jquery-3.1.1.min.js"></script>
<script type="text/javascript" src="lib/fabric.min.js"></script>
</head>
<body>
<button onclick="cloneSelected()">Clone selected</button>
<canvas id="canvas" style="border: 1px solid #cccccc"></canvas>
<script>
var canvas = new fabric.Canvas('canvas', {
width: 500,
height: 500,
});
canvas.add(new fabric.Rect({
left: 100,
top: 100,
width: 50,
height: 50,
fill: '#faa'
}));
canvas.add(new fabric.Circle({
left: 300,
top: 300,
radius: 25,
fill: '#afa'
}));
function cloneSelected() {
console.log('cloneSelected');
var activeObject = canvas.getActiveObject();
var activeGroup = canvas.getActiveGroup();
if (activeObject) {
console.log('object selected');
var clonedObject = null;
var json = activeObject.toJSON();
if (json.type == 'rect') {
clonedObject = new fabric.Rect(json);
} else if (json.type == 'circle') {
clonedObject = new fabric.Circle(json);
} else {
console.log('unknown object type: ' + json.type);
return;
}
var oldLeft = clonedObject.getLeft();
var oldTop = clonedObject.getTop();
clonedObject.setLeft(oldLeft + 10);
clonedObject.setTop(oldTop + 10);
var boundingRect = clonedObject.getBoundingRect(true);
if (boundingRect.left + boundingRect.width > canvas.getWidth()) {
clonedObject.setLeft(oldLeft);
}
if (boundingRect.top + boundingRect.height > canvas.getHeight()) {
clonedObject.setTop(oldTop);
}
canvas.add(clonedObject);
canvas.setActiveObject(clonedObject);
canvas.renderAll();
console.log('selected object cloned');
} else if (activeGroup) {
console.log('group selected');
canvas.discardActiveGroup();
var clonedObjects = [];
activeGroup.getObjects().forEach(function (object) {
var clonedObject = null;
var json = object.toJSON();
if (json.type == 'rect') {
clonedObject = new fabric.Rect(json);
} else if (json.type === 'circle') {
clonedObject = new fabric.Circle(json);
} else {
console.log('unknown object type: ' + json.type);
return;
}
clonedObject.setCoords();
canvas.add(clonedObject);
clonedObject.set('active', true);
clonedObjects.push(clonedObject);
});
var group = new fabric.Group(clonedObjects.reverse(), {
canvas: canvas
});
group.addWithUpdate(null);
var oldLeft = group.getLeft();
var oldTop = group.getTop();
group.setLeft(oldLeft + 10);
group.setTop(oldTop + 10);
var boundingRect = group.getBoundingRect(true);
if (boundingRect.left + boundingRect.width > canvas.getWidth()) {
group.setLeft(oldLeft);
}
if (boundingRect.top + boundingRect.height > canvas.getHeight()) {
group.setTop(oldTop);
}
group.setCoords();
canvas.setActiveGroup(group);
group.saveCoords();
canvas.renderAll();
console.log('selected objects cloned');
} else {
console.log('no object selected');
}
}
</script>
</body>
</html>
You can use
var obj = canvas.getActiveObject();
obj.clone(function(c) {
canvas.add(c.set({ left: 100, top: 100, angle: -15 }));
});
Here you can see it working:
http://fabricjs.com/opacity_mouse_move/
I wanted a clone to be able to reset an item to the originally saved state...like others I found objects pass on variables to the clone and vice versa...
What i did was create a var to hold the clone for later recall like this:
var o;
var object = canvas.getActiveObject().clone(
function(obj){
o = obj;
}
);
one caveat if you set id's and dont make them unique during cloning process it makes the whole thing go wild...unless you delete the original first..
Check the demo for Copy and Paste here:
http://fabricjs.com/copypaste
Here is the code to copy/paste or clone the selected object.
function Clone() {
Copy();
Paste()
}
function Copy() {
// clone what are you copying since you
// may want copy and paste on different moment.
// and you do not want the changes happened
// later to reflect on the copy.
canvas.getActiveObject().clone(function(cloned) {
_clipboard = cloned;
});
}
function Paste() {
// clone again, so you can do multiple copies.
_clipboard.clone(function(clonedObj) {
canvas.discardActiveObject();
clonedObj.set({
left: clonedObj.left + 10,
top: clonedObj.top + 10,
evented: true,
});
if (clonedObj.type === 'activeSelection') {
// active selection needs a reference to the canvas.
clonedObj.canvas = canvas;
clonedObj.forEachObject(function(obj) {
canvas.add(obj);
});
// this should solve the unselectability
clonedObj.setCoords();
} else {
canvas.add(clonedObj);
}
_clipboard.top += 10;
_clipboard.left += 10;
canvas.setActiveObject(clonedObj);
canvas.requestRenderAll();
});
}

Resources