I want to animate multiple points along predetermined paths on a Mapbox map. I have found this example on the Mapbox website. It animates only one point along one line: https://docs.mapbox.com/mapbox-gl-js/example/animate-point-along-route/
I also found this example here on stackoverflow: Animate multiple point along routes using Mapbox-GL.JS
With the second example (multiple points with multiple lines), I started experimenting, adding my own coordinates for routes and points. My issue is that although I can see all points "moving" along their designated paths, only the first one behaves as expected, the other two only jumps from the first coordinate to the second. I'd like all three points to behave as the first one, moving smoothly along an arc.
I include the full code below.
https://jsfiddle.net/szabokrissz96/dfxy0gcu/1/
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Animate a point along a route</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<link href="https://api.mapbox.com/mapbox-gl-js/v2.12.0/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v2.12.0/mapbox-gl.js"></script>
<style>
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
</head>
<body>
<style>
.overlay {
position: absolute;
top: 10px;
left: 10px;
}
.overlay button {
font: 600 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif;
background-color: #3386c0;
color: #fff;
display: inline-block;
margin: 0;
padding: 10px 20px;
border: none;
cursor: pointer;
border-radius: 3px;
}
.overlay button:hover {
background-color: #4ea0da;
}
</style>
<script src="https://unpkg.com/#turf/turf#6/turf.min.js"></script>
<div id="map"></div>
<div class="overlay">
<button id="replay">Replay</button>
</div>
<script>
mapboxgl.accessToken = 'pk.eyJ1IjoiYXRsbyIsImEiOiJjam5mdDVldmkwbTJ6M3BwNW13ZXhuYXM4In0.gikvkhPkud0GJy1FxG6-Zg';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v9',
center: [118.0148634, -2.548926],
zoom: 1
});
var route = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [
[-24.716854,48.197951],[64.228455,17.102469]
]
}
},
{
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [
[-17.861384,-18.016869],[56.845644,-22.305867]
]
}
},
{
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [
[118.017516,30.565573],[11.669865,29.347228]
]
}
}
]
};
// A single point that animates along the route.
// Coordinates are initially set to origin.
var point = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [-24.716854,48.197951]
}
},{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [-17.861384,-18.016869]
}
},{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [118.017516,30.565573]
}
}
]
};
// Calculate the distance in kilometers between route start/end point.
for(i=0;i<2;i++) {
var lineDistance = turf.lineDistance(route.features[0], 'kilometers');
}
var arc = [];
// Number of steps to use in the arc and animation, more steps means
// a smoother arc and animation, but too many steps will result in a
// low frame rate
var steps = 500;
// Draw an arc between the `origin` & `destination` of the two points
for (var i = 0; i < lineDistance; i += lineDistance / steps) {
var segment = turf.along(route.features[0], i, 'kilometers');
arc.push(segment.geometry.coordinates);
}
// Update the route with calculated arc coordinates
route.features[0].geometry.coordinates = arc;
// Used to increment the value of the point measurement against the route.
var counter = 0;
map.on('load', function () {
// Add a source and layer displaying a point which will be animated in a circle.
map.addSource('route', {
"type": "geojson",
"data": route
});
map.addSource('point', {
"type": "geojson",
"data": point
});
map.addLayer({
"id": "route",
"source": "route",
"type": "line",
"paint": {
"line-width": 1,
"line-color": "#007cbf"
}
});
map.addLayer({
"id": "point",
"source": "point",
"type": "symbol",
"layout": {
"icon-image": "airport-15",
"icon-rotate": ["get", "bearing"],
"icon-rotation-alignment": "map",
"icon-allow-overlap": true,
"icon-ignore-placement": true
}
});
function animate(featureIdx, cntr) {
// Update point geometry to a new position based on counter denoting
// the index to access the arc.
if (cntr >= route.features[featureIdx].geometry.coordinates.length){
return;
}
point.features[featureIdx].geometry.coordinates = route.features[featureIdx].geometry.coordinates[cntr];
point.features[featureIdx].properties.bearing = turf.bearing(
turf.point(route.features[featureIdx].geometry.coordinates[cntr >= steps ? cntr - 1 : cntr]),
turf.point(route.features[featureIdx].geometry.coordinates[cntr >= steps ? cntr : cntr + 1])
);
// Update the source with this new data.
map.getSource('point').setData(point);
// Request the next frame of animation so long the end has not been reached.
if (cntr < steps) {
requestAnimationFrame(function(){animate(featureIdx, cntr+1);});
}
}
document.getElementById('replay').addEventListener('click', function() {
// Set the coordinates of the original point back to origin
point.features[0].geometry.coordinates = origin;
// Update the source layer
map.getSource('point').setData(point);
// Reset the counter
cntr = 0;
// Restart the animation.
animate(0,cntr);
animate(1,cntr);
animate(2,cntr)
});
// Start the animation.
animate(0, 0);
animate(1, 0);
animate(2, 0);
});
</script>
</body>
</html>
Related
Example: https://jsfiddle.net/Lkn0j5gz/
When I use the following the legend items break, I want them to be displayed as list.
legend: {
position: 'absolute'
}
Cannot find anything usefull in the
API: http://docs.amcharts.com/3/javascriptcharts/AmLegend
If you meant for the legend to display a single entry per line, you need to set maxColumns to 1.
legend: {
maxColumns: 1,
// ...
}
Demo:
var chart = AmCharts.makeChart("chartdiv", {
"type": "pie",
"startDuration": 0,
"theme": "none",
"addClassNames": true,
"legend": {
"position": 'absolute',
"maxColumns": 1,
"marginRight": 100,
"autoMargins": false
},
"innerRadius": "30%",
"defs": {
"filter": [{
"id": "shadow",
"width": "200%",
"height": "200%",
"feOffset": {
"result": "offOut",
"in": "SourceAlpha",
"dx": 0,
"dy": 0
},
"feGaussianBlur": {
"result": "blurOut",
"in": "offOut",
"stdDeviation": 5
},
"feBlend": {
"in": "SourceGraphic",
"in2": "blurOut",
"mode": "normal"
}
}]
},
"dataProvider": [{
"country": "Lithuania",
"litres": 501.9
}, {
"country": "Czech Republic",
"litres": 301.9
}, {
"country": "Ireland",
"litres": 201.1
}, {
"country": "Germany",
"litres": 165.8
}, {
"country": "Australia",
"litres": 139.9
}, {
"country": "Austria",
"litres": 128.3
}, {
"country": "UK",
"litres": 99
}, {
"country": "Belgium",
"litres": 60
}, {
"country": "The Netherlands",
"litres": 50
}],
"valueField": "litres",
"titleField": "country",
"export": {
"enabled": true
}
});
chart.addListener("init", handleInit);
chart.addListener("rollOverSlice", function(e) {
handleRollOver(e);
});
function handleInit() {
chart.legend.addListener("rollOverItem", handleRollOver);
}
function handleRollOver(e) {
var wedge = e.dataItem.wedge.node;
wedge.parentNode.appendChild(wedge);
}
body, html {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
height: 100%;
width: 100%;
}
#chartdiv {
width: 100%;
height: 100%;
font-size: 11px;
}
.amcharts-pie-slice {
transform: scale(1);
transform-origin: 50% 50%;
transition-duration: 0.3s;
transition: all .3s ease-out;
-webkit-transition: all .3s ease-out;
-moz-transition: all .3s ease-out;
-o-transition: all .3s ease-out;
cursor: pointer;
box-shadow: 0 0 30px 0 #000;
}
.amcharts-pie-slice:hover {
transform: scale(1.1);
filter: url(#shadow);
}
<script src="https://www.amcharts.com/lib/3/amcharts.js"></script>
<script src="https://www.amcharts.com/lib/3/pie.js"></script>
<script src="https://www.amcharts.com/lib/3/plugins/export/export.min.js"></script>
<link rel="stylesheet" href="https://www.amcharts.com/lib/3/plugins/export/export.css" type="text/css" media="all" />
<div id="chartdiv"></div>
I try for a while to put Labels in my Open Layers map.
<script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io#master/en/v6.1.1/build/ol.js">
I managed to make a map with polygons filled in red. And when you hover over a polygon with your mouse; that polygon turns green. Now I want that it also displayes the name of the area as a Text label to feature. Just in the center of that polygon is fine.
what I tried:
I found this example (https://openlayers.org/en/latest/examples/vector-labels.html) but it is too complicated for me. I don't understand where in this example they make the connection to the file with the names related to the polygon.
And I believe that this solution is outdated:
open layer: display a label from properties in geojson file
Below is the hoverStyle which makes the area turn green when you hover over it:
in index.html under <script>
var hoverStyle = new ol.style.Style({
fill: new ol.style.Fill({
color: 'rgba(0, 255, 0, 0.6)' // green
}),
stroke: new ol.style.Stroke({
width: 3,
color: 'rgba(0, 0, 0, 1)' // black
}),
//Something with the Labels here? :
text: new ol.style.Text({
text: feature.get('Area_Name'), *//I believe I have to make the connection to the Feature Area_Name here*
align: 'center',
weight: 'normal',
placement: 'line',
overflow: 'false',
color: 'rgba(0, 0, 0, 1)' // black
})
Below are the first lines of my json file with the Features and Geometry:
in the file: PS2019.json
{
"type": "FeatureCollection",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:EPSG::28992" } },
"features": [
{ "type": "Feature", "properties": { "Area_Name": "Amsterdam", /* more attributes of the Polygon and the polygon geometry */ },
I have no clue anymore. I hope one of you can give me a step in the right direction.
If your hoverstyle is shared by all the polygons you will need a style function to set the text value
In this example https://openlayers.org/en/latest/examples/vector-layer.html where you see
style: function(feature) {
style.getText().setText(feature.get('name'));
return style;
}
and
style: function(feature) {
highlightStyle.getText().setText(feature.get('name'));
return highlightStyle;
}
you would need
style: function(feature) {
hoverStyle.getText().setText(feature.get('Area_Name'));
return hoverStyle;
}
I am trying to get data from json with Leaflet.
I am using Leaflet Data visualization framework.
I want to use ajax, because in future I want to generate random numbers in my data properties (something like Math.random() in time intervals)
What I have already tried
I downloaded Leaflet-ajax plugin, included it in header.
When i use something like that:
new L.geoJSON.ajax
I can't see any of my markers on map. I do not get any errors in this case.
Data, stored in data.js (this file is included in header):
var geojsonred = {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"properties": {
"name": "1",
"value": 20 //I want to replace this with Math.random()
},
"geometry": {
"type": "Point",
"coordinates": [315, -360]
}
},
{
"type": "Feature",
"properties": {
"name": "2",
"value": 50 //I want to replace this with Math.random()
},
"geometry": {
"type": "Point",
"coordinates": [360, -360]
}
}
]
}
This is how my script looks like:
var minHue = 120;
var maxHue = 0;
//var marker = new L.RadialMeterMarker(new L.LatLng(-360, 320), meterMarkerOptions);
var marker = new L.geoJSON(geojsonred, {
pointToLayer: function(feature, latlng) {
return new L.RadialMeterMarker(latlng, {
data: {
'Speed': feature.properties.value
},
chartOptions: {
'Speed': {
displayName: 'Speed',
displayText: function (value) {
return value.toFixed(1);
},
color: 'hsl(240,100%,55%)',
fillColor: 'hsl(240,80%,55%)',
maxValue: 200,
minValue: 0
}
},
displayOptions: {
'Speed': {
color: new L.HSLHueFunction(new L.Point(0,minHue), new L.Point(200,maxHue), {outputSaturation: '100%', outputLuminosity: '25%'}),
fillColor: new L.HSLHueFunction(new L.Point(0,minHue), new L.Point(200,maxHue), {outputSaturation: '100%', outputLuminosity: '50%'})
}
},
fillOpacity: 0.8,
opacity: 1,
weight: 0.5,
radius: 20,
barThickness: 15,
maxDegrees: 360,
rotation: 0,
numSegments: 10
});
},
});
marker.addTo(map);
Do someone have any idea?
I created this Dojo Link that shows me resource by group. problem with this view is it shows long block only if it's a allDay event. for this view how can i apply a template that show a block with time range for each event?
currently it is showing small block and its hard to tell what it is or how long it is booked for.
I want all the event block to take full length despite the duration length.
similar to one highlighted below
Look at the code snippet below. I did a couple of things:
I removed the title data from the template of the event since you said you didn't want to see it.
I placed a space in the template after the <p> element so that the x action of the event will have a place to appear in. There are other ways to do this, I used a simple one.
I've made changes to the styles at the bottom of the code, specifically the following:
.movie-template {
overflow: auto !important;
width: fit-content !important;
}
.k-event {
width: fit-content !important;
}
.space {
width: 20px;
height: 15px;
display: inline-block;
}
.movie-template p {
margin: 5px 0 0;
display: inline-block;
}
EDIT
~~~~
Making the appointments fill the cells they belong to is either impossible or extremely complicated. I'll explain why:
The way the events are programmed, they exist on the same level on the DOM as the table of the scheduler and float above it. The scheduler calculates the position and size of the events based on their start and end times.
In order for you to place them as if they are full-day events you need to do one of two things:
Override the scheduler's automatic placement, with your own code that will calculate the position and size and set them accordingly. Note that this code will need to run every time the window is resized, re-focused, rotated and so on.
Define the events as "whole-day appointments" and store their real start and end times in other fields that you will use in your code. This might break other functionality you might already have in place.
<!DOCTYPE html>
<html>
<head>
<base href="https://demos.telerik.com/kendo-ui/scheduler/resources-grouping-vertical">
<style>html { font-size: 14px; font-family: Arial, Helvetica, sans-serif; }</style>
<title></title>
<link rel="stylesheet" href="https://kendo.cdn.telerik.com/2018.1.221/styles/kendo.common-material.min.css" />
<link rel="stylesheet" href="https://kendo.cdn.telerik.com/2018.1.221/styles/kendo.material.min.css" />
<link rel="stylesheet" href="https://kendo.cdn.telerik.com/2018.1.221/styles/kendo.material.mobile.min.css" />
<script src="https://kendo.cdn.telerik.com/2018.1.221/js/jquery.min.js"></script>
<script src="https://kendo.cdn.telerik.com/2018.1.221/js/kendo.all.min.js"></script>
<script src="https://kendo.cdn.telerik.com/2018.1.221/js/kendo.timezones.min.js"></script>
</head>
<body>
<div id="example" class="k-content">
<div id="scheduler"></div>
</div>
<script id="event-template" type="text/x-kendo-template">
<div class="movie-template" style="width:100px">
<p>#: kendo.toString(start, "hh:mm") # - #: kendo.toString(end, "hh:mm") #</p>
<span class="space"></span>
</div>
</script>
<script>
var MyCustomTimelistView = kendo.ui.TimelineMonthView.extend({
options: {
name: "MyCustomTimelistView",
title: "Timeline Week",
selectedDateFormat: "{0:D} - {1:D}",
selectedShortDateFormat: "{0:d} - {1:d}",
majorTick: 1440,
numberOfDays: 7
},
name: "MyCustomTimelistView",
calculateDateRange: function() {
//create the required number of days
var start = this.options.date,
// start = kendo.date.dayOfWeek(selectedDate, this.calendarInfo().firstDay, -1),
idx, length,
dates = [];
for (idx = 0, length = this.options.numberOfDays; idx < length; idx++) {
dates.push(start);
start = kendo.date.nextDay(start);
}
this._render(dates);
},
previousDate: function() {
var date = new Date(this.startDate());
date.setDate(date.getDate() - this.options.numberOfDays);
return date
}
});
$(function() {
$("#scheduler").kendoScheduler({
date: new Date("2013/6/13"),
startTime: new Date("2013/6/13 07:00 AM"),
height: 600,
eventTemplate: $("#event-template").html(),
views: [
"day",
{ type: "MyCustomTimelistView", selected: true,
},
"month",
"agenda",
"timeline"
],
timezone: "Etc/UTC",
dataBinding: function(e) {
var view = this.view();
$( ".k-scheduler-times > table > tbody > tr:eq(1)" ).hide();
$( ".k-scheduler-header-wrap > table > tbody > tr:eq(1)" ).hide();
},
dataSource: {
batch: true,
transport: {
read: {
url: "https://demos.telerik.com/kendo-ui/service/meetings",
dataType: "jsonp"
},
update: {
url: "https://demos.telerik.com/kendo-ui/service/meetings/update",
dataType: "jsonp"
},
create: {
url: "https://demos.telerik.com/kendo-ui/service/meetings/create",
dataType: "jsonp"
},
destroy: {
url: "https://demos.telerik.com/kendo-ui/service/meetings/destroy",
dataType: "jsonp"
},
parameterMap: function(options, operation) {
if (operation !== "read" && options.models) {
return {models: kendo.stringify(options.models)};
}
}
},
schema: {
model: {
id: "meetingID",
fields: {
meetingID: { from: "MeetingID", type: "number" },
title: { from: "Title", defaultValue: "No title", validation: { required: true } },
start: { type: "date", from: "Start" },
end: { type: "date", from: "End" },
startTimezone: { from: "StartTimezone" },
endTimezone: { from: "EndTimezone" },
description: { from: "Description" },
recurrenceId: { from: "RecurrenceID" },
recurrenceRule: { from: "RecurrenceRule" },
recurrenceException: { from: "RecurrenceException" },
roomId: { from: "RoomID", nullable: true },
attendees: { from: "Attendees", nullable: true },
isAllDay: { type: "boolean", from: "IsAllDay" }
}
}
}
},
group: {
resources: ["Rooms"],
orientation: "vertical"
},
resources: [
{
field: "roomId",
name: "Rooms",
dataSource: [
{ text: "Meeting Room 101", value: 1, color: "#6eb3fa" },
{ text: "Meeting Room 201", value: 2, color: "#f58a8a" }
],
title: "Room"
},
{
field: "attendees",
name: "Attendees",
dataSource: [
{ text: "Alex", value: 1, color: "#f8a398" },
{ text: "Bob", value: 2, color: "#51a0ed" },
{ text: "Charlie", value: 3, color: "#56ca85" }
],
multiple: true,
title: "Attendees"
}
]
});
});
</script>
<style>
.movie-template {
overflow: auto !important;
width: fit-content !important;
}
.k-event {
width: fit-content !important;
}
.space {
width: 20px;
height: 15px;
display: inline-block;
}
.movie-template img {
float: left;
margin: 0 8px;
}
.movie-template p {
margin: 5px 0 0;
display: inline-block;
}
.movie-template h3 {
padding: 0 0 5px;
font-size: 12px;
}
.movie-template a {
color: #ffffff;
font-weight: bold;
text-decoration: none;
}
.k-state-hover .movie-template a,
.movie-template a:hover {
color: #000000;
}
</style>
</body>
</html>
you can create custom views like this and use it in your code
var CustomMonthView = kendo.ui.TimelineMonthView.extend({
options: {
name: "CustomMonth",
title: "Month"
},
name: "CustomMonth",
_positionEvent: function(eventObject) {
var eventHeight = this.options.eventHeight + 2;
var rect = eventObject.slotRange.innerRect(eventObject.start, eventObject.end, true);
var left = this._adjustLeftPosition(rect.left);
var width = rect.right - rect.left - 2;
if (width < 0) {
width = 0;
}
if (width < this.options.eventMinWidth) {
var slotsCollection = eventObject.slotRange.collection;
var lastSlot = slotsCollection._slots[slotsCollection._slots.length - 1];
var offsetRight = lastSlot.offsetLeft + lastSlot.offsetWidth;
width = this.options.eventMinWidth;
if (offsetRight < left + width) {
width = offsetRight - rect.left - 2;
}
}
eventObject.element.css({
top: eventObject.slotRange.start.offsetTop + eventObject.rowIndex * (eventHeight + 2) + 'px',
left: left,
width: width
});
},
_arrangeRows: function (eventObject, slotRange, eventGroup) {
var startIndex = slotRange.start.index;
var endIndex = slotRange.end.index;
var rect = eventObject.slotRange.innerRect(eventObject.start, eventObject.end, true);
var rectRight = rect.right + this.options.eventMinWidth;
var events = kendo.ui.SchedulerView.collidingEvents(eventObject.slotRange.events(), startIndex, endIndex);
slotRange.addEvent({
slotIndex: startIndex,
start: startIndex,
end: endIndex,
rectLeft: rect.left,
rectRight: rectRight,
element: eventObject.element,
uid: eventObject.uid
});
events.push({
start: startIndex,
end: endIndex,
uid: eventObject.uid
});
var rows = kendo.ui.SchedulerView.createRows(events);
if (eventGroup.maxRowCount < rows.length) {
eventGroup.maxRowCount = rows.length;
}
for (var idx = 0, length = rows.length; idx < length; idx++) {
var rowEvents = rows[idx].events;
for (var j = 0, eventLength = rowEvents.length; j < eventLength; j++) {
eventGroup.events[rowEvents[j].uid].rowIndex = idx;
}
}
}
});
hope this helps
I am just starting out with D3. I have looked at the tutorials and now I am trying one my own. Trying to get some coloured div tags based on my data structure. Nothing at all happens and I need some help in how to debug this:
HTML:
<div class="chart"></div>
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
CSS:
.chart div {
font: 10px sans-serif;
background-color: white;
text-align: right;
padding: 3px;
margin: 1px;
color: gray;
width: 15px;
height: 15px;
}
JavaScript:
var threshold = 0.5
var data = []
data[0] = JSON.parse('{ "smiles": "CCCCC=O", "prediction": [ { "pValue": 0, "label": "bar" }, { "pValue": 0.7, "label": "foo" } ] }')
data[1] = JSON.parse('{ "smiles": "CCCCC=O", "prediction": [ { "pValue": 0.3, "label": "bar" }, { "pValue": 0.6, "label": "foo" } ] }')
data[2] = JSON.parse('{ "smiles": "CCCCC=O", "prediction": [ { "pValue": 0.4, "label": "bar" }, { "pValue": 0.4, "label": "foo" } ] }')
data[3] = JSON.parse('{ "smiles": "CCCCC=O", "prediction": [ { "pValue": 0.5, "label": "bar" }, { "pValue": 0.3, "label": "foo" } ] }')
data[4] = JSON.parse('{ "smiles": "CCCCC=O", "prediction": [ { "pValue": 0.6, "label": "bar" }, { "pValue": 0.2, "label": "foo" } ] }')
d3.select(".chart")
.selectAll("div")
.data(data)
.enter().append("div")
.style("background-color", function(d) {
if (d['prediction'][0]['pValue'] > threshold &&
d['prediction'][1]['pValue'] > threshold) {
"green"
}
if (d['prediction'][0]['pValue'] > threshold ) {
"blue"
}
if (d['prediction'][1]['pValue'] > threshold ) {
"yellow"
}
})
JSFiddle
In your function to colour the divs you don't have any returns. Fiddle. I added return color in your if statement and it works now!
if (d['prediction'][0]['pValue'] > threshold &&
d['prediction'][1]['pValue'] > threshold) {
return "green"
}
if (d['prediction'][0]['pValue'] > threshold ) {
return "blue"
}
if (d['prediction'][1]['pValue'] > threshold ) {
return "yellow"
}