package com.github.pfichtner.jrunalyser.ui.overallstats; import static com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPlugin.getI18n; import static com.google.common.base.Functions.compose; import static com.google.common.base.Predicates.notNull; import java.awt.Color; import java.awt.Container; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.GridLayout; import java.awt.Insets; import java.io.IOException; import java.text.DateFormat; import java.text.NumberFormat; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextField; import javax.swing.ScrollPaneConstants; import com.github.pfichtner.jrunalyser.base.data.Distance; import com.github.pfichtner.jrunalyser.base.data.Duration; import com.github.pfichtner.jrunalyser.base.data.Speed; import com.github.pfichtner.jrunalyser.base.data.stat.CombinedStatistics; import com.github.pfichtner.jrunalyser.base.data.stat.Functions; import com.github.pfichtner.jrunalyser.base.data.stat.Orderings; import com.github.pfichtner.jrunalyser.base.data.stat.Statistics; import com.github.pfichtner.jrunalyser.base.data.track.Id; import com.github.pfichtner.jrunalyser.base.data.track.Metadata; import com.github.pfichtner.jrunalyser.base.data.track.StatisticsProvider; import com.github.pfichtner.jrunalyser.base.data.track.Track; import com.github.pfichtner.jrunalyser.base.data.track.TrackpointProvider; import com.github.pfichtner.jrunalyser.base.data.track.Tracks; import com.github.pfichtner.jrunalyser.base.datasource.DatasourceFascade; import com.github.pfichtner.jrunalyser.base.util.format.LatitudeFormatter; import com.github.pfichtner.jrunalyser.base.util.format.LongitudeFormatter; import com.github.pfichtner.jrunalyser.ui.base.Settings; import com.github.pfichtner.jrunalyser.ui.base.StaticSettings; import com.github.pfichtner.jrunalyser.ui.base.components.LoadTrackMouseListener; import com.github.pfichtner.jrunalyser.ui.format.DistanceFormatter; import com.github.pfichtner.jrunalyser.ui.format.DurationFormatter; import com.github.pfichtner.jrunalyser.ui.format.PaceFormatter; import com.github.pfichtner.jrunalyser.ui.format.SpeedFormatter; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Predicate; import com.google.common.base.Strings; import com.google.common.base.Throwables; import com.google.common.collect.FluentIterable; import com.google.common.collect.HashMultiset; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; import com.google.common.collect.Lists; import com.google.common.collect.Multiset; import com.google.common.collect.Multisets; import com.google.common.collect.Ordering; import com.google.common.eventbus.EventBus; public class OverallStatsPanel extends JPanel { private static final String NO_VALUE = "-"; private final Function<Id, Track> id2track = new Function<Id, Track>() { @Override public Track apply(Id id) { try { return OverallStatsPanel.this.dsf.loadTrack(id); } catch (IOException e) { throw Throwables.propagate(e); } } }; private static final long serialVersionUID = -3790468982246646282L; private DatasourceFascade dsf; private JTextField nrOfWorkouts; private JTextField distance; private JTextField duration; private JTextField avgSpeed; private JTextField avgPace; private JTextField ascentDescent; private JTextField longestDuration; private JTextField longestDistance; private JTextField mostWorkouts; private JTextField mostActiveWeekDay; private JTextField mostActiveMonth; private JTextField topSpeed; private JTextField topAvgSpeed; private JTextField maxEle; private JTextField minEle; private JTextField maxEleDiff; private JTextField minEleDiff; // TODO max ascent // TODO max descent // TODO Best 400m, 1mile, 1km, ...? private JTextField maxLat; private JTextField minLat; private JTextField maxLng; private JTextField minLng; private JTextField mostRun; private JTextField longestBreak; private JTextField workoutsTwTmTy; private JTextField distanceTwTmTy; private JTextField durationTwTmTy; private JTextField workoutsPdPmPy; private JTextField distancePdPmPy; private JTextField durationPdPmPy; private EventBus eventBus; public OverallStatsPanel() { super(createLayout()); init(this); } public void addEventBusPoster(EventBus eventBus) { this.eventBus = eventBus; } private void init(JPanel outer) { JPanel pnl = new JPanel(); pnl.setLayout(new GridBagLayout()); JScrollPane scrollPane = new JScrollPane(pnl, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); outer.setLayout(new GridLayout(1, 1)); outer.add(scrollPane); int row = 0; this.nrOfWorkouts = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.amountOfWorkouts.title"), row++); //$NON-NLS-1$ this.distance = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.distance.title"), row++); //$NON-NLS-1$ this.duration = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.duration.title"), row++); //$NON-NLS-1$ this.avgSpeed = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.avgSpeed.title"), row++); //$NON-NLS-1$ this.avgPace = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.avgPace.title"), row++); //$NON-NLS-1$ this.ascentDescent = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.elevation.title"), row++); //$NON-NLS-1$ this.longestDuration = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.longestDuration.title"), row++); //$NON-NLS-1$ this.longestDistance = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.longestDistance.title"), row++); //$NON-NLS-1$ this.mostWorkouts = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.mostWorkouts.title"), row++); //$NON-NLS-1$ this.mostActiveWeekDay = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.mostActiveWeekDay.title"), row++); //$NON-NLS-1$ this.mostActiveMonth = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.mostActiveMonth.title"), row++); //$NON-NLS-1$ this.topSpeed = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.highestSpeed.title"), row++); //$NON-NLS-1$ this.topAvgSpeed = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.highestPace.title"), //$NON-NLS-1$ row++); this.maxEle = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.highestEleveation.title"), row++); //$NON-NLS-1$ this.minEle = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.lowestEleveation.title"), row++); //$NON-NLS-1$ this.maxEleDiff = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.greatestElevationDiff.title"), row++); //$NON-NLS-1$ this.minEleDiff = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.lowestElevationDiff.title"), row++); //$NON-NLS-1$ this.maxLat = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.maxLatitude.title"), row++); //$NON-NLS-1$ this.minLat = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.minLatitude.title"), row++); //$NON-NLS-1$ this.maxLng = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.maxLongitude.title"), row++); //$NON-NLS-1$ this.minLng = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.minLongitude.title"), row++); //$NON-NLS-1$ this.mostRun = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.mostRun.title"), row++); //$NON-NLS-1$ this.longestBreak = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.longestBreak.title"), row++); //$NON-NLS-1$ this.workoutsTwTmTy = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.workoutsWMY.title"), row++); //$NON-NLS-1$ this.distanceTwTmTy = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.distanceWMY.title"), row++); //$NON-NLS-1$ this.durationTwTmTy = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.durationWMY.title"), row++); //$NON-NLS-1$ this.workoutsPdPmPy = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.workoutsPdPmPy.title"), row++); //$NON-NLS-1$ this.distancePdPmPy = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.distancePdPmPy.title"), row++); //$NON-NLS-1$ this.durationPdPmPy = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.durationPdPmPy.title"), row++); //$NON-NLS-1$ JPanel filler = new JPanel(); GridBagConstraints c1 = new GridBagConstraints(); c1.gridy = row++; c1.weighty = 1; c1.fill = GridBagConstraints.BOTH; pnl.add(filler, c1); } private static GridBagLayout createLayout() { GridBagLayout gbl = new GridBagLayout(); gbl.columnWeights = new double[] { 0.0, 1.0 }; return gbl; } private static JTextField createTextField(Container pnl, String label, int row) { GridBagConstraints c1 = new GridBagConstraints(); c1.gridx = 0; c1.gridy = row; c1.anchor = GridBagConstraints.WEST; c1.insets = new Insets(5, 5, 0, 0); pnl.add(new JLabel(label), c1); GridBagConstraints c2 = (GridBagConstraints) c1.clone(); c2.gridx = 1; c2.weightx = 1; c2.fill = GridBagConstraints.HORIZONTAL; JTextField field = createTextField(); pnl.add(field, c2); return field; } private static JTextField createTextField() { JTextField result = new JTextField(); result.setEditable(false); result.setBackground(Color.WHITE); return result; } public void setDatasourceFascade(DatasourceFascade dsf) { this.dsf = dsf; } public void initialize() throws IOException { Set<Id> allIds = this.dsf.getTrackIds(); Iterable<Track> tracks = this.dsf.loadTracks(allIds); // TODO Null all fields if tracks are empty if (!Iterables.isEmpty(tracks)) { int workoutCount = allIds.size(); Statistics stats = CombinedStatistics.of(FluentIterable.from( this.dsf.loadTracks(allIds)).transform( Functions.StatisticsProviders.statistics)); Settings settings = StaticSettings.INSTANCE; DistanceFormatter dif = new DistanceFormatter( DistanceFormatter.Type.SHORT); DurationFormatter duf = new DurationFormatter( DurationFormatter.Type.MEDIUM_SYMBOLS); SpeedFormatter spf = new SpeedFormatter(SpeedFormatter.Type.SHORT); PaceFormatter paf = new PaceFormatter(PaceFormatter.Type.SHORT); this.nrOfWorkouts.setText(String.valueOf(workoutCount)); if (stats.getDistance() != null) { Distance distance = stats.getDistance().convertTo( settings.getDistanceUnit()); this.distance .setText(getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.valueAndAvg.format", //$NON-NLS-1$ dif.format(distance), dif.format(distance .divide(workoutCount)))); } if (stats.getDuration() != null) { Duration b = stats.getDuration().convertTo( settings.getTimeUnit()); this.duration .setText(getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.valueAndAvg.format", duf.format(b), //$NON-NLS-1$ duf.format(b.divide(workoutCount)))); } if (stats.getAvgSpeed() != null) { Speed avgSpeed = stats.getAvgSpeed(); this.avgSpeed.setText(spf.format(settings, avgSpeed)); this.avgPace.setText(paf.format(settings, avgSpeed)); } NumberFormat nf = NumberFormat.getNumberInstance(); this.ascentDescent .setText(getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.elevation.format", //$NON-NLS-1$ nf.format(stats.getAscent()), nf.format(stats.getDescent()))); Track longestDurationTrack = Orderings.duration.max(tracks); setTextAndAddMouseListener( this.longestDuration, getString(longestDurationTrack, duf .format(Orderings.durationFunc .apply(longestDurationTrack))), longestDurationTrack); Track longestDistanceTrack = Orderings.distance.max(tracks); setTextAndAddMouseListener( this.longestDistance, getString(longestDistanceTrack, dif .format(Orderings.distanceFunc .apply(longestDurationTrack))), longestDistanceTrack); Function<StatisticsProvider, Speed> maxSpeedFunc = compose( Functions.LinkedWayPoints.speed(), compose(Functions.Statisticss.maxSpeed, Functions.StatisticsProviders.statistics)); Ordering<StatisticsProvider> maxSpeedOrdering = Ordering.natural() .onResultOf(maxSpeedFunc); Track topSpeedTrack = maxSpeedOrdering.max(tracks); setTextAndAddMouseListener( this.topSpeed, getString( topSpeedTrack, spf.format(settings, maxSpeedFunc.apply(topSpeedTrack))), topSpeedTrack); Function<StatisticsProvider, Speed> maxAvgSpeedFunc = compose( Functions.Statisticss.avgSpeed, Functions.StatisticsProviders.statistics); Ordering<StatisticsProvider> maxAvgSpeedFuncOrdering = Ordering .natural().onResultOf(maxAvgSpeedFunc); Track avgSpeedTrack = maxAvgSpeedFuncOrdering.max(tracks); setTextAndAddMouseListener( this.topAvgSpeed, getString( avgSpeedTrack, spf.format(settings, maxAvgSpeedFunc.apply(avgSpeedTrack))), avgSpeedTrack); Function<StatisticsProvider, Integer> maxEleFunc = compose( Functions.WayPoints.elevation(), compose(Functions.Statisticss.maxEle, Functions.StatisticsProviders.statistics)); Ordering<StatisticsProvider> maxEleOrdering = Ordering.natural() .onResultOf(maxEleFunc); Track maxEleTrack = maxEleOrdering.max(tracks); setTextAndAddMouseListener(this.maxEle, getString(maxEleTrack, maxEleFunc.apply(maxEleTrack)), maxEleTrack); Function<StatisticsProvider, Integer> minEleFunc = compose( Functions.WayPoints.elevation(), compose(Functions.Statisticss.minEle, Functions.StatisticsProviders.statistics)); Ordering<StatisticsProvider> minEleOrdering = Ordering.natural() .onResultOf(minEleFunc); Track minEleTrack = minEleOrdering.min(tracks); setTextAndAddMouseListener(this.minEle, getString(minEleTrack, minEleFunc.apply(minEleTrack)), minEleTrack); Function<StatisticsProvider, Integer> getEleDiffFunc = compose( Functions.Statisticss.eleDiff, Functions.StatisticsProviders.statistics); Ordering<StatisticsProvider> eleDiffOrdering = Ordering.natural() .onResultOf(getEleDiffFunc); Track maxEleDiffTrack = eleDiffOrdering.max(tracks); setTextAndAddMouseListener( this.maxEleDiff, getString(maxEleDiffTrack, getEleDiffFunc.apply(maxEleDiffTrack)), maxEleDiffTrack); Track minEleDiffTrack = eleDiffOrdering.min(tracks); setTextAndAddMouseListener( this.minEleDiff, getString(minEleDiffTrack, getEleDiffFunc.apply(minEleDiffTrack)), minEleDiffTrack); // ----------------------------------------------------------------------- { Multiset<String> byWeek = groupBy(allIds, format("ww yyyy")); //$NON-NLS-1$ String week = Iterators.get(byWeek.iterator(), 0); Multiset<String> byMonth = groupBy(allIds, format("MMMM yyyy")); //$NON-NLS-1$ String month = Iterators.get(byMonth.iterator(), 0); Multiset<String> byYear = groupBy(allIds, format("yyyy")); //$NON-NLS-1$ String year = Iterators.get(byYear.iterator(), 0); this.mostWorkouts .setText(getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.mostWorkouts.format", //$NON-NLS-1$ year, String.valueOf(byYear.count(year)), month, String.valueOf(byMonth.count(month)), week, String.valueOf(byWeek.count(week)))); Multiset<String> byDayOfWeek = groupBy(allIds, format("E")); //$NON-NLS-1$ List<String> texts = Lists.newArrayList(); for (String day : byDayOfWeek.elementSet()) { texts.add(getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.mostActiveWeekDay.format", //$NON-NLS-1$ day, Integer.valueOf(byDayOfWeek.count(day)))); } this.mostActiveWeekDay.setText(Joiner.on(", ").join(texts)); //$NON-NLS-1$ Multiset<String> bySingleMonth = groupBy(allIds, format("MMMM")); //$NON-NLS-1$ String singleMonth = Iterables.get(bySingleMonth, 0); this.mostActiveMonth .setText(getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.mostActiveMonth.format", //$NON-NLS-1$ singleMonth, Integer.valueOf(bySingleMonth .count(singleMonth)))); } // ----------------------------------------------------------------------- Track maxLat = Orderings.maxLatitude.max(tracks); setTextAndAddMouseListener(this.maxLat, getStringLat(maxLat, Orderings.maxLatitudeFunc), maxLat); Track minLat = Orderings.minLatitude.min(tracks); setTextAndAddMouseListener(this.minLat, getStringLat(minLat, Orderings.minLatitudeFunc), minLat); Track maxLng = Orderings.maxLongitude.max(tracks); setTextAndAddMouseListener(this.maxLng, getStringLng(maxLng, Orderings.maxLongitudeFunc), maxLng); Track minLng = Orderings.minLongitude.min(tracks); setTextAndAddMouseListener(this.minLng, getStringLng(minLng, Orderings.minLongitudeFunc), minLng); // --------------------------------------------------------------- this.mostRun.setText(calculateMostRun(allIds)); { FluentIterable<Track> orderedTracks = FluentIterable.from( this.dsf.getTrackIds(new Date(Long.MIN_VALUE), new Date(Long.MAX_VALUE))).transform( this.id2track); Track prev = null, t1 = null, t2 = null; long absDiff = 0; for (Track track : orderedTracks) { long day = getDay(track); if (prev != null) { long pDay = getDay(prev); assert day > pDay : day + "<=" //$NON-NLS-1$ + pDay + "(" //$NON-NLS-1$ + new Date(Tracks.getStartPoint(track) .getTime().longValue()) + " <?> " //$NON-NLS-1$ + new Date(Tracks.getStartPoint(prev).getTime() .longValue()) + ")"; //$NON-NLS-1$ long diff = day - pDay; if (diff >= absDiff) { absDiff = diff; t1 = prev; t2 = track; } } prev = track; } DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM); long oneDay = TimeUnit.DAYS.toMillis(1); String text = absDiff < 2 ? null : getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.longestBreak.format", //$NON-NLS-1$ Long.valueOf(absDiff - 1), df.format(new Date(Tracks .getStartPoint(t1).getTime() .longValue() + oneDay)), df.format(new Date(Tracks .getStartPoint(t2).getTime() .longValue() - oneDay))); this.longestBreak.setText(text); } { Date now = new Date(); Iterable<Id> w = this.dsf.getTrackIds(getWeekStart(now), now); Iterable<Id> m = this.dsf.getTrackIds(getMonthStart(now), now); Iterable<Id> y = this.dsf.getTrackIds(getYearStart(now), now); Statistics wStats = CombinedStatistics.of(FluentIterable.from( this.dsf.loadTracks(w)).transform( Functions.StatisticsProviders.statistics)); Statistics mStats = CombinedStatistics.of(FluentIterable.from( this.dsf.loadTracks(m)).transform( Functions.StatisticsProviders.statistics)); Statistics yStats = CombinedStatistics.of(FluentIterable.from( this.dsf.loadTracks(y)).transform( Functions.StatisticsProviders.statistics)); this.workoutsTwTmTy .setText(getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.TwTmTy.format", //$NON-NLS-1$ Integer.valueOf(Iterables.size(w)), Integer.valueOf(Iterables.size(m)), Integer.valueOf(Iterables.size(y)))); this.distanceTwTmTy .setText(getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.TwTmTy.format", //$NON-NLS-1$ wStats.getDistance() == null ? NO_VALUE : dif.format(wStats .getDistance() .convertTo( settings.getDistanceUnit())), mStats.getDistance() == null ? NO_VALUE : dif.format(mStats .getDistance() .convertTo( settings.getDistanceUnit())), yStats.getDistance() == null ? NO_VALUE : dif.format(yStats .getDistance() .convertTo( settings.getDistanceUnit())))); this.durationTwTmTy .setText(getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.TwTmTy.format", //$NON-NLS-1$ wStats.getDuration() == null ? NO_VALUE : duf.format(wStats .getDuration() .convertTo( settings.getTimeUnit())), mStats.getDuration() == null ? NO_VALUE : duf.format(mStats .getDuration() .convertTo( settings.getTimeUnit())), yStats.getDuration() == null ? NO_VALUE : duf.format(yStats .getDuration() .convertTo( settings.getTimeUnit())))); } Iterable<Id> trackIds = this.dsf.getTrackIds(new Date( Long.MIN_VALUE), new Date(Long.MAX_VALUE)); Id first = Iterables.getFirst(trackIds, null); Id last = Iterables.getLast(trackIds, null); Long days = null; if (first != null && last != null) { days = Long.valueOf(TimeUnit.DAYS.convert( Tracks.getStartPoint(this.dsf.loadTrack(last)) .getTime().longValue() - Tracks.getStartPoint( this.dsf.loadTrack(first)).getTime() .longValue(), TimeUnit.MILLISECONDS)); } { this.workoutsPdPmPy.setText(days == null ? null : nf .format(((double) workoutCount) / days.longValue()) + "/" //$NON-NLS-1$ + nf.format(((double) workoutCount) / days.longValue() * 30) + "/" //$NON-NLS-1$ + nf.format(((double) workoutCount) / days.longValue() * 365)); } { Distance distance = stats.getDistance().convertTo( settings.getDistanceUnit()); this.distancePdPmPy.setText(days == null ? null : dif.format(distance.divide(days.longValue())) + "/" //$NON-NLS-1$ + dif.format(distance.divide(((double) days .longValue()) / 30)) + "/" //$NON-NLS-1$ + dif.format(distance.divide(((double) days .longValue()) / 365))); } { Duration duration = stats.getDuration().convertTo( settings.getTimeUnit()); this.durationPdPmPy.setText(days == null ? null : duf.format(duration.divide(days.longValue())) + "/" //$NON-NLS-1$ + duf.format(duration.divide(((double) days .longValue()) / 30)) + "/" //$NON-NLS-1$ + duf.format(duration.divide(((double) days .longValue()) / 365))); } } } private static Date getWeekStart(Date date) { Calendar cal = newCalendar(date); cal.set(Calendar.DAY_OF_WEEK, cal.getFirstDayOfWeek()); return clearFields(cal); } private static Date getMonthStart(Date date) { Calendar cal = newCalendar(date); cal.set(Calendar.DAY_OF_MONTH, 1); return clearFields(cal); } private static Date getYearStart(Date date) { Calendar cal = newCalendar(date); cal.set(Calendar.DAY_OF_YEAR, 1); return clearFields(cal); } private static Calendar newCalendar(Date date) { Calendar cal = Calendar.getInstance(); cal.setTime(date); return cal; } private static Date clearFields(Calendar cal) { for (int field : new int[] { Calendar.HOUR_OF_DAY, Calendar.MINUTE, Calendar.SECOND, Calendar.MILLISECOND }) { cal.clear(field); } return cal.getTime(); } private static long getDay(Track track) { return TimeUnit.DAYS.convert(Tracks.getStartPoint(track).getTime() .longValue(), TimeUnit.MILLISECONDS); } private Function<Id, String> format(final String string) { return new Function<Id, String>() { SimpleDateFormat sdf = new SimpleDateFormat(string); @Override public String apply(Id id) { try { return this.sdf.format(new Date(Tracks .getStartPoint( OverallStatsPanel.this.dsf.loadTrack(id)) .getTime().longValue())); } catch (IOException e) { throw Throwables.propagate(e); } } }; } private Multiset<String> groupBy(Set<Id> ids, Function<Id, String> f) { Multiset<String> ms = HashMultiset.create(); Iterables.addAll(ms, FluentIterable.from(ids).transform(f)); return Multisets.copyHighestCountFirst(ms); } private String calculateMostRun(Set<Id> allIds) throws IOException { Multiset<Id> ms = HashMultiset.create(); for (Id id : allIds) { ms.add(id, OverallStatsPanel.this.dsf.getSimilarTracks(id).size() + 1); } Id top = Iterables.get(Multisets.copyHighestCountFirst(ms), 0); if (top == null) { return null; } Set<Id> similarTracks = ImmutableSet.<Id> builder().add(top) .addAll(this.dsf.getSimilarTracks(top)).build(); FluentIterable<Metadata> metadata = FluentIterable.from(similarTracks) .transform(this.id2track).transform(Functions.Tracks.metadata); // do the have a common description? Multiset<String> desc = Multisets.copyHighestCountFirst(HashMultiset .create(metadata.transform(Functions.Metadatas.description) .filter(notNull()).filter(notEmpty()))); String cd = desc.size() > 0 ? Iterables.get(desc, 0) : ""; //$NON-NLS-1$ // do the have a common name? Multiset<String> names = Multisets.copyHighestCountFirst(HashMultiset .create(metadata.transform(Functions.Metadatas.name) .filter(notNull()).filter(notEmpty()))); String cn = names.size() > 0 ? Iterables.get(names, 0) : ""; //$NON-NLS-1$ String val; if (desc.count(cd) > desc.size() / 2) { val = cd; } else if (names.count(cn) > names.size() / 2) { val = cn; } else if (!desc.isEmpty()) { val = Joiner.on(", ").join(desc); //$NON-NLS-1$ } else if (!names.isEmpty()) { val = Joiner.on(", ").join(names); //$NON-NLS-1$ } else { val = "???"; //$NON-NLS-1$ } return getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.overallstats.OverallStatsPanel.mostRun.format", val, //$NON-NLS-1$ Integer.valueOf(similarTracks.size())); } private Predicate<String> notEmpty() { return new Predicate<String>() { @Override public boolean apply(String string) { return !Strings.isNullOrEmpty(string); } }; } private void setTextAndAddMouseListener(JTextField textField, String text, Track track) { textField.setText(text); textField.addMouseListener(new LoadTrackMouseListener(track, this.eventBus)); } private String getStringLat(Track track, Function<Track, ? extends Number> function) { return getString(track, new LatitudeFormatter().format(function.apply( track).doubleValue())); } private String getStringLng(Track track, Function<Track, ? extends Number> function) { return getString(track, new LongitudeFormatter().format(function.apply( track).doubleValue())); } private String getString(Track track, Object text) { return getStartDate(track) + " (" //$NON-NLS-1$ + text + ")"; //$NON-NLS-1$ } private static String getStartDate(TrackpointProvider waypointProvider) { return DateFormat.getDateTimeInstance().format( new Date(Iterables.get(waypointProvider.getTrackpoints(), 0) .getTime().longValue())); } }