com.wildplot.android.ankistats.Forecast.java Source code

Java tutorial

Introduction

Here is the source code for com.wildplot.android.ankistats.Forecast.java

Source

/****************************************************************************************
 * Copyright (c) 2011 Norbert Nagold <norbert.nagold@gmail.com>                         *
 * Copyright (c) 2014 Michael Goldbach <trashcutter@googlemail.com>                     *
 *                                                                                      *
 * This program is free software; you can redistribute it and/or modify it under        *
 * the terms of the GNU General Public License as published by the Free Software        *
 * Foundation; either version 3 of the License, or (at your option) any later           *
 * version.                                                                             *
 *                                                                                      *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
 * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
 *                                                                                      *
 * You should have received a copy of the GNU General Public License along with         *
 * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
 ****************************************************************************************/
package com.wildplot.android.ankistats;

import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.util.Log;
import android.widget.ImageView;
import com.wildplot.android.rendering.*;
import com.wildplot.android.rendering.graphics.*;
import com.wildplot.android.rendering.graphics.wrapper.BufferedImage;
import com.wildplot.android.rendering.graphics.wrapper.Color;
import com.wildplot.android.rendering.graphics.wrapper.Graphics2D;
import com.wildplot.android.rendering.graphics.wrapper.Rectangle;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.*;

/**
 * Created by mig on 06.07.2014.
 */
public class Forecast {
    private AnkiDb mAnkiDb;
    private ImageView mImageView;
    private CollectionData mCollectionData;

    private int mFrameThickness = 60;

    int mMaxCards = 0;
    int mMaxElements = 0;
    private int mType;
    private int mTitle;
    private boolean mBackwards;
    private int[] mValueLabels;
    private int[] mColors;
    private int[] mAxisTitles;
    private double[][] mSeriesList;
    private double barThickness = 0.6;
    private double mLastElement = 0;
    private int mZeroIndex = 0;

    public Forecast(AnkiDb ankiDb, ImageView imageView, CollectionData collectionData) {
        mAnkiDb = ankiDb;
        mImageView = imageView;
        mCollectionData = collectionData;
    }

    public Bitmap renderChart(int type) {
        calculateDue(type);
        int height = mImageView.getMeasuredHeight();
        int width = mImageView.getMeasuredWidth();

        Log.d(AnkiStatsApplication.TAG, "heigth: " + height + ", width: " + width + ", " + mImageView.getWidth());

        if (height <= 0 || width <= 0) {
            return null;
        }

        BufferedImage bufferedFrameImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = bufferedFrameImage.createGraphics();
        Rectangle rect = new Rectangle(width, height);
        g.setClip(rect);
        g.setColor(Color.BLACK);
        float textSize = AnkiStatsApplication.getInstance().getmStandardTextSize() * 0.75f;
        g.setFontSize(textSize);

        float FontHeigth = g.getFontMetrics().getHeight(true);
        mFrameThickness = Math.round(FontHeigth * 4.0f);
        //System.out.println("frame thickness: " + mFrameThickness);

        int end = 0;
        switch (mType) {
        case Utils.TYPE_MONTH:
            end = 31;
            break;
        case Utils.TYPE_YEAR:
            end = 52;
            break;
        default:
        case Utils.TYPE_LIFE:
            end = (int) mLastElement;
            break;

        }

        PlotSheet plotSheet = new PlotSheet(-0.5, end + 0.5, 0, mMaxCards * 1.1);
        double xTics = ticsCalcX(150, rect, 0, end);
        double yTics = ticsCalcY(150, rect, 0, mMaxCards);

        plotSheet.setFrameThickness(mFrameThickness);

        //no title because of tab title
        //plotSheet.setTitle(mImageView.getResources().getString(mTitle));

        XAxis xaxis = new XAxis(plotSheet, 0, xTics, xTics / 2.0);
        YAxis yaxis = new YAxis(plotSheet, 0, yTics, yTics / 2.0);
        xaxis.setOnFrame();
        xaxis.setName(mImageView.getResources().getStringArray(R.array.due_x_axis_title)[mAxisTitles[0]]);
        xaxis.setIntegerNumbering(true);
        yaxis.setIntegerNumbering(true);
        yaxis.setName(mImageView.getResources().getString(mAxisTitles[1]));
        yaxis.setOnFrame();

        double[][] bars = new double[2][];
        bars[0] = mSeriesList[0];
        bars[1] = mSeriesList[1];
        BarGraph barGraph = new BarGraph(plotSheet, barThickness, bars,
                new Color(mImageView.getResources().getColor(mColors[0])));
        barGraph.setFilling(true);
        barGraph.setName(mImageView.getResources().getString(mValueLabels[0]));
        //barGraph.setFillColor(Color.GREEN.darker());
        barGraph.setFillColor(new Color(mImageView.getResources().getColor(mColors[0])));
        double[][] bars2 = new double[2][];
        bars2[0] = mSeriesList[0];
        bars2[1] = mSeriesList[2];
        BarGraph barGraphMature = new BarGraph(plotSheet, barThickness, bars2,
                new Color(mImageView.getResources().getColor(mColors[1])));
        barGraphMature.setFilling(true);
        barGraphMature.setFillColor(new Color(mImageView.getResources().getColor(mColors[1])));
        barGraphMature.setName(mImageView.getResources().getString(mValueLabels[1]));

        double[][] cumulative = Utils.createCumulative(bars, mZeroIndex);
        PlotSheet hiddenPlotSheet = new PlotSheet(-0.5, end + 0.5, 0,
                cumulative[1][cumulative[1].length - 1] * 1.1); //for second y-axis

        Lines lines = new Lines(hiddenPlotSheet, cumulative, Color.black);
        lines.setSize(3f);
        lines.setName(mImageView.getResources().getString(R.string.stats_cumulative));
        lines.setShadow(5f, 3f, 3f, Color.BLACK);

        hiddenPlotSheet.setFrameThickness(mFrameThickness);
        double rightYtics = ticsCalc(150, rect, cumulative[1][cumulative[1].length - 1] * 1.1);
        YAxis rightYaxis = new YAxis(hiddenPlotSheet, 0, rightYtics, rightYtics / 2.0);
        rightYaxis.setIntegerNumbering(true);
        rightYaxis.setName(mImageView.getResources().getString(mAxisTitles[2]));
        rightYaxis.setOnRightSideFrame();

        int red = Color.LIGHT_GRAY.getRed();
        int green = Color.LIGHT_GRAY.getGreen();
        int blue = Color.LIGHT_GRAY.getBlue();

        Color newGridColor = new Color(red, green, blue, 222);

        XGrid xGrid = new XGrid(plotSheet, 0, 150);
        YGrid yGrid = new YGrid(plotSheet, 0, 150);

        xGrid.setColor(newGridColor);
        yGrid.setColor(newGridColor);
        plotSheet.setFontSize(textSize);

        plotSheet.addDrawable(barGraph);
        plotSheet.addDrawable(barGraphMature);
        plotSheet.addDrawable(lines);
        plotSheet.addDrawable(xaxis);
        plotSheet.addDrawable(yaxis);
        plotSheet.addDrawable(rightYaxis);
        plotSheet.addDrawable(xGrid);
        plotSheet.addDrawable(yGrid);
        plotSheet.paint(g);
        Bitmap bitmap = bufferedFrameImage.getBitmap();
        bitmap.prepareToDraw();
        return bitmap;
    }

    /**
     * Due and cumulative due
     * ***********************************************************************************************
     */
    private boolean calculateDue(int type) {
        mType = type;
        mBackwards = false;
        mTitle = R.string.stats_forecast;
        mValueLabels = new int[] { R.string.statistics_young, R.string.statistics_mature };
        mColors = new int[] { R.color.stats_young, R.color.stats_mature };
        mAxisTitles = new int[] { type, R.string.stats_cards, R.string.stats_cumulative_cards };
        int end = 0;
        int chunk = 0;
        switch (type) {
        case Utils.TYPE_MONTH:
            end = 31;
            chunk = 1;
            break;
        case Utils.TYPE_YEAR:
            end = 52;
            chunk = 7;
            break;
        case Utils.TYPE_LIFE:
            end = -1;
            chunk = 30;
            break;
        }
        String lim = "";// AND due - " + mCol.getSched().getToday() + " >= " + start; // leave this out in order to show
        // card too which were due the days before
        if (end != -1) {
            lim += " AND day <= " + end;
        }

        ArrayList<int[]> dues = new ArrayList<int[]>();
        Cursor cur = null;
        try {
            String query;
            query = "SELECT (due - " + mCollectionData.getToday() + ")/" + chunk + " AS day, " // day
                    + "count(), " // all cards
                    + "sum(CASE WHEN ivl >= 21 THEN 1 ELSE 0 END) " // mature cards
                    + "FROM cards WHERE did IN " + _limitWholeOnly() + " AND queue IN (2,3)" + lim
                    + " GROUP BY day ORDER BY day";
            //TODO remove:
            System.out.println("forecast query: " + query);
            cur = mAnkiDb.getDatabase().rawQuery(query, null);
            while (cur.moveToNext()) {
                dues.add(new int[] { cur.getInt(0), cur.getInt(1), cur.getInt(2) });
            }
        } finally {
            if (cur != null && !cur.isClosed()) {
                cur.close();
            }
        }
        // small adjustment for a proper chartbuilding with achartengine
        if (dues.size() == 0 || dues.get(0)[0] > 0) {
            dues.add(0, new int[] { 0, 0, 0 });
        }
        if (end == -1 && dues.size() < 2) {
            end = 31;
        }
        if (type != Utils.TYPE_LIFE && dues.get(dues.size() - 1)[0] < end) {
            dues.add(new int[] { end, 0, 0 });
        } else if (type == Utils.TYPE_LIFE && dues.size() < 2) {
            dues.add(new int[] { Math.max(12, dues.get(dues.size() - 1)[0] + 1), 0, 0 });
        }

        mSeriesList = new double[3][dues.size()];
        for (int i = 0; i < dues.size(); i++) {
            int[] data = dues.get(i);

            if (data[1] > mMaxCards)
                mMaxCards = data[1];

            mSeriesList[0][i] = data[0];
            mSeriesList[1][i] = data[1];
            mSeriesList[2][i] = data[2];
            if (data[0] > mLastElement)
                mLastElement = data[0];
            if (data[0] == 0) {
                mZeroIndex = i;
            }
        }
        mMaxElements = dues.size() - 1;
        return dues.size() > 0;
    }

    public double ticsCalcX(int pixelDistance, Rectangle field, int start, int end) {
        double deltaRange = end - start;
        int ticlimit = field.width / pixelDistance;
        double tics = Math.pow(10, (int) Math.log10(deltaRange / ticlimit));
        while (2.0 * (deltaRange / (tics)) <= ticlimit) {
            tics /= 2.0;
        }
        while ((deltaRange / (tics)) / 2 >= ticlimit) {
            tics *= 2.0;
        }
        return tics;
    }

    public double ticsCalcY(int pixelDistance, Rectangle field, int start, int end) {
        double deltaRange = end - start;
        int ticlimit = field.height / pixelDistance;
        double tics = Math.pow(10, (int) Math.log10(deltaRange / ticlimit));
        while (2.0 * (deltaRange / (tics)) <= ticlimit) {
            tics /= 2.0;
        }
        while ((deltaRange / (tics)) / 2 >= ticlimit) {
            tics *= 2.0;
        }
        return tics;
    }

    public double ticsCalc(int pixelDistance, Rectangle field, double deltaRange) {
        int ticlimit = field.height / pixelDistance;
        double tics = Math.pow(10, (int) Math.log10(deltaRange / ticlimit));
        while (2.0 * (deltaRange / (tics)) <= ticlimit) {
            tics /= 2.0;
        }
        while ((deltaRange / (tics)) / 2 >= ticlimit) {
            tics *= 2.0;
        }
        return tics;
    }

    /**
     * Daily cutoff ************************************************************* **********************************
     * This function uses GregorianCalendar so as to be sensitive to leap years, daylight savings, etc.
     */
    private String _limitWholeOnly() {

        ArrayList<Long> ids = new ArrayList<Long>();
        for (JSONObject d : mCollectionData.allDecks()) {
            try {
                ids.add(d.getLong("id"));
            } catch (JSONException e) {
                throw new RuntimeException(e);
            }
        }
        return Utils.ids2str(Utils.arrayList2array(ids));

    }

}