I'm using aChartEngine's 0.7.0 charting library for Android. I have a working XY chart that changes dynamically about every second when new data is received. However, it draws the line chart from left to right and eventually moves out of view. The only way to see the new data being drawn is to scroll the chart view to the right.
Does anyone know how to make the line draw new data on the left side so that older data eventually scrolls right out of view. Basically, reversing the direction of the chart line?
My code:
private XYMultipleSeriesDataset HRDataset = new XYMultipleSeriesDataset();
private XYMultipleSeriesRenderer HeartRateRenderer = new XYMultipleSeriesRenderer();
private XYSeries HRCurrentSeries;
private GraphicalView HRChartView;
in onResume():
if (HRChartView == null) {
LinearLayout layout = (LinearLayout) findViewById(R.id.HRchart);
HRChartView = ChartFactory.getLineChartView(this, HRDataset, HeartRateRenderer);
layout.addView(HRChartView, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
// boolean enabled = HRDataset.getSeriesCount() > 0;
// setSeriesEnabled(enabled);
} else {
HRChartView.repaint();
}
in onCreate():
HeartRateRenderer.setAxisTitleTextSize(16);
HeartRateRenderer.setChartTitleTextSize(20);
HeartRateRenderer.setLabelsTextSize(15);
HeartRateRenderer.setLegendTextSize(15);
HeartRateRenderer.setMargins(new int[] {20, 30, 15, 0});
HeartRateRenderer.setAxesColor(Color.YELLOW);
String seriesTitle = "Heart Rate";
XYSeries series = new XYSeries(seriesTitle);
HRDataset.addSeries(series);
HRCurrentSeries = series;
XYSeriesRenderer renderer = new XYSeriesRenderer();
renderer.setColor(Color.RED);
HeartRateRenderer.addSeriesRenderer(renderer);
I have a service that receives data from a Blue Tooth device. When new data arrives I call this function:
public void setupHRChart(double x, double y)
{
HRCurrentSeries.add(x, y);
if (HRChartView != null) {
HRChartView.repaint();
}
}
in the manifest:
<activity android:name="org.achartengine.GraphicalActivity" />
in my layout:
<LinearLayout android:id="#+id/HRchart" android:orientation="horizontal"
android:layout_width="fill_parent" android:layout_height="300px" android:layout_weight="1" />
-Hope that helps.
I actually decided to go with the natural flow direction of aChartEngine's dynamic line charts. I was able to lock the graph image so that the line no longer scrolls off the screen out of view. I also remove items from the beginning of the serires to keep a certain time span visible without the graph compressing into a mass of lines. It actually works really well.
Here's a youtube video demonstrating the charts in action: http://www.youtube.com/watch?v=mZMrOc5QaG0
I also remove items from the beginning of the series to keep a certain time span visible
The problem with this solution is that you actually loose data...
How about using the setXAxisMax and setXAxisMin of the renderer as follow:
0) set XAxisMax to a value, say 10 and set the XAxisMin value to 0
1) count the "ticks" in an xTick variable
2) once xTick is > XAxisMax:
2.1) set XAxisMax to xTick
2.2) set XAxisMin to what it was + 1
3) render the graph again
This way we actually shift the "visible part" of the graph (by playing with Xmin and Xmax) but do not loose any data (i.e., we can still scroll to see previous data-points):
renderer.setXAxisMin(0.0);
renderer.setXAxisMax(10);
xTick = 0;
lastMinX = 0;
private void refreshChart(double lastValue) {
/* check if we need to shift the x axis */
if (xTick > getRenderer().getXAxisMax()) {
getRenderer().setXAxisMax(xTick);
getRenderer().setXAxisMin(++lastMinX);
}
series.add(xTick++, lastValue);
mChartView.repaint();
}
Related
I have points generated one by one, and when a new point is generated, I want to draw a line segment connecting with the previous point. Like this:
var x by remember { mutableStateOf( 0.0f)}
var y by remember { mutableStateOf( 0.5f)}
var pStart by remember { mutableStateOf(Offset(0f, 0.5f))}
Canvas(modifier = Modifier.fillMaxSize()) {
canvasWidth = size.width
canvasHeight = size.height
val pEnd = Offset(x * canvasWidth, (1-y) * canvasHeight)
val col = if (pEnd.y < pStart.y) Color.Green else Color.Red
drawLine(
start = pStart,
end = pEnd,
strokeWidth = 4f,
color = col
)
pStart = pEnd
}
But this only draws the segment in a flash and no segments stay on the screen.
I know I can save the points to a list and redraw all the segments whenever a new point is added. But I just hope to economize. Is it possible?
There's no practical other way. You COULD in fact, keep track of just two points, adding a whole new Canvas (all Transparent and Filling the maximum Size, stacked on top of one another), for each extra point that is added. This does seem a bit impractical, but maybe try it out and do some benchmarking to see which one checks out. This is the only OTHER way I could think of, where you do not have to store all the points and recompose every time a point is added, since all the other lines would technically be frozen in space.
In response to the somewhat (unreasonably) aggressive comment below, here's some sample code. I assume you have a stream of new points coming in so a LiveData object is assumed to be the source of that, which I shall be converting to a MutableState<T> for my use-case.
var latestPoint by liveData.collectAsState()
var recordedPoint by remember { mutableStateOf(latestPoint) }
var triggerDraw by remember { mutableStateOf(false) }
var canvasList = mutableStateListOf<#Composable () -> Unit>Canvas>() // Canvas is the Composable
if(triggerDraw){
canvasList.add(
Canvas(){
/* you have the recordedPoint, and latestPoint, simply draw a line here */
}
)
triggerDraw = false
}
LaunchedEffect(latestPoint){
triggerDraw = true
}
canvasList.forEach {
it() // Invoke the Composable here
}
Thanks Dad!
Here's the scenario:
I have a 360ยบ panorama image of the world map, running on A-Frame
I would like each continent to be clickable, redirecting to a related Wikipedia page.
What I tried:
I tried using the <map> tag as I would in a normal project, but
with no results.
I also read the documentation, and found nothing
relevant about this.
My question:
Is my goal even possible? If so, how?
My code:
Note: So far, only the African continent has coordinates.
JSFiddle
<script src="https://aframe.io/releases/1.0.4/aframe.min.js"></script>
<a-scene>
<a-entity camera look-controls="reverseMouseDrag: true"></a-entity>
<a-sky id="image-360" radius="10" src="https://upload.wikimedia.org/wikipedia/commons/thumb/8/83/Equirectangular_projection_SW.jpg/1920px-Equirectangular_projection_SW.jpg" usemap="#image_map"></a-sky>
<map name="image_map">
<area alt="Africa" title="Africa" href="https://en.wikipedia.org/wiki/Africa" coords="920,296 871,351 873,424 916,461 979,449 1014,463 1005,490 1032,540 1020,579 1056,663 1081,667 1114,655 1146,600 1175,561 1165,504 1220,451 1228,422 1191,426 1124,314 1063,308 1054,324 1014,302 1016,284 963,290" shape="polygon">
</map>
</a-scene>
The <map> tag places the areas above an image. It won't project the areas onto a three dimentional object just like that.
I'd recommend an approach where you re-use the areas defined in the <map> tag within an a-frame custom component.
tldr:
I've made a component that seems to do the job, and is fairly simple in use:
<a-image material="src: #texture" asourcemap="#some-map"></a-image>
<!-- somewhere else -->
<map id="some-map">
<area shape="rect" alt="rectangle" coords="0,0, 50,50" href="rect.html">
<area shape="polygon" alt="poly" coords="0,0, 40,50, 25,0">
</map>
It should work well with the href attribute, but also the <a-image> will emit a signal with the clicked area name.
You can see it working with 3D planes and cylinders here.
end of tldr
0. Gathering <map> data
Simple parsing. Grab the <map> element, iterate through all children, and collect their data with getAttribute():
var map = document.querySelector(selector);
for (let area of map.children) {
// area.getAttribute("href") - href attribute
// area.getAttribute("alt") - alt name
// area.getAttribute("coords") - coordinates array.
}
Store them for later use. The coordinates are comma separated strings, so you may need to parseInt() them, manage the order (i.e. [[x1,x1], [x2,y2], [x3, y3]])
1. Make the a-frame entity interactable
React on clicks, and what's more important - check where the click occurred:
this.el.addEventListener("click", evt => {
var UVPoint = evt.detail.intersection.uv
})
UV mapping will help us determine which point on the texture was clicked. The UV ranges from <0, 1>, so we will need to re-scale the UVPoint:
// may need waiting for "model-loaded"
let mesh = this.el.getObject3D("mesh")
// this may not be available immidiately
let image = mesh.material.map.image
let x_on_image = UVPoint * image.width
// the y axis goes from <1, 0>
let y_on_image = image.height - UVPoint * image.heigth
So hey, we got the area coordinates and the point coordinates!
There is only one thing left:
2. Determining if an area was clicked
No need to re-invent the wheel here. This SO question on checking if a point is inside a polygon has a simple inside(point, polygon) function. Actually we have everything we need, so the last thing we do is:
iterate through the polygons
check if the clicked point is inside any of the polygons
if positive - do your thing
like this:
var point = [x_on_texture, y_on_texture]
for (var i = 0; i < polygons.length; i++) {
// polygons need to be [[x1, y1], [x2, y2],...[xn, yn]] here
if (inside(point, polygons[i]) {
console.log("polygon", i, "clicked!")
}
}
If you skipped the tldr section - the above steps are combined in this component and used in this example
3. Old, hacky try
Another way of doing this could be:
receive a click on the a-frame entity
grab the clicked coordinates like in 1
hide the scene
check out which <area> is at the coordinates with document.elementFromPoint(x, y);.
show the scene
create a mouse event with document.createEvent("MouseEvent");
dispatch it on the <area> element.
The hide / show trick works really good even on my mobile phone. I was really surprised that the scene wasn't flickering, freezing, even slowing down.
But document.elementFromPoint(x, y); didn't work with firefox, and probably any attempt to make it work would be way more time consuming than the 0-2 steps. Also I believe the trappings would become bigger and case-dependant.
Anyway, here's the old-answer component:
/* SETUP
<a-scene>
<a-image press-map>
</a-scene>
<image id="image" sourcemap="map">
<map name="map">
<area ...>
</map>
*/
AFRAME.registerComponent("press-map", {
init: function() {
// the underlying image
this.img = document.querySelector("#image")
// react on clicks
this.el.addEventListener("click", evt => {
// get the point on the UV
let uvPoint = evt.detail.intersection.uv
// the y is inverted
let pointOnImage = {
x: uvPoint.x * this.img.width,
y: this.img.height - uvPoint.y * this.img.height
}
// the ugly show-hide bits
this.el.sceneEl.style.display = "none";
this.img.style.display = "block";
// !! grab the <area> at the (x,y) position
var el = document.elementFromPoint(pointOnImage.x, pointOnImage.y);
this.el.sceneEl.style.display="block"
this.img.style.display="none"
// create and dispatch the event
var ev = document.createEvent("MouseEvent");
ev.initMouseEvent(
"click",
true /* bubble */, false /* cancelable */,
window, null,
x, y, 0, 0, /* coordinates */
false, false, false, false, /* modifier keys */
0 /*left*/, null
);
el.dispatchEvent(ev);
}
}
})
I recently updated my Cordova mobile mapping app from OL3 V3.1.1 to V3.7.0 to V3.8.2.
Am using PouchDB to store off-line tiles, and with V3.1.1 tiles were visible.
Here is the code snippet:
OSM_bc_offline_pouchdb = new ol.layer.Tile({
//maxResolution: 5000,
//extent: BC,
//projection: spherical_mercator,
//crossOrigin: 'anonymous',
source: new ol.source.XYZ({
//adapted from: http://jsfiddle.net/gussy/LCNWC/
tileLoadFunction: function (imageTile, src) {
pouchTilesDB_osm_bc_baselayer.getAttachment(src, 'tile', function (err, res) {
if (err && err.error == 'not_found')
return;
//if(!res) return; // ?issue -> causes map refresh on movement to stop
imageTile.getImage().src = window.URL.createObjectURL(res);
});
},
tileUrlFunction: function (coordinate, projection) {
if (coordinate == null)
return undefined;
// OSM NW origin style URL
var z = coordinate[0];
var x = coordinate[1];
var y = coordinate[2];
var imgURL = ["tile", z, x, y].join('_');
return imgURL;
}
})
});
trails_mobileMap.addLayer(OSM_bc_offline_pouchdb);
OSM_bc_offline_pouchdb.setVisible(true);
Moving to both V3.7.0 and V3.8.2 causes the tiles to not display. Read the API and I'm missing why this would happen.
What in my code needs updating to work with OL-V3.8.2?
Thanks,
Peter
Your issue might be related to the changes to ol.TileCoord in OpenLayers 3.7.0. From the release notes:
Until now, the API exposed two different types of ol.TileCoord tile coordinates: internal ones that increase left to right and upward, and transformed ones that may increase downward, as defined by a transform function on the tile grid. With this change, the API now only exposes tile coordinates that increase left to right and upward.
Previously, tile grids created by OpenLayers either had their origin at the top-left or at the bottom-left corner of the extent. To make it easier for application developers to transform tile coordinates to the common XYZ tiling scheme, all tile grids that OpenLayers creates internally have their origin now at the top-left corner of the extent.
This change affects applications that configure a custom tileUrlFunction for an ol.source.Tile. Previously, the tileUrlFunction was called with rather unpredictable tile coordinates, depending on whether a tile coordinate transform took place before calling the tileUrlFunction. Now it is always called with OpenLayers tile coordinates. To transform these into the common XYZ tiling scheme, a custom tileUrlFunction has to change the y value (tile row) of the ol.TileCoord:
function tileUrlFunction = function(tileCoord, pixelRatio, projection){
var urlTemplate = '{z}/{x}/{y}';
return urlTemplate
.replace('{z}', tileCoord[0].toString())
.replace('{x}', tileCoord[1].toString())
.replace('{y}', (-tileCoord[2] - 1).toString());
}
If this is your issue, try changing your tileUrlFunction to
function (coordinate, projection) {
if (coordinate == null)
return undefined;
// OSM NW origin style URL
var z = coordinate[0];
var x = coordinate[1];
var y = (-coordinate[2] - 1);
var imgURL = ["tile", z, x, y].join('_');
return imgURL;
}
I want to implement Xamarin forms image zoom property. I did search the internet for quite some time but I didn't find any relevant documentation, can anyone provide me some information on how to implement it in Xamarin Forms?
Currently there is no zoom property for image. You have to use custom renderer to acheive that.
Try using ScaleTo, Itz similar to zoom but not exactly zoom functionality.. Hope it helps...
var img = new Image {
Source = "foo.png",
Scale = .9
};
var stepper = new Stepper {
Minimum = .1,
Maximum = 1,
Increment = .1,
Value = .9,
};
stepper.ValueChanged += async (object sender, ValueChangedEventArgs e) => {
img.ScaleTo(e.NewValue);
};
I had the same need to implement zoom, click, ... on images on Xamarin Forms.
On the forum i find a plugin that define custom renderers for the 3 platforms that can allow you to get coordinates of click on image and then to implement the zoom you want to do.
Here the website of the plugin : http://www.mrgestures.com/
It costs 10euros for every application you develop. Depending on what you need to implement and how many times you will need it it's not expensive at all.
The issue comes with scale property that its scaled to the center of x and y ,
I've implemented this extension no need for extra renderer.
public static Task<bool> ScaleTo(this VisualElement view, double scale, double anchorX, double anchorY, uint length = 250, Easing easing = null)
{
var tcs = new TaskCompletionSource<bool>();
if (easing == null)
{
easing = Easing.Linear;
}
new Animation(
(v) =>
{
view.Scale = scale;
view.AnchorX = anchorX;
view.AnchorY = anchorY;
}, 0, length
).Commit(view, nameof(view), 16, length, easing, finished: (f, a) => tcs.SetResult(a));
return tcs.Task;
}
You can implement pinch to zoom by using a pinch gesture recognizer.
Here is the documentation on pinch to zoom
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/gestures/pinch
At the same time if you want to add pan, you can combine the above code with
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/gestures/pan
Then you would have a pretty good full screen image viewer in Xamarin.Forms
You can use this library, just install on your .netstandard project and that's it!
You can then use Rg.Plugin.Popup and add the image dynamically by providing the link
xmlns:pinch="clr-namespace:Xamarin.Forms.PinchZoomImage;assembly=Xamarin.Forms.PinchZoomImage"
<pinch:PinchZoom.Content>
<Image Source="xxamarin.jpg" />
</pinch:PinchZoom.Content>
I'm seeing some odd behavior in a Highcharts line chart. I have multiple series displayed, and need to let the user change what's called the "Map level" on the chart, which is a straight line across all time periods. Assuming that the correct series is
chart.series[i]
and that the new level that I want it set to is stored in var newMapLevel,
I'm changing that series' data like so:
data = chart.series[i].data;
for(j=0; j<data.length; j++){
data[j].y = newMapLevel;
}
chart.series[i].setData(data);
Calling this function has the desired effect UNLESS the new map level y_value is ONE greater than the highest y_value of all other series, in which case the y-axis scale blows up. In other words, if the y_axis scale is normally from 0 to 275,000, and the highest y_value of any of the other series is, say, 224,000, setting the new map level value to 224,001 causes the y_axis scale to become 0 to 27500M. Yes, that's 27.5 billion.
Might this be a bug in Highcharts? Or is there a better way to change the data in a series?
I've posted a fiddle: http://jsfiddle.net/earachefl/4FuNE/4/
I got my answer from the Highcharts forum:
http://highslide.com/forum/viewtopic.php?f=9&t=13594&p=59888#p59888
This doesn't work as smoothly as I'd like. When you go from 8 as your line to 2 as your line, the scale doesn't adjust back down until you enter another value. Perhaps it's a start in the right direction.
$(document).ready(function(){
$('#clickme').click(function(){
var newMapLevel = $('#newMAP').val();
if(newMapLevel){
for(i=0; i<chart.series.length; i++){
if(chart.series[i].name == 'Map Level'){
data = chart.series[i].data;
for(j=0; j<data.length; j++){
data[j].y = newMapLevel;
}
// get the extremes
var extremes = chart.yAxis[0].getExtremes();
//alert("dataMin: " + extremes.dataMin);
//alert("dataMax: " + extremes.dataMax);
// define a max YAxis value to use when setting the extremes
var myYMax = extremes.dataMax;
if (newMapLevel >= myYMax) {
myYMax = Number(newMapLevel) + 1; // number conversion required
}
if (myYMax > chart.yAxis[0].max) {
alert('cabbbie');
myYMax = chart.yAxis[0].max + 1;
}
//alert("myYMax: " + myYMax);
chart.yAxis[0].setExtremes(extremes.dataMin, myYMax)
// finally, set the line data
chart.series[i].setData(data);
}
}
}
}); });