here is my code for custom label renderer for label of doughnut pie chart, i get this exception and i dont know what to do, please help
exception is from line CategoricalDataPointdataPoint=(CategoricalDataPoint)relatedLabelNode;
and when i change it to CategoricalDataPoint dataPoint =
relatedLabelNode.JavaCast(); i get System.InvalidCastException: Unable to convert instance of type 'Com.Telerik.Widget.Chart.Engine.DataPoints.PieDataPoint' to type 'com/telerik/widget/chart/engine/dataPoints/CategoricalDataPoint'.
Here is my code
class CustomLabelRenderer : BaseLabelRenderer
{
private String labelFormat = "{0}";
private TextPaint paint = new TextPaint();
private Paint strokePaint = new Paint();
private Paint fillPaint = new Paint();
private float labelMargin = 10.0f;
private float labelPadding = 20.0f;
public CustomLabelRenderer(DoughnutSeries owner)
: base(owner)
{
this.strokePaint.SetStyle(Paint.Style.Stroke);
this.strokePaint.Color = Color.White;
this.strokePaint.StrokeWidth = 2;
this.fillPaint.Color = Color.ParseColor("#F5413F");
this.paint.TextSize = 35.0f;
this.paint.Color = Color.White;
}
public override void RenderLabel(Canvas canvas,
Com.Telerik.Widget.Chart.Engine.ElementTree.ChartNode relatedLabelNode)
{
CategoricalDataPoint dataPoint = (CategoricalDataPoint)relatedLabelNode;
RadRect dataPointSlot = dataPoint.LayoutSlot;
Double val = dataPoint.Value;
String labelText = String.Format(this.labelFormat, (int)val);
StaticLayout textInfo = this.CreateTextInfo(labelText, dataPoint);
this.RenderLabel(canvas, dataPointSlot, labelText, textInfo);
}
private StaticLayout CreateTextInfo(String labelText,
CategoricalDataPoint dataPoint)
{
return new StaticLayout(labelText,
0,
labelText.Length,
this.paint,
(int)Math.Round((float)dataPoint.LayoutSlot.Width),
Layout.Alignment.AlignCenter,
1.0f,
1.0f,
false);
}
private void RenderLabel(Canvas canvas, RadRect dataPointSlot,
String labelText, StaticLayout textBounds)
{
RectF labelBounds = new RectF();
float height = textBounds.Height + this.labelPadding * 2;
float top = (float)dataPointSlot.GetY() - this.labelMargin - height;
labelBounds.Set(
(float)dataPointSlot.GetX(),
top,
(float)dataPointSlot.Right,
top + height);
canvas.DrawRect(
labelBounds.Left,
labelBounds.Top,
labelBounds.Right,
labelBounds.Bottom,
this.fillPaint);
canvas.DrawRect(
labelBounds.Left,
labelBounds.Top,
labelBounds.Right,
labelBounds.Bottom,
this.strokePaint);
canvas.DrawText(
labelText,
(float)dataPointSlot.GetX() + (float)(dataPointSlot.Width / 2.0) -
textBounds.GetLineWidth(0) / 2.0f,
labelBounds.CenterY() + textBounds.GetLineBottom(0) -
textBounds.GetLineBaseline(0),
paint);
}
}
}
`
Categorical series data are only supported for cartesian (X/Y) chart type. Pie charts only support PieSeries, so for what you've described that chart node can only be a PieDataPoint. It can never be a CategoricalDataPoint.
Supported series types for Pie charts: http://docs.telerik.com/devtools/xamarin/controls/chart/types/chart-types-pie-chart#supported-series
And here is what cartesion charts support: http://docs.telerik.com/devtools/xamarin/controls/chart/types/chart-types-cartesian-chart#supported-series
http://docs.telerik.com/devtools/xamarin/controls/chart/chart-overview
Related
I'm trying to display text on the left of the LimitLine like this:
However these are the only options I'm getting for setting the position of the Label for limit line.
I'm using LimitLine.LimitLabelPosition.LEFT_TOP and it only displays the Label above the Limit line.
YAxis leftAxis = mChart.getAxisLeft();
LimitLine minimumLimit = new LimitLine(50f, "Minimum Limit");
minimumLimit.setLineWidth(0.5f);
minimumLimit.setTextColor(ContextCompat.getColor(this, R.color.white_60_opacity));
minimumLimit.setLabelPosition(LimitLine.LimitLabelPosition.LEFT_TOP);
leftAxis.addLimitLine(minimumLimit);
How do I display the LimitLine's Label to the left of the LimitLine?
Edit:
I have also tried used the methods .setXOffset(50f) and .setYOffset(50f) but this only shifts the position of the label and not the line minimumLimit.
You can achieve this by using a custom YAxisRenderer with a little modification of the override method public void renderLimitLines(Canvas c).
The modifications needed for this purpose are:
1.To calculate the label width of each limit line to be able to move the limit line to the correct x position like below:
limitLinePath.moveTo(mViewPortHandler.contentLeft()+getLabelTextWidth(l), pts[1]);
2.To draw the label to the new x,y position something like this:
c.drawText(label, mViewPortHandler.contentLeft() + xOffset, pts[1]+l.getYOffset(), mLimitLinePaint);
Below is a custom MyYAxisRenderer containing the above modifications:
public class MyYAxisRenderer extends YAxisRenderer {
private final Paint textPaint;
public MyYAxisRenderer(ViewPortHandler viewPortHandler, YAxis yAxis, Transformer trans) {
super(viewPortHandler, yAxis, trans);
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
}
#Override
public void renderLimitLines(Canvas c) {
List<LimitLine> limitLines = mYAxis.getLimitLines();
if (limitLines == null || limitLines.size() <= 0)
return;
float[] pts = mRenderLimitLinesBuffer;
pts[0] = 0;
pts[1] = 0;
Path limitLinePath = mRenderLimitLines;
limitLinePath.reset();
for (int i = 0; i < limitLines.size(); i++) {
LimitLine l = limitLines.get(i);
if (!l.isEnabled())
continue;
int clipRestoreCount = c.save();
mLimitLineClippingRect.set(mViewPortHandler.getContentRect());
mLimitLineClippingRect.inset(0.f, -l.getLineWidth());
c.clipRect(mLimitLineClippingRect);
mLimitLinePaint.setStyle(Paint.Style.STROKE);
mLimitLinePaint.setColor(l.getLineColor());
mLimitLinePaint.setStrokeWidth(l.getLineWidth());
mLimitLinePaint.setPathEffect(l.getDashPathEffect());
pts[1] = l.getLimit();
mTrans.pointValuesToPixel(pts);
limitLinePath.moveTo(mViewPortHandler.contentLeft()+getLabelTextWidth(l), pts[1]);
limitLinePath.lineTo(mViewPortHandler.contentRight(), pts[1]);
c.drawPath(limitLinePath, mLimitLinePaint);
limitLinePath.reset();
String label = l.getLabel();
// if drawing the limit-value label is enabled
if (label != null && !label.equals("")) {
mLimitLinePaint.setStyle(l.getTextStyle());
mLimitLinePaint.setPathEffect(null);
mLimitLinePaint.setColor(l.getTextColor());
mLimitLinePaint.setTypeface(l.getTypeface());
mLimitLinePaint.setStrokeWidth(0.5f);
mLimitLinePaint.setTextSize(l.getTextSize());
final float labelLineHeight = Utils.calcTextHeight(mLimitLinePaint, label);
float xOffset = getLimitLineXOffset(l);
float yOffset = l.getLineWidth() + labelLineHeight + l.getYOffset();
final LimitLine.LimitLabelPosition position = l.getLabelPosition();
//draw the label on the left in the same y position of the limit line
mLimitLinePaint.setTextAlign(Paint.Align.LEFT);
c.drawText(label,
mViewPortHandler.contentLeft() + xOffset,
pts[1]+l.getYOffset(), mLimitLinePaint);
}
c.restoreToCount(clipRestoreCount);
}
}
private float getLimitLineXOffset(LimitLine l){
return Utils.convertDpToPixel(4f) + l.getXOffset();
}
private float getLabelTextWidth(LimitLine l) {
String label = l.getLabel();
if (label != null && !label.equals("")) {
textPaint.setStyle(l.getTextStyle());
textPaint.setPathEffect(null);
textPaint.setColor(l.getTextColor());
textPaint.setTypeface(l.getTypeface());
textPaint.setStrokeWidth(0.5f);
textPaint.setTextSize(l.getTextSize());
int textWidth = Utils.calcTextWidth(textPaint, label);
float xOffset = getLimitLineXOffset(l);
return textWidth + (xOffset*2);
}
return 0;
}
}
In the above renderer i have added two helper functions one for the calculation of the label text width private float getLabelTextWidth(LimitLine l) for a specific limit line and one to get the x offset of each limit line private float getLimitLineXOffset(LimitLine l) which you can modify based on your needs.
And you can use the above Renderer like the below:
lineChart.setRendererLeftYAxis(new MyYAxisRenderer(lineChart.getViewPortHandler(), lineChart.getAxisLeft(), lineChart.getTransformer(YAxis.AxisDependency.LEFT)));
Result:
Note: This was tested with v3.1.0 ('com.github.PhilJay:MPAndroidChart:v3.1.0')
I'm working on an Android app utilizing xamarin and the oxyplot library. I ran into a problem where I cannot add multiple lines of text in TextAnnotation.
I tried the following options:
var sb = new StringBuilder();
sb.Append("Line1");
sb.AppendLine(); // which is equal to Append(Environment.NewLine);
sb.Append("\n");
sb.Append("\r\n");
sb.Append(System.Environment.NewLine);
sb.Append("Line2");
and added it like so to the TextAnnotation text object:
var textAnnotation1 = new TextAnnotation();
textAnnotation1.Text = sb.ToString();
textAnnotation1.Background = OxyColors.White;
textAnnotation1.TextColor = OxyColors.Black;
textAnnotation1.FontSize = 18;
textAnnotation1.TextPosition = new DataPoint(4,_vericalLineYaxis);
plotModel.Annotations.Add(textAnnotation1);
but all to avail.
My goal is to have the text appear like so:
Line 1
Line 2
Currently it's appearing as:
Line 1 Line2
Any help would be much appreciated.
Multi-line Annotations is not currently supported on the Android platform.
OxyPlot is invoking Android.Canvas.DrawText and that function does not support text wrapping, it is a fairly low-level primitive drawing routine.
Google's Doc: public void drawText (String text, float x, float y, Paint paint)
If you feel like mod'ing the source, this could be done by using a static layout vs. the current canvas.DrawText.
Something like this would get you started (but is untested):
public void DrawText (string text, float x, float y, Paint paint)
{
TextPaint textPaint = new TextPaint();
StaticLayout textLayout = new StaticLayout(text, textPaint, canvas.getWidth(), Alignment.ALIGN_NORMAL, 1.0f, 0.0f, False);
canvas.Save();
canvas.Translate(x, y);
textLayout.Draw(canvas);
canvas.Restore();
}
FYI: Oxyplot's SVG renderer manually handles multi-line text by string splitting on "\r\n" and rendering a separate element for each line so the same thing could be done for the Android instead of using a StaticLayout (slower performance wise, but easy to mod/test):
var lines = Regex.Split(text, "\r\n");
if (valign == VerticalAlignment.Bottom)
{
for (var i = lines.Length - 1; i >= 0; i--)
{
var line = lines[i];
var size = this.MeasureText(line, fontFamily, fontSize, fontWeight);
this.w.WriteText(p, line, c, fontFamily, fontSize, fontWeight, rotate, halign, valign);
p += new ScreenVector(Math.Sin(rotate / 180.0 * Math.PI) * size.Height, Math.Cos(rotate / 180.0 * Math.PI) * size.Height);
}
}
I have the following code that blows the app. Methods like drawLine work fine if they are directly inside "onDraw" but not if placed outside in a separate method as shown below. Does anyone have any idea what's wrong? The version with the method compiles ok but the app crashes when started.
Many thanks
Roberto
public class Painter extends View {
private static Canvas canvas;
private static Paint paintG = null;
public static int screenW; // screen width
public static int screenH; // screen height
private String tag = "Painter";
public Painter(Context context) {
super(context);
readDrawingData();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
screenW = canvas.getWidth(); // screen width
screenH = canvas.getHeight(); // screen height
Rect allScreen = new Rect(0, 0, screenW, screenH);
// works OK
paintG = new Paint();
paintG.setAntiAlias(true);
paintG.setStrokeWidth(2);
paintG.setStyle(Paint.Style.FILL);
paintG.setColor(0xFFf7f9d2);
canvas.drawRect(allScreen, paintG);
// paint circle/oval - works OK
double R = ((screenW > screenH) ? screenH : screenW) / 2 - 100;
double Z = R + 20;
double X = screenW /2;
double Y = screenH /2;
RectF oval1 = new RectF(Math.round(X-R), Math.round(Y-R),
Math.round(X+R), Math.round(Y+R));
paintG.setStyle(Paint.Style.STROKE);
paintG.setColor(0xffff0000);
canvas.drawOval(oval1, paintG);
// this code works OK here
paintG.setColor(0xffff0000);
if (i > 0) {
for (int j=0; j<i; j++) {
canvas.drawLine(x1[j], y1[j], x2[j], y2[j], paintG);
}
}
// calling this will crash app
paintDrawing();
}
// ************************************************************
void paintDrawing() {
// PROBLEM: same code as above - causes app to crash...
// when outside onDraw
paintG.setColor(0xffff0000);
if (i > 0) {
for (int j=0; j<i; j++) {
canvas.drawLine(x1[j], y1[j], x2[j], y2[j], paintG);
}
}
// ************************************************************
}
I am building an Android game where a ball is controlled and moved using the motion sensor.
There are some posts on how to draw an inverted circle like this one, but it is unusable as Android does not support BufferedImage.
I created the player using the codes below
public class Player extends Task {
private final static float MAX_SPEED = 20;
private final static float SIZE = 16;
private Circle _cir = null;
private Paint _paint = new Paint();
private Vec _vec = new Vec();
private Vec _sensorVec = new Vec();
public Player(){
_cir = new Circle( 15, 15, SIZE ); //15,15 is the initial x,y coordinates
}
public final Circle getPt(){
return _cir;
}
private void setVec(){
float x = -AcSensor.Inst().getX()*2;
float y = AcSensor.Inst().getY()*2;
_sensorVec._x = x < 0 ? -x*x : x*x;
_sensorVec._y = y < 0 ? -y*y : y*y;
_sensorVec.setLengthCap(MAX_SPEED);
_vec.blend( _sensorVec, 0.05f );
}
private void Move(){
_cir._x += _vec._x;
_cir._y += _vec._y;
}
#Override
public boolean onUpdate(){
setVec();
Move();
return true;
}
#Override
public void onDraw( Canvas c ){
c.drawCircle(_cir._x, _cir._y, _cir._r, _paint);
}
}
Question is, how to create an inverted circle around the player so that the player only sees a limited distance while the outer part is filled with black color? For example, something like this:
.
I'm trying to do something like this, but I have a little bit of flexibility with how it looks. Essentially either a pie chart with only part of the pie filled (and the rest left blank), or some sort of dial chart.
It would also be relatively easy to use a polar graph to draw two arrows, one at 0 degrees and one at -92 degrees, but I can't find any libraries that will let you do this for Android. I do need it to make 0 degrees actually look like 0 polar degrees.
I've used an AChartEngine DialChart and managed to get something close, but I can't figure out how to get the labels to show up for each arrow. I've tried renderer.setDisplayValues(true); and series.setDisplayChartValues(true);
but it won't show the values for my two arrows, so I'm not sure if it's even possible with a DialChart. I realize that if I showed labels for the dial in the background, my users wouldn't need to have labels on the arrows, but I'm rotating the LinearLayout that the DialChart is added to in order to get 0 to look like 0 degrees in a polar graph. I am also struggling to hide labels for the dial in the background, despite using renderer.setShowLabels(false); and setting just about every other thing you can show to false. My hack is to set the label color to the background color, but if there is a better way to do it, please let me know.
Here is my code for the DialChart.
CategorySeries category = new CategorySeries("Angle");
category.add("Extension", 0);
category.add("Flexion", 90);
renderer = new DialRenderer();
renderer.setLabelsColor(getActivity().getResources().getColor(R.color.background));
renderer.setInScroll(true);
renderer.setDisplayValues(true);
renderer.setShowLegend(false);
renderer.setShowAxes(false);
renderer.setShowLabels(false);
renderer.setShowGrid(false);
renderer.setMargins(new int[] {20, 30, 15, 0});
renderer.setVisualTypes(new DialRenderer.Type[] {Type.ARROW, Type.ARROW});
renderer.setMinValue(-20);
renderer.setMaxValue(280);
renderer.setPanEnabled(false);
renderer.setZoomEnabled(false);
SimpleSeriesRenderer r = new SimpleSeriesRenderer();
series.setColor(getActivity().getResources().getColor(R.color.green));
series.setDisplayChartValues(true);
series.setChartValuesTextSize(30);
visualizationRenderer.addSeriesRenderer(r);
r = new SimpleSeriesRenderer();
series.setColor(getActivity().getResources().getColor(R.color.green));
series.setDisplayChartValues(true);
series.setChartValuesTextSize(30);
renderer.addSeriesRenderer(r);
visualization = ChartFactory.getDialChartView(getActivity(), category, renderer);
LinearLayout layout = (LinearLayout) this.getView().findViewById(R.id.sessions_visualization);
layout.addView(visualization);
layout.setRotation(220.0f);
I'm open to either modifying this code to get something that works, or other libraries that will help me accomplish what I'm trying to do. Thanks!
I'm answering my own question for anyone who wants to do something like this later.
You can create custom views in Android and draw whatever you want to display. There is good documentation here.
Here's a relevant code snippet. It's not perfect but it does the job.
public class AngleVisualization extends View {
private Paint textPaint;
private Paint arcPaint;
private Paint linePaint;
RectF oval;
private float extension;
private float flexion;
private int textColor;
private int arcColor;
private float extensionLabelX;
private float extensionLabelY;
private float flexionLabelX;
private float flexionLabelY;
private Rect extensionBounds = new Rect();
public AngleVisualization(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.AngleVisualization,
0, 0);
try {
extension = a.getFloat(R.styleable.AngleVisualization_extensionValue, 0);
flexion = a.getFloat(R.styleable.AngleVisualization_flexionValue, 0);
textColor = a.getColor(R.styleable.AngleVisualization_textColor, Color.BLACK);
arcColor = a.getColor(R.styleable.AngleVisualization_arcColor, context.getResources().getColor(R.color.green));
extensionLabelX = a.getDimension(R.styleable.AngleVisualization_extensionLabelX, 190);
extensionLabelY = a.getDimension(R.styleable.AngleVisualization_extensionLabelY, 150);
flexionLabelX = a.getDimension(R.styleable.AngleVisualization_flexionLabelX, 50);
extensionLabelY = a.getDimension(R.styleable.AngleVisualization_flexionLabelY, 190);
} finally {
a.recycle();
}
oval = new RectF();
init();
}
private void init() {
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setColor(textColor);
textPaint.setTextSize(30);
arcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
arcPaint.setColor(arcColor);
linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
linePaint.setColor(arcColor);
linePaint.setStrokeWidth(3);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
String extensionString = decimalFormat.format(extension) + "˚";
textPaint.getTextBounds(extensionString, 0, extensionString.length(), extensionBounds);
canvas.drawArc(oval, extension, flexion - extension, true, arcPaint);
canvas.drawLine(0.0f, extensionBounds.height(), oval.right / 2, extensionBounds.height(), linePaint);
canvas.drawText(extensionString, extensionLabelX, extensionLabelY, textPaint);
canvas.drawText(decimalFormat.format(flexion) + "˚", flexionLabelX, flexionLabelY, textPaint);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
// Account for padding
float xpad = (float)(getPaddingLeft() + getPaddingRight());
float ypad = (float)(getPaddingTop() + getPaddingBottom());
float ww = (float)w - xpad;
float hh = (float)h - ypad;
String extensionString = decimalFormat.format(extension) + "˚";
textPaint.getTextBounds(extensionString, 0, extensionString.length(), extensionBounds);
float diameter = Math.min(ww, (hh - extensionBounds.height()) * 2.0f) - extensionBounds.height();
oval = new RectF(
0,
diameter / -2.0f,
diameter,
diameter / 2.0f);
oval.offsetTo(getPaddingLeft(), getPaddingTop() - diameter / 2.0f + extensionBounds.height());
flexionLabelY = diameter / 2.0f + extensionBounds.height();
flexionLabelX = 0;
extensionLabelY = extensionBounds.height();
extensionLabelX = ww / 2;
}
}