bug6150: each device has its statistics options

This commit is contained in:
Masha Kashirina
2026-07-15 14:02:32 +02:00
parent d85522c436
commit 95a3340127
11 changed files with 824 additions and 230 deletions
@@ -329,6 +329,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
String position();
String windSourcesUsed();
String windStatisticsLines();
String toAllSelected();
String errorTryingToUpdateWindSourcesToExclude(String raceName, String message);
String feedback();
String startStopPlaying();
@@ -354,6 +355,9 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
String showWindAverageLine();
String showWindMinLine();
String showWindMaxLine();
String windStatAvg();
String windStatMin();
String windStatMax();
String fleet();
String boat();
String boatClass();
@@ -359,6 +359,7 @@ clickChartToSetTime=Click on the chart to set the time
position=Position
windSourcesUsed=Wind Sources Used
windStatisticsLines=Statistics Lines
toAllSelected=To all selected
errorTryingToUpdateWindSourcesToExclude=Error trying to update wind sources to exclude for race {0}: {1}
feedback=Feedback
startStopPlaying=Start/Stop playing
@@ -390,6 +391,9 @@ showWindDirectionSeries=Show wind directions data
showWindAverageLine=Show average line
showWindMinLine=Show minimum line
showWindMaxLine=Show maximum line
windStatAvg=Avg
windStatMin=Min
windStatMax=Max
fleet=Fleet
boatClass=Boat Class
setDelayToLive=Set delay to live
@@ -357,6 +357,7 @@ clickChartToSetTime=Klicke auf das Diagramm um die Zeit zu setzen
position=Position
windSourcesUsed=Benutzte Windquellen
windStatisticsLines=Statistiklinien
toAllSelected=Für alle ausgewählten
errorTryingToUpdateWindSourcesToExclude=Fehler beim Aktualisieren der auszuschließenden Windquellen für Rennen {0}: {1}
feedback=Feedback
startStopPlaying=Starten/Stoppen des abspielens
@@ -388,6 +389,9 @@ showWindDirectionSeries=Daten für Windrichtung zeigen
showWindAverageLine=Durchschnittslinie zeigen
showWindMinLine=Minimumlinie zeigen
showWindMaxLine=Maximumlinie zeigen
windStatAvg=Avg
windStatMin=Min
windStatMax=Max
fleet=Flotte
boatClass=Bootsklasse
setDelayToLive=Verzögerung zu ''Live'' setzen
@@ -142,7 +142,7 @@ public abstract class AbstractCompetitorLeaderboardChart<SettingsType extends Ge
new Marker().setEnabled(true).setHoverState(
new Marker().setEnabled(true).setRadius(4))).setShadow(false)
.setHoverStateLineWidth(LINE_WIDTH));
ChartUtil.useCheckboxesToShowAndHide(chart);
ChartUtil.useCheckboxesToShowAndHide(chart, () -> {});
chart.getXAxis().setType(Axis.Type.LINEAR);
chart.getXAxis().setLabels(new XAxisLabels().setFormatter(new AxisLabelsFormatter() {
@Override
@@ -177,7 +177,7 @@ public abstract class AbstractCompetitorRaceChart<SettingsType extends ChartSett
.setPlotBorderWidth(0)
.setCredits(new Credits().setEnabled(false));
chart.setStyleName(chartsCss.chartStyle());
ChartUtil.useCheckboxesToShowAndHide(chart);
ChartUtil.useCheckboxesToShowAndHide(chart, () -> {});
if (allowTimeAdjust) {
chart.setClickEventHandler(new ChartClickEventHandler() {
@@ -18,7 +18,7 @@ public class ChartUtil {
* added to the chart and hence visible. Otherwise, the checkbox won't initially be in sync with the series'
* visibility state.
*/
static protected void useCheckboxesToShowAndHide(final Chart chart) {
static protected void useCheckboxesToShowAndHide(final Chart chart, final Runnable onVisibilityChanged) {
chart.setLegend(new Legend().setEnabled(true).setBorderWidth(0).setSymbolPadding(25)); // make room for checkbox
chart.setSeriesPlotOptions(new SeriesPlotOptions().setShowCheckbox(true)
.setSeriesCheckboxClickEventHandler(new SeriesCheckboxClickEventHandler() {
@@ -27,6 +27,7 @@ public class ChartUtil {
// we want to change the visiblity not the selection state, so own handler is necessary
Series series = chart.getSeries(seriesCheckboxClickEvent.getSeriesId());
series.setVisible(seriesCheckboxClickEvent.isChecked());
onVisibilityChanged.run();
return false; // don't toggle the select state of the series
}
}).setSeriesLegendItemClickEventHandler(new SeriesLegendItemClickEventHandler() {
@@ -35,6 +36,7 @@ public class ChartUtil {
Series series = chart.getSeries(seriesLegendItemClickEvent.getSeriesId());
series.setVisible(!series.isVisible());
series.select(series.isVisible());
onVisibilityChanged.run();
return false;
}
}));
@@ -20,6 +20,7 @@ import org.moxieapps.gwt.highcharts.client.PlotLine;
import org.moxieapps.gwt.highcharts.client.PlotLine.DashStyle;
import org.moxieapps.gwt.highcharts.client.Point;
import org.moxieapps.gwt.highcharts.client.Series;
import org.moxieapps.gwt.highcharts.client.Style;
import org.moxieapps.gwt.highcharts.client.ToolTip;
import org.moxieapps.gwt.highcharts.client.ToolTipData;
import org.moxieapps.gwt.highcharts.client.ToolTipFormatter;
@@ -29,6 +30,7 @@ import org.moxieapps.gwt.highcharts.client.events.ChartSelectionEvent;
import org.moxieapps.gwt.highcharts.client.events.ChartSelectionEventHandler;
import org.moxieapps.gwt.highcharts.client.labels.AxisLabelsData;
import org.moxieapps.gwt.highcharts.client.labels.AxisLabelsFormatter;
import org.moxieapps.gwt.highcharts.client.labels.PlotLineLabel;
import org.moxieapps.gwt.highcharts.client.labels.XAxisLabels;
import org.moxieapps.gwt.highcharts.client.labels.YAxisLabels;
import org.moxieapps.gwt.highcharts.client.plotOptions.LinePlotOptions;
@@ -79,20 +81,12 @@ public class WindChart extends AbstractRaceChart<WindChartSettings> implements R
private final Map<WindSource, Point[]> windSourceDirectionPoints;
private final Map<WindSource, Point[]> windSourceSpeedPoints;
private Point firstPointOfFirstSeries;
private PlotLine directionAveragePlotLine;
private PlotLine directionMinPlotLine;
private PlotLine directionMaxPlotLine;
private PlotLine speedAveragePlotLine;
private PlotLine speedMinPlotLine;
private PlotLine speedMaxPlotLine;
private double directionRunningAvg;
private double directionRunningMin;
private double directionRunningMax;
private int directionPointCount;
private double speedRunningAvg;
private double speedRunningMin;
private double speedRunningMax;
private int speedPointCount;
private final Map<WindSource, PlotLine> directionAvgPlotLines = new HashMap<WindSource, PlotLine>();
private final Map<WindSource, PlotLine> directionMinPlotLines = new HashMap<WindSource, PlotLine>();
private final Map<WindSource, PlotLine> directionMaxPlotLines = new HashMap<WindSource, PlotLine>();
private final Map<WindSource, PlotLine> speedAvgPlotLines = new HashMap<WindSource, PlotLine>();
private final Map<WindSource, PlotLine> speedMinPlotLines = new HashMap<WindSource, PlotLine>();
private final Map<WindSource, PlotLine> speedMaxPlotLines = new HashMap<WindSource, PlotLine>();
private Long timeOfEarliestRequestInMillis;
private Long timeOfLatestRequestInMillis;
@@ -147,7 +141,7 @@ public class WindChart extends AbstractRaceChart<WindChartSettings> implements R
new Marker().setEnabled(true).setRadius(4))).setShadow(false)
.setHoverStateLineWidth(LINE_WIDTH));
chart.setStyleName(chartsCss.chartStyle());
ChartUtil.useCheckboxesToShowAndHide(chart);
ChartUtil.useCheckboxesToShowAndHide(chart, this::updateStatPlotLines);
final NumberFormat numberFormat = NumberFormat.getFormat("0");
chart.setToolTip(new ToolTip().setEnabled(true).setFormatter(new ToolTipFormatter() {
@Override
@@ -195,12 +189,6 @@ public class WindChart extends AbstractRaceChart<WindChartSettings> implements R
}
}));
timePlotLine = chart.getXAxis().createPlotLine().setColor("#656565").setWidth(1.5).setDashStyle(DashStyle.SOLID);
directionAveragePlotLine = chart.getYAxis(0).createPlotLine().setColor("#2060C0").setWidth(1.5).setDashStyle(DashStyle.SHORT_DASH);
directionMinPlotLine = chart.getYAxis(0).createPlotLine().setColor("#20A020").setWidth(1.5).setDashStyle(DashStyle.SHORT_DASH);
directionMaxPlotLine = chart.getYAxis(0).createPlotLine().setColor("#C02020").setWidth(1.5).setDashStyle(DashStyle.SHORT_DASH);
speedAveragePlotLine = chart.getYAxis(1).createPlotLine().setColor("#2060C0").setWidth(1.5).setDashStyle(DashStyle.SHORT_DASH);
speedMinPlotLine = chart.getYAxis(1).createPlotLine().setColor("#20A020").setWidth(1.5).setDashStyle(DashStyle.SHORT_DASH);
speedMaxPlotLine = chart.getYAxis(1).createPlotLine().setColor("#C02020").setWidth(1.5).setDashStyle(DashStyle.SHORT_DASH);
chart.getYAxis(0).setAxisTitleText(stringMessages.fromDeg()).setStartOnTick(false)
.setLabels(new YAxisLabels().setFormatter(new AxisLabelsFormatter() {
@@ -396,9 +384,6 @@ public class WindChart extends AbstractRaceChart<WindChartSettings> implements R
&& windSourceDirectionPoints.get(windSource).length != 0) {
previousDirectionPoint = windSourceDirectionPoints.get(windSource)[windSourceDirectionPoints.get(windSource).length - 1];
}
if (!append) {
resetStatAccumulators();
}
Point[] directionPoints = new Point[windTrackInfo.windFixes.size()];
Point[] speedPoints = new Point[windTrackInfo.windFixes.size()];
int currentPointIndex = 0;
@@ -429,20 +414,8 @@ public class WindChart extends AbstractRaceChart<WindChartSettings> implements R
}
directionPoints[currentPointIndex] = newDirectionPoint;
previousDirectionPoint = newDirectionPoint;
final double dirVal = newDirectionPoint.getY().doubleValue();
directionPointCount++;
directionRunningAvg += (dirVal - directionRunningAvg) / directionPointCount;
if (dirVal < directionRunningMin) { directionRunningMin = dirVal; }
if (dirVal > directionRunningMax) { directionRunningMax = dirVal; }
Point newSpeedPoint = new Point(wind.requestTimepoint, wind.dampenedTrueWindSpeedInKnots);
speedPoints[currentPointIndex++] = newSpeedPoint;
if (wind.dampenedTrueWindSpeedInKnots != null) {
final double spdVal = wind.dampenedTrueWindSpeedInKnots;
speedPointCount++;
speedRunningAvg += (spdVal - speedRunningAvg) / speedPointCount;
if (spdVal < speedRunningMin) { speedRunningMin = spdVal; }
if (spdVal > speedRunningMax) { speedRunningMax = spdVal; }
}
}
}
@@ -478,7 +451,6 @@ public class WindChart extends AbstractRaceChart<WindChartSettings> implements R
timeOfEarliestRequestInMillis = newMinTimepoint;
timeOfLatestRequestInMillis = newMaxTimepoint;
updateStatPlotLines();
}
@Override
@@ -506,25 +478,59 @@ public class WindChart extends AbstractRaceChart<WindChartSettings> implements R
}
settings.setShowWindDirectionsSeries(newSettings.isShowWindDirectionsSeries());
settings.setWindDirectionSourcesToDisplay(newSettings.getWindDirectionSourcesToDisplay());
settings.setShowWindSpeedSeries(newSettings.isShowWindSpeedSeries());
settings.setWindSpeedSourcesToDisplay(newSettings.getWindSpeedSourcesToDisplay());
if (!oldWindSourceTypesToRequest.equals(getNamesOfWindSourceTypesOfWhichToDisplaySpeedOrDirection())) {
clearCacheAndReload = true;
}
final boolean statLinesChanged = settings.isShowAverageLine() != newSettings.isShowAverageLine()
|| settings.isShowMinLine() != newSettings.isShowMinLine()
|| settings.isShowMaxLine() != newSettings.isShowMaxLine();
settings.setShowAverageLine(newSettings.isShowAverageLine());
settings.setShowMinLine(newSettings.isShowMinLine());
settings.setShowMaxLine(newSettings.isShowMaxLine());
final boolean dirAvgChanged = !settings.getDirectionAvgSources().equals(newSettings.getDirectionAvgSources());
final boolean dirMinChanged = !settings.getDirectionMinSources().equals(newSettings.getDirectionMinSources());
final boolean dirMaxChanged = !settings.getDirectionMaxSources().equals(newSettings.getDirectionMaxSources());
final boolean spdAvgChanged = !settings.getSpeedAvgSources().equals(newSettings.getSpeedAvgSources());
final boolean spdMinChanged = !settings.getSpeedMinSources().equals(newSettings.getSpeedMinSources());
final boolean spdMaxChanged = !settings.getSpeedMaxSources().equals(newSettings.getSpeedMaxSources());
settings.setDirectionAvgSources(newSettings.getDirectionAvgSources());
settings.setDirectionMinSources(newSettings.getDirectionMinSources());
settings.setDirectionMaxSources(newSettings.getDirectionMaxSources());
settings.setDirectionAvgBulk(newSettings.isDirectionAvgBulk());
settings.setDirectionMinBulk(newSettings.isDirectionMinBulk());
settings.setDirectionMaxBulk(newSettings.isDirectionMaxBulk());
settings.setSpeedAvgSources(newSettings.getSpeedAvgSources());
settings.setSpeedMinSources(newSettings.getSpeedMinSources());
settings.setSpeedMaxSources(newSettings.getSpeedMaxSources());
settings.setSpeedAvgBulk(newSettings.isSpeedAvgBulk());
settings.setSpeedMinBulk(newSettings.isSpeedMinBulk());
settings.setSpeedMaxBulk(newSettings.isSpeedMaxBulk());
if (clearCacheAndReload) {
clearCacheAndReload();
}
if (statLinesChanged) {
updateStatPlotLines();
}
updateVisibleSeries();
if (!clearCacheAndReload) {
if (dirAvgChanged) {
updateStatPlotLinesForStat(windSourceDirectionPoints, windSourceDirectionSeries, 0,
settings.getDirectionAvgSources(), directionAvgPlotLines, StatKind.AVG, true);
}
if (dirMinChanged) {
updateStatPlotLinesForStat(windSourceDirectionPoints, windSourceDirectionSeries, 0,
settings.getDirectionMinSources(), directionMinPlotLines, StatKind.MIN, true);
}
if (dirMaxChanged) {
updateStatPlotLinesForStat(windSourceDirectionPoints, windSourceDirectionSeries, 0,
settings.getDirectionMaxSources(), directionMaxPlotLines, StatKind.MAX, true);
}
if (spdAvgChanged) {
updateStatPlotLinesForStat(windSourceSpeedPoints, windSourceSpeedSeries, 1,
settings.getSpeedAvgSources(), speedAvgPlotLines, StatKind.AVG, false);
}
if (spdMinChanged) {
updateStatPlotLinesForStat(windSourceSpeedPoints, windSourceSpeedSeries, 1,
settings.getSpeedMinSources(), speedMinPlotLines, StatKind.MIN, false);
}
if (spdMaxChanged) {
updateStatPlotLinesForStat(windSourceSpeedPoints, windSourceSpeedSeries, 1,
settings.getSpeedMaxSources(), speedMaxPlotLines, StatKind.MAX, false);
}
}
}
private void clearCacheAndReload() {
@@ -533,21 +539,9 @@ public class WindChart extends AbstractRaceChart<WindChartSettings> implements R
windSourceDirectionPoints.clear();
windSourceSpeedPoints.clear();
firstPointOfFirstSeries = null;
resetStatAccumulators();
loadData(timeRangeWithZoomProvider.getFromTime(), timeRangeWithZoomProvider.getToTime(), /* append */false);
}
private void resetStatAccumulators() {
directionRunningAvg = 0;
directionRunningMin = Double.MAX_VALUE;
directionRunningMax = -Double.MAX_VALUE;
directionPointCount = 0;
speedRunningAvg = 0;
speedRunningMin = Double.MAX_VALUE;
speedRunningMax = -Double.MAX_VALUE;
speedPointCount = 0;
}
/**
* @param append
* if <code>true</code>, the results retrieved from the server will be appended to the wind chart instead
@@ -574,6 +568,7 @@ public class WindChart extends AbstractRaceChart<WindChartSettings> implements R
if (result != null) {
updateChartSeries(result, append);
updateVisibleSeries();
updateStatPlotLines();
} else {
if (!append) {
clearChart(); // no wind known for untracked race
@@ -609,51 +604,143 @@ public class WindChart extends AbstractRaceChart<WindChartSettings> implements R
removeStatPlotLines();
}
/** Removes all six stat plot lines (avg/min/max for direction and speed) from the chart and clears the maps. */
private void removeStatPlotLines() {
chart.getYAxis(0).removePlotLine(directionAveragePlotLine);
chart.getYAxis(0).removePlotLine(directionMinPlotLine);
chart.getYAxis(0).removePlotLine(directionMaxPlotLine);
chart.getYAxis(1).removePlotLine(speedAveragePlotLine);
chart.getYAxis(1).removePlotLine(speedMinPlotLine);
chart.getYAxis(1).removePlotLine(speedMaxPlotLine);
for (final PlotLine pl : directionAvgPlotLines.values()) { chart.getYAxis(0).removePlotLine(pl); }
for (final PlotLine pl : directionMinPlotLines.values()) { chart.getYAxis(0).removePlotLine(pl); }
for (final PlotLine pl : directionMaxPlotLines.values()) { chart.getYAxis(0).removePlotLine(pl); }
for (final PlotLine pl : speedAvgPlotLines.values()) { chart.getYAxis(1).removePlotLine(pl); }
for (final PlotLine pl : speedMinPlotLines.values()) { chart.getYAxis(1).removePlotLine(pl); }
for (final PlotLine pl : speedMaxPlotLines.values()) { chart.getYAxis(1).removePlotLine(pl); }
directionAvgPlotLines.clear();
directionMinPlotLines.clear();
directionMaxPlotLines.clear();
speedAvgPlotLines.clear();
speedMinPlotLines.clear();
speedMaxPlotLines.clear();
}
/** Which statistic a plot line represents. */
private enum StatKind { AVG, MIN, MAX }
/** Computes avg/min/max over a point array. Returns null if there are no valid points. */
private static double[] computeStatValues(final Point[] points) {
double sum = 0, min = Double.MAX_VALUE, max = -Double.MAX_VALUE;
int count = 0;
for (final Point p : points) {
if (p != null && p.getY() != null) {
final double v = p.getY().doubleValue();
sum += v;
if (v < min) { min = v; }
if (v > max) { max = v; }
count++;
}
}
if (count == 0) {
return null;
}
return new double[]{sum / count, min, max};
}
/**
* Creates a single stat plot line. Direction lines are dashed, speed lines are dotted.
* Avg is drawn thicker than min/max. The label sits on the left for direction, right for speed.
* The line color is a darkened version of the series color so it stands out from the main line.
*/
private PlotLine buildStatPlotLine(final int yAxisIndex, final String color, final double value,
final StatKind kind, final boolean isDirection, final String labelText) {
final DashStyle dashStyle = isDirection ? DashStyle.DASH : DashStyle.DOT;
final double width = isDirection
? (kind == StatKind.AVG ? 2 : 1.5)
: (kind == StatKind.AVG ? 3 : 2);
final PlotLineLabel.Align align = isDirection ? PlotLineLabel.Align.LEFT : PlotLineLabel.Align.RIGHT;
final int labelX = isDirection ? 4 : -4;
final String lineColor = darkenColor(color, 0.65);
final PlotLine pl = chart.getYAxis(yAxisIndex).createPlotLine()
.setColor(lineColor)
.setWidth(width)
.setDashStyle(dashStyle)
.setZIndex(5)
.setLabel(new PlotLineLabel()
.setText(labelText)
.setAlign(align)
.setX(labelX)
.setY(-4)
.setStyle(new Style().setOption("fontSize", "11px").setOption("color", lineColor)));
pl.setValue(value);
return pl;
}
/** Multiplies each RGB channel by factor to produce a darker shade of the given hex color. */
private static String darkenColor(final String hex, final double factor) {
final String h = hex.startsWith("#") ? hex.substring(1) : hex;
final int r = (int) (Integer.parseInt(h.substring(0, 2), 16) * factor);
final int g = (int) (Integer.parseInt(h.substring(2, 4), 16) * factor);
final int b = (int) (Integer.parseInt(h.substring(4, 6), 16) * factor);
return "#" + toHex2(r) + toHex2(g) + toHex2(b);
}
private static String toHex2(final int v) {
final String s = Integer.toHexString(v);
return s.length() < 2 ? "0" + s : s;
}
/**
* Redraws the plot lines for one stat type (avg, min, or max) on one axis.
* Removes the old lines first, then adds a new line for each visible source whose type
* appears in enabledTypes. Called individually per stat so only the changed ones are redrawn.
*/
private void updateStatPlotLinesForStat(
final Map<WindSource, Point[]> pointsMap,
final Map<WindSource, Series> seriesMap,
final int yAxisIndex,
final Set<WindSourceType> enabledTypes,
final Map<WindSource, PlotLine> plotLineMap,
final StatKind kind,
final boolean isDirection) {
for (final PlotLine pl : plotLineMap.values()) {
chart.getYAxis(yAxisIndex).removePlotLine(pl);
}
plotLineMap.clear();
final NumberFormat fmt = NumberFormat.getFormat("0.#");
for (final Map.Entry<WindSource, Point[]> entry : pointsMap.entrySet()) {
final WindSource source = entry.getKey();
final Series series = seriesMap.get(source);
if (series != null && series.isVisible() && entry.getValue() != null
&& enabledTypes.contains(source.getType())) {
final double[] stats = computeStatValues(entry.getValue());
if (stats != null) {
final String color = colorMap.getColorByID(source).getAsHtml();
final String sourceName = WindSourceTypeFormatter.format(source, stringMessages);
final double statValue = kind == StatKind.AVG ? stats[0] : (kind == StatKind.MIN ? stats[1] : stats[2]);
final String kindLabel = kind == StatKind.AVG ? stringMessages.windStatAvg()
: (kind == StatKind.MIN ? stringMessages.windStatMin() : stringMessages.windStatMax());
final String unit = isDirection ? stringMessages.degreesShort() : stringMessages.knotsUnit();
final String labelText = kindLabel + " " + sourceName + ": " + fmt.format(statValue) + unit;
final PlotLine pl = buildStatPlotLine(yAxisIndex, color, statValue, kind, isDirection, labelText);
chart.getYAxis(yAxisIndex).addPlotLines(pl);
plotLineMap.put(source, pl);
}
}
}
}
/** Redraws all six stat plot lines at once. Used when all of them need to refresh together
* after new data loads or when a legend series is toggled. When only one stat setting changes
* in the dialog, updateStatPlotLinesForStat is called directly for just that one. */
private void updateStatPlotLines() {
removeStatPlotLines();
final boolean showAvg = settings.isShowAverageLine();
final boolean showMin = settings.isShowMinLine();
final boolean showMax = settings.isShowMaxLine();
if (!showAvg && !showMin && !showMax) {
return;
}
if (directionPointCount > 0) {
if (showAvg) {
directionAveragePlotLine.setValue(directionRunningAvg);
chart.getYAxis(0).addPlotLines(directionAveragePlotLine);
}
if (showMin) {
directionMinPlotLine.setValue(directionRunningMin);
chart.getYAxis(0).addPlotLines(directionMinPlotLine);
}
if (showMax) {
directionMaxPlotLine.setValue(directionRunningMax);
chart.getYAxis(0).addPlotLines(directionMaxPlotLine);
}
}
if (speedPointCount > 0) {
if (showAvg) {
speedAveragePlotLine.setValue(speedRunningAvg);
chart.getYAxis(1).addPlotLines(speedAveragePlotLine);
}
if (showMin) {
speedMinPlotLine.setValue(speedRunningMin);
chart.getYAxis(1).addPlotLines(speedMinPlotLine);
}
if (showMax) {
speedMaxPlotLine.setValue(speedRunningMax);
chart.getYAxis(1).addPlotLines(speedMaxPlotLine);
}
}
updateStatPlotLinesForStat(windSourceDirectionPoints, windSourceDirectionSeries, 0,
settings.getDirectionAvgSources(), directionAvgPlotLines, StatKind.AVG, true);
updateStatPlotLinesForStat(windSourceDirectionPoints, windSourceDirectionSeries, 0,
settings.getDirectionMinSources(), directionMinPlotLines, StatKind.MIN, true);
updateStatPlotLinesForStat(windSourceDirectionPoints, windSourceDirectionSeries, 0,
settings.getDirectionMaxSources(), directionMaxPlotLines, StatKind.MAX, true);
updateStatPlotLinesForStat(windSourceSpeedPoints, windSourceSpeedSeries, 1,
settings.getSpeedAvgSources(), speedAvgPlotLines, StatKind.AVG, false);
updateStatPlotLinesForStat(windSourceSpeedPoints, windSourceSpeedSeries, 1,
settings.getSpeedMinSources(), speedMinPlotLines, StatKind.MIN, false);
updateStatPlotLinesForStat(windSourceSpeedPoints, windSourceSpeedSeries, 1,
settings.getSpeedMaxSources(), speedMaxPlotLines, StatKind.MAX, false);
}
/**
@@ -792,7 +879,7 @@ public class WindChart extends AbstractRaceChart<WindChartSettings> implements R
Set<WindSourceType> windDirectionSourcesToDisplay = new HashSet<>();
windSpeedSourcesToDisplay.add(type);
windDirectionSourcesToDisplay.add(type);
WindChartSettings patched = new WindChartSettings(true, windSpeedSourcesToDisplay, true, windDirectionSourcesToDisplay, settings.getResolutionInMilliseconds(), settings.isShowAverageLine(), settings.isShowMinLine(), settings.isShowMaxLine());
WindChartSettings patched = new WindChartSettings(true, windSpeedSourcesToDisplay, true, windDirectionSourcesToDisplay, settings.getResolutionInMilliseconds(), settings.getDirectionAvgSources(), settings.getDirectionMinSources(), settings.getDirectionMaxSources(), settings.isDirectionAvgBulk(), settings.isDirectionMinBulk(), settings.isDirectionMaxBulk(), settings.getSpeedAvgSources(), settings.getSpeedMinSources(), settings.getSpeedMaxSources(), settings.isSpeedAvgBulk(), settings.isSpeedMinBulk(), settings.isSpeedMaxBulk());
updateSettings(patched);
preselectFilter = windprovider;
updateVisibleSeries();
@@ -22,49 +22,74 @@ public class WindChartSettings extends AbstractGenericSerializableSettings {
private BooleanSetting showWindSpeedSeries;
private BooleanSetting showWindDirectionsSeries;
private BooleanSetting showAverageLine;
private BooleanSetting showMinLine;
private BooleanSetting showMaxLine;
private EnumLinkedHashSetSetting<WindSourceType> directionAvgSources;
private EnumLinkedHashSetSetting<WindSourceType> directionMinSources;
private EnumLinkedHashSetSetting<WindSourceType> directionMaxSources;
private BooleanSetting directionAvgBulk;
private BooleanSetting directionMinBulk;
private BooleanSetting directionMaxBulk;
private EnumLinkedHashSetSetting<WindSourceType> speedAvgSources;
private EnumLinkedHashSetSetting<WindSourceType> speedMinSources;
private EnumLinkedHashSetSetting<WindSourceType> speedMaxSources;
private BooleanSetting speedAvgBulk;
private BooleanSetting speedMinBulk;
private BooleanSetting speedMaxBulk;
@Override
protected void addChildSettings() {
Set<WindSourceType> defaultWindDirectionSourcesToDisplay = new LinkedHashSet<WindSourceType>();
defaultWindDirectionSourcesToDisplay.add(WindSourceType.COMBINED);
windDirectionSourcesToDisplay = new EnumLinkedHashSetSetting<>("windDirectionSourcesToDisplay", this, defaultWindDirectionSourcesToDisplay, WindSourceType::valueOf);
Set<WindSourceType> defaultWindSpeedSourcesToDisplay = new LinkedHashSet<>();
defaultWindSpeedSourcesToDisplay.add(WindSourceType.COMBINED);
windSpeedSourcesToDisplay = new EnumLinkedHashSetSetting<>("windSpeedSourcesToDisplay", this, defaultWindSpeedSourcesToDisplay, WindSourceType::valueOf);
resolutionInMilliseconds = new LongSetting("resolutionInMilliseconds", this, DEFAULT_RESOLUTION_IN_MILLISECONDS);
showWindSpeedSeries = new BooleanSetting("showWindSpeedSeries", this, true);
showWindDirectionsSeries = new BooleanSetting("showWindDirectionsSeries", this, true);
showAverageLine = new BooleanSetting("showAverageLine", this, false);
showMinLine = new BooleanSetting("showMinLine", this, false);
showMaxLine = new BooleanSetting("showMaxLine", this, false);
directionAvgSources = new EnumLinkedHashSetSetting<>("directionAvgSources", this, new LinkedHashSet<WindSourceType>(), WindSourceType::valueOf);
directionMinSources = new EnumLinkedHashSetSetting<>("directionMinSources", this, new LinkedHashSet<WindSourceType>(), WindSourceType::valueOf);
directionMaxSources = new EnumLinkedHashSetSetting<>("directionMaxSources", this, new LinkedHashSet<WindSourceType>(), WindSourceType::valueOf);
directionAvgBulk = new BooleanSetting("directionAvgBulk", this, false);
directionMinBulk = new BooleanSetting("directionMinBulk", this, false);
directionMaxBulk = new BooleanSetting("directionMaxBulk", this, false);
speedAvgSources = new EnumLinkedHashSetSetting<>("speedAvgSources", this, new LinkedHashSet<WindSourceType>(), WindSourceType::valueOf);
speedMinSources = new EnumLinkedHashSetSetting<>("speedMinSources", this, new LinkedHashSet<WindSourceType>(), WindSourceType::valueOf);
speedMaxSources = new EnumLinkedHashSetSetting<>("speedMaxSources", this, new LinkedHashSet<WindSourceType>(), WindSourceType::valueOf);
speedAvgBulk = new BooleanSetting("speedAvgBulk", this, false);
speedMinBulk = new BooleanSetting("speedMinBulk", this, false);
speedMaxBulk = new BooleanSetting("speedMaxBulk", this, false);
}
/**
* The default settings
*/
public WindChartSettings() {
super();
}
public WindChartSettings(boolean showWindSpeedSeries, Set<WindSourceType> windSpeedSourcesToDisplay,
boolean showWindDirectionsSeries, Set<WindSourceType> windDirectionSourcesToDisplay, long resolutionInMilliseconds,
boolean showAverageLine, boolean showMinLine, boolean showMaxLine) {
Set<WindSourceType> directionAvgSources, Set<WindSourceType> directionMinSources, Set<WindSourceType> directionMaxSources,
boolean directionAvgBulk, boolean directionMinBulk, boolean directionMaxBulk,
Set<WindSourceType> speedAvgSources, Set<WindSourceType> speedMinSources, Set<WindSourceType> speedMaxSources,
boolean speedAvgBulk, boolean speedMinBulk, boolean speedMaxBulk) {
this();
this.showWindSpeedSeries.setValue(showWindSpeedSeries);
this.windSpeedSourcesToDisplay.setValues(windSpeedSourcesToDisplay);
this.showWindDirectionsSeries.setValue(showWindDirectionsSeries);
this.windDirectionSourcesToDisplay.setValues(windDirectionSourcesToDisplay);
this.resolutionInMilliseconds.setValue(resolutionInMilliseconds);
this.showAverageLine.setValue(showAverageLine);
this.showMinLine.setValue(showMinLine);
this.showMaxLine.setValue(showMaxLine);
this.directionAvgSources.setValues(directionAvgSources);
this.directionMinSources.setValues(directionMinSources);
this.directionMaxSources.setValues(directionMaxSources);
this.directionAvgBulk.setValue(directionAvgBulk);
this.directionMinBulk.setValue(directionMinBulk);
this.directionMaxBulk.setValue(directionMaxBulk);
this.speedAvgSources.setValues(speedAvgSources);
this.speedMinSources.setValues(speedMinSources);
this.speedMaxSources.setValues(speedMaxSources);
this.speedAvgBulk.setValue(speedAvgBulk);
this.speedMinBulk.setValue(speedMinBulk);
this.speedMaxBulk.setValue(speedMaxBulk);
}
public Set<WindSourceType> getWindDirectionSourcesToDisplay() {
return Util.asSet(windDirectionSourcesToDisplay.getValues());
}
@@ -98,38 +123,110 @@ public class WindChartSettings extends AbstractGenericSerializableSettings {
}
public void setWindDirectionSourcesToDisplay(Set<WindSourceType> windDirectionSourcesToDisplay) {
if(windDirectionSourcesToDisplay != null) {
if (windDirectionSourcesToDisplay != null) {
this.windDirectionSourcesToDisplay.setValues(windDirectionSourcesToDisplay);
}
}
public void setWindSpeedSourcesToDisplay(Set<WindSourceType> windSpeedSourcesToDisplay) {
if(windSpeedSourcesToDisplay != null) {
if (windSpeedSourcesToDisplay != null) {
this.windSpeedSourcesToDisplay.setValues(windSpeedSourcesToDisplay);
}
}
public boolean isShowAverageLine() {
return showAverageLine.getValue();
public Set<WindSourceType> getDirectionAvgSources() {
return Util.asSet(directionAvgSources.getValues());
}
public void setShowAverageLine(final boolean showAverageLine) {
this.showAverageLine.setValue(showAverageLine);
public void setDirectionAvgSources(final Set<WindSourceType> sources) {
directionAvgSources.setValues(sources);
}
public boolean isShowMinLine() {
return showMinLine.getValue();
public Set<WindSourceType> getDirectionMinSources() {
return Util.asSet(directionMinSources.getValues());
}
public void setShowMinLine(final boolean showMinLine) {
this.showMinLine.setValue(showMinLine);
public void setDirectionMinSources(final Set<WindSourceType> sources) {
directionMinSources.setValues(sources);
}
public boolean isShowMaxLine() {
return showMaxLine.getValue();
public Set<WindSourceType> getDirectionMaxSources() {
return Util.asSet(directionMaxSources.getValues());
}
public void setShowMaxLine(final boolean showMaxLine) {
this.showMaxLine.setValue(showMaxLine);
public void setDirectionMaxSources(final Set<WindSourceType> sources) {
directionMaxSources.setValues(sources);
}
public boolean isDirectionAvgBulk() {
return directionAvgBulk.getValue();
}
public void setDirectionAvgBulk(final boolean bulk) {
directionAvgBulk.setValue(bulk);
}
public boolean isDirectionMinBulk() {
return directionMinBulk.getValue();
}
public void setDirectionMinBulk(final boolean bulk) {
directionMinBulk.setValue(bulk);
}
public boolean isDirectionMaxBulk() {
return directionMaxBulk.getValue();
}
public void setDirectionMaxBulk(final boolean bulk) {
directionMaxBulk.setValue(bulk);
}
public Set<WindSourceType> getSpeedAvgSources() {
return Util.asSet(speedAvgSources.getValues());
}
public void setSpeedAvgSources(final Set<WindSourceType> sources) {
speedAvgSources.setValues(sources);
}
public Set<WindSourceType> getSpeedMinSources() {
return Util.asSet(speedMinSources.getValues());
}
public void setSpeedMinSources(final Set<WindSourceType> sources) {
speedMinSources.setValues(sources);
}
public Set<WindSourceType> getSpeedMaxSources() {
return Util.asSet(speedMaxSources.getValues());
}
public void setSpeedMaxSources(final Set<WindSourceType> sources) {
speedMaxSources.setValues(sources);
}
public boolean isSpeedAvgBulk() {
return speedAvgBulk.getValue();
}
public void setSpeedAvgBulk(final boolean bulk) {
speedAvgBulk.setValue(bulk);
}
public boolean isSpeedMinBulk() {
return speedMinBulk.getValue();
}
public void setSpeedMinBulk(final boolean bulk) {
speedMinBulk.setValue(bulk);
}
public boolean isSpeedMaxBulk() {
return speedMaxBulk.getValue();
}
public void setSpeedMaxBulk(final boolean bulk) {
speedMaxBulk.setValue(bulk);
}
}
@@ -0,0 +1,85 @@
.statButton {
display: inline-block;
padding: 2px 6px;
font-size: 11px;
font-weight: bold;
color: #666;
background-color: #e8e8e8;
border: 1px solid #ccc;
border-radius: 3px;
cursor: pointer;
min-width: 30px;
text-align: center;
margin: 1px;
user-select: none;
}
.statButton:hover {
background-color: #d0d0d0;
}
.statButtonActive {
color: #fff;
background-color: #2060C0;
border-color: #1a4a99;
}
.statButtonActive:hover {
background-color: #1a4a99;
}
.statButtonIndeterminate {
color: #fff;
background-color: #7aa3e0;
border-color: #5580c0;
}
.statButtonIndeterminate:hover {
background-color: #5580c0;
}
.statButtonBulkActive {
color: #fff;
background-color: #F0AB00;
border-color: #b07d00;
}
.statButtonBulkActive:hover {
background-color: #b07d00;
}
.statButtonBulkIndeterminate {
color: #fff;
background-color: #f5cc55;
border-color: #b07d00;
}
.statButtonBulkIndeterminate:hover {
background-color: #d4a800;
}
.statButtonSectionDisabled {
opacity: 0.35;
cursor: default;
pointer-events: none;
}
.toAllSelectedLabel {
padding-left: 4px;
font-style: italic;
color: #555;
font-size: 11px;
}
.sourceCheckboxIndent {
padding-left: 16px;
}
.accentCheckbox input[type="checkbox"] {
accent-color: #F0AB00;
}
.sectionDivider {
border-left: 1px solid #cccccc;
margin: 0 8px;
}
@@ -1,166 +1,453 @@
package com.sap.sailing.gwt.ui.client.shared.charts;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.FocusWidget;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.WindSourceType;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.WindSourceTypeFormatter;
import com.sap.sailing.gwt.ui.client.shared.charts.WindChartSettingsDialogCssResources.DialogCss;
import com.sap.sse.gwt.client.controls.IntegerBox;
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
import com.sap.sse.gwt.client.dialog.DataEntryDialog.Validator;
import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent;
public class WindChartSettingsDialogComponent implements SettingsDialogComponent<WindChartSettings> {
private static final DialogCss CSS = WindChartSettingsDialogCssResources.INSTANCE.css();
private static final Iterable<WindSourceType> ALL_SOURCE_TYPES = EnumSet.allOf(WindSourceType.class);
private enum StatState { OFF, ON, INDETERMINATE }
/**
* A small clickable button-like label for toggling avg/min/max on or off.
* Has three visual states: off (grey), on (colored), and indeterminate (used by bulk toggles
* when only some sources have that stat enabled). Bulk toggles use a different color (yellow)
* than per-source toggles (blue). When the section is disabled (show checkbox unchecked),
* the button fades out to signal the selection is preserved but inactive.
*/
private static final class StatToggle {
private final Label label;
private StatState state;
private final DialogCss css;
private final boolean bulk;
private boolean sectionDisabled;
StatToggle(final String text, final boolean initialOn, final DialogCss css, final boolean bulk) {
this.css = css;
this.bulk = bulk;
css.ensureInjected();
label = new Label(text);
label.addStyleName(css.statButton());
setState(initialOn ? StatState.ON : StatState.OFF);
}
void setState(final StatState newState) {
state = newState;
label.removeStyleName(css.statButtonActive());
label.removeStyleName(css.statButtonIndeterminate());
label.removeStyleName(css.statButtonBulkActive());
label.removeStyleName(css.statButtonBulkIndeterminate());
if (state == StatState.ON) {
label.addStyleName(bulk ? css.statButtonBulkActive() : css.statButtonActive());
} else if (state == StatState.INDETERMINATE) {
label.addStyleName(bulk ? css.statButtonBulkIndeterminate() : css.statButtonIndeterminate());
}
}
void setSectionDisabled(final boolean disabled) {
sectionDisabled = disabled;
if (sectionDisabled) {
label.addStyleName(css.statButtonSectionDisabled());
} else {
label.removeStyleName(css.statButtonSectionDisabled());
}
}
void toggle() {
setState(state == StatState.ON ? StatState.OFF : StatState.ON);
}
boolean isOn() {
return state == StatState.ON;
}
Widget asWidget() {
return label;
}
void addClickHandler(final ClickHandler handler) {
label.addClickHandler(handler);
}
}
private final WindChartSettings initialSettings;
private IntegerBox resolutionInSecondsBox;
private final Map<WindSourceType, CheckBox> windDirectionsCheckboxes;
private final Map<WindSourceType, CheckBox> windSpeedCheckboxes;
private CheckBox showWindSpeedSeriesCheckbox;
private CheckBox showWindDirectionsSeriesCheckbox;
private CheckBox showAverageLineCheckbox;
private CheckBox showMinLineCheckbox;
private CheckBox showMaxLineCheckbox;
private CheckBox showWindSpeedSeriesCheckbox;
private final Map<WindSourceType, CheckBox> windDirectionSourceCheckboxes = new LinkedHashMap<WindSourceType, CheckBox>();
private final Map<WindSourceType, CheckBox> windSpeedSourceCheckboxes = new LinkedHashMap<WindSourceType, CheckBox>();
private final Map<WindSourceType, StatToggle> dirAvgToggles = new LinkedHashMap<WindSourceType, StatToggle>();
private final Map<WindSourceType, StatToggle> dirMinToggles = new LinkedHashMap<WindSourceType, StatToggle>();
private final Map<WindSourceType, StatToggle> dirMaxToggles = new LinkedHashMap<WindSourceType, StatToggle>();
private final Map<WindSourceType, StatToggle> spdAvgToggles = new LinkedHashMap<WindSourceType, StatToggle>();
private final Map<WindSourceType, StatToggle> spdMinToggles = new LinkedHashMap<WindSourceType, StatToggle>();
private final Map<WindSourceType, StatToggle> spdMaxToggles = new LinkedHashMap<WindSourceType, StatToggle>();
private StatToggle dirBulkAvg;
private StatToggle dirBulkMin;
private StatToggle dirBulkMax;
private StatToggle spdBulkAvg;
private StatToggle spdBulkMin;
private StatToggle spdBulkMax;
private final StringMessages stringMessages;
public WindChartSettingsDialogComponent(WindChartSettings initialSettings, StringMessages stringMessages) {
public WindChartSettingsDialogComponent(final WindChartSettings initialSettings, final StringMessages stringMessages) {
super();
this.stringMessages = stringMessages;
this.initialSettings = initialSettings;
windDirectionsCheckboxes = new LinkedHashMap<WindSourceType, CheckBox>();
windSpeedCheckboxes = new LinkedHashMap<WindSourceType, CheckBox>();
}
@Override
public Widget getAdditionalWidget(DataEntryDialog<?> dialog) {
VerticalPanel vp = new VerticalPanel();
resolutionInSecondsBox = dialog.createIntegerBox((int) (initialSettings.getResolutionInMilliseconds()/1000), /* visibleLength */ 5);
HorizontalPanel hp = new HorizontalPanel();
@Override
public Widget getAdditionalWidget(final DataEntryDialog<?> dialog) {
final VerticalPanel vp = new VerticalPanel();
resolutionInSecondsBox = dialog.createIntegerBox((int) (initialSettings.getResolutionInMilliseconds() / 1000), 5);
final HorizontalPanel hp = new HorizontalPanel();
hp.add(new Label(stringMessages.stepSizeInSeconds() + ":"));
hp.add(resolutionInSecondsBox);
vp.add(hp);
Label createHeadlineLabel = dialog.createHeadlineLabel(stringMessages.windSourcesUsed());
vp.add(createHeadlineLabel);
final FlexTable dirTable = new FlexTable();
dirTable.getElement().getStyle().setMarginLeft(15.0, Unit.PX);
dirTable.setCellPadding(2);
Grid grid = new Grid(1,2);
grid.setCellPadding(5);
vp.add(grid);
VerticalPanel windDirectionSourcesPanel = new VerticalPanel();
grid.setWidget(0, 0, windDirectionSourcesPanel);
showWindDirectionsSeriesCheckbox = dialog.createCheckbox(stringMessages.showWindDirectionSeries());
showWindDirectionsSeriesCheckbox.setTitle(stringMessages.showWindDirectionSeriesTooltip());
showWindDirectionsSeriesCheckbox.setValue(initialSettings.isShowWindDirectionsSeries());
showWindDirectionsSeriesCheckbox.addStyleName(CSS.accentCheckbox());
dirTable.setWidget(0, 0, showWindDirectionsSeriesCheckbox);
dirTable.getFlexCellFormatter().setColSpan(0, 0, 4);
VerticalPanel windSpeedSourcesPanel = new VerticalPanel();
grid.setWidget(0, 1, windSpeedSourcesPanel);
final StatToggle[] dirBulk = buildSourceSection(dialog, dirTable, 1,
windDirectionSourceCheckboxes, dirAvgToggles, dirMinToggles, dirMaxToggles,
initialSettings.getWindDirectionSourcesToDisplay(),
initialSettings.getDirectionAvgSources(), initialSettings.getDirectionMinSources(), initialSettings.getDirectionMaxSources(),
initialSettings.isDirectionAvgBulk(), initialSettings.isDirectionMinBulk(), initialSettings.isDirectionMaxBulk(),
initialSettings.isShowWindDirectionsSeries(), true);
dirBulkAvg = dirBulk[0];
dirBulkMin = dirBulk[1];
dirBulkMax = dirBulk[2];
if (!initialSettings.isShowWindDirectionsSeries()) {
setSectionTogglesDisabled(dirAvgToggles, dirMinToggles, dirMaxToggles, dirBulkAvg, dirBulkMin, dirBulkMax, true);
}
showWindDirectionsSeriesCheckbox.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
@Override
public void onValueChange(final ValueChangeEvent<Boolean> event) {
final boolean enabled = event.getValue();
for (final CheckBox cb : windDirectionSourceCheckboxes.values()) {
cb.setEnabled(enabled);
}
setSectionTogglesDisabled(dirAvgToggles, dirMinToggles, dirMaxToggles, dirBulkAvg, dirBulkMin, dirBulkMax, !enabled);
}
});
final FlexTable spdTable = new FlexTable();
spdTable.getElement().getStyle().setMarginLeft(8.0, Unit.PX);
spdTable.setCellPadding(2);
showWindSpeedSeriesCheckbox = dialog.createCheckbox(stringMessages.showWindSpeedSeries());
showWindSpeedSeriesCheckbox.setTitle(stringMessages.showWindSpeedSeriesTooltip());
showWindSpeedSeriesCheckbox.setValue(initialSettings.isShowWindSpeedSeries());
showWindSpeedSeriesCheckbox.addStyleName(CSS.accentCheckbox());
spdTable.setWidget(0, 0, showWindSpeedSeriesCheckbox);
spdTable.getFlexCellFormatter().setColSpan(0, 0, 4);
final StatToggle[] spdBulk = buildSourceSection(dialog, spdTable, 1,
windSpeedSourceCheckboxes, spdAvgToggles, spdMinToggles, spdMaxToggles,
initialSettings.getWindSpeedSourcesToDisplay(),
initialSettings.getSpeedAvgSources(), initialSettings.getSpeedMinSources(), initialSettings.getSpeedMaxSources(),
initialSettings.isSpeedAvgBulk(), initialSettings.isSpeedMinBulk(), initialSettings.isSpeedMaxBulk(),
initialSettings.isShowWindSpeedSeries(), false);
spdBulkAvg = spdBulk[0];
spdBulkMin = spdBulk[1];
spdBulkMax = spdBulk[2];
if (!initialSettings.isShowWindSpeedSeries()) {
setSectionTogglesDisabled(spdAvgToggles, spdMinToggles, spdMaxToggles, spdBulkAvg, spdBulkMin, spdBulkMax, true);
}
showWindSpeedSeriesCheckbox.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
@Override
public void onValueChange(ValueChangeEvent<Boolean> event) {
for (Map.Entry<WindSourceType, CheckBox> box : windSpeedCheckboxes.entrySet()) {
box.getValue().setEnabled(event.getValue());
}
}
});
windSpeedSourcesPanel.add(showWindSpeedSeriesCheckbox);
showWindDirectionsSeriesCheckbox = dialog.createCheckbox(stringMessages.showWindDirectionSeries());
showWindDirectionsSeriesCheckbox.setTitle(stringMessages.showWindDirectionSeriesTooltip());
showWindDirectionsSeriesCheckbox.setValue(initialSettings.isShowWindDirectionsSeries());
showWindDirectionsSeriesCheckbox.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
@Override
public void onValueChange(ValueChangeEvent<Boolean> event) {
for (Map.Entry<WindSourceType, CheckBox> box : windDirectionsCheckboxes.entrySet()) {
box.getValue().setEnabled(event.getValue());
public void onValueChange(final ValueChangeEvent<Boolean> event) {
final boolean enabled = event.getValue();
for (final CheckBox cb : windSpeedSourceCheckboxes.values()) {
cb.setEnabled(enabled);
}
setSectionTogglesDisabled(spdAvgToggles, spdMinToggles, spdMaxToggles, spdBulkAvg, spdBulkMin, spdBulkMax, !enabled);
}
});
windDirectionSourcesPanel.add(showWindDirectionsSeriesCheckbox);
final SimplePanel divider = new SimplePanel();
divider.addStyleName(CSS.sectionDivider());
for (WindSourceType windSourceType : getWindSourceTypesToOffer()) {
CheckBox checkbox = dialog.createCheckbox(WindSourceTypeFormatter.format(windSourceType, stringMessages));
checkbox.setTitle(WindSourceTypeFormatter.tooltipFor(windSourceType, stringMessages));
checkbox.setEnabled(initialSettings.isShowWindDirectionsSeries());
windDirectionsCheckboxes.put(windSourceType, checkbox);
checkbox.setValue(initialSettings.getWindDirectionSourcesToDisplay().contains(windSourceType));
checkbox.getElement().getStyle().setMarginLeft(15.0, Unit.PX);
windDirectionSourcesPanel.add(checkbox);
}
for (WindSourceType windSourceType : getWindSourceTypesToOffer()) {
CheckBox checkbox = dialog.createCheckbox(WindSourceTypeFormatter.format(windSourceType, stringMessages));
checkbox.setTitle(WindSourceTypeFormatter.tooltipFor(windSourceType, stringMessages));
checkbox.setEnabled(initialSettings.isShowWindSpeedSeries() && windSourceType.useSpeed());
windSpeedCheckboxes.put(windSourceType, checkbox);
checkbox.setValue(initialSettings.getWindSpeedSourcesToDisplay().contains(windSourceType));
checkbox.getElement().getStyle().setMarginLeft(15.0, Unit.PX);
windSpeedSourcesPanel.add(checkbox);
}
vp.add(dialog.createHeadlineLabel(stringMessages.windStatisticsLines()));
showAverageLineCheckbox = dialog.createCheckbox(stringMessages.showWindAverageLine());
showAverageLineCheckbox.setTitle(stringMessages.showWindAverageLineTooltip());
showAverageLineCheckbox.setValue(initialSettings.isShowAverageLine());
vp.add(showAverageLineCheckbox);
showMinLineCheckbox = dialog.createCheckbox(stringMessages.showWindMinLine());
showMinLineCheckbox.setTitle(stringMessages.showWindMinLineTooltip());
showMinLineCheckbox.setValue(initialSettings.isShowMinLine());
vp.add(showMinLineCheckbox);
showMaxLineCheckbox = dialog.createCheckbox(stringMessages.showWindMaxLine());
showMaxLineCheckbox.setTitle(stringMessages.showWindMaxLineTooltip());
showMaxLineCheckbox.setValue(initialSettings.isShowMaxLine());
vp.add(showMaxLineCheckbox);
final HorizontalPanel sectionsPanel = new HorizontalPanel();
sectionsPanel.setVerticalAlignment(HorizontalPanel.ALIGN_TOP);
sectionsPanel.add(dirTable);
sectionsPanel.add(divider);
sectionsPanel.add(spdTable);
vp.add(sectionsPanel);
return vp;
}
private Iterable<WindSourceType> getWindSourceTypesToOffer() {
final Set<WindSourceType> result = new HashSet<>();
result.addAll(Arrays.asList(WindSourceType.values()));
return result;
/**
* Dims or restores all stat toggles (both bulk and per-source) for a section.
* Called when the "show wind direction/speed data" checkbox is toggled the selection
* is kept intact, just visually faded so it's clear it's not active right now.
*/
private void setSectionTogglesDisabled(
final Map<WindSourceType, StatToggle> avgToggles,
final Map<WindSourceType, StatToggle> minToggles,
final Map<WindSourceType, StatToggle> maxToggles,
final StatToggle bulkAvg, final StatToggle bulkMin, final StatToggle bulkMax,
final boolean disabled) {
bulkAvg.setSectionDisabled(disabled);
bulkMin.setSectionDisabled(disabled);
bulkMax.setSectionDisabled(disabled);
for (final StatToggle t : avgToggles.values()) {
t.setSectionDisabled(disabled);
}
for (final StatToggle t : minToggles.values()) {
t.setSectionDisabled(disabled);
}
for (final StatToggle t : maxToggles.values()) {
t.setSectionDisabled(disabled);
}
}
/**
* Builds one section of the settings table either the direction or speed half.
* Each section has a "to all selected" bulk row at the top, followed by one row per wind source.
* Per-source avg/min/max buttons are hidden while the corresponding bulk button is on.
* When a source is newly checked while a bulk button is on, that source inherits the bulk state.
* Returns the three bulk toggles so the caller can wire them up to the show/hide checkbox.
*/
private StatToggle[] buildSourceSection(final DataEntryDialog<?> dialog, final FlexTable table, final int startRow,
final Map<WindSourceType, CheckBox> sourceCheckboxes,
final Map<WindSourceType, StatToggle> avgToggles,
final Map<WindSourceType, StatToggle> minToggles,
final Map<WindSourceType, StatToggle> maxToggles,
final Set<WindSourceType> selectedSources,
final Set<WindSourceType> avgSources,
final Set<WindSourceType> minSources,
final Set<WindSourceType> maxSources,
final boolean initialBulkAvg, final boolean initialBulkMin, final boolean initialBulkMax,
final boolean sectionEnabled,
final boolean directionSection) {
final StatToggle bulkAvg = new StatToggle(stringMessages.windStatAvg(), initialBulkAvg, CSS, true);
final StatToggle bulkMin = new StatToggle(stringMessages.windStatMin(), initialBulkMin, CSS, true);
final StatToggle bulkMax = new StatToggle(stringMessages.windStatMax(), initialBulkMax, CSS, true);
final Label toAllLabel = new Label(stringMessages.toAllSelected());
toAllLabel.addStyleName(CSS.toAllSelectedLabel());
table.setWidget(startRow, 0, toAllLabel);
table.setWidget(startRow, 1, bulkAvg.asWidget());
table.setWidget(startRow, 2, bulkMin.asWidget());
table.setWidget(startRow, 3, bulkMax.asWidget());
int row = startRow + 1;
for (final WindSourceType type : ALL_SOURCE_TYPES) {
if (!directionSection && !type.useSpeed()) {
continue;
}
final boolean sourceSelected = selectedSources.contains(type);
final CheckBox sourceBox = dialog.createCheckbox(WindSourceTypeFormatter.format(type, stringMessages));
sourceBox.setTitle(WindSourceTypeFormatter.tooltipFor(type, stringMessages));
sourceBox.setValue(sourceSelected);
sourceBox.setEnabled(sectionEnabled);
sourceCheckboxes.put(type, sourceBox);
final StatToggle avgToggle = new StatToggle(stringMessages.windStatAvg(), avgSources.contains(type), CSS, false);
final StatToggle minToggle = new StatToggle(stringMessages.windStatMin(), minSources.contains(type), CSS, false);
final StatToggle maxToggle = new StatToggle(stringMessages.windStatMax(), maxSources.contains(type), CSS, false);
avgToggle.asWidget().setVisible(sourceSelected && !bulkAvg.isOn());
minToggle.asWidget().setVisible(sourceSelected && !bulkMin.isOn());
maxToggle.asWidget().setVisible(sourceSelected && !bulkMax.isOn());
avgToggles.put(type, avgToggle);
minToggles.put(type, minToggle);
maxToggles.put(type, maxToggle);
avgToggle.addClickHandler(new ClickHandler() {
@Override
public void onClick(final ClickEvent event) {
avgToggle.toggle();
}
});
minToggle.addClickHandler(new ClickHandler() {
@Override
public void onClick(final ClickEvent event) {
minToggle.toggle();
}
});
maxToggle.addClickHandler(new ClickHandler() {
@Override
public void onClick(final ClickEvent event) {
maxToggle.toggle();
}
});
sourceBox.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
@Override
public void onValueChange(final ValueChangeEvent<Boolean> event) {
final boolean checked = event.getValue();
if (checked) {
if (bulkAvg.isOn()) {
avgToggle.setState(StatState.ON);
}
if (bulkMin.isOn()) {
minToggle.setState(StatState.ON);
}
if (bulkMax.isOn()) {
maxToggle.setState(StatState.ON);
}
}
avgToggle.asWidget().setVisible(checked && !bulkAvg.isOn());
minToggle.asWidget().setVisible(checked && !bulkMin.isOn());
maxToggle.asWidget().setVisible(checked && !bulkMax.isOn());
}
});
table.getCellFormatter().addStyleName(row, 0, CSS.sourceCheckboxIndent());
table.setWidget(row, 0, sourceBox);
table.setWidget(row, 1, avgToggle.asWidget());
table.setWidget(row, 2, minToggle.asWidget());
table.setWidget(row, 3, maxToggle.asWidget());
row++;
}
bulkAvg.addClickHandler(new ClickHandler() {
@Override
public void onClick(final ClickEvent event) {
bulkAvg.toggle();
if (bulkAvg.isOn()) {
for (final Map.Entry<WindSourceType, CheckBox> e : sourceCheckboxes.entrySet()) {
if (e.getValue().getValue()) {
avgToggles.get(e.getKey()).setState(StatState.ON);
avgToggles.get(e.getKey()).asWidget().setVisible(false);
}
}
} else {
for (final Map.Entry<WindSourceType, CheckBox> e : sourceCheckboxes.entrySet()) {
if (e.getValue().getValue()) {
avgToggles.get(e.getKey()).asWidget().setVisible(true);
}
}
}
}
});
bulkMin.addClickHandler(new ClickHandler() {
@Override
public void onClick(final ClickEvent event) {
bulkMin.toggle();
if (bulkMin.isOn()) {
for (final Map.Entry<WindSourceType, CheckBox> e : sourceCheckboxes.entrySet()) {
if (e.getValue().getValue()) {
minToggles.get(e.getKey()).setState(StatState.ON);
minToggles.get(e.getKey()).asWidget().setVisible(false);
}
}
} else {
for (final Map.Entry<WindSourceType, CheckBox> e : sourceCheckboxes.entrySet()) {
if (e.getValue().getValue()) {
minToggles.get(e.getKey()).asWidget().setVisible(true);
}
}
}
}
});
bulkMax.addClickHandler(new ClickHandler() {
@Override
public void onClick(final ClickEvent event) {
bulkMax.toggle();
if (bulkMax.isOn()) {
for (final Map.Entry<WindSourceType, CheckBox> e : sourceCheckboxes.entrySet()) {
if (e.getValue().getValue()) {
maxToggles.get(e.getKey()).setState(StatState.ON);
maxToggles.get(e.getKey()).asWidget().setVisible(false);
}
}
} else {
for (final Map.Entry<WindSourceType, CheckBox> e : sourceCheckboxes.entrySet()) {
if (e.getValue().getValue()) {
maxToggles.get(e.getKey()).asWidget().setVisible(true);
}
}
}
}
});
return new StatToggle[]{bulkAvg, bulkMin, bulkMax};
}
@Override
public WindChartSettings getResult() {
Set<WindSourceType> windDirectionSourcesToDisplay = new HashSet<WindSourceType>();
for (Map.Entry<WindSourceType, CheckBox> e : windDirectionsCheckboxes.entrySet()) {
final Set<WindSourceType> dirSources = new HashSet<WindSourceType>();
for (final Map.Entry<WindSourceType, CheckBox> e : windDirectionSourceCheckboxes.entrySet()) {
if (e.getValue().getValue()) {
windDirectionSourcesToDisplay.add(e.getKey());
dirSources.add(e.getKey());
}
}
Set<WindSourceType> windSpeedSourcesToDisplay = new HashSet<WindSourceType>();
for (Map.Entry<WindSourceType, CheckBox> e : windSpeedCheckboxes.entrySet()) {
final Set<WindSourceType> spdSources = new HashSet<WindSourceType>();
for (final Map.Entry<WindSourceType, CheckBox> e : windSpeedSourceCheckboxes.entrySet()) {
if (e.getValue().getValue()) {
windSpeedSourcesToDisplay.add(e.getKey());
spdSources.add(e.getKey());
}
}
return new WindChartSettings(showWindSpeedSeriesCheckbox.getValue(), windSpeedSourcesToDisplay,
showWindDirectionsSeriesCheckbox.getValue(), windDirectionSourcesToDisplay,
resolutionInSecondsBox.getValue() == null ? -1 : resolutionInSecondsBox.getValue()*1000,
showAverageLineCheckbox.getValue(), showMinLineCheckbox.getValue(), showMaxLineCheckbox.getValue());
return new WindChartSettings(
showWindSpeedSeriesCheckbox.getValue(), spdSources,
showWindDirectionsSeriesCheckbox.getValue(), dirSources,
resolutionInSecondsBox.getValue() == null ? -1 : resolutionInSecondsBox.getValue() * 1000,
collectEnabled(dirAvgToggles, dirBulkAvg, windDirectionSourceCheckboxes),
collectEnabled(dirMinToggles, dirBulkMin, windDirectionSourceCheckboxes),
collectEnabled(dirMaxToggles, dirBulkMax, windDirectionSourceCheckboxes),
dirBulkAvg.isOn(), dirBulkMin.isOn(), dirBulkMax.isOn(),
collectEnabled(spdAvgToggles, spdBulkAvg, windSpeedSourceCheckboxes),
collectEnabled(spdMinToggles, spdBulkMin, windSpeedSourceCheckboxes),
collectEnabled(spdMaxToggles, spdBulkMax, windSpeedSourceCheckboxes),
spdBulkAvg.isOn(), spdBulkMin.isOn(), spdBulkMax.isOn());
}
private Set<WindSourceType> collectEnabled(final Map<WindSourceType, StatToggle> statToggles,
final StatToggle bulkToggle, final Map<WindSourceType, CheckBox> sourceBoxes) {
final Set<WindSourceType> result = new HashSet<WindSourceType>();
for (final Map.Entry<WindSourceType, CheckBox> e : sourceBoxes.entrySet()) {
if (e.getValue().getValue()) {
final boolean bulkActive = bulkToggle != null && bulkToggle.isOn();
final StatToggle t = statToggles.get(e.getKey());
if (bulkActive || (t != null && t.isOn())) {
result.add(e.getKey());
}
}
}
return result;
}
@Override
public Validator<WindChartSettings> getValidator() {
return new Validator<WindChartSettings>() {
@Override
public String getErrorMessage(WindChartSettings valueToValidate) {
public String getErrorMessage(final WindChartSettings valueToValidate) {
String errorMessage = null;
if (valueToValidate.getResolutionInMilliseconds() < 1) {
errorMessage = stringMessages.stepSizeMustBeGreaterThanNull();
@@ -172,7 +459,6 @@ public class WindChartSettingsDialogComponent implements SettingsDialogComponent
@Override
public FocusWidget getFocusWidget() {
return windDirectionsCheckboxes.values().iterator().next();
return windDirectionSourceCheckboxes.values().iterator().next();
}
}
@@ -0,0 +1,25 @@
package com.sap.sailing.gwt.ui.client.shared.charts;
import com.google.gwt.core.client.GWT;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
public interface WindChartSettingsDialogCssResources extends ClientBundle {
public static final WindChartSettingsDialogCssResources INSTANCE = GWT.create(WindChartSettingsDialogCssResources.class);
@Source("com/sap/sailing/gwt/ui/client/shared/charts/WindChartSettingsDialog.css")
DialogCss css();
public interface DialogCss extends CssResource {
String statButton();
String statButtonActive();
String statButtonIndeterminate();
String statButtonBulkActive();
String statButtonBulkIndeterminate();
String statButtonSectionDisabled();
String toAllSelectedLabel();
String sourceCheckboxIndent();
String accentCheckbox();
String sectionDivider();
}
}