Android Open Source - AnkiStats Review Count






From Project

Back to project page AnkiStats.

License

The source code is released under:

GNU General Public License

If you think the Android project AnkiStats listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/****************************************************************************************
 * 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;
//from   w w w.ja v  a  2s  . c  o m
import android.database.Cursor;
import android.graphics.Bitmap;
import android.util.Log;
import android.widget.ImageView;
import com.wildplot.android.rendering.*;
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.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;

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

    private int mFrameThickness = 60;
    private int targetPixelDistanceBetweenTics = 150;

    private boolean mFoundLearnCards = false;
    private boolean mFoundCramCards = false;

    private int mMaxCards = 0;
    private 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 boolean mFoundRelearnCards;
    private double barThickness = 0.6;
    private double mLastElement = 0;
    private double mFirstElement = 0;
    private int mZeroIndex = 0;

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

    public Bitmap renderChart(int type, boolean reps){
        calculateDone(type, reps);

        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)mFirstElement;
                break;

        }
        Log.d(AnkiStatsApplication.TAG, "MType of PreviewCount: " + mType);


        int height = mImageView.getMeasuredHeight();
        int width = mImageView.getMeasuredWidth();

        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);

        PlotSheet plotSheet = new PlotSheet(end-0.5, 0 + 0.5, 0, mMaxCards*1.1);
        plotSheet.setFrameThickness(mFrameThickness);

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

        double xTics = ticsCalcX(targetPixelDistanceBetweenTics, rect, end, 0);
        double yTics = ticsCalcY(targetPixelDistanceBetweenTics, rect, 0 , mMaxCards);

        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 maxCumulative = 0;
        BarGraph[] barGraphs = new BarGraph[mSeriesList.length-1];
        double[][][] cumulatives = new double[mSeriesList.length-1][][];
        for(int i = 1; i< mSeriesList.length; i++){
            double[][] bars = new double[2][];
            bars[0] = mSeriesList[0];
            bars[1] = mSeriesList[i];

            barGraphs[i-1] = new BarGraph(plotSheet,barThickness, bars, new Color(mImageView.getResources().getColor(mColors[i-1])));
            barGraphs[i-1].setFilling(true);
            barGraphs[i-1].setName(mImageView.getResources().getString(mValueLabels[i-1]));
            //barGraph.setFillColor(Color.GREEN.darker());
            barGraphs[i-1].setFillColor(new Color(mImageView.getResources().getColor(mColors[i-1])));

            cumulatives[i-1] = Utils.createCumulative(bars);
            if(i > 1){
                for(int j = 0; j< cumulatives[i-2][1].length; j++){
                    cumulatives[i-2][1][j] -= cumulatives[i-1][1][j];
                }

                if(maxCumulative < cumulatives[i-2][1][cumulatives[i-2][1].length-1]){
                    maxCumulative = cumulatives[i-2][1][cumulatives[i-2][1].length-1];
                }
            }
            if(i == mSeriesList.length -1) {
                if (maxCumulative < cumulatives[i - 1][1][cumulatives[i - 1][1].length - 1]) {
                    maxCumulative = cumulatives[i - 1][1][cumulatives[i - 1][1].length - 1];
                }
            }


        }

        PlotSheet hiddenPlotSheet = new PlotSheet(end-0.5, 0 + 0.5, 0, maxCumulative*1.1);     //for second y-axis
        hiddenPlotSheet.setFrameThickness(mFrameThickness);

        Lines[] lineses = new Lines[mSeriesList.length-1];
        for(int i = 1; i< mSeriesList.length; i++){
            lineses[i-1] = new Lines(hiddenPlotSheet,cumulatives[i-1] ,new Color(mImageView.getResources().getColor(mColors[i-1])));
            lineses[i-1].setSize(3f);
            lineses[i-1].setShadow(5f, 3f, 3f, Color.BLACK);
            //No names to prevent double entries in legend:
            //lineses[i-1].setName(mImageView.getResources().getString(R.string.stats_cumulative));
        }

        double rightYtics = ticsCalc(targetPixelDistanceBetweenTics, rect,  maxCumulative*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, targetPixelDistanceBetweenTics);
        YGrid yGrid = new YGrid(plotSheet, 0, targetPixelDistanceBetweenTics);

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

        for(BarGraph barGraph : barGraphs){
            plotSheet.addDrawable(barGraph);
        }
        for(Lines lines : lineses){
            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;
    }

    public boolean calculateDone(int type, boolean reps) {
        mType = type;
        mBackwards = true;
        if (reps) {
            mTitle = R.string.stats_review_count;
            mAxisTitles = new int[] { type, R.string.stats_answers, R.string.stats_cumulative_answers };
        } else {
            mTitle = R.string.stats_review_time;
        }
        mValueLabels = new int[] { R.string.statistics_learn, R.string.statistics_relearn, R.string.statistics_young,
                R.string.statistics_mature, R.string.statistics_cram };
        mColors = new int[] { R.color.stats_learn, R.color.stats_relearn, R.color.stats_young, R.color.stats_mature,
                R.color.stats_cram };
        int num = 0;
        int chunk = 0;
        switch (type) {
            case Utils.TYPE_MONTH:
                num = 31;
                chunk = 1;
                break;
            case Utils.TYPE_YEAR:
                num = 52;
                chunk = 7;
                break;
            case Utils.TYPE_LIFE:
                num = -1;
                chunk = 30;
                break;
        }
        ArrayList<String> lims = new ArrayList<String>();
        if (num != -1) {
            lims.add("id > " + ((mCollectionData.getDayCutoff() - ((num + 1) * chunk * 86400)) * 1000));
        }
        String lim = _revlogLimitWholeOnly().replaceAll("[\\[\\]]", "");
        if (lim.length() > 0) {
            lims.add(lim);
        }
        if (lims.size() > 0) {
            lim = "WHERE ";
            while (lims.size() > 1) {
                lim += lims.remove(0) + " AND ";
            }
            lim += lims.remove(0);
        } else {
            lim = "";
        }
        String ti;
        String tf;
        if (!reps) {
            ti = "time/1000";
            if (mType == 0) {
                tf = "/60.0"; // minutes
                mAxisTitles = new int[] { type, R.string.stats_minutes, R.string.stats_cumulative_time_minutes };
            } else {
                tf = "/3600.0"; // hours
                mAxisTitles = new int[] { type, R.string.stats_hours, R.string.stats_cumulative_time_hours };
            }
        } else {
            ti = "1";
            tf = "";
        }
        ArrayList<double[]> list = new ArrayList<double[]>();
        Cursor cur = null;
        String query = "SELECT (cast((id/1000 - " + mCollectionData.getDayCutoff() + ") / 86400.0 AS INT))/"
                + chunk + " AS day, " + "sum(CASE WHEN type = 0 THEN " + ti + " ELSE 0 END)"
                + tf
                + ", " // lrn
                + "sum(CASE WHEN type = 1 AND lastIvl < 21 THEN " + ti + " ELSE 0 END)" + tf
                + ", " // yng
                + "sum(CASE WHEN type = 1 AND lastIvl >= 21 THEN " + ti + " ELSE 0 END)" + tf
                + ", " // mtr
                + "sum(CASE WHEN type = 2 THEN " + ti + " ELSE 0 END)" + tf + ", " // lapse
                + "sum(CASE WHEN type = 3 THEN " + ti + " ELSE 0 END)" + tf // cram
                + " FROM revlog " + lim + " GROUP BY day ORDER BY day";

        Log.d(AnkiStatsApplication.TAG, "ReviewCount query: " + query);

        try {
            cur = mAnkiDb
                    .getDatabase()
                    .rawQuery(
                            query, null);
            while (cur.moveToNext()) {
                list.add(new double[] { cur.getDouble(0), cur.getDouble(1), cur.getDouble(4), cur.getDouble(2),
                        cur.getDouble(3), cur.getDouble(5) });
            }
        } finally {
            if (cur != null && !cur.isClosed()) {
                cur.close();
            }
        }

        // small adjustment for a proper chartbuilding with achartengine
        if (type != Utils.TYPE_LIFE && (list.size() == 0 || list.get(0)[0] > -num)) {
            list.add(0, new double[] { -num, 0, 0, 0, 0, 0 });
        } else if (type == Utils.TYPE_LIFE && list.size() == 0) {
            list.add(0, new double[] { -12, 0, 0, 0, 0, 0 });
        }
        if (list.get(list.size() - 1)[0] < 0) {
            list.add(new double[] { 0, 0, 0, 0, 0, 0 });
        }

        mSeriesList = new double[6][list.size()];
        for (int i = 0; i < list.size(); i++) {
            double[] data = list.get(i);
            mSeriesList[0][i] = data[0]; // day
            mSeriesList[1][i] = data[1] + data[2] + data[3] + data[4] + data[5]; // lrn
            mSeriesList[2][i] = data[2] + data[3] + data[4] + data[5]; // relearn
            mSeriesList[3][i] = data[3] + data[4] + data[5]; // young
            mSeriesList[4][i] = data[4] + data[5]; // mature
            mSeriesList[5][i] = data[5]; // cram
            if(mSeriesList[1][i] > mMaxCards)
                mMaxCards = (int) Math.round(data[1] + data[2] + data[3] + data[4] + data[5]);

            if(data[5] >= 0.999)
                mFoundCramCards = true;

            if(data[1] >= 0.999)
                mFoundLearnCards = true;

            if(data[2] >= 0.999)
                mFoundRelearnCards = true;
            if(data[0] > mLastElement)
                mLastElement = data[0];
            if(data[0] < mFirstElement)
                mFirstElement = data[0];
            if(data[0] == 0){
                mZeroIndex = i;
            }
        }
        mMaxElements = list.size()-1;
        return list.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));

    }
    private String _revlogLimitWholeOnly() {
        return "";
    }



}




Java Source Code List

com.wildplot.android.ankistats.AnkiDb.java
com.wildplot.android.ankistats.AnkiStatsActivity.java
com.wildplot.android.ankistats.AnkiStatsApplication.java
com.wildplot.android.ankistats.AnswerButton.java
com.wildplot.android.ankistats.ApplicationTest.java
com.wildplot.android.ankistats.CardsTypes.java
com.wildplot.android.ankistats.CollectionData.java
com.wildplot.android.ankistats.Forecast.java
com.wildplot.android.ankistats.HourlyBreakdown.java
com.wildplot.android.ankistats.Intervals.java
com.wildplot.android.ankistats.ReviewCount.java
com.wildplot.android.ankistats.Utils.java
com.wildplot.android.ankistats.WeeklyBreakdown.java
com.wildplot.android.parsing.Atom.java
com.wildplot.android.parsing.ExpressionFormatException.java
com.wildplot.android.parsing.Expression.java
com.wildplot.android.parsing.Factor.java
com.wildplot.android.parsing.Pow.java
com.wildplot.android.parsing.Term.java
com.wildplot.android.parsing.TopLevelParser.java
com.wildplot.android.parsing.TreeElement.java
com.wildplot.android.parsing.AtomTypes.FunctionXAtom.java
com.wildplot.android.parsing.AtomTypes.FunctionXYAtom.java
com.wildplot.android.parsing.AtomTypes.MathFunctionAtom.java
com.wildplot.android.parsing.AtomTypes.NumberAtom.java
com.wildplot.android.parsing.AtomTypes.VariableAtom.java
com.wildplot.android.parsing.AtomTypes.XVariableAtom.java
com.wildplot.android.parsing.AtomTypes.YVariableAtom.java
com.wildplot.android.rendering.AdvancedPlotSheet.java
com.wildplot.android.rendering.BarGraph.java
com.wildplot.android.rendering.DrawableContainer.java
com.wildplot.android.rendering.FunctionDrawer.java
com.wildplot.android.rendering.FunctionDrawer_y.java
com.wildplot.android.rendering.Integral.java
com.wildplot.android.rendering.LegendDrawable.java
com.wildplot.android.rendering.LinesPoints.java
com.wildplot.android.rendering.Lines.java
com.wildplot.android.rendering.MultiScreenPart.java
com.wildplot.android.rendering.PieChart.java
com.wildplot.android.rendering.PlotSheet.java
com.wildplot.android.rendering.PointDrawer2D.java
com.wildplot.android.rendering.RelativeColorGradient.java
com.wildplot.android.rendering.ReliefDrawer.java
com.wildplot.android.rendering.XAxisBarGraph.java
com.wildplot.android.rendering.XAxisHistoGram.java
com.wildplot.android.rendering.XAxis.java
com.wildplot.android.rendering.XGrid.java
com.wildplot.android.rendering.YAxisBarGraph.java
com.wildplot.android.rendering.YAxisHistoGram.java
com.wildplot.android.rendering.YAxis.java
com.wildplot.android.rendering.YGrid.java
com.wildplot.android.rendering.graphics.wrapper.BasicStroke.java
com.wildplot.android.rendering.graphics.wrapper.BufferedImage.java
com.wildplot.android.rendering.graphics.wrapper.Color.java
com.wildplot.android.rendering.graphics.wrapper.FontMetrics.java
com.wildplot.android.rendering.graphics.wrapper.Graphics2D.java
com.wildplot.android.rendering.graphics.wrapper.Graphics.java
com.wildplot.android.rendering.graphics.wrapper.Rectangle.java
com.wildplot.android.rendering.graphics.wrapper.Stroke.java
com.wildplot.android.rendering.interfaces.Drawable.java
com.wildplot.android.rendering.interfaces.Function2D.java
com.wildplot.android.rendering.interfaces.Function3D.java
com.wildplot.android.rendering.interfaces.Legendable.java
com.wildplot.android.rendering.interfaces.StepFunction2D.java