NekoX/TMessagesProj/src/main/java/org/telegram/ui/Charts/PieChartView.java

567 lines
18 KiB
Java

package org.telegram.ui.Charts;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.os.Build;
import android.text.TextPaint;
import android.view.HapticFeedbackConstants;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.ui.Charts.data.ChartData;
import org.telegram.ui.Charts.data.StackLinearChartData;
import org.telegram.ui.Charts.view_data.ChartHorizontalLinesData;
import org.telegram.ui.Charts.view_data.LegendSignatureView;
import org.telegram.ui.Charts.view_data.LineViewData;
import org.telegram.ui.Charts.view_data.PieLegendView;
public class PieChartView extends StackLinearChartView<PieChartViewData> {
float[] values;
float[] darawingValuesPercentage;
float sum;
boolean isEmpty;
int currentSelection = -1;
RectF rectF = new RectF();
TextPaint textPaint;
float MIN_TEXT_SIZE = AndroidUtilities.dp(9);
float MAX_TEXT_SIZE = AndroidUtilities.dp(13);
String[] lookupTable = new String[101];
PieLegendView pieLegendView;
float emptyDataAlpha = 1f;
public PieChartView(Context context) {
super(context);
for (int i = 1; i <= 100; i++) {
lookupTable[i] = i + "%";
}
textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setColor(Color.WHITE);
textPaint.setTypeface(Typeface.create("sans-serif-medium", Typeface.NORMAL));
canCaptureChartSelection = true;
}
@Override
protected void drawChart(Canvas canvas) {
if (chartData == null) return;
int transitionAlpha = 255;
canvas.save();
if (transitionMode == TRANSITION_MODE_CHILD) {
transitionAlpha = (int) (transitionParams.progress * transitionParams.progress * 255);
canvas.scale(transitionParams.progress, transitionParams.progress,
chartArea.centerX(),
chartArea.centerY()
);
}
if (isEmpty) {
if (emptyDataAlpha != 0) {
emptyDataAlpha -= 0.12f;
if (emptyDataAlpha < 0) {
emptyDataAlpha = 0;
}
invalidate();
}
} else {
if (emptyDataAlpha != 1f) {
emptyDataAlpha += 0.12f;
if (emptyDataAlpha > 1f) {
emptyDataAlpha = 1f;
}
invalidate();
}
}
transitionAlpha = (int) (transitionAlpha * emptyDataAlpha);
float sc = 0.4f + emptyDataAlpha * 0.6f;
canvas.scale(sc, sc,
chartArea.centerX(),
chartArea.centerY()
);
int radius = (int) ((chartArea.width() > chartArea.height() ? chartArea.height() : chartArea.width()) * 0.45f);
rectF.set(
chartArea.centerX() - radius,
chartArea.centerY() + AndroidUtilities.dp(16) - radius,
chartArea.centerX() + radius,
chartArea.centerY() + AndroidUtilities.dp(16) + radius
);
float a = -90f;
float rText;
int n = lines.size();
float localSum = 0f;
for (int i = 0; i < n; i++) {
float v = lines.get(i).drawingPart * lines.get(i).alpha;
localSum += v;
}
if (localSum == 0) {
canvas.restore();
return;
}
for (int i = 0; i < n; i++) {
if (lines.get(i).alpha <= 0 && !lines.get(i).enabled) continue;
lines.get(i).paint.setAlpha(transitionAlpha);
float currentPercent = lines.get(i).drawingPart / localSum * lines.get(i).alpha;
darawingValuesPercentage[i] = currentPercent;
if (currentPercent == 0) {
continue;
}
canvas.save();
double textAngle = a + (currentPercent / 2f) * 360f;
if (lines.get(i).selectionA > 0f) {
float ai = INTERPOLATOR.getInterpolation(lines.get(i).selectionA);
canvas.translate(
(float) (Math.cos(Math.toRadians(textAngle)) * AndroidUtilities.dp(8) * ai),
(float) (Math.sin(Math.toRadians(textAngle)) * AndroidUtilities.dp(8) * ai)
);
}
lines.get(i).paint.setStyle(Paint.Style.FILL_AND_STROKE);
lines.get(i).paint.setStrokeWidth(1);
lines.get(i).paint.setAntiAlias(!USE_LINES);
canvas.drawArc(
rectF,
a,
(currentPercent) * 360f,
true,
lines.get(i).paint);
lines.get(i).paint.setStyle(Paint.Style.STROKE);
canvas.restore();
lines.get(i).paint.setAlpha(255);
a += currentPercent * 360f;
}
a = -90f;
for (int i = 0; i < n; i++) {
if (lines.get(i).alpha <= 0 && !lines.get(i).enabled) continue;
float currentPercent = (lines.get(i).drawingPart * lines.get(i).alpha / localSum);
canvas.save();
double textAngle = a + (currentPercent / 2f) * 360f;
if (lines.get(i).selectionA > 0f) {
float ai = INTERPOLATOR.getInterpolation(lines.get(i).selectionA);
canvas.translate(
(float) (Math.cos(Math.toRadians(textAngle)) * AndroidUtilities.dp(8) * ai),
(float) (Math.sin(Math.toRadians(textAngle)) * AndroidUtilities.dp(8) * ai)
);
}
int percent = (int) (100f * currentPercent);
if (currentPercent >= 0.02f && percent > 0 && percent <= 100) {
rText = (float) (rectF.width() * 0.42f * Math.sqrt(1f - currentPercent));
textPaint.setTextSize(MIN_TEXT_SIZE + currentPercent * MAX_TEXT_SIZE);
textPaint.setAlpha((int) (transitionAlpha * lines.get(i).alpha));
canvas.drawText(
lookupTable[percent],
(float) (rectF.centerX() + rText * Math.cos(Math.toRadians(textAngle))),
(float) (rectF.centerY() + rText * Math.sin(Math.toRadians(textAngle))) - ((textPaint.descent() + textPaint.ascent()) / 2),
textPaint);
}
canvas.restore();
lines.get(i).paint.setAlpha(255);
a += currentPercent * 360f;
}
canvas.restore();
}
@Override
protected void drawPickerChart(Canvas canvas) {
if (chartData != null) {
int n = chartData.xPercentage.length;
int nl = lines.size();
for (int k = 0; k < lines.size(); k++) {
LineViewData line = lines.get(k);
line.linesPathBottomSize = 0;
}
float p = (1f / chartData.xPercentage.length) * pickerWidth;
for (int i = 0; i < n; i++) {
float stackOffset = 0;
float xPoint = p / 2 + chartData.xPercentage[i] * (pickerWidth - p);
float sum = 0;
int drawingLinesCount = 0;
boolean allDisabled = true;
for (int k = 0; k < nl; k++) {
LineViewData line = lines.get(k);
if (!line.enabled && line.alpha == 0) continue;
float v = line.line.y[i] * line.alpha;
sum += v;
if (v > 0) {
drawingLinesCount++;
if (line.enabled) {
allDisabled = false;
}
}
}
for (int k = 0; k < nl; k++) {
LineViewData line = lines.get(k);
if (!line.enabled && line.alpha == 0) continue;
int[] y = line.line.y;
float yPercentage;
if (drawingLinesCount == 1) {
if (y[i] == 0) {
yPercentage = 0;
} else {
yPercentage = line.alpha;
}
} else {
if (sum == 0) {
yPercentage = 0;
} else if (allDisabled) {
yPercentage = (y[i] / sum) * line.alpha * line.alpha;
} else {
yPercentage = (y[i] / sum) * line.alpha;
}
}
float yPoint = (yPercentage) * (pikerHeight);
line.linesPath[line.linesPathBottomSize++] = xPoint;
line.linesPath[line.linesPathBottomSize++] = pikerHeight - yPoint - stackOffset;
line.linesPath[line.linesPathBottomSize++] = xPoint;
line.linesPath[line.linesPathBottomSize++] = pikerHeight - stackOffset;
stackOffset += yPoint;
}
}
for (int k = 0; k < nl; k++) {
LineViewData line = lines.get(k);
line.paint.setStrokeWidth(p);
line.paint.setAlpha(255);
line.paint.setAntiAlias(false);
canvas.drawLines(line.linesPath, 0, line.linesPathBottomSize, line.paint);
}
}
}
@Override
protected void drawBottomLine(Canvas canvas) {
}
@Override
protected void drawSelection(Canvas canvas) {
}
@Override
protected void drawHorizontalLines(Canvas canvas, ChartHorizontalLinesData a) {
}
@Override
protected void drawSignaturesToHorizontalLines(Canvas canvas, ChartHorizontalLinesData a) {
}
@Override
void drawBottomSignature(Canvas canvas) {
}
@Override
public void setData(StackLinearChartData chartData) {
super.setData(chartData);
if (chartData != null) {
values = new float[chartData.lines.size()];
darawingValuesPercentage = new float[chartData.lines.size()];
onPickerDataChanged(false, true, false);
}
}
@Override
public PieChartViewData createLineViewData(ChartData.Line line) {
return new PieChartViewData(line);
}
protected void selectXOnChart(int x, int y) {
if (chartData == null || isEmpty) return;
double theta = Math.atan2(chartArea.centerY() + AndroidUtilities.dp(16) - y, chartArea.centerX() - x);
float a = (float) (Math.toDegrees(theta) - 90);
if (a < 0) a += 360D;
a /= 360;
float p = 0;
int newSelection = -1;
float selectionStartA = 0f;
float selectionEndA = 0f;
for (int i = 0; i < lines.size(); i++) {
if (!lines.get(i).enabled && lines.get(i).alpha == 0) {
continue;
}
if (a > p && a < p + darawingValuesPercentage[i]) {
newSelection = i;
selectionStartA = p;
selectionEndA = p + darawingValuesPercentage[i];
break;
}
p += darawingValuesPercentage[i];
}
if (currentSelection != newSelection && newSelection >= 0) {
currentSelection = newSelection;
invalidate();
pieLegendView.setVisibility(VISIBLE);
LineViewData l = lines.get(newSelection);
pieLegendView.setData(l.line.name, (int) values[currentSelection], l.lineColor);
float r = rectF.width() / 2;
int xl = (int) Math.min(
rectF.centerX() + r * Math.cos(Math.toRadians((selectionEndA * 360f) - 90f)),
rectF.centerX() + r * Math.cos(Math.toRadians(((selectionStartA * 360f) - 90f)))
);
if (xl < 0) xl = 0;
int yl = (int) Math.min(
(rectF.centerY() + r * Math.sin(Math.toRadians((selectionStartA * 360f) - 90f))),
rectF.centerY() + r * Math.sin(Math.toRadians(((selectionEndA * 360f) - 90f)))
);
yl = (int) Math.min(rectF.centerY(), yl);
yl -= AndroidUtilities.dp(50);
// if (yl < 0) yl = 0;
pieLegendView.setTranslationX(xl);
pieLegendView.setTranslationY(yl);
boolean v = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
v = performHapticFeedback(HapticFeedbackConstants.TEXT_HANDLE_MOVE, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
}
if (!v) {
performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
}
}
moveLegend();
}
@Override
protected void onDraw(Canvas canvas) {
if (chartData != null) {
for (int i = 0; i < lines.size(); i++) {
if (i == currentSelection) {
if (lines.get(i).selectionA < 1f) {
lines.get(i).selectionA += 0.1f;
if (lines.get(i).selectionA > 1f) lines.get(i).selectionA = 1f;
invalidate();
}
} else {
if (lines.get(i).selectionA > 0) {
lines.get(i).selectionA -= 0.1f;
if (lines.get(i).selectionA < 0) lines.get(i).selectionA = 0;
invalidate();
}
}
}
}
super.onDraw(canvas);
}
protected void onActionUp() {
currentSelection = -1;
pieLegendView.setVisibility(GONE);
invalidate();
}
int oldW = 0;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (getMeasuredWidth() != oldW) {
oldW = getMeasuredWidth();
int r = (int) ((chartArea.width() > chartArea.height() ? chartArea.height() : chartArea.width()) * 0.45f);
MIN_TEXT_SIZE = r / 13;
MAX_TEXT_SIZE = r / 7;
}
}
public void updatePicker(ChartData chartData, long d) {
int n = chartData.x.length;
long startOfDay = d - d % 86400000L;
int startIndex = 0;
for (int i = 0; i < n; i++) {
if (startOfDay >= chartData.x[i]) startIndex = i;
}
float p;
if (chartData.xPercentage.length < 2) {
p = 0.5f;
} else {
p = 1f / chartData.x.length;
}
if (startIndex == 0) {
pickerDelegate.pickerStart = 0;
pickerDelegate.pickerEnd = p;
return;
}
if (startIndex >= chartData.x.length - 1) {
pickerDelegate.pickerStart = 1f - p;
pickerDelegate.pickerEnd = 1f;
return;
}
pickerDelegate.pickerStart = p * startIndex;
pickerDelegate.pickerEnd = pickerDelegate.pickerStart + p;
if (pickerDelegate.pickerEnd > 1f) {
pickerDelegate.pickerEnd = 1f;
}
onPickerDataChanged(true, true, false);
}
@Override
protected LegendSignatureView createLegendView() {
return pieLegendView = new PieLegendView(getContext());
}
int lastStartIndex = -1;
int lastEndIndex = -1;
@Override
public void onPickerDataChanged(boolean animated, boolean force, boolean useAnimator) {
super.onPickerDataChanged(animated, force, useAnimator);
if (chartData == null || chartData.xPercentage == null) {
return;
}
float startPercentage = pickerDelegate.pickerStart;
float endPercentage = pickerDelegate.pickerEnd;
updateCharValues(startPercentage, endPercentage, force);
}
private void updateCharValues(float startPercentage, float endPercentage, boolean force) {
if (values == null) {
return;
}
int n = chartData.xPercentage.length;
int nl = lines.size();
int startIndex = -1;
int endIndex = -1;
for (int j = 0; j < n; j++) {
if (chartData.xPercentage[j] >= startPercentage && startIndex == -1) {
startIndex = j;
}
if (chartData.xPercentage[j] <= endPercentage) {
endIndex = j;
}
}
if (endIndex < startIndex) {
startIndex = endIndex;
}
if (!force && lastEndIndex == endIndex && lastStartIndex == startIndex) {
return;
}
lastEndIndex = endIndex;
lastStartIndex = startIndex;
isEmpty = true;
sum = 0;
for (int i = 0; i < nl; i++) {
values[i] = 0;
}
for (int j = startIndex; j <= endIndex; j++) {
for (int i = 0; i < nl; i++) {
values[i] += chartData.lines.get(i).y[j];
sum += chartData.lines.get(i).y[j];
if (isEmpty && lines.get(i).enabled && chartData.lines.get(i).y[j] > 0) {
isEmpty = false;
}
}
}
if (!force) {
for (int i = 0; i < nl; i++) {
PieChartViewData line = lines.get(i);
if (line.animator != null) line.animator.cancel();
float animateTo;
if (sum == 0) {
animateTo = 0;
} else {
animateTo = values[i] / sum;
}
ValueAnimator animator = createAnimator(line.drawingPart, animateTo, (animation -> {
line.drawingPart = (float) animation.getAnimatedValue();
invalidate();
}));
line.animator = animator;
animator.start();
}
} else {
for (int i = 0; i < nl; i++) {
if (sum == 0) {
lines.get(i).drawingPart = 0;
} else {
lines.get(i).drawingPart = values[i] / sum;
}
}
}
}
@Override
public void onPickerJumpTo(float start, float end, boolean force) {
if (chartData == null) return;
if (force) {
updateCharValues(start, end, false);
} else {
updateIndexes();
invalidate();
}
}
}