MapBox GL JS marker/icon animation slow performance - mapbox-gl-js

I'm using mapbox-gl-js to animate many image from one coordinates to another on the map.
It become slow frame rate when I try to add up to 15 image and above.
Doing performance profile on chrome give me the hint to method Actor.receive, which cost the most computational time.
/**
* Util.js
*/
var linesDataSource = {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"properties": {},
"geometry": {
"type": "LineString",
"coordinates": [
[
151.15684390068054, -33.89568424317427
],
[
151.15808844566345, -33.89606717952166
],
[
151.15779876708984, -33.89680633086413
],
[
151.15740180015564, -33.897794824453406
],
[
151.1582601070404, -33.8980085512904
],
[
151.1609423160553, -33.89863191817193
],
[
151.16222977638245, -33.89621857248702
],
[
151.16639256477356, -33.89771467675142
],
[
151.1694610118866, -33.898916884371395
],
[
151.17089867591858, -33.896298721595166
],
[
151.17217540740964, -33.899014841282515
],
[
151.1714780330658, -33.899192944468965
],
[
151.17132782936093, -33.89878330658397
],
[
151.1719822883606, -33.8985784869035
],
[
151.17339849472046, -33.89839147720036
],
[
151.17376327514648, -33.89825789858986
],
[
151.17332339286804, -33.897269410368615
],
[
151.1732053756714, -33.89697553328233
],
[
151.17341995239258, -33.89662822269281
],
[
151.17295861244202, -33.896263099778615
],
[
151.17225050926208, -33.89589797530112
],
[
151.17136001586914, -33.89561299901295
],
[
151.17184281349182, -33.894758064434605
],
[
151.17200374603271, -33.89455323508587
],
[
151.17257237434387, -33.89148073582115
],
[
151.17042660713196, -33.89132042847356
],
[
151.17168188095093, -33.88838140703873
],
[
151.1716067790985, -33.887606557247125
],
[
151.16321682929993, -33.888274531623864
],
[
151.16029858589172, -33.88777577791726
],
[
151.1591076850891, -33.88790937294604
],
[
151.15857124328613, -33.8892809364742
],
[
151.1584746837616, -33.89006467716016
],
[
151.15894675254822, -33.89009139546571
],
[
151.15893602371216, -33.889806399775104
]
]
}
}]
}
var PI = Math.PI;
var TWO_PI = Math.PI * 2;
function rotation(start, end) {
var dx = end[0] - start[0];
var dy = end[1] - start[1];
return -Math.atan2(dy, dx) * (180 / PI);
};
function lerp(v0, v1, t) {
return v0 * (1 - t) + v1 * t
}
function interpolateAngle(fromAngle, toAngle, t) {
fromAngle = fromAngle * (PI / 180);
toAngle = toAngle * (PI / 180);
fromAngle = (fromAngle + TWO_PI) % TWO_PI;
toAngle = (toAngle + TWO_PI) % TWO_PI;
var diff = Math.abs(fromAngle - toAngle);
if (diff < PI) {
return lerp(fromAngle, toAngle, t) * (180 / PI);
} else {
if (fromAngle > toAngle) {
fromAngle = fromAngle - TWO_PI;
return lerp(fromAngle, toAngle, t) * (180 / PI);
} else if (toAngle > fromAngle) {
toAngle = toAngle - TWO_PI;
return lerp(fromAngle, toAngle, t) * (180 / PI);
}
}
}
/**
* Car.js
*/
function Car(name, map, path) {
this.name = name;
this.map = map;
this.path = path;
this.speed = 90; // 30 km/h
this.accumulatedDistance = 0;
this.previousPos = this.path.features[0].geometry.coordinates[0];
this.previousAngle = 0;
this.animate = function(frameInfo) {
this.accumulatedDistance += ((frameInfo.deltaTime / 3600) * this.speed);
var point = turf.along(this.path.features[0], this.accumulatedDistance, 'kilometers');
this.map.getSource(this.name).setData(point);
var newAngle = rotation(this.previousPos, point.geometry.coordinates);
var rotate = interpolateAngle(this.previousAngle, newAngle, 0.1);
this.map.setLayoutProperty(this.name, 'icon-rotate', rotate);
this.previousAngle = rotate;
this.previousPos = point.geometry.coordinates;
};
this.init = function() {
this.map.addSource(this.name, {
"type": "geojson",
"data": {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": this.previousPos
}
}]
}
});
this.map.addLayer({
"id": this.name,
"type": "symbol",
"source": this.name,
"layout": {
"icon-image": "car",
"icon-size": 1,
"icon-rotate": 0,
"icon-rotation-alignment": "map"
}
});
};
}
/**
* MapBoxTest.js
*/
var destination = {};
var cars = [];
var style = 'mapbox://styles/mapbox/streets-v9'; //'/TestEmptyProject/mapbox-gl-styles-master/styles/basic-v8.json';
//'http://localhost:8080/styles/osm-bright.json'; // 'http://localhost:8080/styles/fiord-color-gl.json'
mapboxgl.accessToken = 'pk.eyJ1IjoiZW1wZXJvcjE0MTIiLCJhIjoiY2ozYTYxdXFlMDM3dzJyczRsa2M5ZjE3aCJ9.9zQGtkSsjOw6npohN6ba3w';
var map = new mapboxgl.Map({
container: 'map',
style: style,
center: [132.133333, -23.116667],
zoom: 3
});
// Used to increment the value of the point measurement against the linesData.
var counter = 0;
var linesData = {};
function addCar() {
var car = new Car("Car_" + counter, map, linesData);
car.init();
cars.push(car);
++counter;
}
var previousTimeStamp = 0;
// Add a source and layer displaying a point which will be animated in a circle.
function animate(timeStamp) {
if (timeStamp <= previousTimeStamp) {
console.log("Wrong timeStamp, now: " + timeStamp + "\t previous: " + previousTimeStamp);
return;
}
var i;
var frameInfo = {
"timeStamp": timeStamp,
"previousTimeStamp": previousTimeStamp,
"deltaTime": (timeStamp - previousTimeStamp) / 1000
};
previousTimeStamp = timeStamp;
for (i = 0; i < cars.length; ++i) {
var car = cars[i];
car.animate(frameInfo);
}
requestAnimationFrame(animate);
}
map.on('load', function() {
console.log("map load");
map.loadImage('https://maxcdn.icons8.com/office/PNG/40/Transport/railroad_car-40.png', function(error, image) {
if (error) throw error;
map.addImage('car', image);
});
//fetch('./lines.geojson', {
//method: 'get'
//}).then(function(response) {
// return response.json();
//}).then(function(data) {
linesData = linesDataSource;
var coordinates = linesData.features[0].geometry.coordinates;
var bounds = coordinates.reduce(function(bounds, coord) {
return bounds.extend(coord);
}, new mapboxgl.LngLatBounds(coordinates[0], coordinates[0]));
map.fitBounds(bounds, {
padding: 20,
duration: 2000
});
map.addSource('lines', {
"type": "geojson",
"data": linesData
});
map.addLayer({
"id": "route",
"source": "lines",
"type": "line",
"paint": {
"line-width": 2,
"line-color": "#007cbf"
}
});
// }).catch(function(err) {
//console.log("error: " + err);
//});
document.getElementById('addCar').addEventListener('click', function() {
addCar();
});
});
requestAnimationFrame(animate);
body {
margin: 0;
padding: 0;
}
#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
.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;
}
<script src="https://master.fieldtec.com/vendor/custom-component-modules/car_tracking_animation/scripts/turf.min.js"></script>
<link href="https://api.tiles.mapbox.com/mapbox-gl-js/v0.37.0/mapbox-gl.css" rel="stylesheet"/>
<script src="https://api.tiles.mapbox.com/mapbox-gl-js/v0.37.0/mapbox-gl.js"></script>
<body>
<div id='map'></div>
<div class='overlay'>
<button id='addCar'>Add Car</button>
</div>
</body>

Do all of your animation with one source. Update the source each frame with setData(). Render one layer from the source using data-driven styles. This will use your GPU to render animations.
This will improve performance considerably by reducing the number of layers and setData() calls.
Example code of animating in GL JS with one layer and one source: https://bl.ocks.org/ryanbaumann/9b9b52e09ff86d1ce8346fb76b681427

To animate hundreds of icons, it is more efficient to do the animation in the shaders rather than in the javascript. This allows you to leverage the power of the GPU. Here is a demo : http://misterfresh.github.io/mapbox-animation/

Related

vue-leaflet (leaflet) trying to reset style of the previous country

I'm trying to reset the style of the previous clicked country.
Basically here's what happen:
the maps get a geojson with all countries shapes.
When I hover on a country it will change color and when I hover out it will go back to previous color.
When I click on a country it will change the color (permanent)
If I click on another country it will change the color too but I would like to reset the previous clicked country to the starting style
I think the problem might be related to the way I access the $refs because no matter what I can't use the setStyle. (Or maybe something else has to be done in order to achieve result)
Thanks a lot.
this is my (working) Map component:
It misses the part where I try to achieve the previous clicked country style reset.
<template>
<div class="map-wrap">
<l-map
ref="map"
:options="mapOptions"
:zoom="zoom"
:minZoom="2.5"
:zoomAnimation="true"
:panInsideBounds="true"
:maxBounds="[
[90, 180],
[-90, -180],
]"
:maxBoundsViscosity="1.0"
:maxZoom="2.4"
:center="[47.41322, -1.219482]"
>
<l-tile-layer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
layer-type="base"
name="OpenStreetMap"
></l-tile-layer>
<l-geo-json
:visible="true"
:geojson="geojson"
:options="geojsonOpts"
ref="arearef"
/>
</l-map>
</div>
</template>
<script>
import "leaflet";
import {
LMap,
LTileLayer,
LMarker,
LPolygon,
LGeoJson,
} from "#vue-leaflet/vue-leaflet";
export default {
components: {
LMap,
LTileLayer,
LMarker,
LPolygon,
LGeoJson,
},
data() {
return {
zoom: 2,
geojson: null,
mapOptions: {
zoomSnap: 0.5,
preferCanvas: true,
},
clicked: "",
};
},
async created() {
const response = await fetch("/dataset/countries.geojson");
this.geojson = await response.json();
console.log(this.geojson);
},
computed: {
geojsonOpts() {
return {
onEachFeature: (feature, layer) => {
// set each layer style
layer.setStyle({
weight: 1,
color: "#999",
dashArray: "",
fillOpacity: 0.1,
});
let name = feature.properties.NAME;
layer.on("click", () => {
// reset to default style if the clicked layer is already active
if (this.clicked === name) {
layer.setStyle({
weight: 1,
color: "#999",
dashArray: "",
fillOpacity: 0.1,
});
return;
}
// change style to the clicked layer
layer.setStyle({
weight: 1,
color: "#0f0",
dashArray: "",
fillOpacity: 0.1,
});
this.clicked = name;
});
layer.on("mouseover", () => {
// do nothing if the layer is already active
if (this.clicked === name) {
return;
}
//change style on mouse over
layer.setStyle({
weight: 1,
color: "#0f0",
dashArray: "",
fillOpacity: 0.1,
});
});
layer.on("mouseout", () => {
// do nothing if the layer is already active
if (this.clicked === name) {
return;
}
// reset to default style on mouse out
layer.setStyle({
weight: 1,
color: "#999",
dashArray: "",
fillOpacity: 0.1,
});
});
},
};
},
},
};
</script>
<style scoped>
.map-wrap {
width: 100vw;
height: 100vh;
margin: 0;
padding: 0;
top: 0;
bottom: 0;
left: 0;
right: 0;
position: absolute;
}
</style>
and this is a small part of the geojson
{
"type": "FeatureCollection",
"name": "Countries",
"crs": {
"type": "name",
"properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" }
},
"features": [
{
"type": "Feature",
"properties": { "NAME": "Fiji" },
"geometry": {
"type": "MultiPolygon",
"coordinates": [
[
[
[180.0, -16.067132663642447],
[180.0, -16.555216566639196],
[179.364142661964138, -16.801354076946883],
[178.725059362997115, -17.012041674368039],
[178.596838595117134, -16.63915],
[179.0966093629971, -16.433984277547403],
[179.413509362997104, -16.379054277547404],
[180.0, -16.067132663642447]
]
],
[
[
[178.12557, -17.50481],
[178.3736, -17.33992],
[178.71806, -17.62846],
[178.55271, -18.15059],
[177.93266, -18.28799],
[177.38146, -18.16432],
[177.28504, -17.72465],
[177.67087, -17.38114],
[178.12557, -17.50481]
]
],
[
[
[-179.793320109048636, -16.020882256741224],
[-179.917369384765294, -16.501783135649397],
[-180.0, -16.555216566639196],
[-180.0, -16.067132663642447],
[-179.793320109048636, -16.020882256741224]
]
]
]
}
},
...
ps: sorry if I'm not posting a code sandbox but the map tiling was not properly working on it

issue with creating multiple leaflet layers from one geojson file

I have included my code below. While it is sort of working, ill get to that in a minute, I feel like there is a better, more efficient, more correct, way to achieve my goal. I have a map for different weather options, in this case, Tornado, Severe Weather and Flash flood warnings. These are all included in one geojson file. The geo json file has a property called LayerId. This determines the time in the loop that the layer would show. I have a simple global map loop that constantly runs from 0 - 11. So if I am on loop 5, then only the data that corresponds with LayerId 5 would be visible. All others would be hidden/removed (which ever is preferred). When the loop hits 6, the layer corresponding to LayerId 5 would go away and LayerId 6 would now show and so on. Once the loop reaches 11, it starts over at 0.
I am not using a leaflet control due to the site requirements so i am using my own simple check box controls. when the check box is clicked, it calls a toggleLayer function to apply filters to my data. If FlashFlood is checked then only the data corresponding to the flash flood would show over the course of the loop IF it has data for flash flood at that interval.
When i said that it is sort of working...in my loop function i have a call to remove a layer. this works except every now and then it throws a null or undefined error. problem is is that its never the same layer. each time i start the application, its a different layer that errors out.
Below i have included a sample of my geojson and the code. The entry point for the code is at the toggleLayer function.
Thanks for any and all help.
GEOJSON FILE
{
"name": "WarningsJson",
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"coordinates": [
[
[ -86.00, 31.00 ],
[ -86.00, 32.00 ],
[ -87.00, 30.00 ],
[ -86.00, 31.00 ]
]
],
"type": "Polygon"
},
"properties": {
"type": null,
"strokeColor": "#ff9aa3",
"StartDateTime": "09/29/2020 7:30:00 AM",
"EndDateTime": "09/29/2020 9:30:00 AM",
"strokeThickness": 20,
"InfoboxTitle": "SFFW",
"Station": "KMOB",
"Identifier": "FFW",
"LayerId": "0"
}
},
{
"type": "Feature",
"geometry": {
"coordinates": [
[
[ -87.00, 32.00 ],
[ -87.00, 33.00 ],
[ -88.00, 31.00 ],
[ -87.00, 32.00 ]
]
],
"type": "Polygon"
},
"properties": {
"type": null,
"strokeColor": "#c0ffd4",
"StartDateTime": "09/29/2020 7:30:00 AM",
"EndDateTime": "09/29/2020 9:30:00 AM",
"strokeThickness": 2,
"InfoboxTitle": "TOR",
"Station": "KMOB",
"Identifier": "TOR",
"LayerId": "1"
}
},......
APPLICATION CODE
var WarnStormModel = (function () {
var layer0 = new L.LayerGroup();
var layer1 = new L.LayerGroup();
var layer2 = new L.LayerGroup();
var layer3 = new L.LayerGroup();
var layer4 = new L.LayerGroup();
var layer5 = new L.LayerGroup();
var layer6 = new L.LayerGroup();
var layer7 = new L.LayerGroup();
var layer8 = new L.LayerGroup();
var layer9 = new L.LayerGroup();
var layer10 = new L.LayerGroup();
var layer11 = new L.LayerGroup();
var warnConditionsLayersGroup = [layer0, layer1, layer2, layer3, layer4, layer5, layer6, layer7, layer8, layer9, layer10, layer11];
var tornadoActive = false;
var svrActive = false;
var ffwActive = false;
const WarnFilter = {
tornado: null,
svr: null,
flood: null
}
function init() {
$.getJSON('/Data/GeoJsonFiles/WarningJsons/Warnings_0.json', function (data) {
L.geoJSON(data, {
style: function (feature) {
return {
color: feature.properties.strokeColor,
fillOpacity: 0
};
},
pane: "warnPane",
onEachFeature: function (feature, layer) {
var popupText = '<div>'
+ '<span style="float: right; cursor: pointer; cursor: hand"</i></span><br>'
+ '<b>LAYER: </b>' + layer.feature.properties.LayerId + '<br>'
+ '<b>TYPE: </b>' + layer.feature.properties.InfoboxTitle + '<br>'
+ '<b>STATION:</b>' + layer.feature.properties.Station + '<br>'
+ '<b>START: </b>' + layer.feature.properties.StartDateTime + '<br>'
+ '<b>END: </b>' + layer.feature.properties.EndDateTime + '<br>';
layer.bindPopup(popupText);
layer._leaflet_id = feature.properties.LayerId;
if (feature.properties.LayerId == "0") { layer0.addLayer(layer); }
else if (feature.properties.LayerId == "1") { layer1.addLayer(layer); }
else if (feature.properties.LayerId == "2") { layer2.addLayer(layer); }
else if (feature.properties.LayerId == "3") { layer3.addLayer(layer); }
else if (feature.properties.LayerId == "4") { layer4.addLayer(layer); }
else if (feature.properties.LayerId == "5") { layer5.addLayer(layer); }
else if (feature.properties.LayerId == "6") { layer6.addLayer(layer); }
else if (feature.properties.LayerId == "7") { layer7.addLayer(layer); }
else if (feature.properties.LayerId == "8") { layer8.addLayer(layer); }
else if (feature.properties.LayerId == "9") { layer9.addLayer(layer); }
else if (feature.properties.LayerId == "10") { layer10.addLayer(layer); }
else if (feature.properties.LayerId == "11") { layer11.addLayer(layer); }
},
filter: function (feature, layer) {
return (
feature.properties.Identifier === WarnFilter.tornado ||
feature.properties.Identifier === WarnFilter.svr ||
feature.properties.Identifier === WarnFilter.flood
)
},
interactive: true
});
}).fail(function (err) { console.log('createWarningsErr: ', err); })
};
//**********//
function isActive(layer) {
if (layer == "TOR") { return tornadoActive; }
else if (layer == "SVR") { return tstrmActive; }
else if (layer == "FFW") { return ffwActive; }
}
var isAnyActive = function () { return tornadoActive || svrActive || ffwActive; }
var toggleLayer = function (layer, checkState) {
switch (layer) {
case "TOR": (checkState) ? WarnFilter.tornado = 'TOR' : WarnFilter.tornado = null; tornadoActive = !tornadoActive;
break;
case "SVR": (checkState) ? WarnFilter.svr = 'SVR' : WarnFilter.svr = null; svrActive = !svrActive;
break;
case "FFW": (checkState) ? WarnFilter.flood = 'FFW' : WarnFilter.flood = null; ffwActive = !ffwActive;
break;
default:
if (checkState) {
for (key in WarnFilter) {
if (WarnFilter.hasOwnProperty(key)) {
debugger
WarnFilter[key] = (key.toString()).toUpperCase();
}
}
}
//set all values in filter themselves to show
else {
for (key in WarnFilter) {
if (WarnFilter.hasOwnProperty(key)) {
WarnFilter[key] = null;
}
}
}
break;
}
showHide(layer, checkState);
}
//**********//
var showHide = function (layer, checkState) {
rerender();
if (isAnyActive() && checkState) {
warnConditionsLayersGroup[GlobalMapLoop.getLoopIndex()].addTo(getMap());
}
else {
warnConditionsLayersGroup[GlobalMapLoop.getLoopIndex()].removeLayer(getMap());
}
}
var loop = function (currentIndex, pastIndex) {
console.log("got to warn loop", currentIndex, pastIndex, isAnyActive())
if (isAnyActive()) {
getMap().removeLayer(warnConditionsLayersGroup[pastIndex]);
getMap().addLayer(warnConditionsLayersGroup[currentIndex]);
}
}
var rerender = (function () {
init();
})
return {
init: init,
toggleLayer: toggleLayer,
loop: loop,
rerender: rerender
};
})();

ECharts draw a polygon on the scatter chart

I've googled a lot, but couldn't find any information. Is it possible to draw a polygon on the scatter plot in the ECharts to achieve the same view as on the picture below?
ECharts has an API to draw polygons:
function renderItem(params, api) {
if (params.context.rendered) {
return;
}
params.context.rendered = true;
let points = [];
for (let i = 0; i < data.length; i++) {
points.push(api.coord(data[i]));
}
let color = api.visual('color');
return {
type: 'polygon',
shape: {
points: echarts.graphic.clipPointsByRect(points, {
x: params.coordSys.x,
y: params.coordSys.y,
width: params.coordSys.width,
height: params.coordSys.height
})
},
style: api.style({
fill: color,
stroke: echarts.color.lift(color)
})
};
}
var data = [
[
echarts.number.round(3.8),
echarts.number.round(4)
],
[
echarts.number.round(3.8),
echarts.number.round(4.5)
],
[
echarts.number.round(5),
echarts.number.round(6)
]
];
option = {
xAxis: {},
yAxis: {},
series: [
...other charts here ...
{
type: 'custom',
renderItem: renderItem,
data: data
}
]
};
The result will look like:

How Can I offset the center marker to right?

I want to achieve like this http://techblog.mappy.com/Leaflet-active-area/examples/index.html
I have added a floating content in left of the section. So the marker must remain in the right side.
Here is a demo.
JS:
// ***********************************************
mapboxgl.accessToken = 'pk.eyJ1IjoiZmFseiIsImEiOiJjaXdmczJha28wMG9sMnVsZnlxZWt1bmxhIn0.v8SlQ70Ay1MzNoPXhlXWVg';
// ***********************************************
// DID YOU FORK THIS EXAMPLE?
// Enter your access token below
// and uncomment the line to keep your
// project online!
// Need a token? Create free account
// mapbox.com/signup
// ***********************************************
// mapboxgl.accessToken = 'YOUR-ACCESS-TOKEN-HERE';
// ***********************************************
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/light-v9',
center: [144.985258,-37.807426],
zoom: 14
});
var framesPerSecond = 15;
var initialOpacity = 1
var opacity = initialOpacity;
var initialRadius = 6;
var radius = initialRadius;
var maxRadius = 18;
map.on('load', function () {
// Add a source and layer displaying a point which will be animated in a circle.
map.addSource('point', {
"type": "geojson",
"data": {
"type": "Point",
"coordinates": [
144.985258, -37.807426
]
}
});
map.addLayer({
"id": "point",
"source": "point",
"type": "circle",
"paint": {
"circle-radius": initialRadius,
"circle-radius-transition": {duration: 0},
"circle-opacity-transition": {duration: 0},
"circle-color": "#007cbf"
}
});
map.addLayer({
"id": "point1",
"source": "point",
"type": "circle",
"paint": {
"circle-radius": initialRadius,
"circle-color": "#007cbf"
}
});
function animateMarker(timestamp) {
setTimeout(function(){
requestAnimationFrame(animateMarker);
radius += (maxRadius - radius) / framesPerSecond;
opacity -= ( .9 / framesPerSecond );
map.setPaintProperty('point', 'circle-radius', radius);
map.setPaintProperty('point', 'circle-opacity', opacity);
if (opacity <= 0) {
radius = initialRadius;
opacity = initialOpacity;
}
}, 1000 / framesPerSecond);
}
// Start the animation.
animateMarker(0);
});
Unfortunately there isn't support yet for something like fitBounds with padding. https://github.com/mapbox/mapbox-gl-js/issues/1740
A possible workaround could be flying to the marker with an offset.
map.flyTo({
center: [144.985258, -37.807426],
offset: [300, 0],
speed: 0.8,
curve: .6
});

sencha touch: real time chart

i am using sencha touch to show a chart and add data to store of chart dynamically.but when i add data to store, my chart does not update result.
this is code of my chart:
Ext.define('MyApp.view.MyLineChart1', {
extend: 'Ext.chart.CartesianChart',
requires: [
'Ext.chart.axis.Category',
'Ext.chart.axis.Numeric',
'Ext.chart.series.Line'
],
config: {
itemId: 'xy',
store: 'MyStore',
colors: [
'#115fa6',
'#94ae0a',
'#a61120',
'#ff8809',
'#ffd13e',
'#a61187',
'#24ad9a',
'#7c7474',
'#a66111'
],
axes: [
{
type: 'category',
fields: [
'x'
],
maximum: 5,
minimum: 0
},
{
type: 'numeric',
fields: [
'y'
],
grid: {
odd: {
fill: '#e8e8e8'
}
},
position: 'left'
}
],
series: [
{
type: 'line',
colors: 'rgba(0,200,0,0.3)',
style: {
smooth: true,
stroke: 'rgb(0,200,0)',
},
xField: 'x',
yField: 'y'
}
],
listeners: [
{
fn: 'onChartShow',
event: 'show',
order: 'after'
}
]
},
onChartShow: function(component, eOpts) {
var TaskRunner = Ext.create("MyApp.controller.TaskRunner");
chart = Ext.ComponentQuery.query("#xy")[0];
store = chart.getStore();
chart.animationSuspended = true;
chart.update();
store.removeAll();
this.timeChartTask = TaskRunner.start({
run: this.update_chart,
interval: 1000,
repeat: 10,
scope: this
});
},
update_chart: function(chart) {
var me = this;
chart = Ext.ComponentQuery.query("#xy")[0];
store = chart.getStore();
count = store.getCount();
xAxis = chart.getAxes()[0];
visibleRange = 10000;
second = 1000;
console.log(xAxis.getMinimum());
if (count > 0) {
lastRecord = store.getAt(count - 1);
xValue = lastRecord.get('x') + second;
if (xValue - me.startTime > visibleRange) {
me.startTime = xValue - visibleRange;
xAxis.setMinimum(this.startTime);
xAxis.setMaximum(xValue);
console.log("count >0");
}
store.add({
x: xValue,
y: me.getNextValue()
});
// store.load();
chart.redraw();
} else {
chart.animationSuspended = true;
me.startTime = Math.floor(Ext.Date.now() / second) * second;
xAxis.setMinimum(me.startTime);
xAxis.setMaximum(me.startTime + visibleRange);
store.add({
x: this.startTime,
y: me.getNextValue()
});
chart.animationSuspended = false;
// store.load();
chart.redraw();
console.log("count < 0");
}
},
getNextValue: function(previousValue) {
var delta = Math.random()*4 - 2;
if (Ext.isNumber(previousValue)) {
return Ext.Number.constrain(previousValue + delta, -2, 2);
}
return Math.random()*4 - 2;
}
});
this is my store:
Ext.define('MyApp.store.MyStore', {
extend: 'Ext.data.Store',
requires: [
'MyApp.model.MyModel1'
],
config: {
model: 'MyApp.model.MyModel1',
storeId: 'MyStore'
}
});
and this is my model:
Ext.define('MyApp.model.MyModel1', {
extend: 'Ext.data.Model',
requires: [
'Ext.data.Field'
],
config: {
fields: [
{
name: 'x'
},
{
name: 'y'
}
]
}
});
If you want set data to store use this code:
var store = Ext.getStore('MyStore');
store.load(function () {
var store = Ext.getStore('MyStore');
store.removeAll();
store.add({
x: xValue,
y: me.getNextValue()
});
store.sync();
});