package com.github.wuxudong.rncharts.charts; import android.content.res.ColorStateList; import android.os.Build; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableType; import com.facebook.react.uimanager.SimpleViewManager; import com.facebook.react.uimanager.annotations.ReactProp; import com.github.mikephil.charting.animation.Easing.EasingOption; import com.github.mikephil.charting.charts.Chart; import com.github.mikephil.charting.components.AxisBase; import com.github.mikephil.charting.components.Description; import com.github.mikephil.charting.components.Legend; import com.github.mikephil.charting.components.Legend.LegendForm; import com.github.mikephil.charting.components.Legend.LegendPosition; import com.github.mikephil.charting.components.LegendEntry; import com.github.mikephil.charting.components.LimitLine; import com.github.mikephil.charting.components.XAxis; import com.github.mikephil.charting.components.XAxis.XAxisPosition; import com.github.mikephil.charting.data.Entry; import com.github.mikephil.charting.formatter.IndexAxisValueFormatter; import com.github.mikephil.charting.formatter.LargeValueFormatter; import com.github.mikephil.charting.formatter.PercentFormatter; import com.github.wuxudong.rncharts.data.DataExtract; import com.github.wuxudong.rncharts.listener.RNOnChartValueSelectedListener; import com.github.wuxudong.rncharts.markers.RNRectangleMarkerView; import com.github.wuxudong.rncharts.utils.BridgeUtils; public abstract class ChartBaseManager<T extends Chart, U extends Entry> extends SimpleViewManager { abstract DataExtract getDataExtract(); /** * More details about legend customization: https://github.com/PhilJay/MPAndroidChart/wiki/Legend */ @ReactProp(name = "legend") public void setLegend(T chart, ReadableMap propMap) { Legend legend = chart.getLegend(); if (BridgeUtils.validate(propMap, ReadableType.Boolean, "enabled")) { legend.setEnabled(propMap.getBoolean("enabled")); } // Styling if (BridgeUtils.validate(propMap, ReadableType.Number, "textColor")) { legend.setTextColor(propMap.getInt("textColor")); } if (BridgeUtils.validate(propMap, ReadableType.Number, "textSize")) { legend.setTextSize((float) propMap.getDouble("textSize")); } // Wrapping / clipping avoidance if (BridgeUtils.validate(propMap, ReadableType.Boolean, "wordWrapEnabled")) { legend.setWordWrapEnabled(propMap.getBoolean("wordWrapEnabled")); } if (BridgeUtils.validate(propMap, ReadableType.Number, "maxSizePercent")) { legend.setMaxSizePercent((float) propMap.getDouble("maxSizePercent")); } // Customizing if (BridgeUtils.validate(propMap, ReadableType.String, "position")) { legend.setPosition(LegendPosition.valueOf(propMap.getString("position").toUpperCase())); } if (BridgeUtils.validate(propMap, ReadableType.String, "form")) { legend.setForm(LegendForm.valueOf(propMap.getString("form").toUpperCase())); } if (BridgeUtils.validate(propMap, ReadableType.Number, "formSize")) { legend.setFormSize((float) propMap.getDouble("formSize")); } if (BridgeUtils.validate(propMap, ReadableType.Number, "xEntrySpace")) { legend.setXEntrySpace((float) propMap.getDouble("xEntrySpace")); } if (BridgeUtils.validate(propMap, ReadableType.Number, "yEntrySpace")) { legend.setYEntrySpace((float) propMap.getDouble("yEntrySpace")); } if (BridgeUtils.validate(propMap, ReadableType.Number, "formToTextSpace")) { legend.setFormToTextSpace((float) propMap.getDouble("formToTextSpace")); } // Custom labels & colors if (BridgeUtils.validate(propMap, ReadableType.Map, "custom")) { ReadableMap customMap = propMap.getMap("custom"); if (BridgeUtils.validate(customMap, ReadableType.Array, "colors") && BridgeUtils.validate(customMap, ReadableType.Array, "labels")) { ReadableArray colorsArray = customMap.getArray("colors"); ReadableArray labelsArray = customMap.getArray("labels"); if (colorsArray.size() == labelsArray.size()) { // TODO null label should start a group // TODO -2 color should avoid drawing a form String[] labels = BridgeUtils.convertToStringArray(labelsArray); int[] colorsParsed = BridgeUtils.convertToIntArray(colorsArray); LegendEntry[] legendEntries = new LegendEntry[labels.length]; for (int i = 0; i < legendEntries.length; i++) { legendEntries[i] = new LegendEntry(); legendEntries[i].formColor = colorsParsed[i]; legendEntries[i].label = labels[i]; } legend.setCustom(legendEntries); } } } // TODO resetCustom function // TODO extra chart.invalidate(); // TODO is this necessary? Looks like enabled is not refreshing without it } @ReactProp(name = "logEnabled") public void setLogEnabled(Chart chart, boolean enabled) { chart.setLogEnabled(enabled); } @ReactProp(name = "chartBackgroundColor") public void setChartBackgroundColor(Chart chart, Integer color) { chart.setBackgroundColor(color); } @ReactProp(name = "chartDescription") public void setChartDescription(Chart chart, ReadableMap propMap) { Description description = new Description(); if (BridgeUtils.validate(propMap, ReadableType.String, "text")) { description.setText(propMap.getString("text")); } if (BridgeUtils.validate(propMap, ReadableType.Number, "textColor")) { description.setTextColor(propMap.getInt("textColor")); } if (BridgeUtils.validate(propMap, ReadableType.Number, "textSize")) { description.setTextSize((float) propMap.getDouble("textSize")); } if (BridgeUtils.validate(propMap, ReadableType.Number, "positionX") && BridgeUtils.validate(propMap, ReadableType.Number, "positionY")) { description.setPosition((float) propMap.getDouble("positionX"), (float) propMap.getDouble("positionY")); } chart.setDescription(description); } @ReactProp(name = "noDataText") public void setNoDataText(Chart chart, String noDataText) { chart.setNoDataText(noDataText); } @ReactProp(name = "touchEnabled") public void setTouchEnabled(Chart chart, boolean enabled) { chart.setTouchEnabled(enabled); } @ReactProp(name = "dragDecelerationEnabled") public void setDragDecelerationEnabled(Chart chart, boolean enabled) { chart.setDragDecelerationEnabled(enabled); } @ReactProp(name = "dragDecelerationFrictionCoef") public void setDragDecelerationFrictionCoef(Chart chart, float coef) { chart.setDragDecelerationFrictionCoef(coef); } /** * Animations docs: https://github.com/PhilJay/MPAndroidChart/wiki/Animations */ @ReactProp(name = "animation") public void setAnimation(Chart chart, ReadableMap propMap) { Integer durationX = null; Integer durationY = null; EasingOption easingX = EasingOption.Linear; EasingOption easingY = EasingOption.Linear; if (BridgeUtils.validate(propMap, ReadableType.Number, "durationX")) { durationX = propMap.getInt("durationX"); } if (BridgeUtils.validate(propMap, ReadableType.Number, "durationY")) { durationY = propMap.getInt("durationY"); } if (BridgeUtils.validate(propMap, ReadableType.String, "easingX")) { easingX = EasingOption.valueOf(propMap.getString("easingX")); } if (BridgeUtils.validate(propMap, ReadableType.String, "easingY")) { easingY = EasingOption.valueOf(propMap.getString("easingY")); } if (durationX != null && durationY != null) { chart.animateXY(durationX, durationY, easingX, easingY); } else if (durationX != null) { chart.animateX(durationX, easingX); } else if (durationY != null) { chart.animateY(durationY, easingY); } } /** * xAxis config details: https://github.com/PhilJay/MPAndroidChart/wiki/XAxis */ @ReactProp(name = "xAxis") public void setXAxis(Chart chart, ReadableMap propMap) { XAxis axis = chart.getXAxis(); setCommonAxisConfig(chart, axis, propMap); if (BridgeUtils.validate(propMap, ReadableType.Number, "labelRotationAngle")) { axis.setLabelRotationAngle((float) propMap.getDouble("labelRotationAngle")); } if (BridgeUtils.validate(propMap, ReadableType.Boolean, "avoidFirstLastClipping")) { axis.setAvoidFirstLastClipping(propMap.getBoolean("avoidFirstLastClipping")); } if (BridgeUtils.validate(propMap, ReadableType.String, "position")) { axis.setPosition(XAxisPosition.valueOf(propMap.getString("position"))); } } @ReactProp(name = "marker") public void setMarker(Chart chart, ReadableMap propMap) { if (!BridgeUtils.validate(propMap, ReadableType.Boolean, "enabled") || !propMap.getBoolean("enabled")) { chart.setMarker(null); return; } RNRectangleMarkerView marker = new RNRectangleMarkerView(chart.getContext()); marker.setChartView(chart); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && BridgeUtils.validate(propMap, ReadableType.Number, "markerColor")) { marker.getTvContent() .setBackgroundTintList( ColorStateList.valueOf(propMap.getInt("markerColor")) ); } if (BridgeUtils.validate(propMap, ReadableType.Number, "textColor")) { marker.getTvContent().setTextColor(propMap.getInt("textColor")); } if (BridgeUtils.validate(propMap, ReadableType.Number, "textSize")) { marker.getTvContent().setTextSize(propMap.getInt("textSize")); } chart.setMarker(marker); } /** * General axis config details: https://github.com/PhilJay/MPAndroidChart/wiki/The-Axis */ protected void setCommonAxisConfig(Chart chart, AxisBase axis, ReadableMap propMap) { // what is drawn if (BridgeUtils.validate(propMap, ReadableType.Boolean, "enabled")) { axis.setEnabled(propMap.getBoolean("enabled")); } if (BridgeUtils.validate(propMap, ReadableType.Boolean, "drawLabels")) { axis.setDrawLabels(propMap.getBoolean("drawLabels")); } if (BridgeUtils.validate(propMap, ReadableType.Boolean, "drawAxisLine")) { axis.setDrawAxisLine(propMap.getBoolean("drawAxisLine")); } if (BridgeUtils.validate(propMap, ReadableType.Boolean, "drawGridLines")) { axis.setDrawGridLines(propMap.getBoolean("drawGridLines")); } // style if (BridgeUtils.validate(propMap, ReadableType.Number, "textColor")) { axis.setTextColor(propMap.getInt("textColor")); } if (BridgeUtils.validate(propMap, ReadableType.Number, "textSize")) { axis.setTextSize((float) propMap.getDouble("textSize")); } if (BridgeUtils.validate(propMap, ReadableType.Number, "gridColor")) { axis.setGridColor(propMap.getInt("gridColor")); } if (BridgeUtils.validate(propMap, ReadableType.Number, "gridLineWidth")) { axis.setGridLineWidth((float) propMap.getDouble("gridLineWidth")); } if (BridgeUtils.validate(propMap, ReadableType.Number, "axisLineColor")) { axis.setAxisLineColor(propMap.getInt("axisLineColor")); } if (BridgeUtils.validate(propMap, ReadableType.Number, "axisLineWidth")) { axis.setAxisLineWidth((float) propMap.getDouble("axisLineWidth")); } if (BridgeUtils.validate(propMap, ReadableType.Map, "gridDashedLine")) { ReadableMap gridDashedLine = propMap.getMap("gridDashedLine"); float lineLength = 0; float spaceLength = 0; float phase = 0; if (BridgeUtils.validate(gridDashedLine, ReadableType.Number, "lineLength")) { lineLength = (float) gridDashedLine.getDouble("lineLength"); } if (BridgeUtils.validate(gridDashedLine, ReadableType.Number, "spaceLength")) { spaceLength = (float) gridDashedLine.getDouble("spaceLength"); } if (BridgeUtils.validate(gridDashedLine, ReadableType.Number, "phase")) { phase = (float) gridDashedLine.getDouble("phase"); } axis.enableGridDashedLine(lineLength, spaceLength, phase); } // limit lines if (BridgeUtils.validate(propMap, ReadableType.Array, "limitLines")) { ReadableArray limitLines = propMap.getArray("limitLines"); for (int i = 0; i < limitLines.size(); i++) { if (!ReadableType.Map.equals(limitLines.getType(i))) { continue; } ReadableMap limitLineMap = limitLines.getMap(i); if (BridgeUtils.validate(limitLineMap, ReadableType.Number, "limit")) { LimitLine limitLine = new LimitLine((float) limitLineMap.getDouble("limit")); if (BridgeUtils.validate(limitLineMap, ReadableType.String, "label")) { limitLine.setLabel(limitLineMap.getString("label")); } if (BridgeUtils.validate(limitLineMap, ReadableType.Number, "lineColor")) { limitLine.setLineColor(limitLineMap.getInt("lineColor")); } if (BridgeUtils.validate(limitLineMap, ReadableType.Number, "lineWidth")) { limitLine.setLineWidth((float) limitLineMap.getDouble("lineWidth")); } axis.addLimitLine(limitLine); } } } if (BridgeUtils.validate(propMap, ReadableType.Boolean, "drawLimitLinesBehindData")) { axis.setDrawLimitLinesBehindData(propMap.getBoolean("drawLimitLinesBehindData")); } if (BridgeUtils.validate(propMap, ReadableType.Number, "axisMaximum")) { axis.setAxisMaximum((float) propMap.getDouble("axisMaximum")); } if (BridgeUtils.validate(propMap, ReadableType.Number, "axisMinimum")) { axis.setAxisMinimum((float) propMap.getDouble("axisMinimum")); } if (BridgeUtils.validate(propMap, ReadableType.Number, "granularity")) { axis.setGranularity((float) propMap.getDouble("granularity")); } if (BridgeUtils.validate(propMap, ReadableType.Boolean, "granularityEnabled")) { axis.setGranularityEnabled(propMap.getBoolean("granularityEnabled")); } if (BridgeUtils.validate(propMap, ReadableType.Number, "labelCount")) { boolean labelCountForce = false; if (BridgeUtils.validate(propMap, ReadableType.Boolean, "labelCountForce")) { labelCountForce = propMap.getBoolean("labelCountForce"); } axis.setLabelCount(propMap.getInt("labelCount"), labelCountForce); } // formatting if (BridgeUtils.validate(propMap, ReadableType.String, "valueFormatter")) { String valueFormatter = propMap.getString("valueFormatter"); if ("largeValue".equals(valueFormatter)) { axis.setValueFormatter(new LargeValueFormatter()); } else if ("percent".equals(valueFormatter)) { axis.setValueFormatter(new PercentFormatter()); } else { axis.setValueFormatter(new CustomFormatter(valueFormatter)); } } else if (BridgeUtils.validate(propMap, ReadableType.Array, "valueFormatter")) { axis.setValueFormatter(new IndexAxisValueFormatter(BridgeUtils.convertToStringArray(propMap.getArray("valueFormatter")))); } if (BridgeUtils.validate(propMap, ReadableType.Boolean, "centerAxisLabels")) { axis.setCenterAxisLabels(propMap.getBoolean("centerAxisLabels")); } } /** * Dataset config details: https://github.com/PhilJay/MPAndroidChart/wiki/DataSet-classes-in-detail */ @ReactProp(name = "data") public void setData(Chart chart, ReadableMap propMap) { chart.setData(getDataExtract().extract(propMap)); chart.invalidate(); } }