package com.github.pfichtner.jrunalyser.ui.trackstat; import static com.github.pfichtner.jrunalyser.base.data.Speeds.is; import static com.github.pfichtner.jrunalyser.ui.trackstat.TrackStatsPlugin.getI18n; import static com.google.common.base.Functions.compose; import static com.google.common.base.Predicates.equalTo; import static com.google.common.collect.Iterables.indexOf; 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.DecimalFormat; import java.text.NumberFormat; 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.DefaultDistance; import com.github.pfichtner.jrunalyser.base.data.DefaultDuration; import com.github.pfichtner.jrunalyser.base.data.DefaultSpeed; import com.github.pfichtner.jrunalyser.base.data.Distance; import com.github.pfichtner.jrunalyser.base.data.DistanceUnit; import com.github.pfichtner.jrunalyser.base.data.Duration; import com.github.pfichtner.jrunalyser.base.data.SegmentationUnit; import com.github.pfichtner.jrunalyser.base.data.Speed; import com.github.pfichtner.jrunalyser.base.data.WayPoint; import com.github.pfichtner.jrunalyser.base.data.stat.Functions; 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.StatisticsProvider; import com.github.pfichtner.jrunalyser.base.data.track.Track; import com.github.pfichtner.jrunalyser.base.data.track.Tracks; import com.github.pfichtner.jrunalyser.base.datasource.DatasourceFascade; import com.github.pfichtner.jrunalyser.base.util.FixedDistance; import com.github.pfichtner.jrunalyser.base.util.FixedDuration; import com.github.pfichtner.jrunalyser.ui.base.Settings; import com.github.pfichtner.jrunalyser.ui.base.StaticSettings; import com.github.pfichtner.jrunalyser.ui.base.components.SegmentTrackMouseListener; import com.github.pfichtner.jrunalyser.ui.dock.ebus.TrackLoaded; 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.Predicates; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Ordering; import com.google.common.eventbus.EventBus; public class TrackStatsPanel extends JPanel { private static final long serialVersionUID = 617730841965291592L; private DatasourceFascade dsf; private JTextField name; private JTextField description; private JTextField startTime; private JTextField distance; private JTextField duration; private JTextField elevation; private JTextField ascentDescent; private JTextField placing; private JTextField speed; private JTextField pace; // -------------------------- private JTextField _400minpos; private JTextField _12metpos; private EventBus eventBus; public TrackStatsPanel() { 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.name = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.trackstat.TrackStatsPanel.name.title"), row++); //$NON-NLS-1$ this.description = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.trackstat.TrackStatsPanel.description.title"), row++); //$NON-NLS-1$ this.distance = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.trackstat.TrackStatsPanel.distance.title"), row++); //$NON-NLS-1$ this.startTime = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.trackstat.TrackStatsPanel.starttime.title"), row++); //$NON-NLS-1$ this.duration = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.trackstat.TrackStatsPanel.duration.title"), row++); //$NON-NLS-1$ this.elevation = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.trackstat.TrackStatsPanel.elevation.title"), row++); //$NON-NLS-1$ this.ascentDescent = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.trackstat.TrackStatsPanel.elevationGainedLost.title"), row++); //$NON-NLS-1$ this.speed = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.trackstat.TrackStatsPanel.avgSpeed.title"), row++); //$NON-NLS-1$ this.pace = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.trackstat.TrackStatsPanel.avgPace.title"), row++); //$NON-NLS-1$ this.placing = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.trackstat.TrackStatsPanel.placement.title"), row++); //$NON-NLS-1$ this._400minpos = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.trackstat.TrackStatsPanel.400m.title"), row++); //$NON-NLS-1$ this._12metpos = createTextField( pnl, getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.trackstat.TrackStatsPanel.cooper.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 setTrack(TrackLoaded message) throws IOException { Track track = message.getTrack(); Statistics stats = track.getStatistics(); Settings settings = StaticSettings.INSTANCE; DistanceFormatter dif = new DistanceFormatter( DistanceFormatter.Type.SHORT); DurationFormatter duf = new DurationFormatter( DurationFormatter.Type.SHORT); SpeedFormatter spf = new SpeedFormatter(SpeedFormatter.Type.SHORT); PaceFormatter paf = new PaceFormatter(PaceFormatter.Type.SHORT); this.name.setText(track.getMetadata().getName()); this.description.setText(track.getMetadata().getDescription()); this.startTime.setText(DateFormat.getDateTimeInstance().format( new Date(Tracks.getStartPoint(track).getTime().longValue()))); this.distance.setText(dif.format(stats.getDistance().convertTo( settings.getDistanceUnit()))); this.duration.setText(duf.format(stats.getDuration().convertTo( settings.getTimeUnit()))); WayPoint minEle = stats.getMinElevation(); WayPoint maxEle = stats.getMaxElevation(); NumberFormat nf = NumberFormat.getNumberInstance(); this.elevation .setText(minEle == null || maxEle == null || minEle.getElevation() == null || maxEle.getElevation() == null ? null : getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.trackstat.TrackStatsPanel.elevationGainedLost.format", nf.format(minEle.getElevation().intValue()), //$NON-NLS-1$ nf.format(maxEle.getElevation() .intValue()))); this.ascentDescent .setText(getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.trackstat.TrackStatsPanel.elevation.format", //$NON-NLS-1$ nf.format(stats.getAscent()), nf.format(stats.getDescent()))); Speed avgSpeed = stats.getAvgSpeed(); this.speed.setText(spf.format(settings, avgSpeed)); this.pace.setText(paf.format(settings, avgSpeed)); // ------------------------------------------------ { Set<Id> all = ImmutableSet.<Id> builder() .addAll(this.dsf.getSimilarTracks(track.getId())) .add(track.getId()).build(); Function<StatisticsProvider, Speed> maxAvgSpeedFunc = compose( Functions.Statisticss.avgSpeed, Functions.StatisticsProviders.statistics); Ordering<StatisticsProvider> maxAvgSpeedFuncOrdering = Ordering .natural().onResultOf(maxAvgSpeedFunc); List<Track> sortedTracks = maxAvgSpeedFuncOrdering.reverse() .sortedCopy(this.dsf.loadTracks(all)); int idx = Iterables.indexOf(sortedTracks, Predicates.compose( equalTo(track.getId()), Functions.Tracks.id)); String txt = String .format(getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.trackstat.TrackStatsPanel.placement.format"), Integer.valueOf((idx + 1)), //$NON-NLS-1$ Integer.valueOf(sortedTracks.size())); if (sortedTracks.size() > 1) { boolean best = idx == 0; DecimalFormat nfp = new DecimalFormat("+#;-#"); //$NON-NLS-1$ if (!best) { // Compared to best int percent = is( sortedTracks.get(0).getStatistics().getAvgSpeed()) .fasterThan(avgSpeed).inPercent(); txt += " (" + nfp.format(percent) + "% vs. best)"; //$NON-NLS-1$ //$NON-NLS-2$ } // Compared to average (including actual track) int percent = is(calcAverageSpeed(sortedTracks)).fasterThan( avgSpeed).inPercent(); txt += " (" + nfp.format(percent) + "% vs. Ø)"; //$NON-NLS-1$ //$NON-NLS-2$ } this.placing.setText(txt); } { Distance di = DefaultDistance.of(400, DistanceUnit.METERS); List<Id> top400m = this.dsf.listTracks(di); int idx = indexOf(top400m, equalTo(track.getId())); if (idx >= 0) { // TOOD orNull: Handle null! Statistics statistics = this.dsf.loadBestSegment( top400m.get(idx), di).orNull(); FixedDistance fixedDistance = new FixedDistance(di); String text = getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.trackstat.TrackStatsPanel.valueAndPlacement.format", //$NON-NLS-1$ Integer.valueOf(idx + 1), duf.format(fixedDistance.getDuration( statistics.getDistance(), statistics.getDuration()))); setTextAndAddMouseListener(this._400minpos, text, di); } } { Duration du = DefaultDuration.of(12, TimeUnit.MINUTES); List<Id> top12min = this.dsf.listTracks(du); int idx = indexOf(top12min, equalTo(track.getId())); if (idx >= 0) { // TOOD orNull: Handle null! Statistics statistics = this.dsf.loadBestSegment( top12min.get(idx), du).orNull(); FixedDuration fixedDuration = new FixedDuration(du); String text = getI18n() .getText( "com.github.pfichtner.jrunalyser.ui.trackstat.TrackStatsPanel.valueAndPlacement.format", //$NON-NLS-1$ Integer.valueOf(idx + 1), dif.format(fixedDuration.getDistance( statistics.getDistance(), statistics.getDuration()))); setTextAndAddMouseListener(this._12metpos, text, du); } } } // TODO Move to class Tracks private static Speed calcAverageSpeed(Iterable<Track> tracks) { return new DefaultSpeed(dis(tracks), dus(tracks)); } // TODO Move to class Tracks private static Distance dis(Iterable<Track> tracks) { Distance distance = DefaultDistance.of(0, DistanceUnit.KILOMETERS); for (Track track : tracks) { Distance distance2 = track.getStatistics().getDistance(); distance = distance.add(distance2).convertTo( distance2.getDistanceUnit()); } return distance; } // TODO Move to class Tracks private static Duration dus(Iterable<Track> tracks) { Duration duration = DefaultDuration.of(0, TimeUnit.DAYS); for (Track track : tracks) { Duration duration2 = track.getStatistics().getDuration(); duration = duration.add(duration2).convertTo( duration2.getTimeUnit()); } return duration; } private void setTextAndAddMouseListener(JTextField textField, String text, SegmentationUnit segmentationUnit) { textField.setText(text); textField.addMouseListener(new SegmentTrackMouseListener( segmentationUnit, this.eventBus)); } }