1 2 package com.github.mikephil.charting.utils; 3 4 import android.graphics.Matrix; 5 import android.graphics.Path; 6 import android.graphics.RectF; 7 8 import com.github.mikephil.charting.data.CandleEntry; 9 import com.github.mikephil.charting.data.Entry; 10 import com.github.mikephil.charting.interfaces.datasets.IBubbleDataSet; 11 import com.github.mikephil.charting.interfaces.datasets.ICandleDataSet; 12 import com.github.mikephil.charting.interfaces.datasets.ILineDataSet; 13 import com.github.mikephil.charting.interfaces.datasets.IScatterDataSet; 14 15 import java.util.List; 16 17 /** 18 * Transformer class that contains all matrices and is responsible for 19 * transforming values into pixels on the screen and backwards. 20 * 21 * @author Philipp Jahoda 22 */ 23 public class Transformer { 24 25 /** 26 * matrix to map the values to the screen pixels 27 */ 28 protected Matrix mMatrixValueToPx = new Matrix(); 29 30 /** 31 * matrix for handling the different offsets of the chart 32 */ 33 protected Matrix mMatrixOffset = new Matrix(); 34 35 protected ViewPortHandler mViewPortHandler; 36 Transformer(ViewPortHandler viewPortHandler)37 public Transformer(ViewPortHandler viewPortHandler) { 38 this.mViewPortHandler = viewPortHandler; 39 } 40 41 /** 42 * Prepares the matrix that transforms values to pixels. Calculates the 43 * scale factors from the charts size and offsets. 44 * 45 * @param xChartMin 46 * @param deltaX 47 * @param deltaY 48 * @param yChartMin 49 */ prepareMatrixValuePx(float xChartMin, float deltaX, float deltaY, float yChartMin)50 public void prepareMatrixValuePx(float xChartMin, float deltaX, float deltaY, float yChartMin) { 51 52 float scaleX = (float) ((mViewPortHandler.contentWidth()) / deltaX); 53 float scaleY = (float) ((mViewPortHandler.contentHeight()) / deltaY); 54 55 if (Float.isInfinite(scaleX)) { 56 scaleX = 0; 57 } 58 if (Float.isInfinite(scaleY)) { 59 scaleY = 0; 60 } 61 62 // setup all matrices 63 mMatrixValueToPx.reset(); 64 mMatrixValueToPx.postTranslate(-xChartMin, -yChartMin); 65 mMatrixValueToPx.postScale(scaleX, -scaleY); 66 } 67 68 /** 69 * Prepares the matrix that contains all offsets. 70 * 71 * @param inverted 72 */ prepareMatrixOffset(boolean inverted)73 public void prepareMatrixOffset(boolean inverted) { 74 75 mMatrixOffset.reset(); 76 77 // offset.postTranslate(mOffsetLeft, getHeight() - mOffsetBottom); 78 79 if (!inverted) 80 mMatrixOffset.postTranslate(mViewPortHandler.offsetLeft(), 81 mViewPortHandler.getChartHeight() - mViewPortHandler.offsetBottom()); 82 else { 83 mMatrixOffset 84 .setTranslate(mViewPortHandler.offsetLeft(), -mViewPortHandler.offsetTop()); 85 mMatrixOffset.postScale(1.0f, -1.0f); 86 } 87 } 88 89 protected float[] valuePointsForGenerateTransformedValuesScatter = new float[1]; 90 91 /** 92 * Transforms an List of Entry into a float array containing the x and 93 * y values transformed with all matrices for the SCATTERCHART. 94 * 95 * @param data 96 * @return 97 */ generateTransformedValuesScatter(IScatterDataSet data, float phaseX, float phaseY, int from, int to)98 public float[] generateTransformedValuesScatter(IScatterDataSet data, float phaseX, 99 float phaseY, int from, int to) { 100 101 final int count = (int) ((to - from) * phaseX + 1) * 2; 102 103 if (valuePointsForGenerateTransformedValuesScatter.length != count) { 104 valuePointsForGenerateTransformedValuesScatter = new float[count]; 105 } 106 float[] valuePoints = valuePointsForGenerateTransformedValuesScatter; 107 108 for (int j = 0; j < count; j += 2) { 109 110 Entry e = data.getEntryForIndex(j / 2 + from); 111 112 if (e != null) { 113 valuePoints[j] = e.getX(); 114 valuePoints[j + 1] = e.getY() * phaseY; 115 } else { 116 valuePoints[j] = 0; 117 valuePoints[j + 1] = 0; 118 } 119 } 120 121 getValueToPixelMatrix().mapPoints(valuePoints); 122 123 return valuePoints; 124 } 125 126 protected float[] valuePointsForGenerateTransformedValuesBubble = new float[1]; 127 128 /** 129 * Transforms an List of Entry into a float array containing the x and 130 * y values transformed with all matrices for the BUBBLECHART. 131 * 132 * @param data 133 * @return 134 */ generateTransformedValuesBubble(IBubbleDataSet data, float phaseY, int from, int to)135 public float[] generateTransformedValuesBubble(IBubbleDataSet data, float phaseY, int from, int to) { 136 137 final int count = (to - from + 1) * 2; // (int) Math.ceil((to - from) * phaseX) * 2; 138 139 if (valuePointsForGenerateTransformedValuesBubble.length != count) { 140 valuePointsForGenerateTransformedValuesBubble = new float[count]; 141 } 142 float[] valuePoints = valuePointsForGenerateTransformedValuesBubble; 143 144 for (int j = 0; j < count; j += 2) { 145 146 Entry e = data.getEntryForIndex(j / 2 + from); 147 148 if (e != null) { 149 valuePoints[j] = e.getX(); 150 valuePoints[j + 1] = e.getY() * phaseY; 151 } else { 152 valuePoints[j] = 0; 153 valuePoints[j + 1] = 0; 154 } 155 } 156 157 getValueToPixelMatrix().mapPoints(valuePoints); 158 159 return valuePoints; 160 } 161 162 protected float[] valuePointsForGenerateTransformedValuesLine = new float[1]; 163 164 /** 165 * Transforms an List of Entry into a float array containing the x and 166 * y values transformed with all matrices for the LINECHART. 167 * 168 * @param data 169 * @return 170 */ generateTransformedValuesLine(ILineDataSet data, float phaseX, float phaseY, int min, int max)171 public float[] generateTransformedValuesLine(ILineDataSet data, 172 float phaseX, float phaseY, 173 int min, int max) { 174 175 final int count = ((int) ((max - min) * phaseX) + 1) * 2; 176 177 if (valuePointsForGenerateTransformedValuesLine.length != count) { 178 valuePointsForGenerateTransformedValuesLine = new float[count]; 179 } 180 float[] valuePoints = valuePointsForGenerateTransformedValuesLine; 181 182 for (int j = 0; j < count; j += 2) { 183 184 Entry e = data.getEntryForIndex(j / 2 + min); 185 186 if (e != null) { 187 valuePoints[j] = e.getX(); 188 valuePoints[j + 1] = e.getY() * phaseY; 189 } else { 190 valuePoints[j] = 0; 191 valuePoints[j + 1] = 0; 192 } 193 } 194 195 getValueToPixelMatrix().mapPoints(valuePoints); 196 197 return valuePoints; 198 } 199 200 protected float[] valuePointsForGenerateTransformedValuesCandle = new float[1]; 201 202 /** 203 * Transforms an List of Entry into a float array containing the x and 204 * y values transformed with all matrices for the CANDLESTICKCHART. 205 * 206 * @param data 207 * @return 208 */ generateTransformedValuesCandle(ICandleDataSet data, float phaseX, float phaseY, int from, int to)209 public float[] generateTransformedValuesCandle(ICandleDataSet data, 210 float phaseX, float phaseY, int from, int to) { 211 212 final int count = (int) ((to - from) * phaseX + 1) * 2; 213 214 if (valuePointsForGenerateTransformedValuesCandle.length != count) { 215 valuePointsForGenerateTransformedValuesCandle = new float[count]; 216 } 217 float[] valuePoints = valuePointsForGenerateTransformedValuesCandle; 218 219 for (int j = 0; j < count; j += 2) { 220 221 CandleEntry e = data.getEntryForIndex(j / 2 + from); 222 223 if (e != null) { 224 valuePoints[j] = e.getX(); 225 valuePoints[j + 1] = e.getHigh() * phaseY; 226 } else { 227 valuePoints[j] = 0; 228 valuePoints[j + 1] = 0; 229 } 230 } 231 232 getValueToPixelMatrix().mapPoints(valuePoints); 233 234 return valuePoints; 235 } 236 237 /** 238 * transform a path with all the given matrices VERY IMPORTANT: keep order 239 * to value-touch-offset 240 * 241 * @param path 242 */ pathValueToPixel(Path path)243 public void pathValueToPixel(Path path) { 244 245 path.transform(mMatrixValueToPx); 246 path.transform(mViewPortHandler.getMatrixTouch()); 247 path.transform(mMatrixOffset); 248 } 249 250 /** 251 * Transforms multiple paths will all matrices. 252 * 253 * @param paths 254 */ pathValuesToPixel(List<Path> paths)255 public void pathValuesToPixel(List<Path> paths) { 256 257 for (int i = 0; i < paths.size(); i++) { 258 pathValueToPixel(paths.get(i)); 259 } 260 } 261 262 /** 263 * Transform an array of points with all matrices. VERY IMPORTANT: Keep 264 * matrix order "value-touch-offset" when transforming. 265 * 266 * @param pts 267 */ pointValuesToPixel(float[] pts)268 public void pointValuesToPixel(float[] pts) { 269 270 mMatrixValueToPx.mapPoints(pts); 271 mViewPortHandler.getMatrixTouch().mapPoints(pts); 272 mMatrixOffset.mapPoints(pts); 273 } 274 275 /** 276 * Transform a rectangle with all matrices. 277 * 278 * @param r 279 */ rectValueToPixel(RectF r)280 public void rectValueToPixel(RectF r) { 281 282 mMatrixValueToPx.mapRect(r); 283 mViewPortHandler.getMatrixTouch().mapRect(r); 284 mMatrixOffset.mapRect(r); 285 } 286 287 /** 288 * Transform a rectangle with all matrices with potential animation phases. 289 * 290 * @param r 291 * @param phaseY 292 */ rectToPixelPhase(RectF r, float phaseY)293 public void rectToPixelPhase(RectF r, float phaseY) { 294 295 // multiply the height of the rect with the phase 296 r.top *= phaseY; 297 r.bottom *= phaseY; 298 299 mMatrixValueToPx.mapRect(r); 300 mViewPortHandler.getMatrixTouch().mapRect(r); 301 mMatrixOffset.mapRect(r); 302 } 303 rectToPixelPhaseHorizontal(RectF r, float phaseY)304 public void rectToPixelPhaseHorizontal(RectF r, float phaseY) { 305 306 // multiply the height of the rect with the phase 307 r.left *= phaseY; 308 r.right *= phaseY; 309 310 mMatrixValueToPx.mapRect(r); 311 mViewPortHandler.getMatrixTouch().mapRect(r); 312 mMatrixOffset.mapRect(r); 313 } 314 315 /** 316 * Transform a rectangle with all matrices with potential animation phases. 317 * 318 * @param r 319 */ rectValueToPixelHorizontal(RectF r)320 public void rectValueToPixelHorizontal(RectF r) { 321 322 mMatrixValueToPx.mapRect(r); 323 mViewPortHandler.getMatrixTouch().mapRect(r); 324 mMatrixOffset.mapRect(r); 325 } 326 327 /** 328 * Transform a rectangle with all matrices with potential animation phases. 329 * 330 * @param r 331 * @param phaseY 332 */ rectValueToPixelHorizontal(RectF r, float phaseY)333 public void rectValueToPixelHorizontal(RectF r, float phaseY) { 334 335 // multiply the height of the rect with the phase 336 r.left *= phaseY; 337 r.right *= phaseY; 338 339 mMatrixValueToPx.mapRect(r); 340 mViewPortHandler.getMatrixTouch().mapRect(r); 341 mMatrixOffset.mapRect(r); 342 } 343 344 /** 345 * transforms multiple rects with all matrices 346 * 347 * @param rects 348 */ rectValuesToPixel(List<RectF> rects)349 public void rectValuesToPixel(List<RectF> rects) { 350 351 Matrix m = getValueToPixelMatrix(); 352 353 for (int i = 0; i < rects.size(); i++) 354 m.mapRect(rects.get(i)); 355 } 356 357 protected Matrix mPixelToValueMatrixBuffer = new Matrix(); 358 359 /** 360 * Transforms the given array of touch positions (pixels) (x, y, x, y, ...) 361 * into values on the chart. 362 * 363 * @param pixels 364 */ pixelsToValue(float[] pixels)365 public void pixelsToValue(float[] pixels) { 366 367 Matrix tmp = mPixelToValueMatrixBuffer; 368 tmp.reset(); 369 370 // invert all matrixes to convert back to the original value 371 mMatrixOffset.invert(tmp); 372 tmp.mapPoints(pixels); 373 374 mViewPortHandler.getMatrixTouch().invert(tmp); 375 tmp.mapPoints(pixels); 376 377 mMatrixValueToPx.invert(tmp); 378 tmp.mapPoints(pixels); 379 } 380 381 /** 382 * buffer for performance 383 */ 384 float[] ptsBuffer = new float[2]; 385 386 /** 387 * Returns a recyclable MPPointD instance. 388 * returns the x and y values in the chart at the given touch point 389 * (encapsulated in a MPPointD). This method transforms pixel coordinates to 390 * coordinates / values in the chart. This is the opposite method to 391 * getPixelForValues(...). 392 * 393 * @param x 394 * @param y 395 * @return 396 */ getValuesByTouchPoint(float x, float y)397 public MPPointD getValuesByTouchPoint(float x, float y) { 398 399 MPPointD result = MPPointD.getInstance(0, 0); 400 getValuesByTouchPoint(x, y, result); 401 return result; 402 } 403 getValuesByTouchPoint(float x, float y, MPPointD outputPoint)404 public void getValuesByTouchPoint(float x, float y, MPPointD outputPoint) { 405 406 ptsBuffer[0] = x; 407 ptsBuffer[1] = y; 408 409 pixelsToValue(ptsBuffer); 410 411 outputPoint.x = ptsBuffer[0]; 412 outputPoint.y = ptsBuffer[1]; 413 } 414 415 /** 416 * Returns a recyclable MPPointD instance. 417 * Returns the x and y coordinates (pixels) for a given x and y value in the chart. 418 * 419 * @param x 420 * @param y 421 * @return 422 */ getPixelForValues(float x, float y)423 public MPPointD getPixelForValues(float x, float y) { 424 425 ptsBuffer[0] = x; 426 ptsBuffer[1] = y; 427 428 pointValuesToPixel(ptsBuffer); 429 430 double xPx = ptsBuffer[0]; 431 double yPx = ptsBuffer[1]; 432 433 return MPPointD.getInstance(xPx, yPx); 434 } 435 getValueMatrix()436 public Matrix getValueMatrix() { 437 return mMatrixValueToPx; 438 } 439 getOffsetMatrix()440 public Matrix getOffsetMatrix() { 441 return mMatrixOffset; 442 } 443 444 private Matrix mMBuffer1 = new Matrix(); 445 getValueToPixelMatrix()446 public Matrix getValueToPixelMatrix() { 447 mMBuffer1.set(mMatrixValueToPx); 448 mMBuffer1.postConcat(mViewPortHandler.mMatrixTouch); 449 mMBuffer1.postConcat(mMatrixOffset); 450 return mMBuffer1; 451 } 452 453 private Matrix mMBuffer2 = new Matrix(); 454 getPixelToValueMatrix()455 public Matrix getPixelToValueMatrix() { 456 getValueToPixelMatrix().invert(mMBuffer2); 457 return mMBuffer2; 458 } 459 } 460