Hi I am using MPAndroid chart as charting library in my android app. I am trying to highlight some out of the range values on line dataset with different color (Picture 1)and if the value is beyond range then I want to also change marker-view drawable image also.
I have achieved this (Picture 2 ) for now I have managed to change color to red of out of the range values. How can I achieve Picture 1 in chart?
private void populateChart() {
chart = binding.lcCharts;
chart.setBackgroundColor(Color.WHITE);
chart.getDescription().setEnabled(false);
chart.setDoubleTapToZoomEnabled(false);
chart.setPinchZoom(false);
chart.setScaleEnabled(false);
getXAxisData()
LineData lineData = new LineData(setLineDataSet());
lineData.setDrawValues(false);
chart.setData(lineData);
chart.setTouchEnabled(true);
chart.setDrawMarkers(false);
chart.setHighlightPerTapEnabled(true);
chart.setMarker(new YourMarkerView(fragment.requireContext(), R.layout.layout_pop_up));
chart.setClipChildren(false);
chart.setClipToPadding(false);
chart.invalidate();
chart.notifyDataSetChanged();
}
private ArrayList<ILineDataSet> setLineDataSet() {
ArrayList<ILineDataSet> dataSet = new ArrayList<>();
for (int i = 0; i < response.size(); i++) {
LineDataSet lineDataSet = new LineDataSet(setData(i),
response.get(i).getName());
lineDataSet.setLineWidth(3);
lineDataSet.setColor(this.getResources().getColor(colorArray[i]));
lineDataSet.setDrawCircleHole(false);
lineDataSet.setCircleRadius(4);
lineDataSet.setCircleColors(setColorOfLineDataSet(i));
dataSet.add(lineDataSet);
}
return dataSet;
}
private ArrayList<Integer> setColorOfLineDataSet(int i) {
ArrayList<Integer> color = new ArrayList<Integer>();
for (int j = 0; j < response.get(i).size(); j++) {
if (!response.get(i).isNormal()) {
color.add(R.color.Red);
} else {
color.add(colorArray[i]);
}
}
return color;
} private void getXAxisData() {
XAxis xAxis = chart.getXAxis();
xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
xAxis.setDrawAxisLine(false);
xAxis.setDrawGridLines(false);
// xAxis.setGranularity(1f);
xAxis.setValueFormatter(new MyXAxisValueFormatter(getDateArrayForXAxis()));//getDateArrayForXAxis function returns a String array of size 14 with values of dates of past two weeks.
xAxis.setLabelCount(14, true);
}
public class MyXAxisValueFormatter extends ValueFormatter {
private String[] mValues;
public MyXAxisValueFormatter(String[] values) {
this.mValues = values;
}
public String getFormattedValue(float value) {
String val = null;
try {
val = String.valueOf(mValues[(int) value]);
} catch (IndexOutOfBoundsException e) {
}
return val;
}
/**
* Used to draw axis labels, calls {#link #getFormattedValue(float)} by default.
*
* #param value float to be formatted
* #param axis axis being labeled
* #return formatted string label
*/
public String getAxisLabel(float value, AxisBase axis) {
return getFormattedValue(value);
}
}
This code is crashing with arrayIndexOutOfBoundException.
enter code here
For customizing the Circle shape you can use following methods of the LineDataSet:
setCircleColor(/some Color value/)
setCircleRadius(/* some float value */)
setDrawCircleHole(/* boolean value */)
setDrawFilled(/* boolean value*/)
try these out, maybe you'll find something which you want. But I don't think it will allow you to change the shape completely.
I have referred to this article for an example:https://www.studytutorial.in/android-line-chart-or-line-graph-using-mpandroid-library-tutorial
Related
This MainActivity.java was written for quantised models and I'm trying to use unquantised model.
After making the changes as mentioned here, here to MainActivity.java, my code is
public class MainActivity extends AppCompatActivity implements AdapterView.OnItemSelectedListener {
private static final String TAG = "MainActivity";
private Button mRun;
private ImageView mImageView;
private Bitmap mSelectedImage;
private GraphicOverlay mGraphicOverlay;
// Max width (portrait mode)
private Integer mImageMaxWidth;
// Max height (portrait mode)
private Integer mImageMaxHeight;
private final String[] mFilePaths =
new String[]{"mountain.jpg", "tennis.jpg","96580.jpg"};
/**
* Name of the model file hosted with Firebase.
*/
private static final String HOSTED_MODEL_NAME = "mobilenet_v1_224_quant";
private static final String LOCAL_MODEL_ASSET = "retrained_graph_mobilenet_1_224.tflite";
/**
* Name of the label file stored in Assets.
*/
private static final String LABEL_PATH = "labels.txt";
/**
* Number of results to show in the UI.
*/
private static final int RESULTS_TO_SHOW = 3;
/**
* Dimensions of inputs.
*/
private static final int DIM_BATCH_SIZE = 1;
private static final int DIM_PIXEL_SIZE = 3;
private static final int DIM_IMG_SIZE_X = 224;
private static final int DIM_IMG_SIZE_Y = 224;
private static final int IMAGE_MEAN = 128;
private static final float IMAGE_STD = 128.0f;
/**
* Labels corresponding to the output of the vision model.
*/
private List<String> mLabelList;
private final PriorityQueue<Map.Entry<String, Float>> sortedLabels =
new PriorityQueue<>(
RESULTS_TO_SHOW,
new Comparator<Map.Entry<String, Float>>() {
#Override
public int compare(Map.Entry<String, Float> o1, Map.Entry<String, Float>
o2) {
return (o1.getValue()).compareTo(o2.getValue());
}
});
/* Preallocated buffers for storing image data. */
private final int[] intValues = new int[DIM_IMG_SIZE_X * DIM_IMG_SIZE_Y];
/**
* An instance of the driver class to run model inference with Firebase.
*/
private FirebaseModelInterpreter mInterpreter;
/**
* Data configuration of input & output data of model.
*/
private FirebaseModelInputOutputOptions mDataOptions;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mGraphicOverlay = findViewById(R.id.graphic_overlay);
mImageView = findViewById(R.id.image_view);
Spinner dropdown = findViewById(R.id.spinner);
List<String> items = new ArrayList<>();
for (int i = 0; i < mFilePaths.length; i++) {
items.add("Image " + (i + 1));
}
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout
.simple_spinner_dropdown_item, items);
dropdown.setAdapter(adapter);
dropdown.setOnItemSelectedListener(this);
mLabelList = loadLabelList(this);
mRun = findViewById(R.id.button_run);
mRun.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
runModelInference();
}
});
int[] inputDims = {DIM_BATCH_SIZE, DIM_IMG_SIZE_X, DIM_IMG_SIZE_Y, DIM_PIXEL_SIZE};
int[] outputDims = {DIM_BATCH_SIZE, mLabelList.size()};
try {
mDataOptions =
new FirebaseModelInputOutputOptions.Builder()
.setInputFormat(0, FirebaseModelDataType.FLOAT32, inputDims)
.setOutputFormat(0, FirebaseModelDataType.FLOAT32, outputDims)
.build();
FirebaseModelDownloadConditions conditions = new FirebaseModelDownloadConditions
.Builder()
.requireWifi()
.build();
FirebaseLocalModelSource localModelSource =
new FirebaseLocalModelSource.Builder("asset")
.setAssetFilePath(LOCAL_MODEL_ASSET).build();
FirebaseCloudModelSource cloudSource = new FirebaseCloudModelSource.Builder
(HOSTED_MODEL_NAME)
.enableModelUpdates(true)
.setInitialDownloadConditions(conditions)
.setUpdatesDownloadConditions(conditions) // You could also specify
// different conditions
// for updates
.build();
FirebaseModelManager manager = FirebaseModelManager.getInstance();
manager.registerLocalModelSource(localModelSource);
manager.registerCloudModelSource(cloudSource);
FirebaseModelOptions modelOptions =
new FirebaseModelOptions.Builder()
.setCloudModelName(HOSTED_MODEL_NAME)
.setLocalModelName("asset")
.build();
mInterpreter = FirebaseModelInterpreter.getInstance(modelOptions);
} catch (FirebaseMLException e) {
showToast("Error while setting up the model");
e.printStackTrace();
}
}
private void runModelInference() {
if (mInterpreter == null) {
Log.e(TAG, "Image classifier has not been initialized; Skipped.");
return;
}
// Create input data.
ByteBuffer imgData = convertBitmapToByteBuffer(mSelectedImage, mSelectedImage.getWidth(),
mSelectedImage.getHeight());
try {
FirebaseModelInputs inputs = new FirebaseModelInputs.Builder().add(imgData).build();
// Here's where the magic happens!!
mInterpreter
.run(inputs, mDataOptions)
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
e.printStackTrace();
showToast("Error running model inference");
}
})
.continueWith(
new Continuation<FirebaseModelOutputs, List<String>>() {
#Override
public List<String> then(Task<FirebaseModelOutputs> task) {
float[][] labelProbArray = task.getResult()
.<float[][]>getOutput(0);
List<String> topLabels = getTopLabels(labelProbArray);
mGraphicOverlay.clear();
GraphicOverlay.Graphic labelGraphic = new LabelGraphic
(mGraphicOverlay, topLabels);
mGraphicOverlay.add(labelGraphic);
return topLabels;
}
});
} catch (FirebaseMLException e) {
e.printStackTrace();
showToast("Error running model inference");
}
}
/**
* Gets the top labels in the results.
*/
private synchronized List<String> getTopLabels(float[][] labelProbArray) {
for (int i = 0; i < mLabelList.size(); ++i) {
sortedLabels.add(
new AbstractMap.SimpleEntry<>(mLabelList.get(i), (labelProbArray[0][i] )));
if (sortedLabels.size() > RESULTS_TO_SHOW) {
sortedLabels.poll();
}
}
List<String> result = new ArrayList<>();
final int size = sortedLabels.size();
for (int i = 0; i < size; ++i) {
Map.Entry<String, Float> label = sortedLabels.poll();
result.add(label.getKey() + ":" + label.getValue());
}
Log.d(TAG, "labels: " + result.toString());
return result;
}
/**
* Reads label list from Assets.
*/
private List<String> loadLabelList(Activity activity) {
List<String> labelList = new ArrayList<>();
try (BufferedReader reader =
new BufferedReader(new InputStreamReader(activity.getAssets().open
(LABEL_PATH)))) {
String line;
while ((line = reader.readLine()) != null) {
labelList.add(line);
}
} catch (IOException e) {
Log.e(TAG, "Failed to read label list.", e);
}
return labelList;
}
/**
* Writes Image data into a {#code ByteBuffer}.
*/
private synchronized ByteBuffer convertBitmapToByteBuffer(
Bitmap bitmap, int width, int height) {
ByteBuffer imgData =
ByteBuffer.allocateDirect(
4*DIM_BATCH_SIZE * DIM_IMG_SIZE_X * DIM_IMG_SIZE_Y * DIM_PIXEL_SIZE);
imgData.order(ByteOrder.nativeOrder());
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, DIM_IMG_SIZE_X, DIM_IMG_SIZE_Y,
true);
imgData.rewind();
scaledBitmap.getPixels(intValues, 0, scaledBitmap.getWidth(), 0, 0,
scaledBitmap.getWidth(), scaledBitmap.getHeight());
// Convert the image to int points.
int pixel = 0;
for (int i = 0; i < DIM_IMG_SIZE_X; ++i) {
for (int j = 0; j < DIM_IMG_SIZE_Y; ++j) {
final int val = intValues[pixel++];
imgData.putFloat((((val >> 16) & 0xFF)-IMAGE_MEAN)/IMAGE_STD);
imgData.putFloat((((val >> 8) & 0xFF)-IMAGE_MEAN)/IMAGE_STD);
imgData.putFloat(((val & 0xFF)-IMAGE_MEAN)/IMAGE_STD);
}
}
return imgData;
}
private void showToast(String message) {
Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show();
}
public void onItemSelected(AdapterView<?> parent, View v, int position, long id) {
mGraphicOverlay.clear();
mSelectedImage = getBitmapFromAsset(this, mFilePaths[position]);
if (mSelectedImage != null) {
// Get the dimensions of the View
Pair<Integer, Integer> targetedSize = getTargetedWidthHeight();
int targetWidth = targetedSize.first;
int maxHeight = targetedSize.second;
// Determine how much to scale down the image
float scaleFactor =
Math.max(
(float) mSelectedImage.getWidth() / (float) targetWidth,
(float) mSelectedImage.getHeight() / (float) maxHeight);
Bitmap resizedBitmap =
Bitmap.createScaledBitmap(
mSelectedImage,
(int) (mSelectedImage.getWidth() / scaleFactor),
(int) (mSelectedImage.getHeight() / scaleFactor),
true);
mImageView.setImageBitmap(resizedBitmap);
mSelectedImage = resizedBitmap;
}
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
// Do nothing
}
// Utility functions for loading and resizing images from app asset folder.
public static Bitmap getBitmapFromAsset(Context context, String filePath) {
AssetManager assetManager = context.getAssets();
InputStream is;
Bitmap bitmap = null;
try {
is = assetManager.open(filePath);
bitmap = BitmapFactory.decodeStream(is);
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
}
// Returns max image width, always for portrait mode. Caller needs to swap width / height for
// landscape mode.
private Integer getImageMaxWidth() {
if (mImageMaxWidth == null) {
// Calculate the max width in portrait mode. This is done lazily since we need to
// wait for a UI layout pass to get the right values. So delay it to first time image
// rendering time.
mImageMaxWidth = mImageView.getWidth();
}
return mImageMaxWidth;
}
// Returns max image height, always for portrait mode. Caller needs to swap width / height for
// landscape mode.
private Integer getImageMaxHeight() {
if (mImageMaxHeight == null) {
// Calculate the max width in portrait mode. This is done lazily since we need to
// wait for a UI layout pass to get the right values. So delay it to first time image
// rendering time.
mImageMaxHeight =
mImageView.getHeight();
}
return mImageMaxHeight;
}
// Gets the targeted width / height.
private Pair<Integer, Integer> getTargetedWidthHeight() {
int targetWidth;
int targetHeight;
int maxWidthForPortraitMode = getImageMaxWidth();
int maxHeightForPortraitMode = getImageMaxHeight();
targetWidth = maxWidthForPortraitMode;
targetHeight = maxHeightForPortraitMode;
return new Pair<>(targetWidth, targetHeight);
}
}
But I'm still getting Failed to get input dimensions. 0-th input should have 268203 bytes, but found 1072812 bytes for inception and 0-th input should have 150528 bytes, but found 602112 bytes for mobilenet. So, a factor is 4 there always.
To see what I've changed, the output of diff original.java changed.java is: (Ignore the line numbers)
32a33,34
> private static final int IMAGE_MEAN = 128;
> private static final float IMAGE_STD = 128.0f;
150,151c152,153
< byte[][] labelProbArray = task.getResult()
< .<byte[][]>getOutput(0);
---
> float[][] labelProbArray = task.getResult()
> .<float[][]>getOutput(0);
170c172
< private synchronized List<String> getTopLabels(byte[][] labelProbArray) {
---
> private synchronized List<String> getTopLabels(float[][] labelProbArray) {
173,174c175
< new AbstractMap.SimpleEntry<>(mLabelList.get(i), (labelProbArray[0][i] &
< 0xff) / 255.0f));
---
> new AbstractMap.SimpleEntry<>(mLabelList.get(i), (labelProbArray[0][i] )));
214c215,216
< DIM_BATCH_SIZE * DIM_IMG_SIZE_X * DIM_IMG_SIZE_Y * DIM_PIXEL_SIZE);
---
> 4*DIM_BATCH_SIZE * DIM_IMG_SIZE_X * DIM_IMG_SIZE_Y * DIM_PIXEL_SIZE);
>
226,228c228,232
< imgData.put((byte) ((val >> 16) & 0xFF));
< imgData.put((byte) ((val >> 8) & 0xFF));
< imgData.put((byte) (val & 0xFF));
---
> imgData.putFloat((((val >> 16) & 0xFF)-IMAGE_MEAN)/IMAGE_STD);
> imgData.putFloat((((val >> 8) & 0xFF)-IMAGE_MEAN)/IMAGE_STD);
> imgData.putFloat(((val & 0xFF)-IMAGE_MEAN)/IMAGE_STD);
This is how the buffer is allocated in the code lab:
ByteBuffer imgData = ByteBuffer.allocateDirect(
DIM_BATCH_SIZE * DIM_IMG_SIZE_X * DIM_IMG_SIZE_Y * DIM_PIXEL_SIZE);
DIM_BATCH_SIZE - A typical usage is for supporting batch processing (if the model supports it). In our sample and probably your test, you feed 1 image at a time and just keep it as 1.
DIM_PIXEL_SIZE - We set 3 in the code lab, which corresponds to r/g/b 1 byte each.
However, looks like you are using a float model. Then instead of one byte each for r/g/b, you use a float (4 bytes) to represent r/g/b each (you figured out this part already yourself). Then the buffer you allocated using above code is no longer sufficient.
You can follow example here for float models:
https://github.com/tensorflow/tensorflow/blob/25b4086bb5ba1788ceb6032eda58348f6e20a71d/tensorflow/contrib/lite/java/demo/app/src/main/java/com/example/android/tflitecamerademo/ImageClassifierFloatInception.java
To be exact on imgData population, below should be the formula for allocation:
ByteBuffer imgData = ByteBuffer.allocateDirect(
DIM_BATCH_SIZE * getImageSizeX() * getImageSizeY() * DIM_PIXEL_SIZE
* getNumBytesPerChannel());
getNumBytesPerChannel() should be 4 in your case.
[Update for the new question, in regards of below error]:
Failed to get input dimensions. 0-th input should have 268203 bytes, but found 1072812 bytes
This is the check that number of bytes expected by the model == number of bytes passed in. 268203 = 299 * 299 * 3 & 1072812 = 4 * 299 * 299 * 3. Looks like you are using a quantized model but fed it with data for float model. Could you double check the model you used? To make things simple, don't specify cloud model source and use local model from assets only.
[Update 0628, developer said they trained a float model]:
It could be your model is wrong; it could also be you have a Cloud model downloaded which overrides your local model. But the error message tells us that the model being loaded is NOT a float model.
To isolate the issue, I'd recommend below few testings:
1) Remove setCloudModelName / registerCloudModelSource from quick start app
2) Play with official TFLite float model You will have to download the model mentioned in comment and change Camera2BasicFragment to use that ImageClassifierFloatInception (instead of ImageClassifierQuantizedMobileNet)
3) Still use the same TFLite sample app, switch to your own trained model. Make sure to tune the image size to your values.
I have an array of y values that I am displaying over the dates of a month. To simplify, for the first week of April, I would have the values {0,200,0,0,500,0,100} over the x values {1,2,3,4,5,6,7}. I am able to display them as a bar chart using MPAndroidChart. I am also able to hide and display the values over each bar using
barChart.getData().setDrawValues(true); //or false when I want to hide
However, I want to display only the number that are non-zero, how would I be able to do so? Any pointers would be appreciated!
I tried creating my formatter the following way:
public class MyYAxisValueFormatter implements IAxisValueFormatter {
private DecimalFormat mFormat;
public MyYAxisValueFormatter() {
// format values to 1 decimal digit
mFormat = new DecimalFormat("###,###,##0.0");
}
#Override
public String getFormattedValue(float value, AxisBase axis) {
String val = "";
if(value != 0)
val = String.valueOf(value);
return mFormat.format(value) + " $";
}
}
and called it using this in my main function:
YAxis yAxis = barChart.getAxisLeft();
yAxis.setValueFormatter(new MyYAxisValueFormatter());
However the values of zero are still displayed.
Try making your own IValueFormatter Interface
public class MyYAxisValueFormatter implements IValueFormatter {
private DecimalFormat mFormat;
public MyYAxisValueFormatter() {
// format values to 1 decimal digit
mFormat = new DecimalFormat("###,###,##0.0");
}
#Override
public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) {
// "value" represents the position of the label on the axis (x or y)
if(value > 0) {
return mFormat.format(value);
} else {
return "";
}
}
}
try setting value formatter to your barchart.
bar.setValueFormatter(new MyYAxisValueFormatter ());
try this:
private class MyValueFormatter implements ValueFormatter {
#Override
public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) {
// write your logic here
if(value > 0)
return value+"";
else
return "";
}
}
OR
try this likn it helps you
https://github.com/PhilJay/MPAndroidChart/issues/2402
So I tried creating an animation which fades a string in (from black) on screen, but when I run it, it doesn't seem to work properly. I'm not getting any errors, just a black screen (the stage is black).
Here is the code:
public static void fadeIn(String string, Text tBox) {
final IntegerProperty counter = new SimpleIntegerProperty(0);
final BoolProp firstLoop = new BoolProp(false);
Color fadeIn[] = new Color[16];
String hexVal = "";
String hash = "#";
for (int i = 0; i > 10; i += 1) {
hexVal = "";
if (i == 10)
break;
for (int c = 0; c >6; c += 1) {
hexVal += i;
}
fadeIn[i] = Color.web(hash + hexVal);
}
fadeIn[10] = Color.web("#aaaaaa");
fadeIn[11] = Color.web("#bbbbbb");
fadeIn[12] = Color.web("#cccccc");
fadeIn[13] = Color.web("#dddddd");
fadeIn[14] = Color.web("#eeeeee");
fadeIn[15] = Color.web("#ffffff");
Timeline line = new Timeline();
KeyFrame frame = new KeyFrame(Duration.seconds(0.05), event -> {
if (counter.get() == 16) {
line.stop();
} else {
if (firstLoop.get()) {
firstLoop.set(false);
tBox.setText(string);
}
tBox.setFill(fadeIn[counter.get()]);
counter.set(counter.get()+1);
}
});
line.getKeyFrames().add(frame);
line.setCycleCount(Animation.INDEFINITE);
line.play();
}
And it's being referenced as follows:
public class Tester extends Application {
public static void main(String args[]) {
launch(args);
}
#Override public void start(Stage stage) {
VBox box = new VBox();
Text text = new Text();
box.getChildren().addAll(text);
stage.setScene(new Scene(box, 500, 500, Color.BLACK));
stage.show();
Animations.fadeIn("Testing 123", text);
}
}
The class BoolProp is as follows (I know one already exists, but I didn't have access to the documentation when I was writing the method.):
public class BoolProp {
private boolean val;
public BoolProp() {
val = false;
}
public BoolProp(boolean val) {
this.val = val;
}
public boolean get() {
return val;
}
public void set(boolean val) {
this.val = val;
}
}
There are multiple issues in your code. Lets get to them one by one :
The for loops inside the fadeIn() are incorrect.
Code:
for (int i = 0; i > 10; i += 1)
This loop will never execute since the condition will always be false. What you are looking for is :
for (int i = 0; i < 10; i++)
Similarly, fix the other loop as well.
Inside fadeIn(), you initialize the firstLoop to false and then inside the KeyFrame constructor, you try to check if the value is true, which leads to never setting the text on the Text.
There are few other issues with initializing of String, which we can overlook for now.
If you fix 1 and 2, you should be good to have a running application.
I'm trying to display 550 data points with periodic peaks (the flat line is 61). The problem is, that androidplot isn't drawing all the points correctly! From my log:
ECG I values 61,61,62,63,62,61,61,61,61,67,71,68,61,53,61,61,61,61,61,61,61,61,62,63,64,64,64,63,62,61,61,61
I've got the rangeboundaries set to plot.setRangeBoundaries(0,100, BoundaryMode.AUTO);, but as you can see, the peaks never drop to the 53 data point. I can see this lower point sometimes, but it gets smoothed out a fraction of a second later (as you can see in the screenshot).
My line and point formatter is:
LineAndPointFormatter lapf = new LineAndPointFormatter(p.color, null, null, null);
lapf.getLinePaint().setStrokeJoin(Paint.Join.MITER);
lapf.getLinePaint().setStrokeWidth(1);
I've tried with the both Paint.Join.ROUND and Paint.Join.BEVEL and got the same effect. I've also used the debugger to check that 53 is being inserted into the series.
EDIT
After some debugging, it looks like my pulse loop thread is wrong:
while (keepRunning) {
for (PulseXYSeries j : series) {
for (int k = 0; k < j.plotStep; k++) {
int at = (j.position + k) % j.getSize();
if (j.pulsing) {
if (j.pulsePosition == j.pulseValues.size() - 1) {
j.pulsing = false;
j.pulsePosition = 0;
} else {
try {
int pulseVal = j.pulseValues.get(j.pulsePosition);
j.setY(pulseVal,at);
j.pulsePosition += 1;
} catch(IndexOutOfBoundsException e) {
j.pulsePosition = 0;
}
}
} else {
j.setY(j.pulseValues.get(0), at);
long currTime = SystemClock.elapsedRealtime();
if (currTime - j.getLastPulse() >= j.getPulseDelay()) {
j.pulsing = true;
j.setLastPulse(currTime);
}
}
j.remove(((at + j.eraserSize) % j.getSize()));
}
j.position = (j.position + 1) % j.getSize(); // fixed it by changing +1 to + j.plotStep
}
Thread.sleep(delay);
}
My custom series looks like:
private class PulseXYSeries implements XYSeries {
private List<Integer> pulseValues = new ArrayList<Integer>();
private int pulsePerMinute;
public int pulsePosition;
public int position;
private ArrayList<Integer> values;
private String title;
private long lastPulse;
public boolean pulsing = false;
public int eraserSize = 20;
public int plotStep = 3;
}
I'm trying to set up Collision detection on the cactus-property items on all cactuses in the TMXmap example from andengine Gles2. I have tried various methods - can anyone give me one that works?
Original Code
Tmxmaps andengine
One suggested solution:
collision detection
Another suggested solution:
from andengine.org
I've tried:
if(pTMXTileProperties.containsTMXProperty("cactus", "true")) {
final Rectangle rect = new Rectangle(pTMXTile.getTileX()+10, pTMXTile.getTileY(),14, 14);
final FixtureDef boxFixtureDef = PhysicsFactory.createFixtureDef(0, 0, 1f);
PhysicsFactory.createBoxBody(mPhysicsWorld, rect, BodyType.StaticBody, boxFixtureDef);
rect.setVisible(false);
mScene.attachChild(rect);
}
This is from AndEngine: Handling collisions with TMX Objects
But I get this error:
Physicsfactory not found
I'm using the TMX example you have there as a basis for my game.
This is the main block of code for collisions:
// Define the block behavior
mPathFinderMap = new IPathFinderMap<TMXLayer>(){
private boolean mCollide;
#Override
public boolean isBlocked(final int pX, final int pY, final TMXLayer pTMXLayer) {
/*
* This is where collisions happen and are detected
*/
mCollide = false;
//Null check. Used since not all tiles have properties
if(pTMXLayer.getTMXTile(pX, pY).getTMXTileProperties(mTiledMap) != null){
//Get tiles with collision property
if(pTMXLayer.getTMXTile(pX, pY).getTMXTileProperties(mTiledMap).containsTMXProperty("COLLISION", "true"))
mCollide = true;
}
if(mTMXmapLoader.getCollideTiles().contains(pTMXLayer.getTMXTile(pX, pY)))
mCollide = true;
return mCollide;
}
};
/*
* This method moves the sprite to the designated location
*/
public void walkTo(TMXTile pFinalPosition) {
if(mHasFinishedPath){
mHasFinishedPath = false;//This prevents overlapping paths when the user double clicks. Used to prevent stutter
//Player coordinates
final float[] lPlayerCordinates = mPlayerSprite.convertLocalToSceneCoordinates(mPlayerSprite.getWidth()/2, mPlayerSprite.getHeight()/2);
// Get the tile the center of the player are currently waking on.
TMXTile lPlayerPosition = SceneManager.mWorldScene.getTouchLayer().getTMXTileAt(lPlayerCordinates[Constants.VERTEX_INDEX_X], lPlayerCordinates[Constants.VERTEX_INDEX_Y]);
mFinalPosition = pFinalPosition;
// Sets the A* path from the player location to the touched location.
if(mPathFinderMap.isBlocked(pFinalPosition.getTileColumn(), pFinalPosition.getTileRow(), SceneManager.mWorldScene.getTouchLayer())){
pFinalPosition = getNextTile(lPlayerPosition, pFinalPosition);
}
// These are the parameters used to determine the
int lFromCol = lPlayerPosition.getTileColumn(); int lFromRow = lPlayerPosition.getTileRow();
int lToCol = pFinalPosition.getTileColumn(); int lToRow = pFinalPosition.getTileRow();
boolean lAllowDiagonal = false;
// Find the path. This needs to be refreshed
AStarPath = mAStarPathFinder.findPath(MAX_SEARCH_DEPTH, mPathFinderMap, 0, 0, mTiledMap.getTileColumns() - 1, mTiledMap.getTileRows() - 1, SceneManager.mWorldScene.getTouchLayer(),
lFromCol, lFromRow, lToCol, lToRow, lAllowDiagonal, mHeuristic, mCostCallback);
//Log.i("AstarPath", "AStarPath " + AStarPath);
//Only loads the path if the AStarPath is not null
Path lPlayerPath = loadPathFound();
//Log.i("AstarPath", "lPlayerPath " + lPlayerPath);
if(lPlayerPath != null)
moveSprite(lPlayerPath);//Moves the sprite along the path
else
mHasFinishedPath = true;//If the path is null the player has not moved. Set the flag to true allows input to effect the sprite
}else{
//Update parameters
mFinalPosition = pFinalPosition;
mWaypointIndex = 0;
}
}
/*
* Updates the path
*/
public void updatePath(TMXTile pFinalPosition) {
//Player coordinates
final float[] lPlayerCordinates = mPlayerSprite.convertLocalToSceneCoordinates(mPlayerSprite.getWidth()/2, mPlayerSprite.getHeight()/2);
// Get the tile the feet of the player are currently waking on.
TMXTile lPlayerPosition = SceneManager.mWorldScene.getTouchLayer().getTMXTileAt(lPlayerCordinates[Constants.VERTEX_INDEX_X], lPlayerCordinates[Constants.VERTEX_INDEX_Y]);
// Sets the A* path from the player location to the touched location.
if(mPathFinderMap.isBlocked(pFinalPosition.getTileColumn(), pFinalPosition.getTileRow(), SceneManager.mWorldScene.getTouchLayer())){
pFinalPosition = getNextTile(lPlayerPosition, pFinalPosition);
}
// Determine the tile locations
int FromCol = lPlayerPosition.getTileColumn();
int FromRow = lPlayerPosition.getTileRow();
int ToCol = pFinalPosition.getTileColumn();
int ToRow = pFinalPosition.getTileRow();
// Find the path. This needs to be refreshed
AStarPath = mAStarPathFinder.findPath(MAX_SEARCH_DEPTH, mPathFinderMap, 0, 0, mTiledMap.getTileColumns()-1, mTiledMap.getTileRows()-1, SceneManager.mWorldScene.getTouchLayer(),
FromCol, FromRow, ToCol, ToRow, false, mHeuristic, mCostCallback);
//Loads the path with the astar specifications
Path lPlayerPath = loadPathFound();
//Moves the sprite along the path
if(lPlayerPath != null){
moveSprite(lPlayerPath);
}else{
//If the path is still null after the path manipulation then the path is finished
mHasFinishedPath = true;
mWaypointIndex = 0;
//mPlayerSprite.stopAnimation();
//AStarPath = null;
}
}
The TMXmapLoader does the rest:
//Get the collision, ext, and changing tiles from the object sets on the map
mCollideTiles = this.getObjectGroupPropertyTiles("COLLIDE", TMXGroupObjects);
mExitTiles = this.getObjectPropertyTiles("EXIT", mTMXObjects);
mChangingTiles = this.getObjectGroupPropertyTiles("CHANGE", TMXGroupObjects);
...
public ArrayList<TMXTile> getCollideTiles(){
return mCollideTiles;
}
...
public ArrayList<TMXTile> getObjectGroupPropertyTiles(String pName, final int pLayer, ArrayList<TMXObjectGroup> pTMXObjectGroups){
ArrayList<TMXTile> ObjectTile = new ArrayList<TMXTile>();
for (final TMXObjectGroup pObjectGroups : pTMXObjectGroups) {
// Iterates through the properties and assigns them to the new variable
for (final TMXObjectGroupProperty pGroupProperties : pObjectGroups.getTMXObjectGroupProperties()) {
//Sees if any of the elements have this condition
if (pGroupProperties.getName().contains(pName)) {
for (final TMXObject pObjectTiles : pObjectGroups.getTMXObjects()) {
int ObjectX = pObjectTiles.getX();
int ObjectY = pObjectTiles.getY();
// Gets the number of rows and columns in the object
int ObjectRows = pObjectTiles.getHeight() / WorldActivity.TILE_HEIGHT;
int ObjectColumns = pObjectTiles.getWidth() / WorldActivity.TILE_WIDTH;
for (int TileRow = 0; TileRow < ObjectRows; TileRow++) {
for (int TileColumn = 0; TileColumn < ObjectColumns; TileColumn++) {
float lObjectTileX = ObjectX + TileColumn * WorldActivity.TILE_WIDTH;
float lObjectTileY = ObjectY + TileRow * WorldActivity.TILE_HEIGHT;
ObjectTile.add(mTMXTiledMap.getTMXLayers().get(pLayer).getTMXTileAt(lObjectTileX, lObjectTileY));
}
}
}
}
}
}
return ObjectTile;
}
I'm not familiar with android development, but the error seems to indicate that PhysicsFactory hasn't been imported. Maybe try adding an import statement like this to the top of your file?
import org.anddev.andengine.extension.physics.box2d.PhysicsFactory;