pl.llp.aircasting.view.NoisePlot.java Source code

Java tutorial

Introduction

Here is the source code for pl.llp.aircasting.view.NoisePlot.java

Source

/**
 AirCasting - Share your Air!
 Copyright (C) 2011-2012 HabitatMap, Inc.
    
 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/>.
    
 You can contact the authors by email at <info@habitatmap.org>
 */
package pl.llp.aircasting.view;

import android.view.MotionEvent;
import pl.llp.aircasting.R;
import pl.llp.aircasting.activity.AirCastingActivity;
import pl.llp.aircasting.android.Logger;
import pl.llp.aircasting.event.ui.TapEvent;
import pl.llp.aircasting.helper.ResourceHelper;
import pl.llp.aircasting.helper.SettingsHelper;
import pl.llp.aircasting.model.Measurement;
import pl.llp.aircasting.model.Note;
import pl.llp.aircasting.model.Sensor;
import pl.llp.aircasting.model.internal.MeasurementLevel;
import pl.llp.aircasting.sensor.ThresholdsHolder;
import pl.llp.aircasting.util.Search;
import pl.llp.aircasting.view.presenter.MeasurementAggregator;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import com.google.common.base.Stopwatch;

import java.util.ArrayList;
import java.util.concurrent.TimeUnit;

import static com.google.common.collect.Iterables.getLast;
import static com.google.common.collect.Lists.newArrayList;
import static pl.llp.aircasting.util.DrawableTransformer.centerBottomAt;
import static pl.llp.aircasting.util.Search.binarySearch;

public class NoisePlot extends View {
    public static final int OPAQUE = 255;
    private static final double CLICK_RADIUS = 50;

    private AirCastingActivity activity;
    private SettingsHelper settingsHelper;
    private ThresholdsHolder thresholds;

    private ArrayList<Measurement> measurements = new ArrayList<Measurement>();
    private ArrayList<Note> notes;
    private ResourceHelper resourceHelper;
    private int bottom;
    private int top;
    private Sensor sensor;

    MeasurementAggregator aggregator = new MeasurementAggregator();

    @SuppressWarnings("UnusedDeclaration")
    public NoisePlot(Context context) {
        super(context);
    }

    @SuppressWarnings("UnusedDeclaration")
    public NoisePlot(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @SuppressWarnings("UnusedDeclaration")
    public NoisePlot(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public void initialize(AirCastingActivity activity, SettingsHelper settingsHelper, ThresholdsHolder thresholds,
            ResourceHelper resourceHelper) {
        this.activity = activity;
        this.settingsHelper = settingsHelper;
        this.thresholds = thresholds;
        this.resourceHelper = resourceHelper;
    }

    @SuppressWarnings("AssignmentToCollectionOrArrayFieldFromParameter")
    public void update(Sensor sensor, ArrayList<Measurement> measurements, ArrayList<Note> notes) {
        this.measurements = measurements;
        this.notes = notes;
        this.sensor = sensor;
        invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        Stopwatch stopwatch = new Stopwatch().start();

        bottom = thresholds.getValue(sensor, MeasurementLevel.VERY_LOW);
        top = thresholds.getValue(sensor, MeasurementLevel.VERY_HIGH);

        Paint paint = new Paint();
        drawBackground(canvas, paint);

        if (!measurements.isEmpty()) {
            measurements = aggregator.smoothenSamplesToReduceCount(newArrayList(measurements), 1000);
            Path path = new Path();

            float lastY = project(measurements.get(0).getValue());
            path.moveTo(0, lastY);

            // Avoid concurrent modification
            for (int i = 1; i < measurements.size(); i++) {
                Measurement measurement = measurements.get(i);
                Point place = place(measurement);
                path.lineTo(place.x, place.y);
            }
            Logger.logGraphPerformance("onDraw to path creation took " + stopwatch.elapsed(TimeUnit.MILLISECONDS));

            initializePaint(paint);

            canvas.drawPath(path, paint);
            Logger.logGraphPerformance("onDraw to path draw took " + stopwatch.elapsed(TimeUnit.MILLISECONDS));

            for (Note note : notes) {
                drawNote(canvas, note);
            }

            if (settingsHelper.showGraphMetadata()) {
                String message = "[" + measurements.size() + "] pts";
                String message2 = "drawing took " + stopwatch.elapsed(TimeUnit.MILLISECONDS);
                long textSize = getResources().getDimensionPixelSize(R.dimen.debugFontSize);
                Paint textPaint = new Paint();
                textPaint.setColor(Color.WHITE);
                textPaint.setAlpha(OPAQUE);
                textPaint.setAntiAlias(true);
                textPaint.setTextSize(textSize);

                float textWidth = Math.max(textPaint.measureText(message), textPaint.measureText(message2));

                canvas.drawText(message, getWidth() - textWidth - 5, getHeight() - textSize - 5, textPaint);
                canvas.drawText(message2, getWidth() - textWidth - 5, getHeight() - 5, textPaint);
            }
        }
    }

    private Point place(Measurement measurement) {
        long time = measurement.getTime().getTime();
        float span = lastTime() - firstTime();
        float place = time - firstTime();
        int x = (int) (getWidth() * (place / span));

        double value = measurement.getValue();
        int y = project(value);

        return new Point(x, y);
    }

    private void drawNote(Canvas canvas, Note note) {
        if (!inRange(note))
            return;

        Point place = place(note);

        Drawable noteArrow = resourceHelper.getNoteArrow();
        centerBottomAt(noteArrow, place);
        noteArrow.draw(canvas);
    }

    private Point place(Note note) {
        Measurement measurement = findClosestMeasurement(note);
        return place(measurement);
    }

    private boolean inRange(Note note) {
        long noteTime = note.getDate().getTime();
        return noteTime >= firstTime() && noteTime <= lastTime();
    }

    private long lastTime() {
        return getLast(measurements).getTime().getTime();
    }

    private long firstTime() {
        return measurements.get(0).getTime().getTime();
    }

    private Measurement findClosestMeasurement(final Note note) {
        int index = binarySearch(measurements, new Search.Visitor<Measurement>() {
            @Override
            public int compareTo(Measurement value) {
                return note.getDate().compareTo(value.getTime());
            }
        });

        return measurements.get(index);
    }

    private void initializePaint(Paint paint) {
        paint.setColor(Color.WHITE);
        paint.setAlpha(OPAQUE);
        paint.setStrokeWidth(3);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeJoin(Paint.Join.ROUND);
        paint.setStrokeCap(Paint.Cap.ROUND);
        paint.setAntiAlias(true);
    }

    private void drawBackground(Canvas canvas, Paint paint) {
        // Make the NoisePlot play nicely with Layout preview
        if (resourceHelper == null || settingsHelper == null)
            return;

        paint.setStrokeWidth(0);
        paint.setStyle(Paint.Style.FILL);

        int lastThreshold = top;

        for (MeasurementLevel measurementLevel : new MeasurementLevel[] { MeasurementLevel.HIGH,
                MeasurementLevel.MID, MeasurementLevel.LOW }) {
            paint.setColor(resourceHelper.getGraphColor(measurementLevel));
            int threshold = settingsHelper.getThreshold(sensor, measurementLevel);

            canvas.drawRect(0, project(lastThreshold), getWidth(), project(threshold), paint);
            lastThreshold = threshold;
        }

        paint.setColor(resourceHelper.getGraphColor(MeasurementLevel.VERY_LOW));
        canvas.drawRect(0, project(lastThreshold), getWidth(), getHeight(), paint);
    }

    private int project(double value) {
        return (int) (getHeight() * (top - value) / (top - bottom));
    }

    public boolean onTap(MotionEvent event) {
        for (int i = 0; i < notes.size(); i++) {
            Note note = notes.get(i);
            Point place = place(note);

            if (inRange(note) && isClose(event, place)) {
                activity.noteClicked(i);
                return true;
            }
        }
        return false;
    }

    private boolean isClose(MotionEvent event, Point place) {
        float x = event.getX() - place.x;
        float y = event.getY() - place.y;
        return Math.sqrt(x * x + y * y) < CLICK_RADIUS;
    }
}