refactor GraphType

This commit is contained in:
Nazar-Kutz 2020-10-12 17:18:01 +03:00
parent 3c2bda87ce
commit a6d117f5a1
4 changed files with 148 additions and 197 deletions

View file

@ -11,7 +11,7 @@
Thx - Hardy Thx - Hardy
--> -->
<string name="message_need_calculate_route_for_show_graph">Altitude data available only on the roads, you need to calculate a route using “Route between points” to get it.</string> <string name="message_need_calculate_route_before_show_graph">%1$s data available only on the roads, you need to calculate a route using “Route between points” to get it.</string>
<string name="shared_string_graph">Graph</string> <string name="shared_string_graph">Graph</string>
<string name="osm_edit_logout_success">Logout successful</string> <string name="osm_edit_logout_success">Logout successful</string>
<string name="clear_osm_token">Clear OpenStreetMap OAuth token</string> <string name="clear_osm_token">Clear OpenStreetMap OAuth token</string>

View file

@ -189,9 +189,11 @@ public abstract class SettingsBaseActivity extends ActionBarPreferenceActivity
if(propertyValue == null) { if(propertyValue == null) {
return ""; return "";
} }
int valueId = getStringRouteInfoPropertyValueId(propertyValue); final String propertyValueReplaced = propertyValue.replaceAll("\\s+","_");
if (valueId != -1) { Field f = R.string.class.getField("routeInfo_" + propertyValueReplaced + "_name");
return ctx.getString(valueId); if (f != null) {
Integer in = (Integer) f.get(null);
return ctx.getString(in);
} }
} catch (Exception e) { } catch (Exception e) {
System.err.println(e.getMessage()); System.err.println(e.getMessage());
@ -199,19 +201,6 @@ public abstract class SettingsBaseActivity extends ActionBarPreferenceActivity
return propertyValue; return propertyValue;
} }
public static int getStringRouteInfoPropertyValueId(String propertyValue) {
try {
final String propertyValueReplaced = propertyValue.replaceAll("\\s+","_");
Field f = R.string.class.getField("routeInfo_" + propertyValueReplaced + "_name");
if (f != null) {
return (Integer) f.get(null);
}
} catch (Exception e) {
System.err.println(e.getMessage());
}
return -1;
}
public <T> void registerListPreference(OsmandPreference<T> b, PreferenceGroup screen, String[] names, T[] values) { public <T> void registerListPreference(OsmandPreference<T> b, PreferenceGroup screen, String[] names, T[] values) {
ListPreference p = (ListPreference) screen.findPreference(b.getId()); ListPreference p = (ListPreference) screen.findPreference(b.getId());
prepareListPreference(b, names, values, p); prepareListPreference(b, names, values, p);

View file

@ -314,7 +314,39 @@ public class MeasurementEditingContext {
} }
public List<RouteSegmentResult> getAllRouteSegments() { public List<RouteSegmentResult> getAllRouteSegments() {
class TmpRouteSegmentData { // prepare data for sorting
List<TmpRouteSegmentData> fullList = new ArrayList<>();
for (Map.Entry<Pair<WptPt, WptPt>, RoadSegmentData> entry : roadSegmentData.entrySet()) {
fullList.add(new TmpRouteSegmentData(
entry.getKey().first,
entry.getKey().second,
entry.getValue().getSegments()));
}
// sorting data by connecting together
while (fullList.size() > 1) {
TmpRouteSegmentData firstInList = fullList.get(0);
for (int i = 1; i < fullList.size(); i++) {
TmpRouteSegmentData other = fullList.get(i);
boolean isMatched = false;
if (firstInList.isAfterOf(other)) {
isMatched = true;
firstInList.joinBefore(other);
} else if (firstInList.isBeforeOf(other)) {
isMatched = true;
firstInList.joinAfter(other);
}
if (isMatched) {
fullList.remove(other);
break;
}
}
}
return fullList.size() > 0 ? fullList.get(0).getRouteSegments() : null;
}
private static class TmpRouteSegmentData {
private WptPt start; private WptPt start;
private WptPt end; private WptPt end;
private List<RouteSegmentResult> routeSegments; private List<RouteSegmentResult> routeSegments;
@ -343,40 +375,10 @@ public class MeasurementEditingContext {
start = other.start; start = other.start;
routeSegments.addAll(0, other.routeSegments); routeSegments.addAll(0, other.routeSegments);
} }
}
// prepare data for sorting public List<RouteSegmentResult> getRouteSegments() {
List<TmpRouteSegmentData> fullList = new ArrayList<>(); return routeSegments;
for (Map.Entry<Pair<WptPt, WptPt>, RoadSegmentData> entry : roadSegmentData.entrySet()) {
fullList.add(new TmpRouteSegmentData(
entry.getKey().first,
entry.getKey().second,
entry.getValue().getSegments()));
} }
// sorting data by connecting together
while (fullList.size() > 1) {
TmpRouteSegmentData firstInList = fullList.get(0);
for (int i = 1; i < fullList.size(); i++) {
TmpRouteSegmentData other = fullList.get(i);
boolean isMatched = false;
if (firstInList.isAfterOf(other)) {
isMatched = true;
firstInList.joinBefore(other);
} else if (firstInList.isBeforeOf(other)) {
isMatched = true;
firstInList.joinAfter(other);
}
if (isMatched) {
fullList.remove(other);
break;
}
}
}
return fullList.size() > 0 ? fullList.get(0).routeSegments : null;
} }
void splitSegments(int position) { void splitSegments(int position) {

View file

@ -41,9 +41,7 @@ import static net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter.Ho
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
public class MtGraphFragment extends Fragment public class MtGraphFragment extends Fragment
implements MeasurementToolFragment.OnUpdateAdditionalInfoListener { implements MeasurementToolFragment.OnUpdateAdditionalInfoListener {
@ -55,62 +53,26 @@ public class MtGraphFragment extends Fragment
private View messageContainer; private View messageContainer;
private LineChart commonGraphChart; private LineChart commonGraphChart;
private HorizontalBarChart customGraphChart; private HorizontalBarChart customGraphChart;
private RecyclerView rvMenu; private RecyclerView rvGraphTypesMenu;
private boolean nightMode; private boolean nightMode;
private MeasurementEditingContext editingCtx; private MeasurementEditingContext editingCtx;
private GraphType currentGraphType; private GraphType currentGraphType;
private Map<GraphType, Object> graphData = new HashMap<>(); private List<GraphType> graphTypes = new ArrayList<>();
private enum GraphType { private enum CommonGraphType {
OVERVIEW(R.string.shared_string_overview, false, false), OVERVIEW(R.string.shared_string_overview, false),
ALTITUDE(R.string.altitude, false, true), ALTITUDE(R.string.altitude, true),
// SLOPE(R.string.shared_string_slope, false, true), SLOPE(R.string.shared_string_slope, true),
SPEED(R.string.map_widget_speed, false, false), SPEED(R.string.map_widget_speed, false);
SURFACE(R.string.routeInfo_surface_name, true, false), CommonGraphType(int titleId, boolean canBeCalculated) {
ROAD_TYPE(R.string.routeInfo_roadClass_name, true, false),
STEEPNESS(R.string.routeInfo_steepness_name, true, false),
SMOOTHNESS(R.string.routeInfo_smoothness_name, true, false);
GraphType(int titleId, boolean isCustomType, boolean canBeCalculated) {
this.titleId = titleId; this.titleId = titleId;
this.isCustomType = isCustomType;
this.canBeCalculated = canBeCalculated; this.canBeCalculated = canBeCalculated;
} }
final int titleId; final int titleId;
final boolean isCustomType;
final boolean canBeCalculated; final boolean canBeCalculated;
private static List<GraphType> commonTypes;
private static List<GraphType> customTypes;
static List<GraphType> getCommonTypes() {
if (commonTypes == null) {
prepareLists();
}
return commonTypes;
}
static List<GraphType> getCustomTypes() {
if (customTypes == null) {
prepareLists();
}
return customTypes;
}
private static void prepareLists() {
commonTypes = new ArrayList<>();
customTypes = new ArrayList<>();
for (GraphType type : values()) {
if (type.isCustomType) {
customTypes.add(type);
} else {
commonTypes.add(type);
}
}
}
} }
@Nullable @Nullable
@ -136,35 +98,33 @@ public class MtGraphFragment extends Fragment
customGraphChart = (HorizontalBarChart) view.findViewById(R.id.horizontal_chart); customGraphChart = (HorizontalBarChart) view.findViewById(R.id.horizontal_chart);
updateGraphData(); updateGraphData();
rvMenu = view.findViewById(R.id.graph_types_recycler_view); rvGraphTypesMenu = view.findViewById(R.id.graph_types_recycler_view);
rvMenu.setLayoutManager( rvGraphTypesMenu.setLayoutManager(
new LinearLayoutManager(mapActivity, RecyclerView.HORIZONTAL, false)); new LinearLayoutManager(mapActivity, RecyclerView.HORIZONTAL, false));
prepareGraphTypesSelectionMenu(); refreshGraphTypesSelectionMenu();
setupVisibleGraphType(GraphType.OVERVIEW); setupVisibleGraphType(graphTypes.get(0));
return view; return view;
} }
private void prepareGraphTypesSelectionMenu() { private void refreshGraphTypesSelectionMenu() {
rvMenu.removeAllViews(); rvGraphTypesMenu.removeAllViews();
OsmandApplication app = getMyApplication(); OsmandApplication app = getMyApplication();
int activeColorId = nightMode ? R.color.active_color_primary_dark : R.color.active_color_primary_light; int activeColorId = nightMode ? R.color.active_color_primary_dark : R.color.active_color_primary_light;
final HorizontalSelectionAdapter adapter = new HorizontalSelectionAdapter(app, nightMode); final HorizontalSelectionAdapter adapter = new HorizontalSelectionAdapter(app, nightMode);
final ArrayList<HorizontalSelectionItem> items = new ArrayList<>(); final ArrayList<HorizontalSelectionItem> items = new ArrayList<>();
for (GraphType type : GraphType.values()) { for (GraphType type : graphTypes) {
String title = getString(type.titleId); HorizontalSelectionItem item = new HorizontalSelectionItem(type.getTitle(), type);
HorizontalSelectionItem item = new HorizontalSelectionItem(title, type); if (type.isCustom()) {
if (type.isCustomType) {
item.setTitleColorId(activeColorId); item.setTitleColorId(activeColorId);
} }
if (isDataAvailableFor(type) || type.canBeCalculated) { if (type.hasData() || type.canBeCalculated) {
items.add(item); items.add(item);
} }
} }
adapter.setItems(items); adapter.setItems(items);
String selectedItemKey = currentGraphType != null ? String selectedItemKey = currentGraphType != null ? currentGraphType.getTitle() : items.get(0).getTitle();
getString(currentGraphType.titleId) : items.get(0).getTitle();
adapter.setSelectedItemByTitle(selectedItemKey); adapter.setSelectedItemByTitle(selectedItemKey);
adapter.setListener(new HorizontalSelectionAdapter.HorizontalSelectionAdapterListener() { adapter.setListener(new HorizontalSelectionAdapter.HorizontalSelectionAdapterListener() {
@Override @Override
@ -177,53 +137,52 @@ public class MtGraphFragment extends Fragment
} }
} }
}); });
rvMenu.setAdapter(adapter); rvGraphTypesMenu.setAdapter(adapter);
adapter.notifyDataSetChanged(); adapter.notifyDataSetChanged();
} }
@Override @Override
public void onUpdateAdditionalInfo() { public void onUpdateAdditionalInfo() {
updateGraphData(); updateGraphData();
prepareGraphTypesSelectionMenu(); refreshGraphTypesSelectionMenu();
setupVisibleGraphType(currentGraphType); setupVisibleGraphType(currentGraphType);
} }
private void setupVisibleGraphType(GraphType preferredType) { private void setupVisibleGraphType(GraphType preferredType) {
currentGraphType = isDataAvailableFor(preferredType) ? currentGraphType = preferredType.hasData() ? preferredType : getFirstAvailableGraphType();
preferredType : getFirstAvailableGraphType();
updateDataView(); updateDataView();
} }
private GraphType getFirstAvailableGraphType() { private GraphType getFirstAvailableGraphType() {
for (GraphType type : GraphType.values()) { for (GraphType type : graphTypes) {
if (isDataAvailableFor(type) || type.canBeCalculated) { if (type.hasData() || type.canBeCalculated()) {
return type; return type;
} }
} }
return GraphType.OVERVIEW; return null;
} }
private void updateDataView() { private void updateDataView() {
if (isDataAvailableFor(currentGraphType)) { if (currentGraphType.hasData()) {
showGraph(); showGraph();
} else if (currentGraphType.canBeCalculated) { } else if (currentGraphType.canBeCalculated()) {
showMessage(); showMessage();
} }
} }
private void showGraph() { private void showGraph() {
if (currentGraphType.isCustomType) { if (currentGraphType.isCustom()) {
customGraphChart.clear(); customGraphChart.clear();
commonGraphContainer.setVisibility(View.GONE); commonGraphContainer.setVisibility(View.GONE);
customGraphContainer.setVisibility(View.VISIBLE); customGraphContainer.setVisibility(View.VISIBLE);
messageContainer.setVisibility(View.GONE); messageContainer.setVisibility(View.GONE);
prepareCustomGraphView(); prepareCustomGraphView((BarData) currentGraphType.getData());
} else { } else {
commonGraphChart.clear(); commonGraphChart.clear();
commonGraphContainer.setVisibility(View.VISIBLE); commonGraphContainer.setVisibility(View.VISIBLE);
customGraphContainer.setVisibility(View.GONE); customGraphContainer.setVisibility(View.GONE);
messageContainer.setVisibility(View.GONE); messageContainer.setVisibility(View.GONE);
prepareCommonGraphView(); prepareCommonGraphView((LineData) currentGraphType.getData());
} }
} }
@ -233,24 +192,19 @@ public class MtGraphFragment extends Fragment
messageContainer.setVisibility(View.VISIBLE); messageContainer.setVisibility(View.VISIBLE);
TextView tvMessage = messageContainer.findViewById(R.id.message_text); TextView tvMessage = messageContainer.findViewById(R.id.message_text);
ImageView icon = messageContainer.findViewById(R.id.message_icon); ImageView icon = messageContainer.findViewById(R.id.message_icon);
if (GraphType.ALTITUDE.equals(currentGraphType)) { String message = getString(R.string.message_need_calculate_route_before_show_graph, currentGraphType.getTitle());
tvMessage.setText(R.string.message_need_calculate_route_for_show_graph); tvMessage.setText(message);
icon.setImageResource(R.drawable.ic_action_altitude_average); icon.setImageResource(R.drawable.ic_action_altitude_average);
} }
}
private void prepareCommonGraphView() {
LineData data = (LineData) graphData.get(currentGraphType);
if (data == null) return;
private void prepareCommonGraphView(LineData data) {
GpxUiHelper.setupGPXChart(commonGraphChart, 4, 24f, 16f, !nightMode, true); GpxUiHelper.setupGPXChart(commonGraphChart, 4, 24f, 16f, !nightMode, true);
commonGraphChart.setData(data); commonGraphChart.setData(data);
} }
private void prepareCustomGraphView() { private void prepareCustomGraphView(BarData data) {
BarData data = (BarData) graphData.get(currentGraphType); OsmandApplication app = getMyApplication();
OsmandApplication app = getMapActivity().getMyApplication(); if (app == null) return;
if (data == null || app == null) return;
GpxUiHelper.setupHorizontalGPXChart(app, customGraphChart, 5, 9, 24, true, nightMode); GpxUiHelper.setupHorizontalGPXChart(app, customGraphChart, 5, 9, 24, true, nightMode);
customGraphChart.setExtraRightOffset(16); customGraphChart.setExtraRightOffset(16);
@ -259,75 +213,57 @@ public class MtGraphFragment extends Fragment
} }
private void updateGraphData() { private void updateGraphData() {
graphTypes.clear();
OsmandApplication app = getMyApplication(); OsmandApplication app = getMyApplication();
GPXTrackAnalysis analysis = createGpxTrackAnalysis(); GPXTrackAnalysis analysis = createGpxTrackAnalysis();
// update common graph data // update common graph data
for (GraphType type : GraphType.getCommonTypes()) { for (CommonGraphType commonType : CommonGraphType.values()) {
List<ILineDataSet> dataSets = getDataSets(type, commonGraphChart, analysis); List<ILineDataSet> dataSets = getDataSets(commonType, commonGraphChart, analysis);
Object data = null;
if (!Algorithms.isEmpty(dataSets)) { if (!Algorithms.isEmpty(dataSets)) {
graphData.put(type, new LineData(dataSets)); data = new LineData(dataSets);
} else {
graphData.put(type, null);
} }
String title = getString(commonType.titleId);
graphTypes.add(new GraphType(title, false, commonType.canBeCalculated, data));
} }
// update custom graph data // update custom graph data
List<RouteSegmentResult> routeSegments = editingCtx.getAllRouteSegments(); List<RouteSegmentResult> routeSegments = editingCtx.getAllRouteSegments();
List<RouteStatistics> routeStatistics = calculateRouteStatistics(routeSegments); List<RouteStatistics> routeStatistics = calculateRouteStatistics(routeSegments);
for (GraphType type : GraphType.getCustomTypes()) { if (analysis != null && routeStatistics != null) {
RouteStatistics statistic = getStatisticForGraphType(routeStatistics, type); for (RouteStatistics statistics : routeStatistics) {
if (statistic != null && !Algorithms.isEmpty(statistic.elements)) { String title = SettingsBaseActivity.getStringRouteInfoPropertyValue(app, statistics.name);
BarData data = GpxUiHelper.buildStatisticChart( BarData data = null;
app, customGraphChart, statistic, analysis, true, nightMode); if (!Algorithms.isEmpty(statistics.elements)) {
graphData.put(type, data); data = GpxUiHelper.buildStatisticChart(
} else { app, customGraphChart, statistics, analysis, true, nightMode);
graphData.put(type, null); }
graphTypes.add(new GraphType(title, true, false, data));
} }
} }
} }
private RouteStatistics getStatisticForGraphType(List<RouteStatistics> routeStatistics, GraphType graphType) { private List<ILineDataSet> getDataSets(CommonGraphType type, LineChart chart, GPXTrackAnalysis analysis) {
if (routeStatistics == null) return null;
for (RouteStatistics statistic : routeStatistics) {
int graphTypeId = graphType.titleId;
int statisticId = SettingsBaseActivity.getStringRouteInfoPropertyValueId(statistic.name);
if (graphTypeId == statisticId) {
return statistic;
}
}
return null;
}
private List<ILineDataSet> getDataSets(GraphType graphType, LineChart chart, GPXTrackAnalysis analysis) {
List<ILineDataSet> dataSets = new ArrayList<>(); List<ILineDataSet> dataSets = new ArrayList<>();
if (chart != null && analysis != null) { if (chart != null && analysis != null) {
OsmandApplication app = getMyApplication(); OsmandApplication app = getMyApplication();
switch (graphType) { switch (type) {
case OVERVIEW: { case OVERVIEW: {
GpxUiHelper.OrderedLineDataSet speedDataSet = null;
GpxUiHelper.OrderedLineDataSet elevationDataSet = null;
// GpxUiHelper.OrderedLineDataSet slopeDataSet = null;
if (analysis.hasSpeedData) {
speedDataSet = GpxUiHelper.createGPXSpeedDataSet(app, chart,
analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, true, true, false);
}
if (analysis.hasElevationData) {
elevationDataSet = GpxUiHelper.createGPXElevationDataSet(app, chart,
analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, false, true, false);
// slopeDataSet = GpxUiHelper.createGPXSlopeDataSet(app, chart,
// analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, null, true, true, false);
}
List<GpxUiHelper.OrderedLineDataSet> dataList = new ArrayList<>(); List<GpxUiHelper.OrderedLineDataSet> dataList = new ArrayList<>();
if (speedDataSet != null) { if (analysis.hasSpeedData) {
GpxUiHelper.OrderedLineDataSet speedDataSet = GpxUiHelper.createGPXSpeedDataSet(app, chart,
analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, true, true, false);
dataList.add(speedDataSet); dataList.add(speedDataSet);
} }
if (elevationDataSet != null) { if (analysis.hasElevationData) {
GpxUiHelper.OrderedLineDataSet elevationDataSet = GpxUiHelper.createGPXElevationDataSet(app, chart,
analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, false, true, false);
dataList.add(elevationDataSet); dataList.add(elevationDataSet);
GpxUiHelper.OrderedLineDataSet slopeDataSet = GpxUiHelper.createGPXSlopeDataSet(app, chart,
analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, null, true, true, false);
dataList.add(slopeDataSet);
} }
// if (slopeDataSet != null) {
// dataList.add(slopeDataSet);
// }
if (dataList.size() > 0) { if (dataList.size() > 0) {
Collections.sort(dataList, new Comparator<GpxUiHelper.OrderedLineDataSet>() { Collections.sort(dataList, new Comparator<GpxUiHelper.OrderedLineDataSet>() {
@Override @Override
@ -343,29 +279,23 @@ public class MtGraphFragment extends Fragment
if (analysis.hasElevationData) { if (analysis.hasElevationData) {
GpxUiHelper.OrderedLineDataSet elevationDataSet = GpxUiHelper.createGPXElevationDataSet(app, chart, GpxUiHelper.OrderedLineDataSet elevationDataSet = GpxUiHelper.createGPXElevationDataSet(app, chart,
analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, false, true, false);//calcWithoutGaps); analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, false, true, false);//calcWithoutGaps);
if (elevationDataSet != null) {
dataSets.add(elevationDataSet); dataSets.add(elevationDataSet);
} }
}
break; break;
} }
// case SLOPE: case SLOPE:
// if (analysis.hasElevationData) { if (analysis.hasElevationData) {
// GpxUiHelper.OrderedLineDataSet slopeDataSet = GpxUiHelper.createGPXSlopeDataSet(app, chart, GpxUiHelper.OrderedLineDataSet slopeDataSet = GpxUiHelper.createGPXSlopeDataSet(app, chart,
// analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, null, true, true, false); analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, null, true, true, false);
// if (slopeDataSet != null) { dataSets.add(slopeDataSet);
// dataSets.add(slopeDataSet); }
// } break;
// }
// break;
case SPEED: { case SPEED: {
if (analysis.hasSpeedData) { if (analysis.hasSpeedData) {
GpxUiHelper.OrderedLineDataSet speedDataSet = GpxUiHelper.createGPXSpeedDataSet(app, chart, GpxUiHelper.OrderedLineDataSet speedDataSet = GpxUiHelper.createGPXSpeedDataSet(app, chart,
analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, false, true, false);//calcWithoutGaps); analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, false, true, false);//calcWithoutGaps);
if (speedDataSet != null) {
dataSets.add(speedDataSet); dataSets.add(speedDataSet);
} }
}
break; break;
} }
} }
@ -398,10 +328,6 @@ public class MtGraphFragment extends Fragment
defaultRender, currentSearchRequest, defaultSearchRequest); defaultRender, currentSearchRequest, defaultSearchRequest);
} }
private boolean isDataAvailableFor(GraphType graphType) {
return graphData != null && graphData.get(graphType) != null;
}
private OsmandApplication getMyApplication() { private OsmandApplication getMyApplication() {
return getMapActivity().getMyApplication(); return getMapActivity().getMyApplication();
} }
@ -409,4 +335,38 @@ public class MtGraphFragment extends Fragment
private MapActivity getMapActivity() { private MapActivity getMapActivity() {
return (MapActivity) getActivity(); return (MapActivity) getActivity();
} }
private static class GraphType {
private String title;
private boolean isCustom;
private boolean canBeCalculated;
private Object data;
public GraphType(String title, boolean isCustom, boolean canBeCalculated, Object data) {
this.title = title;
this.isCustom = isCustom;
this.canBeCalculated = canBeCalculated;
this.data = data;
}
public String getTitle() {
return title;
}
public boolean isCustom() {
return isCustom;
}
public boolean canBeCalculated() {
return canBeCalculated;
}
public boolean hasData() {
return getData() != null;
}
public Object getData() {
return data;
}
}
} }