Merge pull request #10116 from osmandapp/graphs_iteration_2

Graphs iteration 2
This commit is contained in:
Vitaliy 2020-11-05 02:10:44 +02:00 committed by GitHub
commit 355fe6a270
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 1215 additions and 724 deletions

View file

@ -26,9 +26,9 @@
android:id="@+id/common_graphs_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible"
android:orientation="vertical">
tools:visibility="visible">
<com.github.mikephil.charting.charts.LineChart
android:id="@+id/line_chart"
@ -42,17 +42,22 @@
android:id="@+id/custom_graphs_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible"
android:orientation="vertical">
tools:visibility="visible">
<com.github.mikephil.charting.charts.HorizontalBarChart
android:id="@+id/horizontal_chart"
android:layout_width="match_parent"
android:layout_height="@dimen/route_info_chart_height" />
<LinearLayout
android:id="@+id/route_items"
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/list_divider" />
<FrameLayout
android:id="@+id/route_legend"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
@ -65,48 +70,106 @@
android:id="@+id/message_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/content_padding"
android:layout_marginLeft="@dimen/content_padding"
android:layout_marginTop="@dimen/content_padding_small"
android:layout_marginEnd="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding"
android:layout_marginBottom="@dimen/content_padding_small"
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible"
android:orientation="horizontal">
tools:visibility="visible">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/message_icon"
android:layout_width="@dimen/standard_icon_size"
android:layout_height="@dimen/standard_icon_size"
android:layout_gravity="center"
android:layout_marginEnd="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding"
android:tint="?attr/default_icon_color"
tools:src="@drawable/ic_action_info_dark" />
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="@dimen/card_button_progress_size"
android:layout_height="@dimen/card_button_progress_size"
android:layout_gravity="center"
android:layout_marginEnd="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding"
android:indeterminate="true"
android:visibility="gone" />
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/message_text"
android:layout_width="0dp"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:layout_weight="1"
android:letterSpacing="@dimen/description_letter_spacing"
android:lineSpacingMultiplier="@dimen/bottom_sheet_text_spacing_multiplier"
android:textColor="?android:textColorSecondary"
android:textSize="@dimen/default_desc_text_size"
osmand:typeface="@string/font_roboto_regular"
tools:text="Altitude data available only on the roads, you need to calculate a route using “Route between points” to get it." />
android:layout_marginStart="@dimen/content_padding"
android:layout_marginLeft="@dimen/content_padding"
android:layout_marginTop="@dimen/content_padding_small"
android:layout_marginEnd="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/message_icon"
android:layout_width="@dimen/standard_icon_size"
android:layout_height="@dimen/standard_icon_size"
android:layout_marginEnd="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding"
android:tint="?attr/default_icon_color"
tools:src="@drawable/ic_action_info_dark" />
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="@dimen/card_button_progress_size"
android:layout_height="@dimen/card_button_progress_size"
android:layout_marginEnd="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding"
android:indeterminate="true"
android:visibility="gone" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/message_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:letterSpacing="@dimen/description_letter_spacing"
android:lineSpacingMultiplier="@dimen/bottom_sheet_text_spacing_multiplier"
android:textColor="?android:textColorSecondary"
android:textSize="@dimen/default_desc_text_size"
osmand:typeface="@string/font_roboto_regular"
tools:text="Altitude data available only on the roads, you need to calculate a route using “Route between points” to get it." />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/btn_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/content_padding"
android:background="?attr/selectableItemBackground"
android:orientation="horizontal">
<View
android:layout_width="@dimen/standard_icon_size"
android:layout_height="@dimen/standard_icon_size"
android:layout_marginLeft="@dimen/content_padding"
android:layout_marginStart="@dimen/content_padding"
android:layout_marginEnd="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding"
android:visibility="invisible"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:minHeight="@dimen/bottom_sheet_list_item_height"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/divider_color_basic" />
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/btn_text"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_gravity="start|center_vertical"
android:layout_weight="1"
android:gravity="start|center_vertical"
android:lineSpacingMultiplier="@dimen/bottom_sheet_text_spacing_multiplier"
android:textColor="@color/preference_category_title"
android:textSize="@dimen/default_list_text_size"
osmand:typeface="@string/font_roboto_medium"
tools:text="@string/route_between_points" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

View file

@ -19,6 +19,7 @@
<attr name="expandable_list_background" format="color"/>
<attr name="bg_color" format="reference" />
<attr name="bg_circle" format="reference"/>
<attr name="bg_circle_contour" format="reference" />
<attr name="btn_round" format="reference" />
<attr name="btn_round_border" format="reference" />
<attr name="btn_round_border_2" format="reference" />

View file

@ -11,6 +11,7 @@
Thx - Hardy
-->
<string name="message_you_need_add_two_points_to_show_graphs">You must add at least two points.</string>
<string name="icon_group_travel">Travel</string>
<string name="icon_group_emergency">Emergency</string>
<string name="icon_group_sport">Sport</string>

View file

@ -92,6 +92,7 @@
<item name="actionBarStyle">@style/Widget.Styled.ActionBarLight</item>
<item name="bg_color">@color/list_background_color_light</item>
<item name="bg_circle">@drawable/circle_background_light</item>
<item name="bg_circle_contour">@drawable/circle_contour_bg_light</item>
<item name="btn_round">@drawable/btn_round_light</item>
<item name="btn_round_border">@drawable/btn_round_border_light</item>
<item name="btn_round_border_2">@drawable/btn_round_border_light_2</item>
@ -394,6 +395,7 @@
<item name="actionBarStyle">@style/Widget.Styled.ActionBarDark</item>
<item name="bg_color">@color/list_background_color_dark</item>
<item name="bg_circle">@drawable/circle_background_dark</item>
<item name="bg_circle_contour">@drawable/circle_contour_bg_dark</item>
<item name="btn_round">@drawable/btn_round_dark</item>
<item name="btn_round_border">@drawable/btn_round_border_dark</item>
<item name="btn_round_border_2">@drawable/btn_round_border_dark_2</item>

View file

@ -0,0 +1,30 @@
package net.osmand.plus.helpers;
import android.graphics.RectF;
import androidx.annotation.NonNull;
import com.github.mikephil.charting.charts.BarChart;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.renderer.HorizontalBarChartRenderer;
import net.osmand.AndroidUtils;
public class CustomBarChartRenderer extends HorizontalBarChartRenderer {
private float highlightHalfWidth;
public CustomBarChartRenderer(@NonNull BarChart chart) {
this(chart, AndroidUtils.dpToPx(chart.getContext(), 1f) / 2f);
}
public CustomBarChartRenderer(@NonNull BarChart chart, float highlightHalfWidth) {
super(chart, chart.getAnimator(), chart.getViewPortHandler());
this.highlightHalfWidth = highlightHalfWidth;
}
@Override
protected void setHighlightDrawPos(Highlight high, RectF bar) {
bar.left = high.getDrawX() - highlightHalfWidth;
bar.right = high.getDrawX() + highlightHalfWidth;
}
}

View file

@ -76,6 +76,8 @@ import net.osmand.plus.ContextMenuAdapter;
import net.osmand.plus.ContextMenuItem;
import net.osmand.plus.GPXDatabase.GpxDataItem;
import net.osmand.plus.GpxDbHelper.GpxDataItemCallback;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayGroup;
import net.osmand.plus.GpxSelectionHelper.SelectedGpxFile;
import net.osmand.plus.OsmAndConstants;
import net.osmand.plus.OsmAndFormatter;
@ -2045,6 +2047,91 @@ public class GpxUiHelper {
return gpx;
}
public enum LineGraphType {
ALTITUDE,
SLOPE,
SPEED;
}
public static List<ILineDataSet> getDataSets(LineChart chart,
OsmandApplication app,
GPXTrackAnalysis analysis,
@NonNull LineGraphType firstType,
@Nullable LineGraphType secondType,
boolean calcWithoutGaps) {
if (app == null || chart == null || analysis == null) {
return new ArrayList<>();
}
List<ILineDataSet> result = new ArrayList<>();
if (secondType == null) {
ILineDataSet dataSet = getDataSet(chart, app, analysis, calcWithoutGaps, false, firstType);
if (dataSet != null) {
result.add(dataSet);
}
} else {
OrderedLineDataSet dataSet1 = getDataSet(chart, app, analysis, calcWithoutGaps, false, firstType);
OrderedLineDataSet dataSet2 = getDataSet(chart, app, analysis, calcWithoutGaps, true, secondType);
if (dataSet1 == null && dataSet2 == null) {
return new ArrayList<>();
} else if (dataSet1 == null) {
result.add(dataSet2);
} else if (dataSet2 == null) {
result.add(dataSet1);
} else if (dataSet1.getPriority() < dataSet2.getPriority()) {
result.add(dataSet2);
result.add(dataSet1);
} else {
result.add(dataSet1);
result.add(dataSet2);
}
}
return result;
}
private static OrderedLineDataSet getDataSet(@NonNull LineChart chart,
@NonNull OsmandApplication app,
@NonNull GPXTrackAnalysis analysis,
boolean calcWithoutGaps,
boolean useRightAxis,
@NonNull LineGraphType type) {
OrderedLineDataSet dataSet = null;
switch (type) {
case ALTITUDE: {
if (analysis.hasElevationData) {
dataSet = GpxUiHelper.createGPXElevationDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, useRightAxis, true, calcWithoutGaps);
}
break;
}
case SLOPE:
if (analysis.hasElevationData) {
dataSet = GpxUiHelper.createGPXSlopeDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, null, useRightAxis, true, calcWithoutGaps);
}
break;
case SPEED: {
if (analysis.hasSpeedData) {
dataSet = GpxUiHelper.createGPXSpeedDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, useRightAxis, true, calcWithoutGaps);
}
break;
}
}
return dataSet;
}
public static GpxDisplayItem makeGpxDisplayItem(OsmandApplication app, GPXUtilities.GPXFile gpx) {
GpxDisplayItem gpxItem = null;
String groupName = app.getString(R.string.current_route);
GpxDisplayGroup group = app.getSelectedGpxHelper().buildGpxDisplayGroup(gpx, 0, groupName);
if (group != null && group.getModifiableList().size() > 0) {
gpxItem = group.getModifiableList().get(0);
if (gpxItem != null) {
gpxItem.route = true;
}
}
return gpxItem;
}
public static class GPXInfo {

View file

@ -125,8 +125,7 @@ public class TrackDetailsMenu {
hidding = true;
fragment.dismiss(backPressed);
} else {
segment = null;
trackChartPoints = null;
reset();
}
}
@ -274,8 +273,7 @@ public class TrackDetailsMenu {
if (hidding) {
hidding = false;
visible = false;
segment = null;
trackChartPoints = null;
reset();
}
}
@ -532,6 +530,11 @@ public class TrackDetailsMenu {
fitTrackOnMap(chart, location, forceFit);
}
public void reset() {
segment = null;
trackChartPoints = null;
}
private List<LatLon> getXAxisPoints(LineChart chart) {
float[] entries = chart.getXAxis().mEntries;
LineData lineData = chart.getLineData();

View file

@ -1,11 +1,14 @@
package net.osmand.plus.measurementtool;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@ -16,15 +19,23 @@ import com.github.mikephil.charting.data.ChartData;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem;
import net.osmand.AndroidUtils;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.helpers.GpxUiHelper;
import net.osmand.plus.helpers.GpxUiHelper.GPXDataSetAxisType;
import net.osmand.plus.helpers.GpxUiHelper.OrderedLineDataSet;
import net.osmand.plus.helpers.GpxUiHelper.LineGraphType;
import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter;
import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter.HorizontalSelectionAdapterListener;
import net.osmand.plus.mapcontextmenu.other.TrackDetailsMenu;
import net.osmand.plus.measurementtool.MeasurementToolFragment.OnUpdateAdditionalInfoListener;
import net.osmand.plus.measurementtool.graph.CommonGraphAdapter;
import net.osmand.plus.measurementtool.graph.CustomGraphAdapter;
import net.osmand.plus.measurementtool.graph.CustomGraphAdapter.LegendViewType;
import net.osmand.plus.measurementtool.graph.BaseGraphAdapter;
import net.osmand.plus.measurementtool.graph.GraphAdapterHelper;
import net.osmand.plus.measurementtool.graph.GraphAdapterHelper.RefreshMapCallback;
import net.osmand.plus.routepreparationmenu.RouteDetailsFragment;
import net.osmand.plus.routepreparationmenu.cards.BaseCard;
import net.osmand.router.RouteSegmentResult;
@ -32,48 +43,44 @@ import net.osmand.util.Algorithms;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import static net.osmand.GPXUtilities.GPXFile;
import static net.osmand.GPXUtilities.GPXTrackAnalysis;
import static net.osmand.plus.helpers.GpxUiHelper.LineGraphType.ALTITUDE;
import static net.osmand.plus.helpers.GpxUiHelper.LineGraphType.SLOPE;
import static net.osmand.plus.helpers.GpxUiHelper.LineGraphType.SPEED;
import static net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter.HorizontalSelectionItem;
import static net.osmand.router.RouteStatisticsHelper.RouteStatistics;
public class GraphsCard extends BaseCard implements OnUpdateAdditionalInfoListener {
private static String GRAPH_DATA_GPX_FILE_NAME = "graph_data_tmp";
private static int INVALID_ID = -1;
private MeasurementEditingContext editingCtx;
private MeasurementToolFragment fragment;
private GraphType visibleGraphType;
private List<GraphType> graphTypes = new ArrayList<>();
private TrackDetailsMenu trackDetailsMenu;
private RefreshMapCallback refreshMapCallback;
private GPXFile gpxFile;
private GPXTrackAnalysis analysis;
private GpxDisplayItem gpxItem;
private View commonGraphContainer;
private View customGraphContainer;
private View messageContainer;
private LineChart commonGraphChart;
private HorizontalBarChart customGraphChart;
private CommonGraphAdapter commonGraphAdapter;
private CustomGraphAdapter customGraphAdapter;
private RecyclerView graphTypesMenu;
private enum CommonGraphType {
OVERVIEW(R.string.shared_string_overview, false),
ALTITUDE(R.string.altitude, true),
SLOPE(R.string.shared_string_slope, true),
SPEED(R.string.map_widget_speed, false);
private GraphType visibleType;
private List<GraphType> graphTypes = new ArrayList<>();
CommonGraphType(int titleId, boolean canBeCalculated) {
this.titleId = titleId;
this.canBeCalculated = canBeCalculated;
}
final int titleId;
final boolean canBeCalculated;
}
public GraphsCard(@NonNull MapActivity mapActivity, MeasurementToolFragment fragment) {
public GraphsCard(@NonNull MapActivity mapActivity,
TrackDetailsMenu trackDetailsMenu,
MeasurementToolFragment fragment) {
super(mapActivity);
this.trackDetailsMenu = trackDetailsMenu;
this.fragment = fragment;
}
@ -82,19 +89,27 @@ public class GraphsCard extends BaseCard implements OnUpdateAdditionalInfoListen
if (mapActivity == null || fragment == null) return;
editingCtx = fragment.getEditingCtx();
graphTypesMenu = view.findViewById(R.id.graph_types_recycler_view);
graphTypesMenu.setLayoutManager(new LinearLayoutManager(mapActivity, RecyclerView.HORIZONTAL, false));
commonGraphContainer = view.findViewById(R.id.common_graphs_container);
customGraphContainer = view.findViewById(R.id.custom_graphs_container);
messageContainer = view.findViewById(R.id.message_container);
commonGraphChart = (LineChart) view.findViewById(R.id.line_chart);
customGraphChart = (HorizontalBarChart) view.findViewById(R.id.horizontal_chart);
updateGraphData();
LineChart lineChart = (LineChart) view.findViewById(R.id.line_chart);
HorizontalBarChart barChart = (HorizontalBarChart) view.findViewById(R.id.horizontal_chart);
commonGraphAdapter = new CommonGraphAdapter(lineChart, true);
customGraphAdapter = new CustomGraphAdapter(barChart, true);
graphTypesMenu = view.findViewById(R.id.graph_types_recycler_view);
graphTypesMenu.setLayoutManager(
new LinearLayoutManager(mapActivity, RecyclerView.HORIZONTAL, false));
customGraphAdapter.setLegendContainer((ViewGroup) view.findViewById(R.id.route_legend));
customGraphAdapter.setLayoutChangeListener(new BaseGraphAdapter.LayoutChangeListener() {
@Override
public void onLayoutChanged() {
setLayoutNeeded();
}
});
refreshGraphTypesSelectionMenu();
updateDataView();
GraphAdapterHelper.bindGraphAdapters(commonGraphAdapter, Collections.singletonList((BaseGraphAdapter) customGraphAdapter), (ViewGroup) view);
refreshMapCallback = GraphAdapterHelper.bindToMap(commonGraphAdapter, mapActivity, trackDetailsMenu);
fullUpdate();
}
@Override
@ -104,15 +119,32 @@ public class GraphsCard extends BaseCard implements OnUpdateAdditionalInfoListen
@Override
public void onUpdateAdditionalInfo() {
if (!isRouteCalculating()) {
updateGraphData();
refreshGraphTypesSelectionMenu();
if (editingCtx != null) {
fullUpdate();
}
updateDataView();
}
private void refreshGraphTypesSelectionMenu() {
graphTypesMenu.removeAllViews();
private void fullUpdate() {
if (!isRouteCalculating()) {
updateData();
setupVisibleType();
updateMenu();
}
updateView();
updateChartOnMap();
}
private void updateMenu() {
if (!editingCtx.isPointsEnoughToCalculateRoute()) {
graphTypesMenu.setVisibility(View.GONE);
} else {
graphTypesMenu.setVisibility(View.VISIBLE);
graphTypesMenu.removeAllViews();
fillInMenu();
}
}
private void fillInMenu() {
OsmandApplication app = getMyApplication();
int activeColorId = nightMode ? R.color.active_color_primary_dark : R.color.active_color_primary_light;
final HorizontalSelectionAdapter adapter = new HorizontalSelectionAdapter(app, nightMode);
@ -127,16 +159,16 @@ public class GraphsCard extends BaseCard implements OnUpdateAdditionalInfoListen
}
}
adapter.setItems(items);
String selectedItemKey = visibleGraphType.getTitle();
String selectedItemKey = visibleType.getTitle();
adapter.setSelectedItemByTitle(selectedItemKey);
adapter.setListener(new HorizontalSelectionAdapter.HorizontalSelectionAdapterListener() {
adapter.setListener(new HorizontalSelectionAdapterListener() {
@Override
public void onItemSelected(HorizontalSelectionAdapter.HorizontalSelectionItem item) {
public void onItemSelected(HorizontalSelectionItem item) {
adapter.setItems(items);
adapter.setSelectedItem(item);
GraphType chosenGraphType = (GraphType) item.getObject();
if (!isCurrentVisibleType(chosenGraphType)) {
setupVisibleGraphType(chosenGraphType);
GraphType chosenType = (GraphType) item.getObject();
if (!isVisibleType(chosenType)) {
changeVisibleType(chosenType);
}
}
});
@ -144,19 +176,19 @@ public class GraphsCard extends BaseCard implements OnUpdateAdditionalInfoListen
adapter.notifyDataSetChanged();
}
private void setupVisibleGraphType(GraphType type) {
visibleGraphType = type;
updateDataView();
private void changeVisibleType(GraphType type) {
visibleType = type;
updateView();
}
private boolean isCurrentVisibleType(GraphType type) {
if (visibleGraphType != null && type != null) {
return Algorithms.objectEquals(visibleGraphType.getTitle(), type.getTitle());
private boolean isVisibleType(GraphType type) {
if (visibleType != null && type != null) {
return Algorithms.objectEquals(visibleType.getTitle(), type.getTitle());
}
return false;
}
private GraphType getFirstAvailableGraphType() {
private GraphType getFirstAvailableType() {
for (GraphType type : graphTypes) {
if (type.isAvailable()) {
return type;
@ -165,208 +197,181 @@ public class GraphsCard extends BaseCard implements OnUpdateAdditionalInfoListen
return null;
}
private void updateDataView() {
if (isRouteCalculating()) {
showProgressMessage();
} else if (visibleGraphType.hasData()) {
private void updateView() {
hideAll();
if (!editingCtx.isPointsEnoughToCalculateRoute()) {
showMessage(app.getString(R.string.message_you_need_add_two_points_to_show_graphs));
} else if (isRouteCalculating()) {
showMessage(app.getString(R.string.message_graph_will_be_available_after_recalculation), true);
} else if (visibleType.hasData()) {
showGraph();
} else if (visibleGraphType.canBeCalculated()) {
showMessage();
} else if (visibleType.canBeCalculated()) {
showMessage(app.getString(R.string.message_need_calculate_route_before_show_graph,
visibleType.getTitle()), R.drawable.ic_action_altitude_average,
app.getString(R.string.route_between_points), new View.OnClickListener() {
@Override
public void onClick(View v) {
fragment.startSnapToRoad(false);
}
});
}
}
private void showProgressMessage() {
private void hideAll() {
commonGraphContainer.setVisibility(View.GONE);
customGraphContainer.setVisibility(View.GONE);
messageContainer.setVisibility(View.GONE);
}
private void showMessage(String text) {
showMessage(text, INVALID_ID, false, null, null);
}
private void showMessage(String text, @DrawableRes int iconResId, String btnTitle, View.OnClickListener btnListener) {
showMessage(text, iconResId, false, btnTitle, btnListener);
}
private void showMessage(String text, boolean showProgressBar) {
showMessage(text, INVALID_ID, showProgressBar, null, null);
}
private void showMessage(@NonNull String text,
@DrawableRes int iconResId,
boolean showProgressBar,
String btnTitle,
View.OnClickListener btnListener) {
messageContainer.setVisibility(View.VISIBLE);
TextView tvMessage = messageContainer.findViewById(R.id.message_text);
tvMessage.setText(text);
ImageView icon = messageContainer.findViewById(R.id.message_icon);
if (iconResId != INVALID_ID) {
icon.setVisibility(View.VISIBLE);
icon.setImageResource(iconResId);
} else {
icon.setVisibility(View.GONE);
}
ProgressBar pb = messageContainer.findViewById(R.id.progress_bar);
pb.setVisibility(View.VISIBLE);
icon.setVisibility(View.GONE);
tvMessage.setText(R.string.message_graph_will_be_available_after_recalculation);
pb.setVisibility(showProgressBar ? View.VISIBLE : View.GONE);
View btnContainer = messageContainer.findViewById(R.id.btn_container);
if (btnTitle != null) {
TextView tvBtnTitle = btnContainer.findViewById(R.id.btn_text);
tvBtnTitle.setText(btnTitle);
btnContainer.setVisibility(View.VISIBLE);
} else {
btnContainer.setVisibility(View.GONE);
}
if (btnListener != null) {
btnContainer.setOnClickListener(btnListener);
}
}
private void showGraph() {
if (visibleGraphType.isCustom()) {
customGraphChart.clear();
commonGraphContainer.setVisibility(View.GONE);
if (visibleType.isCustom()) {
CustomGraphType customGraphType = (CustomGraphType) visibleType;
customGraphContainer.setVisibility(View.VISIBLE);
messageContainer.setVisibility(View.GONE);
prepareCustomGraphView((BarData) visibleGraphType.getGraphData());
customGraphAdapter.setLegendViewType(LegendViewType.ONE_ELEMENT);
customGraphAdapter.updateContent(customGraphType.getChartData(), customGraphType.getStatistics());
} else {
commonGraphChart.clear();
CommonGraphType commonGraphType = (CommonGraphType) visibleType;
commonGraphContainer.setVisibility(View.VISIBLE);
customGraphContainer.setVisibility(View.GONE);
messageContainer.setVisibility(View.GONE);
prepareCommonGraphView((LineData) visibleGraphType.getGraphData());
customGraphAdapter.setLegendViewType(LegendViewType.GONE);
commonGraphAdapter.updateContent(commonGraphType.getChartData(), gpxItem);
}
}
private void showMessage() {
commonGraphContainer.setVisibility(View.GONE);
customGraphContainer.setVisibility(View.GONE);
messageContainer.setVisibility(View.VISIBLE);
TextView tvMessage = messageContainer.findViewById(R.id.message_text);
ImageView icon = messageContainer.findViewById(R.id.message_icon);
ProgressBar pb = messageContainer.findViewById(R.id.progress_bar);
pb.setVisibility(View.GONE);
icon.setVisibility(View.VISIBLE);
tvMessage.setText(app.getString(
R.string.message_need_calculate_route_before_show_graph,
visibleGraphType.getTitle()));
icon.setImageResource(R.drawable.ic_action_altitude_average);
}
private void prepareCommonGraphView(LineData data) {
GpxUiHelper.setupGPXChart(commonGraphChart, 4, 24f, 16f, !nightMode, true);
commonGraphChart.setData(data);
}
private void prepareCustomGraphView(BarData data) {
OsmandApplication app = getMyApplication();
if (app == null) return;
GpxUiHelper.setupHorizontalGPXChart(app, customGraphChart, 5, 9, 24, true, nightMode);
customGraphChart.setExtraRightOffset(16);
customGraphChart.setExtraLeftOffset(16);
customGraphChart.setData(data);
}
private void updateGraphData() {
private void updateData() {
graphTypes.clear();
OsmandApplication app = getMyApplication();
GPXTrackAnalysis analysis = createGpxTrackAnalysis();
gpxFile = getGpxFile();
analysis = gpxFile != null ? gpxFile.getAnalysis(0) : null;
gpxItem = gpxFile != null ? GpxUiHelper.makeGpxDisplayItem(app, gpxFile) : null;
if (gpxItem != null) {
trackDetailsMenu.setGpxItem(gpxItem);
}
if (analysis == null) return;
// update common graph data
for (CommonGraphType commonType : CommonGraphType.values()) {
List<ILineDataSet> dataSets = getDataSets(commonType, commonGraphChart, analysis);
LineData data = null;
if (!Algorithms.isEmpty(dataSets)) {
data = new LineData(dataSets);
}
String title = app.getString(commonType.titleId);
graphTypes.add(new GraphType(title, false, commonType.canBeCalculated, data));
}
boolean hasElevationData = analysis.hasElevationData;
boolean hasSpeedData = analysis.isSpeedSpecified();
addCommonType(R.string.shared_string_overview, true, hasElevationData, ALTITUDE, SLOPE);
addCommonType(R.string.altitude, true, hasElevationData, ALTITUDE, null);
addCommonType(R.string.shared_string_slope, true, hasElevationData, SLOPE, null);
addCommonType(R.string.map_widget_speed, false, hasSpeedData, SPEED, null);
// update custom graph data
List<RouteSegmentResult> routeSegments = editingCtx.getAllRouteSegments();
List<RouteStatistics> routeStatistics = calculateRouteStatistics(routeSegments);
List<RouteStatistics> routeStatistics = calculateRouteStatistics();
if (analysis != null && routeStatistics != null) {
for (RouteStatistics statistics : routeStatistics) {
String title = AndroidUtils.getStringRouteInfoPropertyValue(app, statistics.name);
BarData data = null;
if (!Algorithms.isEmpty(statistics.elements)) {
data = GpxUiHelper.buildStatisticChart(
app, customGraphChart, statistics, analysis, true, nightMode);
}
graphTypes.add(new GraphType(title, true, false, data));
graphTypes.add(new CustomGraphType(title, statistics));
}
}
}
// update current visible graph type
if (visibleGraphType == null) {
visibleGraphType = getFirstAvailableGraphType();
private void updateChartOnMap() {
if (hasVisibleGraph()) {
trackDetailsMenu.reset();
refreshMapCallback.refreshMap(false);
}
}
private void addCommonType(int titleId,
boolean canBeCalculated,
boolean hasData,
LineGraphType firstType,
LineGraphType secondType) {
OsmandApplication app = getMyApplication();
String title = app.getString(titleId);
graphTypes.add(new CommonGraphType(title, canBeCalculated, hasData, firstType, secondType));
}
private void setupVisibleType() {
if (visibleType == null) {
visibleType = getFirstAvailableType();
} else {
for (GraphType type : graphTypes) {
if (isCurrentVisibleType(type)) {
visibleGraphType = type.isAvailable() ? type : getFirstAvailableGraphType();
if (isVisibleType(type)) {
visibleType = type.isAvailable() ? type : getFirstAvailableType();
break;
}
}
}
}
private List<ILineDataSet> getDataSets(CommonGraphType type, LineChart chart, GPXTrackAnalysis analysis) {
List<ILineDataSet> dataSets = new ArrayList<>();
if (chart != null && analysis != null) {
OsmandApplication app = getMyApplication();
switch (type) {
case OVERVIEW: {
List<OrderedLineDataSet> dataList = new ArrayList<>();
if (analysis.hasSpeedData) {
OrderedLineDataSet speedDataSet = GpxUiHelper.createGPXSpeedDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, true, true, false);
dataList.add(speedDataSet);
}
if (analysis.hasElevationData) {
OrderedLineDataSet elevationDataSet = GpxUiHelper.createGPXElevationDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, false, true, false);
dataList.add(elevationDataSet);
OrderedLineDataSet slopeDataSet = GpxUiHelper.createGPXSlopeDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, null, true, true, false);
dataList.add(slopeDataSet);
}
if (dataList.size() > 0) {
Collections.sort(dataList, new Comparator<OrderedLineDataSet>() {
@Override
public int compare(OrderedLineDataSet o1, OrderedLineDataSet o2) {
return Float.compare(o1.getPriority(), o2.getPriority());
}
});
}
dataSets.addAll(dataList);
break;
}
case ALTITUDE: {
if (analysis.hasElevationData) {
OrderedLineDataSet elevationDataSet = GpxUiHelper.createGPXElevationDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, false, true, false);//calcWithoutGaps);
dataSets.add(elevationDataSet);
}
break;
}
case SLOPE:
if (analysis.hasElevationData) {
OrderedLineDataSet slopeDataSet = GpxUiHelper.createGPXSlopeDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, null, true, true, false);
dataSets.add(slopeDataSet);
}
break;
case SPEED: {
if (analysis.hasSpeedData) {
OrderedLineDataSet speedDataSet = GpxUiHelper.createGPXSpeedDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, false, true, false);//calcWithoutGaps);
dataSets.add(speedDataSet);
}
break;
}
}
}
return dataSets;
}
private GPXTrackAnalysis createGpxTrackAnalysis() {
GPXFile gpx;
if (editingCtx.getGpxData() != null) {
gpx = editingCtx.getGpxData().getGpxFile();
private GPXFile getGpxFile() {
if (fragment.isTrackReadyToCalculate()) {
return editingCtx.exportGpx(GRAPH_DATA_GPX_FILE_NAME);
} else {
gpx = editingCtx.exportGpx(GRAPH_DATA_GPX_FILE_NAME);
GpxData gpxData = editingCtx.getGpxData();
return gpxData != null ? gpxData.getGpxFile() : null;
}
return gpx != null ? gpx.getAnalysis(0) : null;
}
private List<RouteStatistics> calculateRouteStatistics(List<RouteSegmentResult> route) {
private List<RouteStatistics> calculateRouteStatistics() {
OsmandApplication app = getMyApplication();
if (route == null || app == null) return null;
return RouteDetailsFragment.calculateRouteStatistics(app, route, nightMode);
List<RouteSegmentResult> route = editingCtx.getAllRouteSegments();
if (route != null && app != null) {
return RouteDetailsFragment.calculateRouteStatistics(app, route, nightMode);
}
return null;
}
private boolean isRouteCalculating() {
return fragment.isProgressBarVisible();
}
private static class GraphType {
private String title;
private boolean isCustom;
private boolean canBeCalculated;
private ChartData graphData;
public boolean hasVisibleGraph() {
return (commonGraphContainer != null && commonGraphContainer.getVisibility() == View.VISIBLE)
|| (customGraphContainer != null && customGraphContainer.getVisibility() == View.VISIBLE);
}
public GraphType(String title, boolean isCustom, boolean canBeCalculated, ChartData graphData) {
private abstract class GraphType<T extends ChartData> {
private String title;
private boolean canBeCalculated;
public GraphType(String title, boolean canBeCalculated) {
this.title = title;
this.isCustom = isCustom;
this.canBeCalculated = canBeCalculated;
this.graphData = graphData;
}
public String getTitle() {
@ -374,23 +379,80 @@ public class GraphsCard extends BaseCard implements OnUpdateAdditionalInfoListen
}
public boolean isCustom() {
return isCustom;
return this instanceof CustomGraphType;
}
public boolean isAvailable() {
return hasData() || canBeCalculated();
return isPointsCountEnoughToCalculateRoute() && (hasData() || canBeCalculated());
}
private boolean isPointsCountEnoughToCalculateRoute() {
return editingCtx.getPointsCount() >= 2;
}
public boolean canBeCalculated() {
return canBeCalculated;
}
public boolean hasData() {
return getGraphData() != null;
public abstract boolean hasData();
public abstract T getChartData();
}
private class CommonGraphType extends GraphType<LineData> {
private boolean hasData;
private LineGraphType firstType;
private LineGraphType secondType;
public CommonGraphType(String title, boolean canBeCalculated, boolean hasData, @NonNull LineGraphType firstType, @Nullable LineGraphType secondType) {
super(title, canBeCalculated);
this.hasData = hasData;
this.firstType = firstType;
this.secondType = secondType;
}
public ChartData getGraphData() {
return graphData;
@Override
public boolean hasData() {
return hasData;
}
@Override
public LineData getChartData() {
GpxUiHelper.setupGPXChart(commonGraphAdapter.getChart(), 4, 24f, 16f, !nightMode, true);
List<ILineDataSet> dataSets = GpxUiHelper.getDataSets(commonGraphAdapter.getChart(),
app, analysis, firstType, secondType, false);
return !Algorithms.isEmpty(dataSets) ? new LineData(dataSets) : null;
}
}
private class CustomGraphType extends GraphType<BarData> {
private RouteStatistics statistics;
public CustomGraphType(String title, RouteStatistics statistics) {
super(title, false);
this.statistics = statistics;
}
public RouteStatistics getStatistics() {
return statistics;
}
@Override
public boolean hasData() {
return !Algorithms.isEmpty(statistics.elements);
}
@Override
public BarData getChartData() {
GpxUiHelper.setupHorizontalGPXChart(app, customGraphAdapter.getChart(), 5, 9, 24, true, nightMode);
BarData data = null;
if (!Algorithms.isEmpty(statistics.elements)) {
data = GpxUiHelper.buildStatisticChart(app, customGraphAdapter.getChart(),
statistics, analysis, true, nightMode);
}
return data;
}
}
}

View file

@ -398,6 +398,10 @@ public class MeasurementEditingContext {
return before.points.size();
}
public boolean isPointsEnoughToCalculateRoute() {
return getPointsCount() >= 2;
}
public List<RouteSegmentResult> getAllRouteSegments() {
List<RouteSegmentResult> allSegments = new ArrayList<>();
for (Pair<WptPt, WptPt> key : getOrderedRoadSegmentDataKeys()) {

View file

@ -50,6 +50,7 @@ import net.osmand.plus.activities.TrackActivity;
import net.osmand.plus.base.BaseOsmAndFragment;
import net.osmand.plus.base.ContextMenuFragment.MenuState;
import net.osmand.plus.helpers.AndroidUiHelper;
import net.osmand.plus.mapcontextmenu.other.TrackDetailsMenu;
import net.osmand.plus.measurementtool.GpxApproximationFragment.GpxApproximationFragmentListener;
import net.osmand.plus.measurementtool.OptionsBottomSheetDialogFragment.OptionsFragmentListener;
import net.osmand.plus.measurementtool.RouteBetweenPointsBottomSheetDialogFragment.RouteBetweenPointsDialogMode;
@ -113,8 +114,10 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
private TextView distanceToCenterTv;
private String pointsSt;
private View additionalInfoContainer;
private ViewGroup additionalInfoCardsContainer;
private BaseCard visibleAdditionalInfoCard;
private ViewGroup cardsContainer;
private BaseCard visibleCard;
private PointsCard pointsCard;
private GraphsCard graphsCard;
private LinearLayout customRadioButton;
private View mainView;
private ImageView upDownBtn;
@ -141,6 +144,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
private int cachedMapPosition;
private MeasurementEditingContext editingCtx = new MeasurementEditingContext();
private GraphDetailsMenu detailsMenu;
private LatLon initialPoint;
@ -160,6 +164,19 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
GRAPH
}
private class GraphDetailsMenu extends TrackDetailsMenu {
@Override
protected int getFragmentWidth() {
return mainView.getWidth();
}
@Override
protected int getFragmentHeight() {
return mainView.getHeight();
}
}
private void setEditingCtx(MeasurementEditingContext editingCtx) {
this.editingCtx = editingCtx;
}
@ -253,8 +270,9 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
mainView = view.findViewById(R.id.main_view);
AndroidUtils.setBackground(mapActivity, mainView, nightMode, R.drawable.bg_bottom_menu_light, R.drawable.bg_bottom_menu_dark);
detailsMenu = new GraphDetailsMenu();
additionalInfoContainer = mainView.findViewById(R.id.additional_info_container);
additionalInfoCardsContainer = mainView.findViewById(R.id.cards_container);
cardsContainer = mainView.findViewById(R.id.cards_container);
if (portrait) {
customRadioButton = mainView.findViewById(R.id.custom_radio_buttons);
@ -278,6 +296,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
}
});
}
pointsCard = new PointsCard(mapActivity, this);
graphsCard = new GraphsCard(mapActivity, detailsMenu, this);
if (progressBarVisible) {
showProgressBar();
@ -513,29 +533,33 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
if (!additionalInfoExpanded || !isCurrentAdditionalInfoType(type)) {
MapActivity ma = getMapActivity();
if (ma == null) return;
currentAdditionalInfoType = type;
updateUpDownBtn();
OsmandApplication app = ma.getMyApplication();
BaseCard additionalInfoCard = null;
if (AdditionalInfoType.POINTS == type) {
additionalInfoCard = new PointsCard(ma, this);
visibleCard = pointsCard;
UiUtilities.updateCustomRadioButtons(app, customRadioButton, nightMode, START);
} else if (AdditionalInfoType.GRAPH == type) {
additionalInfoCard = new GraphsCard(ma, this);
visibleCard = graphsCard;
UiUtilities.updateCustomRadioButtons(app, customRadioButton, nightMode, END);
}
if (additionalInfoCard != null) {
visibleAdditionalInfoCard = additionalInfoCard;
additionalInfoCardsContainer.removeAllViews();
additionalInfoCardsContainer.addView(additionalInfoCard.build(ma));
additionalInfoExpanded = true;
}
cardsContainer.removeAllViews();
View cardView = visibleCard.getView() != null ? visibleCard.getView() : visibleCard.build(ma);
cardsContainer.addView(cardView);
currentAdditionalInfoType = type;
additionalInfoExpanded = true;
updateUpDownBtn();
}
}
private void updateAdditionalInfoView() {
if (visibleAdditionalInfoCard instanceof OnUpdateAdditionalInfoListener) {
((OnUpdateAdditionalInfoListener) visibleAdditionalInfoCard).onUpdateAdditionalInfo();
updateAdditionalInfoView(pointsCard);
updateAdditionalInfoView(graphsCard);
}
private void updateAdditionalInfoView(OnUpdateAdditionalInfoListener listener) {
if (listener != null) {
listener.onUpdateAdditionalInfo();
}
}
@ -588,6 +612,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
super.onResume();
MapActivity mapActivity = getMapActivity();
if (mapActivity != null) {
detailsMenu.setMapActivity(mapActivity);
mapActivity.getMapLayers().getMapControlsLayer().addThemeInfoProviderTag(TAG);
mapActivity.getMapLayers().getMapControlsLayer().showMapControlsIfHidden();
cachedMapPosition = mapActivity.getMapView().getMapPosition();
@ -603,6 +628,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
if (mapActivity != null) {
mapActivity.getMapLayers().getMapControlsLayer().removeThemeInfoProviderTag(TAG);
}
detailsMenu.onDismiss();
detailsMenu.setMapActivity(null);
setMapPosition(cachedMapPosition);
}
@ -669,7 +696,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
mainIcon.setImageDrawable(getActiveIcon(gpxData != null ? R.drawable.ic_action_polygom_dark : R.drawable.ic_action_ruler));
}
private void startSnapToRoad(boolean rememberPreviousTitle) {
public void startSnapToRoad(boolean rememberPreviousTitle) {
MapActivity mapActivity = getMapActivity();
if (mapActivity != null) {
if (rememberPreviousTitle) {
@ -1189,7 +1216,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
final ApplicationMode appMode = editingCtx.getAppMode();
if (mapActivity != null) {
Drawable icon;
if (!editingCtx.isApproximationNeeded() || editingCtx.isNewData()) {
if (isTrackReadyToCalculate()) {
if (appMode == MeasurementEditingContext.DEFAULT_APP_MODE) {
icon = getActiveIcon(R.drawable.ic_action_split_interval);
} else {
@ -1204,6 +1231,10 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
}
}
public boolean isTrackReadyToCalculate() {
return !editingCtx.isApproximationNeeded() || editingCtx.isNewData();
}
private void hideSnapToRoadIcon() {
MapActivity mapActivity = getMapActivity();
if (mapActivity != null) {
@ -1488,6 +1519,10 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
return type.equals(currentAdditionalInfoType);
}
public boolean hasVisibleGraph() {
return graphsCard != null && graphsCard.hasVisibleGraph();
}
private String getSuggestedFileName() {
GpxData gpxData = editingCtx.getGpxData();
String displayedName = null;

View file

@ -24,7 +24,9 @@ public class PointsCard extends BaseCard implements OnUpdateAdditionalInfoListen
@Override
public void onUpdateAdditionalInfo() {
adapter.notifyDataSetChanged();
if (adapter != null) {
adapter.notifyDataSetChanged();
}
}
@Override

View file

@ -0,0 +1,82 @@
package net.osmand.plus.measurementtool.graph;
import android.view.MotionEvent;
import com.github.mikephil.charting.charts.Chart;
import com.github.mikephil.charting.data.ChartData;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.listener.ChartTouchListener;
import net.osmand.plus.OsmandApplication;
public abstract class BaseGraphAdapter<_Chart extends Chart, _ChartData extends ChartData, _Data> {
private Highlight lastKnownHighlight;
protected _Chart chart;
protected _ChartData chartData;
protected _Data additionalData;
protected boolean usedOnMap;
public BaseGraphAdapter(_Chart chart, boolean usedOnMap) {
this.chart = chart;
this.usedOnMap = usedOnMap;
prepareChartView();
}
protected void prepareChartView() {
chart.setExtraRightOffset(16);
chart.setExtraLeftOffset(16);
}
public _Chart getChart() {
return chart;
}
protected void updateHighlight() {
highlight(lastKnownHighlight);
}
public void highlight(Highlight h) {
this.lastKnownHighlight = h;
}
public void updateContent(_ChartData chartData, _Data data) {
updateData(chartData, data);
updateView();
}
public void updateData(_ChartData chartData, _Data data) {
this.chartData = chartData;
this.additionalData = data;
}
public abstract void updateView();
protected boolean isNightMode() {
OsmandApplication app = getMyApplication();
if (app != null) {
return usedOnMap ? app.getDaynightHelper().isNightModeForMapControls()
: !app.getSettings().isLightContent();
}
return false;
}
protected OsmandApplication getMyApplication() {
return (OsmandApplication) chart.getContext().getApplicationContext();
}
public interface ExternalValueSelectedListener {
void onValueSelected(Entry e, Highlight h);
void onNothingSelected();
}
public interface ExternalGestureListener {
void onChartGestureStart(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture);
void onChartGestureEnd(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture, boolean hasTranslated);
}
public interface LayoutChangeListener {
void onLayoutChanged();
}
}

View file

@ -0,0 +1,138 @@
package net.osmand.plus.measurementtool.graph;
import android.graphics.Matrix;
import android.view.MotionEvent;
import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.listener.ChartTouchListener;
import com.github.mikephil.charting.listener.OnChartGestureListener;
import com.github.mikephil.charting.listener.OnChartValueSelectedListener;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem;
import java.util.HashMap;
import java.util.Map;
public class CommonGraphAdapter extends BaseGraphAdapter<LineChart, LineData, GpxDisplayItem> {
private Highlight highlight;
private Map<String, ExternalValueSelectedListener> externalValueSelectedListeners = new HashMap<>();
private ExternalGestureListener externalGestureListener;
public CommonGraphAdapter(LineChart chart, boolean usedOnMap) {
super(chart, usedOnMap);
}
@Override
protected void prepareChartView() {
super.prepareChartView();
chart.setOnChartValueSelectedListener(new OnChartValueSelectedListener() {
@Override
public void onValueSelected(Entry e, Highlight h) {
highlight = h;
for (ExternalValueSelectedListener listener : externalValueSelectedListeners.values()) {
listener.onValueSelected(e, h);
}
}
@Override
public void onNothingSelected() {
for (ExternalValueSelectedListener listener : externalValueSelectedListeners.values()) {
listener.onNothingSelected();
}
}
});
chart.setOnChartGestureListener(new OnChartGestureListener() {
boolean hasTranslated = false;
float highlightDrawX = -1;
@Override
public void onChartGestureStart(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture) {
hasTranslated = false;
if (chart.getHighlighted() != null && chart.getHighlighted().length > 0) {
highlightDrawX = chart.getHighlighted()[0].getDrawX();
} else {
highlightDrawX = -1;
}
if (externalGestureListener != null) {
externalGestureListener.onChartGestureStart(me, lastPerformedGesture);
}
}
@Override
public void onChartGestureEnd(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture) {
GpxDisplayItem gpxItem = getGpxItem();
gpxItem.chartMatrix = new Matrix(chart.getViewPortHandler().getMatrixTouch());
Highlight[] highlights = chart.getHighlighted();
if (highlights != null && highlights.length > 0) {
gpxItem.chartHighlightPos = highlights[0].getX();
} else {
gpxItem.chartHighlightPos = -1;
}
if (externalGestureListener != null) {
externalGestureListener.onChartGestureEnd(me, lastPerformedGesture, hasTranslated);
}
}
@Override
public void onChartLongPressed(MotionEvent me) {
}
@Override
public void onChartDoubleTapped(MotionEvent me) {
}
@Override
public void onChartSingleTapped(MotionEvent me) {
}
@Override
public void onChartFling(MotionEvent me1, MotionEvent me2, float velocityX, float velocityY) {
}
@Override
public void onChartScale(MotionEvent me, float scaleX, float scaleY) {
}
@Override
public void onChartTranslate(MotionEvent me, float dX, float dY) {
hasTranslated = true;
if (highlightDrawX != -1) {
Highlight h = chart.getHighlightByTouchPoint(highlightDrawX, 0f);
if (h != null) {
chart.highlightValue(h, true);
}
}
}
});
}
public void addValueSelectedListener(String key, ExternalValueSelectedListener listener) {
this.externalValueSelectedListeners.put(key, listener);
}
public void setExternalGestureListener(ExternalGestureListener listener) {
this.externalGestureListener = listener;
}
@Override
public void updateView() {
chart.setData(chartData);
updateHighlight();
}
@Override
public void highlight(Highlight h) {
super.highlight(h);
chart.highlightValue(highlight);
}
public GpxDisplayItem getGpxItem() {
return additionalData;
}
}

View file

@ -0,0 +1,183 @@
package net.osmand.plus.measurementtool.graph;
import android.graphics.drawable.Drawable;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.StyleSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.core.graphics.ColorUtils;
import com.github.mikephil.charting.charts.HorizontalBarChart;
import com.github.mikephil.charting.data.BarData;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.listener.OnChartValueSelectedListener;
import net.osmand.AndroidUtils;
import net.osmand.plus.OsmAndFormatter;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.helpers.CustomBarChartRenderer;
import net.osmand.router.RouteStatisticsHelper;
import net.osmand.router.RouteStatisticsHelper.RouteStatistics;
import net.osmand.router.RouteStatisticsHelper.RouteSegmentAttribute;
import net.osmand.util.Algorithms;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static net.osmand.plus.track.ColorsCard.MINIMUM_CONTRAST_RATIO;
public class CustomGraphAdapter extends BaseGraphAdapter<HorizontalBarChart, BarData, RouteStatistics> {
private String selectedPropertyName;
private ViewGroup legendContainer;
private LegendViewType legendViewType;
private LayoutChangeListener layoutChangeListener;
public enum LegendViewType {
ONE_ELEMENT,
ALL_AS_LIST,
GONE
}
public CustomGraphAdapter(HorizontalBarChart chart, boolean usedOnMap) {
super(chart, usedOnMap);
}
@Override
protected void prepareChartView() {
super.prepareChartView();
legendViewType = LegendViewType.GONE;
chart.setRenderer(new CustomBarChartRenderer(chart));
chart.setOnChartValueSelectedListener(new OnChartValueSelectedListener() {
@Override
public void onValueSelected(Entry e, Highlight h) {
if (getStatistics() == null) return;
List<RouteStatisticsHelper.RouteSegmentAttribute> elems = getStatistics().elements;
int i = h.getStackIndex();
if (i >= 0 && elems.size() > i) {
selectedPropertyName = elems.get(i).getPropertyName();
updateLegend();
}
}
@Override
public void onNothingSelected() {
selectedPropertyName = null;
updateLegend();
}
});
}
@Override
public void updateView() {
chart.setData(chartData);
updateHighlight();
updateLegend();
}
public void setLegendContainer(ViewGroup legendContainer) {
this.legendContainer = legendContainer;
}
public void setLegendViewType(LegendViewType legendViewType) {
this.legendViewType = legendViewType;
}
public void setLayoutChangeListener(LayoutChangeListener layoutChangeListener) {
this.layoutChangeListener = layoutChangeListener;
}
public void highlight(Highlight h) {
super.highlight(h);
Highlight bh = h != null ? chart.getHighlighter().getHighlight(1, h.getXPx()) : null;
if (bh != null) {
bh.setDraw(h.getXPx(), 0);
}
chart.highlightValue(bh, true);
}
private void updateLegend() {
if (legendContainer != null) {
legendContainer.removeAllViews();
attachLegend();
if (layoutChangeListener != null) {
layoutChangeListener.onLayoutChanged();
}
}
}
private void attachLegend() {
if (getSegmentsList() == null) return;
switch (legendViewType) {
case ONE_ELEMENT:
for (RouteSegmentAttribute segment : getSegmentsList()) {
if (segment.getPropertyName().equals(selectedPropertyName)) {
attachLegend(Collections.singletonList(segment), null);
break;
}
}
break;
case ALL_AS_LIST:
attachLegend(getSegmentsList(), selectedPropertyName);
break;
}
}
private void attachLegend(List<RouteSegmentAttribute> list,
String propertyNameToFullSpan) {
OsmandApplication app = getMyApplication();
LayoutInflater inflater = LayoutInflater.from(app);
for (RouteStatisticsHelper.RouteSegmentAttribute segment : list) {
View view = inflater.inflate(R.layout.route_details_legend, legendContainer, false);
int segmentColor = segment.getColor();
Drawable circle = app.getUIUtilities().getPaintedIcon(R.drawable.ic_action_circle, segmentColor);
ImageView legendIcon = (ImageView) view.findViewById(R.id.legend_icon_color);
legendIcon.setImageDrawable(circle);
double contrastRatio = ColorUtils.calculateContrast(segmentColor,
AndroidUtils.getColorFromAttr(app, R.attr.card_and_list_background_basic));
if (contrastRatio < MINIMUM_CONTRAST_RATIO) {
legendIcon.setBackgroundResource(AndroidUtils.resolveAttribute(app, R.attr.bg_circle_contour));
}
String propertyName = segment.getUserPropertyName();
String name = AndroidUtils.getRenderingStringPropertyName(app, propertyName, propertyName.replaceAll("_", " "));
boolean selected = segment.getPropertyName().equals(propertyNameToFullSpan);
Spannable text = getSpanLegend(name, segment, selected);
TextView legend = (TextView) view.findViewById(R.id.legend_text);
legend.setText(text);
legendContainer.addView(view);
}
}
private Spannable getSpanLegend(String title,
RouteSegmentAttribute segment,
boolean fullSpan) {
String formattedDistance = OsmAndFormatter.getFormattedDistance(segment.getDistance(), getMyApplication());
title = Algorithms.capitalizeFirstLetter(title);
SpannableStringBuilder spannable = new SpannableStringBuilder(title);
spannable.append(": ");
int startIndex = fullSpan ? -0 : spannable.length();
spannable.append(formattedDistance);
spannable.setSpan(new StyleSpan(android.graphics.Typeface.BOLD),
startIndex, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return spannable;
}
private List<RouteSegmentAttribute> getSegmentsList() {
return getStatistics() != null ? new ArrayList<>(getStatistics().partition.values()) : null;
}
private RouteStatistics getStatistics() {
return additionalData;
}
}

View file

@ -0,0 +1,155 @@
package net.osmand.plus.measurementtool.graph;
import android.annotation.SuppressLint;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import com.github.mikephil.charting.charts.BarChart;
import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.listener.ChartTouchListener;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.mapcontextmenu.other.TrackDetailsMenu;
import net.osmand.plus.measurementtool.graph.BaseGraphAdapter.ExternalValueSelectedListener;
import net.osmand.plus.measurementtool.graph.BaseGraphAdapter.ExternalGestureListener;
import java.util.List;
public class GraphAdapterHelper {
public static final String BIND_GRAPH_ADAPTERS_KEY = "bind_graph_adapters_key";
public static final String BIND_TO_MAP_KEY = "bind_to_map_key";
public static void bindGraphAdapters(final CommonGraphAdapter mainGraphAdapter,
final List<BaseGraphAdapter> otherGraphAdapters,
final ViewGroup mainView) {
if (mainGraphAdapter == null || mainGraphAdapter.getChart() == null
|| otherGraphAdapters == null || otherGraphAdapters.size() == 0) {
return;
}
final LineChart mainChart = mainGraphAdapter.getChart();
View.OnTouchListener mainChartTouchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent ev) {
if (mainView != null) {
mainView.requestDisallowInterceptTouchEvent(true);
}
for (BaseGraphAdapter adapter : otherGraphAdapters) {
if (adapter.getChart() != null) {
MotionEvent event = MotionEvent.obtainNoHistory(ev);
event.setSource(0);
adapter.getChart().dispatchTouchEvent(event);
}
}
return false;
}
};
mainChart.setOnTouchListener(mainChartTouchListener);
mainGraphAdapter.addValueSelectedListener(BIND_GRAPH_ADAPTERS_KEY,
new ExternalValueSelectedListener() {
@Override
public void onValueSelected(Entry e, Highlight h) {
for (BaseGraphAdapter adapter : otherGraphAdapters) {
adapter.highlight(h);
}
}
@Override
public void onNothingSelected() {
for (BaseGraphAdapter adapter : otherGraphAdapters) {
adapter.highlight(null);
}
}
}
);
View.OnTouchListener otherChartsTouchListener = new View.OnTouchListener() {
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouch(View v, MotionEvent ev) {
if (ev.getSource() != 0) {
final MotionEvent event = MotionEvent.obtainNoHistory(ev);
event.setSource(0);
mainChart.dispatchTouchEvent(event);
return true;
}
return false;
}
};
for (BaseGraphAdapter adapter : otherGraphAdapters) {
if (adapter.getChart() != null) {
if (adapter.getChart() instanceof BarChart) {
// maybe we should find min and max axis from all charters
BarChart barChart = (BarChart) adapter.getChart();
barChart.getAxisRight().setAxisMinimum(mainChart.getXChartMin());
barChart.getAxisRight().setAxisMaximum(mainChart.getXChartMax());
barChart.setHighlightPerDragEnabled(false);
barChart.setHighlightPerTapEnabled(false);
}
adapter.getChart().setOnTouchListener(otherChartsTouchListener);
}
}
}
public static RefreshMapCallback bindToMap(@NonNull final CommonGraphAdapter graphAdapter,
@NonNull final MapActivity mapActivity,
@NonNull final TrackDetailsMenu detailsMenu) {
final RefreshMapCallback refreshMapCallback = new RefreshMapCallback() {
@Override
public void refreshMap(boolean forceFit) {
LineChart chart = graphAdapter.getChart();
OsmandApplication app = mapActivity.getMyApplication();
if (!app.getRoutingHelper().isFollowingMode()) {
detailsMenu.refreshChart(chart, forceFit);
mapActivity.refreshMap();
}
}
};
graphAdapter.addValueSelectedListener(BIND_TO_MAP_KEY,
new CommonGraphAdapter.ExternalValueSelectedListener() {
@Override
public void onValueSelected(Entry e, Highlight h) {
refreshMapCallback.refreshMap(false);
}
@Override
public void onNothingSelected() {
}
});
graphAdapter.setExternalGestureListener(new ExternalGestureListener() {
@Override
public void onChartGestureStart(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture) {
}
@Override
public void onChartGestureEnd(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture, boolean hasTranslated) {
if ((lastPerformedGesture == ChartTouchListener.ChartGesture.DRAG && hasTranslated) ||
lastPerformedGesture == ChartTouchListener.ChartGesture.X_ZOOM ||
lastPerformedGesture == ChartTouchListener.ChartGesture.Y_ZOOM ||
lastPerformedGesture == ChartTouchListener.ChartGesture.PINCH_ZOOM ||
lastPerformedGesture == ChartTouchListener.ChartGesture.DOUBLE_TAP ||
lastPerformedGesture == ChartTouchListener.ChartGesture.ROTATE) {
refreshMapCallback.refreshMap(true);
}
}
});
return refreshMapCallback;
}
public interface RefreshMapCallback {
void refreshMap(boolean forceFit);
}
}

View file

@ -62,6 +62,7 @@ import net.osmand.plus.activities.TrackActivity;
import net.osmand.plus.base.OsmAndListFragment;
import net.osmand.plus.helpers.AndroidUiHelper;
import net.osmand.plus.helpers.GpxUiHelper;
import net.osmand.plus.helpers.GpxUiHelper.LineGraphType;
import net.osmand.plus.helpers.GpxUiHelper.GPXDataSetAxisType;
import net.osmand.plus.helpers.GpxUiHelper.GPXDataSetType;
import net.osmand.plus.helpers.GpxUiHelper.OrderedLineDataSet;
@ -86,6 +87,10 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static net.osmand.plus.helpers.GpxUiHelper.LineGraphType.ALTITUDE;
import static net.osmand.plus.helpers.GpxUiHelper.LineGraphType.SLOPE;
import static net.osmand.plus.helpers.GpxUiHelper.LineGraphType.SPEED;
public class TrackSegmentFragment extends OsmAndListFragment implements TrackBitmapDrawerListener {
private OsmandApplication app;
@ -424,64 +429,17 @@ public class TrackSegmentFragment extends OsmAndListFragment implements TrackBit
}
}
private List<ILineDataSet> getDataSets(GPXTabItemType tabType, LineChart chart) {
private List<ILineDataSet> getDataSets(LineChart chart,
GPXTabItemType tabType,
LineGraphType firstType,
LineGraphType secondType) {
List<ILineDataSet> dataSets = dataSetsMap.get(tabType);
if (dataSets == null && chart != null) {
dataSets = new ArrayList<>();
GPXTrackAnalysis analysis = gpxItem.analysis;
GpxDataItem gpxDataItem = getGpxDataItem();
boolean calcWithoutGaps = gpxItem.isGeneralTrack() && gpxDataItem != null && !gpxDataItem.isJoinSegments();
switch (tabType) {
case GPX_TAB_ITEM_GENERAL: {
OrderedLineDataSet speedDataSet = null;
OrderedLineDataSet elevationDataSet = null;
if (analysis.hasSpeedData) {
speedDataSet = GpxUiHelper.createGPXSpeedDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, true, true, calcWithoutGaps);
}
if (analysis.hasElevationData) {
elevationDataSet = GpxUiHelper.createGPXElevationDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, false, true, calcWithoutGaps);
}
if (speedDataSet != null) {
dataSets.add(speedDataSet);
if (elevationDataSet != null) {
dataSets.add(elevationDataSet.getPriority() < speedDataSet.getPriority()
? 1 : 0, elevationDataSet);
}
} else if (elevationDataSet != null) {
dataSets.add(elevationDataSet);
}
dataSetsMap.put(GPXTabItemType.GPX_TAB_ITEM_GENERAL, dataSets);
break;
}
case GPX_TAB_ITEM_ALTITUDE: {
OrderedLineDataSet elevationDataSet = GpxUiHelper.createGPXElevationDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, false, true, calcWithoutGaps);
if (elevationDataSet != null) {
dataSets.add(elevationDataSet);
}
if (analysis.hasElevationData) {
List<Entry> eleValues = elevationDataSet != null && !gpxItem.isGeneralTrack() ? elevationDataSet.getValues() : null;
OrderedLineDataSet slopeDataSet = GpxUiHelper.createGPXSlopeDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, eleValues, true, true, calcWithoutGaps);
if (slopeDataSet != null) {
dataSets.add(slopeDataSet);
}
}
dataSetsMap.put(GPXTabItemType.GPX_TAB_ITEM_ALTITUDE, dataSets);
break;
}
case GPX_TAB_ITEM_SPEED: {
OrderedLineDataSet speedDataSet = GpxUiHelper.createGPXSpeedDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, false, true, calcWithoutGaps);
if (speedDataSet != null) {
dataSets.add(speedDataSet);
}
dataSetsMap.put(GPXTabItemType.GPX_TAB_ITEM_SPEED, dataSets);
break;
}
}
dataSets = GpxUiHelper.getDataSets(chart, app, analysis, firstType, secondType, calcWithoutGaps);
dataSetsMap.put(tabType, dataSets);
}
return dataSets;
}
@ -702,7 +660,7 @@ public class TrackSegmentFragment extends OsmAndListFragment implements TrackBit
if (analysis != null) {
if (analysis.hasElevationData || analysis.hasSpeedData) {
GpxUiHelper.setupGPXChart(app, chart, 4);
chart.setData(new LineData(getDataSets(GPXTabItemType.GPX_TAB_ITEM_GENERAL, chart)));
chart.setData(new LineData(getDataSets(chart, GPXTabItemType.GPX_TAB_ITEM_GENERAL, ALTITUDE, SPEED)));
updateChart(chart);
chart.setVisibility(View.VISIBLE);
} else {
@ -820,7 +778,7 @@ public class TrackSegmentFragment extends OsmAndListFragment implements TrackBit
if (analysis != null) {
if (analysis.hasElevationData) {
GpxUiHelper.setupGPXChart(app, chart, 4);
chart.setData(new LineData(getDataSets(GPXTabItemType.GPX_TAB_ITEM_ALTITUDE, chart)));
chart.setData(new LineData(getDataSets(chart, GPXTabItemType.GPX_TAB_ITEM_ALTITUDE, ALTITUDE, SLOPE)));
updateChart(chart);
chart.setVisibility(View.VISIBLE);
} else {
@ -922,7 +880,7 @@ public class TrackSegmentFragment extends OsmAndListFragment implements TrackBit
if (analysis != null && analysis.isSpeedSpecified()) {
if (analysis.hasSpeedData) {
GpxUiHelper.setupGPXChart(app, chart, 4);
chart.setData(new LineData(getDataSets(GPXTabItemType.GPX_TAB_ITEM_SPEED, chart)));
chart.setData(new LineData(getDataSets(chart, GPXTabItemType.GPX_TAB_ITEM_SPEED, SPEED, null)));
updateChart(chart);
chart.setVisibility(View.VISIBLE);
} else {
@ -1188,7 +1146,7 @@ public class TrackSegmentFragment extends OsmAndListFragment implements TrackBit
LatLon location = null;
WptPt wpt = null;
gpxItem.chartTypes = null;
List<ILineDataSet> ds = getDataSets(tabType, null);
List<ILineDataSet> ds = getDataSets(null, tabType, null, null);
if (ds != null && ds.size() > 0) {
gpxItem.chartTypes = new GPXDataSetType[ds.size()];
for (int i = 0; i < ds.size(); i++) {

View file

@ -2,7 +2,6 @@ package net.osmand.plus.routepreparationmenu;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
@ -14,11 +13,9 @@ import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
@ -30,15 +27,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import com.github.mikephil.charting.animation.ChartAnimator;
import com.github.mikephil.charting.charts.HorizontalBarChart;
import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.interfaces.dataprovider.BarDataProvider;
import com.github.mikephil.charting.listener.ChartTouchListener.ChartGesture;
import com.github.mikephil.charting.renderer.HorizontalBarChartRenderer;
import com.github.mikephil.charting.utils.ViewPortHandler;
import net.osmand.AndroidUtils;
import net.osmand.GPXUtilities.GPXFile;
@ -68,10 +57,13 @@ import net.osmand.plus.helpers.GpxUiHelper.GPXDataSetType;
import net.osmand.plus.helpers.GpxUiHelper.OrderedLineDataSet;
import net.osmand.plus.mapcontextmenu.CollapsableView;
import net.osmand.plus.mapcontextmenu.other.TrackDetailsMenu;
import net.osmand.plus.measurementtool.graph.BaseGraphAdapter;
import net.osmand.plus.measurementtool.graph.CommonGraphAdapter;
import net.osmand.plus.measurementtool.graph.GraphAdapterHelper;
import net.osmand.plus.measurementtool.graph.GraphAdapterHelper.RefreshMapCallback;
import net.osmand.plus.render.MapRenderRepositories;
import net.osmand.plus.routepreparationmenu.cards.BaseCard;
import net.osmand.plus.routepreparationmenu.cards.BaseCard.CardListener;
import net.osmand.plus.routepreparationmenu.cards.CardChartListener;
import net.osmand.plus.routepreparationmenu.cards.PublicTransportCard;
import net.osmand.plus.routepreparationmenu.cards.PublicTransportCard.PublicTransportCardListener;
import net.osmand.plus.routepreparationmenu.cards.RouteDirectionsCard;
@ -98,8 +90,8 @@ import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
public class RouteDetailsFragment extends ContextMenuFragment implements PublicTransportCardListener,
CardListener, CardChartListener {
public class RouteDetailsFragment extends ContextMenuFragment
implements PublicTransportCardListener, CardListener {
public static final String ROUTE_ID_KEY = "route_id_key";
private static final float PAGE_MARGIN = 5f;
@ -122,6 +114,7 @@ public class RouteDetailsFragment extends ContextMenuFragment implements PublicT
private RouteStatisticCard statisticCard;
private List<RouteInfoCard> routeInfoCards = new ArrayList<>();
private RouteDetailsMenu routeDetailsMenu;
private RefreshMapCallback refreshMapCallback;
public interface RouteDetailsFragmentListener {
void onNavigationRequested();
@ -311,24 +304,7 @@ public class RouteDetailsFragment extends ContextMenuFragment implements PublicT
return;
}
OsmandApplication app = mapActivity.getMyApplication();
statisticCard = new RouteStatisticCard(mapActivity, gpx, new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent ev) {
LinearLayout mainView = getMainView();
if (mainView != null) {
mainView.requestDisallowInterceptTouchEvent(true);
}
for (RouteInfoCard card : routeInfoCards) {
final HorizontalBarChart ch = card.getChart();
if (ch != null) {
final MotionEvent event = MotionEvent.obtainNoHistory(ev);
event.setSource(0);
ch.dispatchTouchEvent(event);
}
}
return false;
}
}, new OnClickListener() {
statisticCard = new RouteStatisticCard(mapActivity, gpx, new OnClickListener() {
@Override
public void onClick(View v) {
openDetails();
@ -336,7 +312,6 @@ public class RouteDetailsFragment extends ContextMenuFragment implements PublicT
});
statisticCard.setTransparentBackground(true);
statisticCard.setListener(this);
statisticCard.setChartListener(this);
menuCards.add(statisticCard);
cardsContainer.addView(statisticCard.build(mapActivity));
buildRowDivider(cardsContainer, false);
@ -359,13 +334,25 @@ public class RouteDetailsFragment extends ContextMenuFragment implements PublicT
routeDetailsMenu.setGpxItem(gpxItem);
}
routeDetailsMenu.setMapActivity(mapActivity);
LineChart chart = statisticCard.getChart();
if (chart != null) {
chart.setExtraRightOffset(16);
chart.setExtraLeftOffset(16);
CommonGraphAdapter mainGraphAdapter = statisticCard.getGraphAdapter();
if (mainGraphAdapter != null) {
GraphAdapterHelper.bindGraphAdapters(mainGraphAdapter, getRouteInfoCardsGraphAdapters(), getMainView());
refreshMapCallback = GraphAdapterHelper.bindToMap(mainGraphAdapter, mapActivity, routeDetailsMenu);
}
}
private List<BaseGraphAdapter> getRouteInfoCardsGraphAdapters() {
List<BaseGraphAdapter> adapters = new ArrayList<>();
for (RouteInfoCard card : routeInfoCards) {
BaseGraphAdapter adapter = card.getGraphAdapter();
if (adapter != null) {
adapters.add(adapter);
}
}
return adapters;
}
public static List<RouteStatistics> calculateRouteStatistics(OsmandApplication app,
List<RouteSegmentResult> route,
boolean nightMode) {
@ -384,7 +371,7 @@ public class RouteDetailsFragment extends ContextMenuFragment implements PublicT
protected void calculateLayout(View view, boolean initLayout) {
super.calculateLayout(view, initLayout);
if (!initLayout && getCurrentMenuState() != MenuState.FULL_SCREEN) {
refreshChart(false);
refreshMapCallback.refreshMap(false);
}
}
@ -401,48 +388,15 @@ public class RouteDetailsFragment extends ContextMenuFragment implements PublicT
buildRowDivider(cardsContainer, false);
}
private OnTouchListener getChartTouchListener() {
return new OnTouchListener() {
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouch(View v, MotionEvent ev) {
if (ev.getSource() != 0 && v instanceof HorizontalBarChart) {
if (statisticCard != null) {
LineChart ch = statisticCard.getChart();
if (ch != null) {
final MotionEvent event = MotionEvent.obtainNoHistory(ev);
event.setSource(0);
ch.dispatchTouchEvent(event);
}
}
return true;
}
return false;
}
};
}
@SuppressLint("ClickableViewAccessibility")
private void addRouteCard(final LinearLayout cardsContainer, RouteInfoCard routeInfoCard) {
private void addRouteCard(LinearLayout cardsContainer,
RouteInfoCard routeInfoCard) {
OsmandApplication app = requireMyApplication();
menuCards.add(routeInfoCard);
routeInfoCard.setListener(this);
cardsContainer.addView(routeInfoCard.build(app));
buildRowDivider(cardsContainer, false);
routeInfoCards.add(routeInfoCard);
HorizontalBarChart chart = routeInfoCard.getChart();
if (chart != null) {
LineChart mainChart = statisticCard.getChart();
if (mainChart != null) {
chart.getAxisRight().setAxisMinimum(mainChart.getXChartMin());
chart.getAxisRight().setAxisMaximum(mainChart.getXChartMax());
}
chart.setRenderer(new CustomBarChartRenderer(chart, chart.getAnimator(), chart.getViewPortHandler(), AndroidUtils.dpToPx(app, 1f) / 2f));
chart.setHighlightPerDragEnabled(false);
chart.setHighlightPerTapEnabled(false);
chart.setOnTouchListener(getChartTouchListener());
}
}
public Drawable getCollapseIcon(boolean collapsed) {
@ -1487,14 +1441,7 @@ public class RouteDetailsFragment extends ContextMenuFragment implements PublicT
private void makeGpx() {
OsmandApplication app = requireMyApplication();
gpx = GpxUiHelper.makeGpxFromRoute(app.getRoutingHelper().getRoute(), app);
String groupName = getString(R.string.current_route);
GpxDisplayGroup group = app.getSelectedGpxHelper().buildGpxDisplayGroup(gpx, 0, groupName);
if (group != null && group.getModifiableList().size() > 0) {
gpxItem = group.getModifiableList().get(0);
if (gpxItem != null) {
gpxItem.route = true;
}
}
gpxItem = GpxUiHelper.makeGpxDisplayItem(app, gpx);
}
void openDetails() {
@ -1615,59 +1562,6 @@ public class RouteDetailsFragment extends ContextMenuFragment implements PublicT
}
}
private void refreshChart(boolean forceFit) {
MapActivity mapActivity = getMapActivity();
if (mapActivity != null && routeDetailsMenu != null && statisticCard != null &&
!mapActivity.getMyApplication().getRoutingHelper().isFollowingMode()) {
LineChart chart = statisticCard.getChart();
if (chart != null) {
routeDetailsMenu.refreshChart(chart, forceFit);
mapActivity.refreshMap();
}
}
}
private void highlightRouteInfoCharts(@Nullable Highlight h) {
for (RouteInfoCard rc : routeInfoCards) {
HorizontalBarChart chart = rc.getChart();
if (chart != null) {
Highlight bh = h != null ? chart.getHighlighter().getHighlight(1, h.getXPx()) : null;
if (bh != null) {
bh.setDraw(h.getXPx(), 0);
}
chart.highlightValue(bh, true);
}
}
}
@Override
public void onValueSelected(BaseCard card, Entry e, Highlight h) {
refreshChart(false);
highlightRouteInfoCharts(h);
}
@Override
public void onNothingSelected(BaseCard card) {
highlightRouteInfoCharts(null);
}
@Override
public void onChartGestureStart(BaseCard card, MotionEvent me, ChartGesture lastPerformedGesture) {
}
@Override
public void onChartGestureEnd(BaseCard card, MotionEvent me, ChartGesture lastPerformedGesture, boolean hasTranslated) {
if ((lastPerformedGesture == ChartGesture.DRAG && hasTranslated) ||
lastPerformedGesture == ChartGesture.X_ZOOM ||
lastPerformedGesture == ChartGesture.Y_ZOOM ||
lastPerformedGesture == ChartGesture.PINCH_ZOOM ||
lastPerformedGesture == ChartGesture.DOUBLE_TAP ||
lastPerformedGesture == ChartGesture.ROTATE) {
refreshChart(true);
}
}
public static class CumulativeInfo {
public int distance;
public int time;
@ -1696,20 +1590,4 @@ public class RouteDetailsFragment extends ContextMenuFragment implements PublicT
final int timeInSeconds = model.getExpectedTime();
return Algorithms.formatDuration(timeInSeconds, app.accessibilityEnabled());
}
private static class CustomBarChartRenderer extends HorizontalBarChartRenderer {
private float highlightHalfWidth;
CustomBarChartRenderer(BarDataProvider chart, ChartAnimator animator, ViewPortHandler viewPortHandler, float highlightHalfWidth) {
super(chart, animator, viewPortHandler);
this.highlightHalfWidth = highlightHalfWidth;
}
@Override
protected void setHighlightDrawPos(Highlight high, RectF bar) {
bar.left = high.getDrawX() - highlightHalfWidth;
bar.right = high.getDrawX() + highlightHalfWidth;
}
}
}

View file

@ -31,7 +31,6 @@ public abstract class BaseCard {
protected boolean nightMode;
private CardListener listener;
private CardChartListener chartListener;
public interface CardListener {
void onCardLayoutNeeded(@NonNull BaseCard card);
@ -78,14 +77,6 @@ public abstract class BaseCard {
this.listener = listener;
}
public CardChartListener getChartListener() {
return chartListener;
}
public void setChartListener(CardChartListener chartListener) {
this.chartListener = chartListener;
}
public void setLayoutNeeded() {
CardListener listener = this.listener;
if (listener != null) {

View file

@ -1,54 +1,33 @@
package net.osmand.plus.routepreparationmenu.cards;
import android.graphics.drawable.Drawable;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.StyleSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.appcompat.view.ContextThemeWrapper;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.ColorUtils;
import com.github.mikephil.charting.charts.HorizontalBarChart;
import com.github.mikephil.charting.data.BarData;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.listener.OnChartValueSelectedListener;
import net.osmand.AndroidUtils;
import net.osmand.GPXUtilities.GPXTrackAnalysis;
import net.osmand.plus.OsmAndFormatter;
import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.helpers.GpxUiHelper;
import net.osmand.router.RouteStatisticsHelper.RouteSegmentAttribute;
import net.osmand.plus.measurementtool.graph.CustomGraphAdapter;
import net.osmand.plus.measurementtool.graph.CustomGraphAdapter.LegendViewType;
import net.osmand.router.RouteStatisticsHelper.RouteStatistics;
import net.osmand.util.Algorithms;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static net.osmand.plus.track.ColorsCard.MINIMUM_CONTRAST_RATIO;
public class RouteInfoCard extends BaseCard {
private RouteStatistics routeStatistics;
private RouteStatistics statistics;
private GPXTrackAnalysis analysis;
private String selectedPropertyName;
private CustomGraphAdapter graphAdapter;
private boolean showLegend;
public RouteInfoCard(MapActivity mapActivity, RouteStatistics routeStatistics, GPXTrackAnalysis analysis) {
public RouteInfoCard(MapActivity mapActivity, RouteStatistics statistics, GPXTrackAnalysis analysis) {
super(mapActivity);
this.routeStatistics = routeStatistics;
this.statistics = statistics;
this.analysis = analysis;
}
@ -59,112 +38,47 @@ public class RouteInfoCard extends BaseCard {
@Override
protected void updateContent() {
updateContent(routeStatistics);
}
@Nullable
public HorizontalBarChart getChart() {
return (HorizontalBarChart) view.findViewById(R.id.chart);
}
private void updateContent(final RouteStatistics routeStatistics) {
updateHeader();
final HorizontalBarChart chart = (HorizontalBarChart) view.findViewById(R.id.chart);
GpxUiHelper.setupHorizontalGPXChart(app, chart, 5, 9, 24, true, nightMode);
chart.setExtraRightOffset(16);
chart.setExtraLeftOffset(16);
BarData barData = GpxUiHelper.buildStatisticChart(app, chart, routeStatistics, analysis, true, nightMode);
chart.setData(barData);
chart.setOnChartValueSelectedListener(new OnChartValueSelectedListener() {
@Override
public void onValueSelected(Entry e, Highlight h) {
List<RouteSegmentAttribute> elems = routeStatistics.elements;
int i = h.getStackIndex();
if (i >= 0 && elems.size() > i) {
selectedPropertyName = elems.get(i).getPropertyName();
if (showLegend) {
updateLegend(routeStatistics);
}
}
}
@Override
public void onNothingSelected() {
selectedPropertyName = null;
if (showLegend) {
updateLegend(routeStatistics);
}
}
});
LinearLayout container = (LinearLayout) view.findViewById(R.id.route_items);
container.removeAllViews();
if (showLegend) {
attachLegend(container, routeStatistics);
}
final ImageView iconViewCollapse = (ImageView) view.findViewById(R.id.up_down_icon);
iconViewCollapse.setImageDrawable(getCollapseIcon(!showLegend));
HorizontalBarChart chart = (HorizontalBarChart) view.findViewById(R.id.chart);
GpxUiHelper.setupHorizontalGPXChart(getMyApplication(), chart, 5, 9, 24, true, nightMode);
BarData barData = GpxUiHelper.buildStatisticChart(app, chart, statistics, analysis, true, nightMode);
graphAdapter = new CustomGraphAdapter(chart, true);
graphAdapter.setLegendContainer(container);
graphAdapter.updateData(barData, statistics);
updateView();
view.findViewById(R.id.info_type_details_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showLegend = !showLegend;
updateContent();
updateView();
setLayoutNeeded();
}
});
}
protected void updateLegend(RouteStatistics routeStatistics) {
LinearLayout container = (LinearLayout) view.findViewById(R.id.route_items);
container.removeAllViews();
attachLegend(container, routeStatistics);
setLayoutNeeded();
}
private Drawable getCollapseIcon(boolean collapsed) {
return collapsed ? getContentIcon(R.drawable.ic_action_arrow_down) : getActiveIcon(R.drawable.ic_action_arrow_up);
private void updateView() {
updateCollapseIcon();
graphAdapter.setLegendViewType(showLegend ? LegendViewType.ALL_AS_LIST : LegendViewType.GONE);
graphAdapter.updateView();
}
private void updateHeader() {
TextView title = (TextView) view.findViewById(R.id.info_type_title);
String name = AndroidUtils.getStringRouteInfoPropertyValue(app, routeStatistics.name);
String name = AndroidUtils.getStringRouteInfoPropertyValue(app, statistics.name);
title.setText(name);
}
private void attachLegend(ViewGroup container, RouteStatistics routeStatistics) {
Map<String, RouteSegmentAttribute> partition = routeStatistics.partition;
List<Map.Entry<String, RouteSegmentAttribute>> list = new ArrayList<>(partition.entrySet());
ContextThemeWrapper ctx = new ContextThemeWrapper(mapActivity, !nightMode ? R.style.OsmandLightTheme : R.style.OsmandDarkTheme);
LayoutInflater inflater = LayoutInflater.from(ctx);
for (Map.Entry<String, RouteSegmentAttribute> entry : list) {
RouteSegmentAttribute segment = entry.getValue();
View view = inflater.inflate(R.layout.route_details_legend, container, false);
int segmentColor = segment.getColor();
Drawable circle = app.getUIUtilities().getPaintedIcon(R.drawable.ic_action_circle, segmentColor);
ImageView legendIcon = (ImageView) view.findViewById(R.id.legend_icon_color);
legendIcon.setImageDrawable(circle);
double contrastRatio = ColorUtils.calculateContrast(segmentColor, ContextCompat.getColor(app, nightMode ? R.color.card_and_list_background_dark : R.color.card_and_list_background_light));
if (contrastRatio < MINIMUM_CONTRAST_RATIO) {
legendIcon.setBackgroundResource(nightMode ? R.drawable.circle_contour_bg_dark : R.drawable.circle_contour_bg_light);
}
String propertyName = segment.getUserPropertyName();
String name = AndroidUtils.getRenderingStringPropertyName(app, propertyName, propertyName.replaceAll("_", " "));
Spannable text = getSpanLegend(name, segment, segment.getUserPropertyName().equals(selectedPropertyName));
TextView legend = (TextView) view.findViewById(R.id.legend_text);
legend.setText(text);
container.addView(view);
}
private void updateCollapseIcon() {
ImageView ivCollapse = (ImageView) view.findViewById(R.id.up_down_icon);
Drawable drawable = showLegend ?
getContentIcon(R.drawable.ic_action_arrow_down) :
getActiveIcon(R.drawable.ic_action_arrow_up);
ivCollapse.setImageDrawable(drawable);
}
private Spannable getSpanLegend(String title, RouteSegmentAttribute segment, boolean selected) {
String formattedDistance = OsmAndFormatter.getFormattedDistance(segment.getDistance(), getMyApplication());
title = Algorithms.capitalizeFirstLetter(title);
SpannableStringBuilder spannable = new SpannableStringBuilder(title);
spannable.append(": ");
int startIndex = selected ? -0 : spannable.length();
spannable.append(formattedDistance);
spannable.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), startIndex, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return spannable;
public CustomGraphAdapter getGraphAdapter() {
return graphAdapter;
}
}

View file

@ -1,13 +1,10 @@
package net.osmand.plus.routepreparationmenu.cards;
import android.graphics.Matrix;
import android.os.Build;
import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
@ -16,18 +13,12 @@ import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
import com.github.mikephil.charting.listener.ChartTouchListener.ChartGesture;
import com.github.mikephil.charting.listener.OnChartGestureListener;
import com.github.mikephil.charting.listener.OnChartValueSelectedListener;
import net.osmand.AndroidUtils;
import net.osmand.GPXUtilities.GPXFile;
import net.osmand.GPXUtilities.GPXTrackAnalysis;
import net.osmand.plus.GpxSelectionHelper;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem;
import net.osmand.plus.OsmAndFormatter;
import net.osmand.plus.OsmandApplication;
@ -36,6 +27,7 @@ import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.helpers.GpxUiHelper;
import net.osmand.plus.helpers.GpxUiHelper.GPXDataSetAxisType;
import net.osmand.plus.helpers.GpxUiHelper.OrderedLineDataSet;
import net.osmand.plus.measurementtool.graph.CommonGraphAdapter;
import net.osmand.plus.routing.RoutingHelper;
import java.util.ArrayList;
@ -52,16 +44,15 @@ public class RouteStatisticCard extends BaseCard {
private OrderedLineDataSet slopeDataSet;
@Nullable
private OrderedLineDataSet elevationDataSet;
private OnTouchListener onTouchListener;
private OnClickListener onAnalyseClickListener;
private CommonGraphAdapter graphAdapter;
public RouteStatisticCard(MapActivity mapActivity, GPXFile gpx, OnTouchListener onTouchListener,
public RouteStatisticCard(MapActivity mapActivity, GPXFile gpx,
OnClickListener onAnalyseClickListener) {
super(mapActivity);
this.gpx = gpx;
this.onTouchListener = onTouchListener;
this.onAnalyseClickListener = onAnalyseClickListener;
makeGpxDisplayItem();
this.gpxItem = GpxUiHelper.makeGpxDisplayItem(app, gpx);
}
@Nullable
@ -219,26 +210,15 @@ public class RouteStatisticCard extends BaseCard {
return elevationDataSet;
}
private void makeGpxDisplayItem() {
String groupName = getMyApplication().getString(R.string.current_route);
GpxSelectionHelper.GpxDisplayGroup group = getMyApplication().getSelectedGpxHelper().buildGpxDisplayGroup(gpx, 0, groupName);
if (group != null && group.getModifiableList().size() > 0) {
gpxItem = group.getModifiableList().get(0);
if (gpxItem != null) {
gpxItem.route = true;
}
}
}
@Nullable
public LineChart getChart() {
return (LineChart) view.findViewById(R.id.chart);
public CommonGraphAdapter getGraphAdapter() {
return graphAdapter;
}
private void buildHeader(GPXTrackAnalysis analysis) {
final LineChart mChart = (LineChart) view.findViewById(R.id.chart);
LineChart mChart = (LineChart) view.findViewById(R.id.chart);
GpxUiHelper.setupGPXChart(mChart, 4, 24f, 16f, !nightMode, true);
mChart.setOnTouchListener(onTouchListener);
graphAdapter = new CommonGraphAdapter(mChart, true);
if (analysis.hasElevationData) {
List<ILineDataSet> dataSets = new ArrayList<>();
@ -256,99 +236,7 @@ public class RouteStatisticCard extends BaseCard {
this.elevationDataSet = elevationDataSet;
this.slopeDataSet = slopeDataSet;
LineData data = new LineData(dataSets);
mChart.setData(data);
mChart.setOnChartValueSelectedListener(new OnChartValueSelectedListener() {
@Override
public void onValueSelected(Entry e, Highlight h) {
CardChartListener chartListener = getChartListener();
if (chartListener != null) {
chartListener.onValueSelected(RouteStatisticCard.this, e, h);
}
}
@Override
public void onNothingSelected() {
CardChartListener chartListener = getChartListener();
if (chartListener != null) {
chartListener.onNothingSelected(RouteStatisticCard.this);
}
}
});
mChart.setOnChartGestureListener(new OnChartGestureListener() {
boolean hasTranslated = false;
float highlightDrawX = -1;
@Override
public void onChartGestureStart(MotionEvent me, ChartGesture lastPerformedGesture) {
hasTranslated = false;
if (mChart.getHighlighted() != null && mChart.getHighlighted().length > 0) {
highlightDrawX = mChart.getHighlighted()[0].getDrawX();
} else {
highlightDrawX = -1;
}
CardChartListener chartListener = getChartListener();
if (chartListener != null) {
chartListener.onChartGestureStart(RouteStatisticCard.this, me, lastPerformedGesture);
}
}
@Override
public void onChartGestureEnd(MotionEvent me, ChartGesture lastPerformedGesture) {
gpxItem.chartMatrix = new Matrix(mChart.getViewPortHandler().getMatrixTouch());
Highlight[] highlights = mChart.getHighlighted();
if (highlights != null && highlights.length > 0) {
gpxItem.chartHighlightPos = highlights[0].getX();
} else {
gpxItem.chartHighlightPos = -1;
}
CardChartListener chartListener = getChartListener();
if (chartListener != null) {
chartListener.onChartGestureEnd(RouteStatisticCard.this, me, lastPerformedGesture, hasTranslated);
}
}
@Override
public void onChartLongPressed(MotionEvent me) {
}
@Override
public void onChartDoubleTapped(MotionEvent me) {
}
@Override
public void onChartSingleTapped(MotionEvent me) {
}
@Override
public void onChartFling(MotionEvent me1, MotionEvent me2, float velocityX, float velocityY) {
}
@Override
public void onChartScale(MotionEvent me, float scaleX, float scaleY) {
}
@Override
public void onChartTranslate(MotionEvent me, float dX, float dY) {
hasTranslated = true;
if (highlightDrawX != -1) {
Highlight h = mChart.getHighlightByTouchPoint(highlightDrawX, 0f);
if (h != null) {
/*
ILineDataSet set = mChart.getLineData().getDataSetByIndex(h.getDataSetIndex());
if (set != null && set.isHighlightEnabled()) {
Entry e = set.getEntryForXValue(h.getX(), h.getY());
MPPointD pix = mChart.getTransformer(set.getAxisDependency()).getPixelForValues(e.getX(), e.getY());
h.setDraw((float) pix.x, (float) pix.y);
}
*/
mChart.highlightValue(h, true);
}
}
}
});
graphAdapter.updateContent(new LineData(dataSets), gpxItem);
mChart.setVisibility(View.VISIBLE);
} else {
mChart.setVisibility(View.GONE);

View file

@ -26,7 +26,9 @@ import net.osmand.data.QuadRect;
import net.osmand.data.RotatedTileBox;
import net.osmand.data.TransportStop;
import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.mapcontextmenu.other.TrackChartPoints;
import net.osmand.plus.measurementtool.MeasurementToolFragment;
import net.osmand.plus.profiles.LocationIcon;
import net.osmand.plus.routing.RouteCalculationResult;
import net.osmand.plus.routing.RouteDirectionInfo;
@ -158,7 +160,8 @@ public class RouteLayer extends OsmandMapLayer implements ContextMenuLayer.ICont
@Override
public void onPrepareBufferImage(Canvas canvas, RotatedTileBox tileBox, DrawSettings settings) {
if ((helper.isPublicTransportMode() && transportHelper.getRoutes() != null) ||
(helper.getFinalLocation() != null && helper.getRoute().isCalculated())) {
(helper.getFinalLocation() != null && helper.getRoute().isCalculated()) ||
isPlanRouteGraphsAvailable()) {
updateAttrs(settings, tileBox);
@ -202,6 +205,17 @@ public class RouteLayer extends OsmandMapLayer implements ContextMenuLayer.ICont
}
private boolean isPlanRouteGraphsAvailable() {
if (view.getContext() instanceof MapActivity) {
MapActivity mapActivity = (MapActivity) view.getContext();
MeasurementToolFragment fragment = mapActivity.getMeasurementToolFragment();
if (fragment != null) {
return fragment.hasVisibleGraph();
}
}
return false;
}
private void updateAttrs(DrawSettings settings, RotatedTileBox tileBox) {
boolean updatePaints = attrs.updatePaints(view.getApplication(), settings, tileBox);
attrs.isPaint3 = false;