bug6150: control of the time shown on the chart

This commit is contained in:
Masha Kashirina committed 2026-10-13 15:12:54 +02:00
1 parent 999be66b10
commit 7df43e4e4a
7 files changed
+363 -110

No files matched your search

@@ -33,7 +33,6 @@ import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CaptionPanel;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Focusable;
@@ -297,12 +296,6 @@ public class IgtimiDevicesPanel extends FlowPanel implements FilterablePanelProv
dialogContent.setWidth("100%");
final HorizontalPanel dialogHeader = new HorizontalPanel();
dialogHeader.setWidth("100%");
final CheckBox orientationToggle = new CheckBox(stringMessages.windChartVertical());
orientationToggle.addValueChangeHandler(event -> {
windChartPanel.toggleOrientation();
windChartPanel.resizeChart(resizableContainer.getOffsetWidth(), resizableContainer.getOffsetHeight());
});
dialogHeader.add(orientationToggle);
dialogHeader.setHorizontalAlignment(HorizontalPanel.ALIGN_RIGHT);
final Button closeButton = new Button("✕");
closeButton.addClickHandler(event -> windChartDialog.hide());
@@ -16,10 +16,14 @@ import org.moxieapps.gwt.highcharts.client.labels.YAxisLabels;
import org.moxieapps.gwt.highcharts.client.plotOptions.LinePlotOptions;
import org.moxieapps.gwt.highcharts.client.plotOptions.Marker;
import org.moxieapps.gwt.highcharts.client.events.ChartSelectionEvent;
import org.moxieapps.gwt.highcharts.client.events.ChartSelectionEventHandler;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.RequiresResize;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.impl.ColorMapImpl;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.shared.charts.ChartUtil;
import com.sap.sailing.gwt.ui.client.shared.charts.WindChartDataRenderer;
@@ -29,12 +33,18 @@ public class WindLiveChart extends Composite implements RequiresResize {
private final Chart chart;
private final WindChartDataRenderer windChartDataRenderer;
private final boolean inverted;
private boolean userZoomed = false;
public WindLiveChart(final StringMessages stringMessages) {
this(stringMessages, /* inverted */ false);
this(stringMessages, /* inverted */ false, new ColorMapImpl<>());
}
public WindLiveChart(final StringMessages stringMessages, final boolean inverted) {
this(stringMessages, inverted, new ColorMapImpl<>());
}
public WindLiveChart(final StringMessages stringMessages, final boolean inverted,
final ColorMapImpl<WindSource> colorMap) {
this.inverted = inverted;
chart = new Chart()
.setWidth100()
@@ -44,7 +54,14 @@ public class WindLiveChart extends Composite implements RequiresResize {
.setChartTitle(new ChartTitle().setText(""))
.setCredits(new Credits().setEnabled(false))
.setLinePlotOptions(new LinePlotOptions()
.setMarker(new Marker().setEnabled(false)));
.setMarker(new Marker().setEnabled(false)))
.setSelectionEventHandler(new ChartSelectionEventHandler() {
@Override
public boolean onSelection(final ChartSelectionEvent event) {
userZoomed = event.getXAxisMinAsLongOrNull() != null;
return true;
}
});
chart.getXAxis()
.setType(Axis.Type.DATE_TIME)
.setLabels(new XAxisLabels().setFormatter((AxisLabelsData data) ->
@@ -73,11 +90,16 @@ public class WindLiveChart extends Composite implements RequiresResize {
public void setSeriesPoints(final Series series, final Point[] points) {
series.setPoints(points, /* redraw */ false);
}
});
},
colorMap);
initWidget(chart);
ensureDebugId("WindLiveChart");
}
public ColorMapImpl<WindSource> getColorMap() {
return windChartDataRenderer.getColorMap();
}
public void showData(final WindInfoForRaceDTO result) {
windChartDataRenderer.updateChartSeries(result, /* append */ false);
chart.redraw();
@@ -103,6 +125,14 @@ public class WindLiveChart extends Composite implements RequiresResize {
chart.setSize(widthPx, heightPx, false);
}
public void setXAxisExtremes(final long fromMillis, final long toMillis) {
chart.getXAxis().setExtremes(fromMillis, toMillis, /* redraw */ false, /* animation */ false);
}
public boolean isUserZoomed() {
return userZoomed;
}
public boolean isInverted() {
return inverted;
}
@@ -7,11 +7,16 @@ import java.util.HashSet;
import java.util.Set;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.CaptionPanel;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RequiresResize;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.gwt.ui.client.SailingServiceWriteAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
@@ -20,20 +25,25 @@ import com.sap.sailing.gwt.ui.shared.WindTrackInfoDTO;
import com.sap.sse.common.Duration;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.Notification;
import com.sap.sse.gwt.client.Notification.NotificationType;
import com.sap.sse.gwt.client.controls.IntegerBox;
import com.sap.sse.gwt.client.controls.datetime.DateAndTimeInput;
import com.sap.sse.gwt.client.controls.datetime.DateTimeInput.Accuracy;
public class WindLiveChartPanel extends CaptionPanel implements RequiresResize {
public class WindLiveChartPanel extends VerticalPanel implements RequiresResize {
@FunctionalInterface
public interface WindHistoryLoader {
void load(WindSource windSource, Date from, Date to, AsyncCallback<WindInfoForRaceDTO> callback);
}
private static final Duration DEFAULT_WIND_HISTORY_DURATION = Duration.ONE_MINUTE.times(10);
private static final int DEFAULT_DURATION_MINUTES = 5;
private static final int MAX_DURATION_MINUTES = 120;
private static final Duration DEFAULT_WIND_UPDATE_INTERVAL = Duration.ONE_SECOND.times(3);
private final SailingServiceWriteAsync sailingServiceWrite;
private final WindHistoryLoader windHistoryLoader;
private final ErrorReporter errorReporter;
private final Duration windHistoryDuration;
private final Duration windUpdateInterval;
private final StringMessages stringMessages;
private final SimplePanel chartContainer;
@@ -44,6 +54,14 @@ public class WindLiveChartPanel extends CaptionPanel implements RequiresResize {
private int windSelectionVersion;
private String windLiveUpdateRequestPendingSubscriptionId;
private final HorizontalPanel header;
private final CheckBox liveCheckBox;
private final IntegerBox durationBox;
private final SimplePanel durationPanel;
private final DateAndTimeInput fromInput;
private final DateAndTimeInput toInput;
private final SimplePanel staticPanel;
private final Timer windLiveUpdateTimer = new Timer() {
@Override
public void run() {
@@ -51,42 +69,140 @@ public class WindLiveChartPanel extends CaptionPanel implements RequiresResize {
}
};
/**
* Creates a live wind chart using ten minutes of initial history and a three-second live update interval.
*/
public WindLiveChartPanel(final SailingServiceWriteAsync sailingServiceWrite,
final WindHistoryLoader windHistoryLoader, final StringMessages stringMessages,
final ErrorReporter errorReporter) {
this(sailingServiceWrite, windHistoryLoader, stringMessages, errorReporter,
DEFAULT_WIND_HISTORY_DURATION, DEFAULT_WIND_UPDATE_INTERVAL);
DEFAULT_WIND_UPDATE_INTERVAL);
}
/**
* Creates a live wind chart with caller-defined initial history and live update intervals.
*/
public WindLiveChartPanel(final SailingServiceWriteAsync sailingServiceWrite,
final WindHistoryLoader windHistoryLoader, final StringMessages stringMessages,
final ErrorReporter errorReporter, final Duration windHistoryDuration,
final Duration windUpdateInterval) {
//no name because window has the same one already (careful with getChild to avoid null exceptions)
super("");
final ErrorReporter errorReporter, final Duration windUpdateInterval) {
this.sailingServiceWrite = sailingServiceWrite;
this.windHistoryLoader = windHistoryLoader;
this.errorReporter = errorReporter;
this.windHistoryDuration = windHistoryDuration;
this.windUpdateInterval = windUpdateInterval;
this.stringMessages = stringMessages;
// controls
header = new HorizontalPanel();
header.setSpacing(4);
liveCheckBox = new CheckBox(stringMessages.windChartLive());
liveCheckBox.setValue(true);
header.add(liveCheckBox);
// live mode: duration input
durationBox = new IntegerBox();
durationBox.setVisibleLength(3);
durationBox.setValue(DEFAULT_DURATION_MINUTES);
final HorizontalPanel durationInner = new HorizontalPanel();
durationInner.setSpacing(2);
durationInner.add(durationBox);
durationInner.add(new Label(stringMessages.windChartDurationMinutes()));
durationPanel = new SimplePanel(durationInner);
header.add(durationPanel);
// static mode: from/to pickers + show button
fromInput = new DateAndTimeInput(Accuracy.MINUTES);
toInput = new DateAndTimeInput(Accuracy.MINUTES);
final Button showButton = new Button(stringMessages.windChartShow());
final HorizontalPanel staticInner = new HorizontalPanel();
staticInner.setSpacing(2);
staticInner.add(new Label(stringMessages.windChartFrom()));
staticInner.add(fromInput);
staticInner.add(new Label(stringMessages.windChartTo()));
staticInner.add(toInput);
staticInner.add(showButton);
staticPanel = new SimplePanel(staticInner);
staticPanel.setVisible(false);
header.add(staticPanel);
// orientation toggle
final CheckBox orientationToggle = new CheckBox(stringMessages.windChartVertical());
orientationToggle.addValueChangeHandler(event -> toggleOrientation());
header.add(orientationToggle);
add(header);
// chart
windChart = new WindLiveChart(stringMessages);
chartContainer = new SimplePanel(windChart);
chartContainer.setWidth("100%");
chartContainer.setHeight("100%");
add(chartContainer);
setVisible(false);
// wire controls
liveCheckBox.addValueChangeHandler(this::onLiveToggled);
durationBox.addValueChangeHandler(event -> onSettingsChanged());
showButton.addClickHandler(event -> onShowClicked());
}
/**
* Handles the Live checkbox toggle.
* <p>
* Live → static: stops the subscription, freezes the current [now-duration, now] window into
* the from/to pickers and locks the x-axis there. The already-displayed data stays visible —
* no re-fetch.
* <p>
* Static → live: resets duration to the default and restarts live mode.
*/
private void onLiveToggled(final ValueChangeEvent<Boolean> event) {
final boolean live = Boolean.TRUE.equals(event.getValue());
durationPanel.setVisible(live);
staticPanel.setVisible(!live);
if (live) {
durationBox.setValue(DEFAULT_DURATION_MINUTES);
onSettingsChanged();
} else {
// freeze at current live window(stop timer, populate pickers, keep displayed data as-is)
stopWindLiveSubscription();
final int durationMinutes = clampDuration(durationBox.getValue());
final Date to = new Date();
final Date from = new MillisecondsTimePoint(to).minus(Duration.ONE_MINUTE.times(durationMinutes)).asDate();
fromInput.setValue(from);
toInput.setValue(to);
windChart.setXAxisExtremes(from.getTime(), to.getTime());
}
}
/**
* Validates the from/to range and triggers a static history load on success.
* Shows a WARNING notification if to ≤ from or if the range exceeds 2 hours.
*/
private void onShowClicked() {
final Date from = fromInput.getValue();
final Date to = toInput.getValue();
if (from == null || to == null) {
return;
}
if (!to.after(from)) {
Notification.notify(stringMessages.windChartToBeforeFrom(), NotificationType.WARNING);
return;
}
final long rangeMillis = to.getTime() - from.getTime();
final long twoHoursMillis = Duration.ONE_MINUTE.times(MAX_DURATION_MINUTES).asMillis();
if (rangeMillis > twoHoursMillis) {
Notification.notify(stringMessages.windChartRangeExceeded(), NotificationType.WARNING);
return;
}
if (!shownWindSources.isEmpty()) {
loadStaticHistory(new HashSet<>(shownWindSources), from, to);
}
}
/** Called when the duration box changes; restarts live mode with the new window size. */
private void onSettingsChanged() {
final Integer raw = durationBox.getValue();
if (raw != null && raw > MAX_DURATION_MINUTES) {
Notification.notify(stringMessages.windChartRangeExceeded(), NotificationType.WARNING);
return;
}
if (!shownWindSources.isEmpty()) {
reloadForCurrentSettings();
}
}
/**
* Sets the wind sources to display. Hides the panel when the set is empty.
* Switches to live mode unconditionally: any existing subscription is stopped,
* history is loaded for [now-duration, now], and live updates begin.
*/
public void setSelectedWindSources(final Set<WindSource> selectedWindSources) {
final Set<WindSource> windSources = new HashSet<>(selectedWindSources);
final int selectionVersion = ++windSelectionVersion;
stopWindLiveSubscription();
lastHistoryData = null;
for (final WindSource windSource : shownWindSources) {
@@ -95,82 +211,80 @@ public class WindLiveChartPanel extends CaptionPanel implements RequiresResize {
shownWindSources.clear();
setVisible(!windSources.isEmpty());
if (!windSources.isEmpty()) {
shownWindSources.addAll(windSources);
Scheduler.get().scheduleDeferred(windChart::onResize);
sailingServiceWrite.startWindLiveSubscription(
windSources, /* correctByDeclination */ true,
new AsyncCallback<String>() {
@Override
public void onSuccess(final String subscriptionId) {
if (selectionVersion != windSelectionVersion) {
stopWindLiveSubscription(subscriptionId);
} else {
windLiveSubscriptionId = subscriptionId;
shownWindSources.addAll(windSources);
loadWindHistory(windSources, new Date(), selectionVersion);
}
}
@Override
public void onFailure(final Throwable caught) {
if (selectionVersion == windSelectionVersion) {
setVisible(false);
errorReporter.reportError(caught.getMessage());
}
}
});
reloadForCurrentSettings();
}
}
private void loadWindHistory(final Set<WindSource> windSources, final Date to, final int selectionVersion) {
final Date from = new MillisecondsTimePoint(to).minus(windHistoryDuration).asDate();
final Set<WindSource> windSourcesPending = new HashSet<>(windSources);
final WindInfoForRaceDTO combinedHistory = new WindInfoForRaceDTO();
combinedHistory.windTrackInfoByWindSource = new HashMap<>();
for (final WindSource windSource : windSources) {
windHistoryLoader.load(
windSource,
from,
to,
new AsyncCallback<WindInfoForRaceDTO>() {
@Override
public void onSuccess(final WindInfoForRaceDTO result) {
if (selectionVersion == windSelectionVersion) {
if (result != null && result.windTrackInfoByWindSource != null) {
combinedHistory.windTrackInfoByWindSource.putAll(result.windTrackInfoByWindSource);
}
onWindHistoryRequestCompleted(windSource, windSourcesPending, combinedHistory,
selectionVersion);
}
}
@Override
public void onFailure(final Throwable caught) {
if (selectionVersion == windSelectionVersion) {
errorReporter.reportError(caught.getMessage());
onWindHistoryRequestCompleted(windSource, windSourcesPending, combinedHistory,
selectionVersion);
}
}
});
}
/** Stops any current subscription and restarts live mode for the current duration. */
private void reloadForCurrentSettings() {
stopWindLiveSubscription();
lastHistoryData = null;
final int durationMinutes = clampDuration(durationBox.getValue());
final Duration duration = Duration.ONE_MINUTE.times(durationMinutes);
final Date to = new Date();
final Date from = new MillisecondsTimePoint(to).minus(duration).asDate();
startLiveMode(new HashSet<>(shownWindSources), from, to, duration);
}
private void onWindHistoryRequestCompleted(
final WindSource windSource,
final Set<WindSource> windSourcesPending,
final WindInfoForRaceDTO combinedHistory,
final int selectionVersion) {
windSourcesPending.remove(windSource);
if (selectionVersion == windSelectionVersion
&& windSourcesPending.isEmpty()
&& windLiveSubscriptionId != null) {
lastHistoryData = combinedHistory;
windChart.showData(combinedHistory);
loadWindLiveUpdates();
windLiveUpdateTimer.scheduleRepeating((int) windUpdateInterval.asMillis());
/** Returns the duration clamped to [1, MAX_DURATION_MINUTES], falling back to the default if null or invalid. */
private int clampDuration(final Integer raw) {
if (raw == null || raw < 1) {
return DEFAULT_DURATION_MINUTES;
}
return Math.min(raw, MAX_DURATION_MINUTES);
}
/**
* Opens a server-side live subscription, loads history for [from, to] across all wind sources
* in parallel, then — once all responses arrive — shows the combined data, applies the initial
* x-axis window, and starts the periodic update timer.
* <p>
* Uses {@code windSelectionVersion} to discard stale callbacks: if the version has advanced by
* the time a response arrives, the result is dropped and any orphaned subscription is stopped.
*/
private void startLiveMode(final Set<WindSource> windSources, final Date from, final Date to,
final Duration duration) {
final int selectionVersion = ++windSelectionVersion;
sailingServiceWrite.startWindLiveSubscription(
windSources, /* correctByDeclination */ true,
new AsyncCallback<String>() {
@Override
public void onSuccess(final String subscriptionId) {
if (selectionVersion != windSelectionVersion) {
stopWindLiveSubscription(subscriptionId);
} else {
windLiveSubscriptionId = subscriptionId;
loadHistory(windSources, from, to, selectionVersion, () -> {
applyLiveExtremes(duration);
loadWindLiveUpdates();
windLiveUpdateTimer.scheduleRepeating((int) windUpdateInterval.asMillis());
});
}
}
@Override
public void onFailure(final Throwable caught) {
if (selectionVersion == windSelectionVersion) {
errorReporter.reportError(caught.getMessage());
}
}
});
}
/** Sets the x-axis to [now-duration, now]. Called on each live tick (unless user is zoomed). */
private void applyLiveExtremes(final Duration duration) {
final long toMillis = new Date().getTime();
final long fromMillis = toMillis - duration.asMillis();
windChart.setXAxisExtremes(fromMillis, toMillis);
}
/**
* Fetches the latest live data points for the current subscription and appends them to the chart.
* Guards against concurrent requests: a second call while one is in flight is a no-op.
* After appending, advances the x-axis sliding window unless the user has dragged to zoom.
*/
private void loadWindLiveUpdates() {
final String subscriptionId = windLiveSubscriptionId;
if (subscriptionId != null
@@ -187,6 +301,10 @@ public class WindLiveChartPanel extends CaptionPanel implements RequiresResize {
if (subscriptionId.equals(windLiveSubscriptionId)) {
mergeIntoLastHistoryData(result);
windChart.appendData(result);
if (!windChart.isUserZoomed()) {
final int durationMinutes = clampDuration(durationBox.getValue());
applyLiveExtremes(Duration.ONE_MINUTE.times(durationMinutes));
}
}
}
@@ -203,6 +321,68 @@ public class WindLiveChartPanel extends CaptionPanel implements RequiresResize {
}
}
/**
* Loads history for the given [from, to] range once and locks the x-axis to that window.
* No timer is started; the chart is static until the user clicks Show again.
*/
private void loadStaticHistory(final Set<WindSource> windSources, final Date from, final Date to) {
final int selectionVersion = ++windSelectionVersion;
loadHistory(windSources, from, to, selectionVersion, () -> {
windChart.setXAxisExtremes(from.getTime(), to.getTime());
});
}
/**
* Fires one history request per wind source in parallel and collects the results into a single
* {@link WindInfoForRaceDTO}. Calls {@code onComplete} once all requests have returned (success
* or failure). Stale responses (version mismatch) are silently discarded.
*/
private void loadHistory(final Set<WindSource> windSources, final Date from, final Date to,
final int selectionVersion, final Runnable onComplete) {
final Set<WindSource> pending = new HashSet<>(windSources);
final WindInfoForRaceDTO combined = new WindInfoForRaceDTO();
combined.windTrackInfoByWindSource = new HashMap<>();
for (final WindSource windSource : windSources) {
windHistoryLoader.load(windSource, from, to, new AsyncCallback<WindInfoForRaceDTO>() {
@Override
public void onSuccess(final WindInfoForRaceDTO result) {
if (selectionVersion == windSelectionVersion) {
if (result != null && result.windTrackInfoByWindSource != null) {
combined.windTrackInfoByWindSource.putAll(result.windTrackInfoByWindSource);
}
onHistoryRequestCompleted(windSource, pending, combined, selectionVersion, onComplete);
}
}
@Override
public void onFailure(final Throwable caught) {
if (selectionVersion == windSelectionVersion) {
errorReporter.reportError(caught.getMessage());
onHistoryRequestCompleted(windSource, pending, combined, selectionVersion, onComplete);
}
}
});
}
}
/**
* Called after each individual history request completes. When the last pending source is
* done, renders the combined data and runs {@code onComplete} (e.g. apply x-axis extremes,
* start live timer).
*/
private void onHistoryRequestCompleted(final WindSource windSource, final Set<WindSource> pending,
final WindInfoForRaceDTO combined, final int selectionVersion, final Runnable onComplete) {
pending.remove(windSource);
if (selectionVersion == windSelectionVersion && pending.isEmpty()) {
lastHistoryData = combined;
windChart.showData(combined);
onComplete.run();
}
}
/**
* Appends new wind fixes from a live update into {@code lastHistoryData} so that orientation
* toggles and re-renders always have the full accumulated data set available.
*/
private void mergeIntoLastHistoryData(final WindInfoForRaceDTO appendedData) {
if (appendedData == null || appendedData.windTrackInfoByWindSource == null) {
return;
@@ -231,7 +411,8 @@ public class WindLiveChartPanel extends CaptionPanel implements RequiresResize {
}
}
}
/** Cancels the update timer and fires a server-side stop for the current subscription, if any. */
private void stopWindLiveSubscription() {
windLiveUpdateTimer.cancel();
windLiveUpdateRequestPendingSubscriptionId = null;
@@ -243,36 +424,50 @@ public class WindLiveChartPanel extends CaptionPanel implements RequiresResize {
}
private void stopWindLiveSubscription(final String subscriptionId) {
sailingServiceWrite.stopWindLiveSubscription(
subscriptionId,
new AsyncCallback<Void>() {
@Override
public void onSuccess(final Void result) {
}
sailingServiceWrite.stopWindLiveSubscription(subscriptionId, new AsyncCallback<Void>() {
@Override
public void onSuccess(final Void result) {
}
@Override
public void onFailure(final Throwable caught) {
}
});
@Override
public void onFailure(final Throwable caught) {
}
});
}
public void toggleOrientation() {
/**
* Replaces the current chart with a new one with the opposite orientation, preserving the
* color map so series keep their assigned colors. Re-renders {@code lastHistoryData} and
* restores the x-axis window (live or static). Resumes the live update timer if a subscription
* is active.
*/
private void toggleOrientation() {
windLiveUpdateTimer.cancel();
windLiveUpdateRequestPendingSubscriptionId = null;
final WindLiveChart newChart = new WindLiveChart(stringMessages, !windChart.isInverted());
final WindLiveChart newChart = new WindLiveChart(stringMessages, !windChart.isInverted(),
windChart.getColorMap());
windChart = newChart;
chartContainer.setWidget(newChart);
if (lastHistoryData != null) {
newChart.showData(lastHistoryData);
}
if (windLiveSubscriptionId != null) {
final int durationMinutes = clampDuration(durationBox.getValue());
applyLiveExtremes(Duration.ONE_MINUTE.times(durationMinutes));
loadWindLiveUpdates();
windLiveUpdateTimer.scheduleRepeating((int) windUpdateInterval.asMillis());
} else {
final Date from = fromInput.getValue();
final Date to = toInput.getValue();
if (from != null && to != null) {
newChart.setXAxisExtremes(from.getTime(), to.getTime());
}
}
}
/** Resizes the chart, subtracting the header row height so the legend is not clipped. */
public void resizeChart(final int containerWidthPx, final int containerHeightPx) {
windChart.resize(containerWidthPx, containerHeightPx);
windChart.resize(containerWidthPx, containerHeightPx - header.getOffsetHeight());
}
@Override
@@ -1220,6 +1220,13 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
String backToLiveTimePanelMode();
String windChart();
String windChartVertical();
String windChartLive();
String windChartDurationMinutes();
String windChartFrom();
String windChartTo();
String windChartShow();
String windChartRangeExceeded();
String windChartToBeforeFrom();
String windChartLoading();
String mediaNoVideosCaption();
String mediaShowVideoCaption();
@@ -1235,6 +1235,13 @@ raceIsInLiveTimePanelMode=Race is Live
backToLiveTimePanelMode=Back to Live
windChart=Wind Chart
windChartVertical=Vertical
windChartLive=Live
windChartDurationMinutes=min
windChartFrom=From
windChartTo=To
windChartShow=Show
windChartRangeExceeded=Time range exceeds the 2 hour limit.
windChartToBeforeFrom=End time must be after start time.
windChartLoading=Loading wind data...
mediaNoVideosCaption=No media tracks
mediaShowVideoCaption=Show media track
@@ -1210,6 +1210,13 @@ raceIsInLiveTimePanelMode=Rennen ist live
backToLiveTimePanelMode=Zurück zu live
windChart=Winddiagramm
windChartVertical=Vertikal
windChartLive=Live
windChartDurationMinutes=Min
windChartFrom=Von
windChartTo=Bis
windChartShow=Anzeigen
windChartRangeExceeded=Zeitraum überschreitet das 2-Stunden-Limit.
windChartToBeforeFrom=Endzeit muss nach der Startzeit liegen.
windChartLoading=Winddaten werden geladen...
mediaNoVideosCaption=Keine Medienspuren
mediaShowVideoCaption=Medienspur an
@@ -43,7 +43,7 @@ public class WindChartDataRenderer {
private final Map<WindSource, Series> windSourceSpeedSeries = new HashMap<WindSource, Series>();
private final Map<WindSource, Point[]> windSourceDirectionPoints = new HashMap<WindSource, Point[]>();
private final Map<WindSource, Point[]> windSourceSpeedPoints = new HashMap<WindSource, Point[]>();
private final ColorMapImpl<WindSource> colorMap = new ColorMapImpl<WindSource>();
private final ColorMapImpl<WindSource> colorMap;
private Point firstPointOfFirstSeries;
private Long timeOfEarliestRequestInMillis;
private Long timeOfLatestRequestInMillis;
@@ -69,12 +69,26 @@ public class WindChartDataRenderer {
private WindChartDataRenderer(final Chart chart, final StringMessages stringMessages, final Integer maxSeriesPoints,
final boolean addSeriesToChartOnCreation, final SeriesPointsUpdater seriesPointsUpdater,
final PointsAddedHandler pointsAddedHandler) {
this(chart, stringMessages, maxSeriesPoints, addSeriesToChartOnCreation, seriesPointsUpdater,
pointsAddedHandler, new ColorMapImpl<>());
}
private WindChartDataRenderer(final Chart chart, final StringMessages stringMessages, final Integer maxSeriesPoints,
final boolean addSeriesToChartOnCreation, final SeriesPointsUpdater seriesPointsUpdater,
final PointsAddedHandler pointsAddedHandler, final ColorMapImpl<WindSource> colorMap) {
this.chart = chart;
this.stringMessages = stringMessages;
this.maxSeriesPoints = maxSeriesPoints;
this.addSeriesToChartOnCreation = addSeriesToChartOnCreation;
this.seriesPointsUpdater = seriesPointsUpdater;
this.pointsAddedHandler = pointsAddedHandler;
this.colorMap = colorMap;
}
public WindChartDataRenderer(final Chart chart, final StringMessages stringMessages,
final boolean addSeriesToChartOnCreation, final SeriesPointsUpdater seriesPointsUpdater,
final ColorMapImpl<WindSource> colorMap) {
this(chart, stringMessages, null, addSeriesToChartOnCreation, seriesPointsUpdater, null, colorMap);
}
/**