1 2 package com.github.mikephil.charting.renderer; 3 4 import android.graphics.Canvas; 5 import android.graphics.Color; 6 import android.graphics.Paint; 7 import android.graphics.Paint.Align; 8 import android.graphics.Path; 9 import android.graphics.RectF; 10 11 import com.github.mikephil.charting.components.LimitLine; 12 import com.github.mikephil.charting.components.XAxis; 13 import com.github.mikephil.charting.components.XAxis.XAxisPosition; 14 import com.github.mikephil.charting.utils.FSize; 15 import com.github.mikephil.charting.utils.MPPointD; 16 import com.github.mikephil.charting.utils.MPPointF; 17 import com.github.mikephil.charting.utils.Transformer; 18 import com.github.mikephil.charting.utils.Utils; 19 import com.github.mikephil.charting.utils.ViewPortHandler; 20 21 import java.util.List; 22 23 public class XAxisRenderer extends AxisRenderer { 24 25 protected XAxis mXAxis; 26 XAxisRenderer(ViewPortHandler viewPortHandler, XAxis xAxis, Transformer trans)27 public XAxisRenderer(ViewPortHandler viewPortHandler, XAxis xAxis, Transformer trans) { 28 super(viewPortHandler, trans, xAxis); 29 30 this.mXAxis = xAxis; 31 32 mAxisLabelPaint.setColor(Color.BLACK); 33 mAxisLabelPaint.setTextAlign(Align.CENTER); 34 mAxisLabelPaint.setTextSize(Utils.convertDpToPixel(10f)); 35 } 36 setupGridPaint()37 protected void setupGridPaint() { 38 mGridPaint.setColor(mXAxis.getGridColor()); 39 mGridPaint.setStrokeWidth(mXAxis.getGridLineWidth()); 40 mGridPaint.setPathEffect(mXAxis.getGridDashPathEffect()); 41 } 42 43 @Override computeAxis(float min, float max, boolean inverted)44 public void computeAxis(float min, float max, boolean inverted) { 45 46 // calculate the starting and entry point of the y-labels (depending on 47 // zoom / contentrect bounds) 48 if (mViewPortHandler.contentWidth() > 10 && !mViewPortHandler.isFullyZoomedOutX()) { 49 50 MPPointD p1 = mTrans.getValuesByTouchPoint(mViewPortHandler.contentLeft(), mViewPortHandler.contentTop()); 51 MPPointD p2 = mTrans.getValuesByTouchPoint(mViewPortHandler.contentRight(), mViewPortHandler.contentTop()); 52 53 if (inverted) { 54 55 min = (float) p2.x; 56 max = (float) p1.x; 57 } else { 58 59 min = (float) p1.x; 60 max = (float) p2.x; 61 } 62 63 MPPointD.recycleInstance(p1); 64 MPPointD.recycleInstance(p2); 65 } 66 67 computeAxisValues(min, max); 68 } 69 70 @Override computeAxisValues(float min, float max)71 protected void computeAxisValues(float min, float max) { 72 super.computeAxisValues(min, max); 73 74 computeSize(); 75 } 76 computeSize()77 protected void computeSize() { 78 79 String longest = mXAxis.getLongestLabel(); 80 81 mAxisLabelPaint.setTypeface(mXAxis.getTypeface()); 82 mAxisLabelPaint.setTextSize(mXAxis.getTextSize()); 83 84 final FSize labelSize = Utils.calcTextSize(mAxisLabelPaint, longest); 85 86 final float labelWidth = labelSize.width; 87 final float labelHeight = Utils.calcTextHeight(mAxisLabelPaint, "Q"); 88 89 final FSize labelRotatedSize = Utils.getSizeOfRotatedRectangleByDegrees( 90 labelWidth, 91 labelHeight, 92 mXAxis.getLabelRotationAngle()); 93 94 95 mXAxis.mLabelWidth = Math.round(labelWidth); 96 mXAxis.mLabelHeight = Math.round(labelHeight); 97 mXAxis.mLabelRotatedWidth = Math.round(labelRotatedSize.width); 98 mXAxis.mLabelRotatedHeight = Math.round(labelRotatedSize.height); 99 100 FSize.recycleInstance(labelRotatedSize); 101 FSize.recycleInstance(labelSize); 102 } 103 104 @Override renderAxisLabels(Canvas c)105 public void renderAxisLabels(Canvas c) { 106 107 if (!mXAxis.isEnabled() || !mXAxis.isDrawLabelsEnabled()) 108 return; 109 110 float yoffset = mXAxis.getYOffset(); 111 112 mAxisLabelPaint.setTypeface(mXAxis.getTypeface()); 113 mAxisLabelPaint.setTextSize(mXAxis.getTextSize()); 114 mAxisLabelPaint.setColor(mXAxis.getTextColor()); 115 116 MPPointF pointF = MPPointF.getInstance(0,0); 117 if (mXAxis.getPosition() == XAxisPosition.TOP) { 118 pointF.x = 0.5f; 119 pointF.y = 1.0f; 120 drawLabels(c, mViewPortHandler.contentTop() - yoffset, pointF); 121 122 } else if (mXAxis.getPosition() == XAxisPosition.TOP_INSIDE) { 123 pointF.x = 0.5f; 124 pointF.y = 1.0f; 125 drawLabels(c, mViewPortHandler.contentTop() + yoffset + mXAxis.mLabelRotatedHeight, pointF); 126 127 } else if (mXAxis.getPosition() == XAxisPosition.BOTTOM) { 128 pointF.x = 0.5f; 129 pointF.y = 0.0f; 130 drawLabels(c, mViewPortHandler.contentBottom() + yoffset, pointF); 131 132 } else if (mXAxis.getPosition() == XAxisPosition.BOTTOM_INSIDE) { 133 pointF.x = 0.5f; 134 pointF.y = 0.0f; 135 drawLabels(c, mViewPortHandler.contentBottom() - yoffset - mXAxis.mLabelRotatedHeight, pointF); 136 137 } else { // BOTH SIDED 138 pointF.x = 0.5f; 139 pointF.y = 1.0f; 140 drawLabels(c, mViewPortHandler.contentTop() - yoffset, pointF); 141 pointF.x = 0.5f; 142 pointF.y = 0.0f; 143 drawLabels(c, mViewPortHandler.contentBottom() + yoffset, pointF); 144 } 145 MPPointF.recycleInstance(pointF); 146 } 147 148 @Override renderAxisLine(Canvas c)149 public void renderAxisLine(Canvas c) { 150 151 if (!mXAxis.isDrawAxisLineEnabled() || !mXAxis.isEnabled()) 152 return; 153 154 mAxisLinePaint.setColor(mXAxis.getAxisLineColor()); 155 mAxisLinePaint.setStrokeWidth(mXAxis.getAxisLineWidth()); 156 mAxisLinePaint.setPathEffect(mXAxis.getAxisLineDashPathEffect()); 157 158 if (mXAxis.getPosition() == XAxisPosition.TOP 159 || mXAxis.getPosition() == XAxisPosition.TOP_INSIDE 160 || mXAxis.getPosition() == XAxisPosition.BOTH_SIDED) { 161 c.drawLine(mViewPortHandler.contentLeft(), 162 mViewPortHandler.contentTop(), mViewPortHandler.contentRight(), 163 mViewPortHandler.contentTop(), mAxisLinePaint); 164 } 165 166 if (mXAxis.getPosition() == XAxisPosition.BOTTOM 167 || mXAxis.getPosition() == XAxisPosition.BOTTOM_INSIDE 168 || mXAxis.getPosition() == XAxisPosition.BOTH_SIDED) { 169 c.drawLine(mViewPortHandler.contentLeft(), 170 mViewPortHandler.contentBottom(), mViewPortHandler.contentRight(), 171 mViewPortHandler.contentBottom(), mAxisLinePaint); 172 } 173 } 174 175 /** 176 * draws the x-labels on the specified y-position 177 * 178 * @param pos 179 */ drawLabels(Canvas c, float pos, MPPointF anchor)180 protected void drawLabels(Canvas c, float pos, MPPointF anchor) { 181 182 final float labelRotationAngleDegrees = mXAxis.getLabelRotationAngle(); 183 boolean centeringEnabled = mXAxis.isCenterAxisLabelsEnabled(); 184 185 float[] positions = new float[mXAxis.mEntryCount * 2]; 186 187 for (int i = 0; i < positions.length; i += 2) { 188 189 // only fill x values 190 if (centeringEnabled) { 191 positions[i] = mXAxis.mCenteredEntries[i / 2]; 192 } else { 193 positions[i] = mXAxis.mEntries[i / 2]; 194 } 195 } 196 197 mTrans.pointValuesToPixel(positions); 198 199 for (int i = 0; i < positions.length; i += 2) { 200 201 float x = positions[i]; 202 203 if (mViewPortHandler.isInBoundsX(x)) { 204 205 String label = mXAxis.getValueFormatter().getFormattedValue(mXAxis.mEntries[i / 2], mXAxis); 206 207 if (mXAxis.isAvoidFirstLastClippingEnabled()) { 208 209 // avoid clipping of the last 210 if (i / 2 == mXAxis.mEntryCount - 1 && mXAxis.mEntryCount > 1) { 211 float width = Utils.calcTextWidth(mAxisLabelPaint, label); 212 213 if (width > mViewPortHandler.offsetRight() * 2 214 && x + width > mViewPortHandler.getChartWidth()) 215 x -= width / 2; 216 217 // avoid clipping of the first 218 } else if (i == 0) { 219 220 float width = Utils.calcTextWidth(mAxisLabelPaint, label); 221 x += width / 2; 222 } 223 } 224 225 drawLabel(c, label, x, pos, anchor, labelRotationAngleDegrees); 226 } 227 } 228 } 229 drawLabel(Canvas c, String formattedLabel, float x, float y, MPPointF anchor, float angleDegrees)230 protected void drawLabel(Canvas c, String formattedLabel, float x, float y, MPPointF anchor, float angleDegrees) { 231 Utils.drawXAxisValue(c, formattedLabel, x, y, mAxisLabelPaint, anchor, angleDegrees); 232 } 233 protected Path mRenderGridLinesPath = new Path(); 234 protected float[] mRenderGridLinesBuffer = new float[2]; 235 @Override renderGridLines(Canvas c)236 public void renderGridLines(Canvas c) { 237 238 if (!mXAxis.isDrawGridLinesEnabled() || !mXAxis.isEnabled()) 239 return; 240 241 int clipRestoreCount = c.save(); 242 c.clipRect(getGridClippingRect()); 243 244 if(mRenderGridLinesBuffer.length != mAxis.mEntryCount * 2){ 245 mRenderGridLinesBuffer = new float[mXAxis.mEntryCount * 2]; 246 } 247 float[] positions = mRenderGridLinesBuffer; 248 249 for (int i = 0; i < positions.length; i += 2) { 250 positions[i] = mXAxis.mEntries[i / 2]; 251 positions[i + 1] = mXAxis.mEntries[i / 2]; 252 } 253 254 mTrans.pointValuesToPixel(positions); 255 256 setupGridPaint(); 257 258 Path gridLinePath = mRenderGridLinesPath; 259 gridLinePath.reset(); 260 261 for (int i = 0; i < positions.length; i += 2) { 262 263 drawGridLine(c, positions[i], positions[i + 1], gridLinePath); 264 } 265 266 c.restoreToCount(clipRestoreCount); 267 } 268 269 protected RectF mGridClippingRect = new RectF(); 270 getGridClippingRect()271 public RectF getGridClippingRect() { 272 mGridClippingRect.set(mViewPortHandler.getContentRect()); 273 mGridClippingRect.inset(-mAxis.getGridLineWidth(), 0.f); 274 return mGridClippingRect; 275 } 276 277 /** 278 * Draws the grid line at the specified position using the provided path. 279 * 280 * @param c 281 * @param x 282 * @param y 283 * @param gridLinePath 284 */ drawGridLine(Canvas c, float x, float y, Path gridLinePath)285 protected void drawGridLine(Canvas c, float x, float y, Path gridLinePath) { 286 287 gridLinePath.moveTo(x, mViewPortHandler.contentBottom()); 288 gridLinePath.lineTo(x, mViewPortHandler.contentTop()); 289 290 // draw a path because lines don't support dashing on lower android versions 291 c.drawPath(gridLinePath, mGridPaint); 292 293 gridLinePath.reset(); 294 } 295 296 protected float[] mRenderLimitLinesBuffer = new float[2]; 297 protected RectF mLimitLineClippingRect = new RectF(); 298 299 /** 300 * Draws the LimitLines associated with this axis to the screen. 301 * 302 * @param c 303 */ 304 @Override renderLimitLines(Canvas c)305 public void renderLimitLines(Canvas c) { 306 307 List<LimitLine> limitLines = mXAxis.getLimitLines(); 308 309 if (limitLines == null || limitLines.size() <= 0) 310 return; 311 312 float[] position = mRenderLimitLinesBuffer; 313 position[0] = 0; 314 position[1] = 0; 315 316 for (int i = 0; i < limitLines.size(); i++) { 317 318 LimitLine l = limitLines.get(i); 319 320 if (!l.isEnabled()) 321 continue; 322 323 int clipRestoreCount = c.save(); 324 mLimitLineClippingRect.set(mViewPortHandler.getContentRect()); 325 mLimitLineClippingRect.inset(-l.getLineWidth(), 0.f); 326 c.clipRect(mLimitLineClippingRect); 327 328 position[0] = l.getLimit(); 329 position[1] = 0.f; 330 331 mTrans.pointValuesToPixel(position); 332 333 renderLimitLineLine(c, l, position); 334 renderLimitLineLabel(c, l, position, 2.f + l.getYOffset()); 335 336 c.restoreToCount(clipRestoreCount); 337 } 338 } 339 340 float[] mLimitLineSegmentsBuffer = new float[4]; 341 private Path mLimitLinePath = new Path(); 342 renderLimitLineLine(Canvas c, LimitLine limitLine, float[] position)343 public void renderLimitLineLine(Canvas c, LimitLine limitLine, float[] position) { 344 mLimitLineSegmentsBuffer[0] = position[0]; 345 mLimitLineSegmentsBuffer[1] = mViewPortHandler.contentTop(); 346 mLimitLineSegmentsBuffer[2] = position[0]; 347 mLimitLineSegmentsBuffer[3] = mViewPortHandler.contentBottom(); 348 349 mLimitLinePath.reset(); 350 mLimitLinePath.moveTo(mLimitLineSegmentsBuffer[0], mLimitLineSegmentsBuffer[1]); 351 mLimitLinePath.lineTo(mLimitLineSegmentsBuffer[2], mLimitLineSegmentsBuffer[3]); 352 353 mLimitLinePaint.setStyle(Paint.Style.STROKE); 354 mLimitLinePaint.setColor(limitLine.getLineColor()); 355 mLimitLinePaint.setStrokeWidth(limitLine.getLineWidth()); 356 mLimitLinePaint.setPathEffect(limitLine.getDashPathEffect()); 357 358 c.drawPath(mLimitLinePath, mLimitLinePaint); 359 } 360 renderLimitLineLabel(Canvas c, LimitLine limitLine, float[] position, float yOffset)361 public void renderLimitLineLabel(Canvas c, LimitLine limitLine, float[] position, float yOffset) { 362 String label = limitLine.getLabel(); 363 364 // if drawing the limit-value label is enabled 365 if (label != null && !label.equals("")) { 366 367 mLimitLinePaint.setStyle(limitLine.getTextStyle()); 368 mLimitLinePaint.setPathEffect(null); 369 mLimitLinePaint.setColor(limitLine.getTextColor()); 370 mLimitLinePaint.setStrokeWidth(0.5f); 371 mLimitLinePaint.setTextSize(limitLine.getTextSize()); 372 373 374 float xOffset = limitLine.getLineWidth() + limitLine.getXOffset(); 375 376 final LimitLine.LimitLabelPosition labelPosition = limitLine.getLabelPosition(); 377 378 if (labelPosition == LimitLine.LimitLabelPosition.RIGHT_TOP) { 379 380 final float labelLineHeight = Utils.calcTextHeight(mLimitLinePaint, label); 381 mLimitLinePaint.setTextAlign(Align.LEFT); 382 c.drawText(label, position[0] + xOffset, mViewPortHandler.contentTop() + yOffset + labelLineHeight, 383 mLimitLinePaint); 384 } else if (labelPosition == LimitLine.LimitLabelPosition.RIGHT_BOTTOM) { 385 386 mLimitLinePaint.setTextAlign(Align.LEFT); 387 c.drawText(label, position[0] + xOffset, mViewPortHandler.contentBottom() - yOffset, mLimitLinePaint); 388 } else if (labelPosition == LimitLine.LimitLabelPosition.LEFT_TOP) { 389 390 mLimitLinePaint.setTextAlign(Align.RIGHT); 391 final float labelLineHeight = Utils.calcTextHeight(mLimitLinePaint, label); 392 c.drawText(label, position[0] - xOffset, mViewPortHandler.contentTop() + yOffset + labelLineHeight, 393 mLimitLinePaint); 394 } else { 395 396 mLimitLinePaint.setTextAlign(Align.RIGHT); 397 c.drawText(label, position[0] - xOffset, mViewPortHandler.contentBottom() - yOffset, mLimitLinePaint); 398 } 399 } 400 } 401 } 402