package com.vaadin.vaadininmuija;
import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import akka.actor.Props;
import com.vaadin.addon.charts.Chart;
import com.vaadin.addon.charts.model.AxisType;
import com.vaadin.addon.charts.model.ChartType;
import com.vaadin.addon.charts.model.Configuration;
import com.vaadin.addon.charts.model.DataSeries;
import com.vaadin.addon.charts.model.DataSeriesItem;
import com.vaadin.addon.charts.model.Marker;
import com.vaadin.addon.charts.model.PlotOptionsSpline;
import com.vaadin.addon.charts.model.Series;
import com.vaadin.addon.charts.model.style.SolidColor;
import com.vaadin.annotations.Push;
import com.vaadin.annotations.Theme;
import com.vaadin.annotations.Title;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinServletService;
import com.vaadin.shared.ui.label.ContentMode;
import com.vaadin.ui.CheckBox;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.Notification;
import com.vaadin.ui.TextField;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.vaadininmuija.akka.UIActor;
import com.vaadin.vaadininmuija.akka.messages.StockQuote;
import com.vaadin.vaadininmuija.akka.messages.UnwatchStock;
import com.vaadin.vaadininmuija.akka.messages.WatchStock;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
/**
* This is the actual Vaadin UI, showing data produced by Akka actors.
* <p>
* The interesting thing in the demo is the uiActor of type UIActor. StockUI
* instantiates one for itself and shuts it down when detached. During activity,
* the UIActor, calls methods in this class to update the ui.
* <p>
* The code also communicates with the hub actor, telling which stocks the
* uiActor should watch/unwatch.
*
*/
@Theme("valo")
@SuppressWarnings("serial")
@Push
@Title("Vaadin( )in Akka")
public class StockUI extends UI {
final VerticalLayout layout = new VerticalLayout();
private final Label msg = new Label("<em>The demo backend generates random stock "
+ "prices at random time. Last 50 points per symbol are "
+ "displayed at once.</em>",
ContentMode.HTML);
Chart chart = new Chart(ChartType.SPLINE);
private ActorRef uiActor;
public static final String[] STOCKS = {"GOOG", "AAPL", "ORCL"};
@Override
protected void init(VaadinRequest request) {
// Create an actor that mediates the data from the "actor system" to
// this particular UI instance
uiActor = getSystem().actorOf(Props.create(UIActor.class, StockUI.this));
layout.setMargin(true);
layout.setSpacing(true);
configureChart();
layout.addComponent(chart);
HorizontalLayout controls = new HorizontalLayout();
controls.setSpacing(true);
for (String symbol : STOCKS) {
CheckBox checkBox = new CheckBox(symbol);
checkBox.addValueChangeListener(e -> {
Object msg = checkBox.getValue()
? new WatchStock(symbol) : new UnwatchStock(symbol);
getHub().tell(msg, uiActor);
if (!checkBox.getValue()) {
hideSymbol(symbol);
}
});
checkBox.setValue(true);
controls.addComponent(checkBox);
}
TextField customSymbol = new TextField();
customSymbol.setWidth("6em");
customSymbol.setInputPrompt("CSTM");
CheckBox customToggle = new CheckBox("Follow custom stock:");
customToggle.addValueChangeListener(e -> {
String symbol = customSymbol.getValue();
if (symbol.length() < 2) {
if (customToggle.getValue()) {
Notification.show("Fill valid stock symbol!");
customToggle.setValue(false);
}
} else {
Object msg = customToggle.getValue()
? new WatchStock(symbol) : new UnwatchStock(symbol);
getHub().tell(msg, uiActor);
customSymbol.setEnabled(!customToggle.getValue());
if (!customToggle.getValue()) {
hideSymbol(symbol);
}
}
});
controls.addComponents(customToggle, customSymbol);
layout.addComponents(controls, msg);
setContent(layout);
// TODO add sell/buy suggestions
}
/**
* This is the public API for UIActor to update the UI
*
* @param stockSymbol
* @param values
*/
public void updateStockDetails(String stockSymbol, StockQuote... values) {
// wrap modification into UI.access(Runnable), as the calls comes from
// "non UI thread", this is the "hard" part of the Vaadin( )in Akka
// integration
access(() -> {
DataSeries series = symbolToChart.get(stockSymbol);
if (series == null) {
series = new DataSeries(stockSymbol);
symbolToChart.put(stockSymbol, series);
chart.getConfiguration().addSeries(series);
chart.drawChart();
}
for (StockQuote value : values) {
final boolean shift = series.size() >= 50;
final DataSeriesItem dataSeriesItem = new DataSeriesItem(
Date.from(value.getTimeStamp().toInstant(ZoneOffset.UTC)), value.getPrice());
series.add(dataSeriesItem, shift, shift);
}
});
}
private ActorSystem getSystem() {
// Fetch the ActorSystem reference from servlet, in real life apps,
// you'll probably inject this using Spring or CDI
StockServlet servlet = (StockServlet) ((VaadinServletService) getSession().
getService()).getServlet();
return servlet.getSystem();
}
public ActorRef getHub() {
// Fetch the hub in real life apps,
// you'll probably inject this using Spring or CDI
StockServlet servlet = (StockServlet) ((VaadinServletService) getSession().
getService()).getServlet();
return servlet.getStocksWatch();
}
private void hideSymbol(String symbol) {
symbolToChart.remove(symbol);
chart.getConfiguration().
setSeries(new ArrayList<Series>(symbolToChart.values()));
chart.drawChart();
}
private void configureChart() {
PlotOptionsSpline po = new PlotOptionsSpline();
po.setMarker(new Marker(false));
po.setShadow(false);
final Configuration cfg = chart.getConfiguration();
cfg.getChart().setBackgroundColor(new SolidColor(0, 0, 0, 0));
/*
* This controls the duration of dynamic update animation. If you have
* really lots of updates, it might be better to disable chart
* animations and/or to add one chart per series.
* Ticket about missing Java API: http://dev.vaadin.com/ticket/13468
*/
chart.setJsonConfig("{chart:{animation:{duration: 150}}}");
cfg.setPlotOptions(po);
cfg.setTitle("Vaadin( )in Akka");
cfg.setSubTitle("aka Reactive Stocks example with Vaadin & Java 8");
cfg.getxAxis().setType(AxisType.DATETIME);
}
private final HashMap<String, DataSeries> symbolToChart = new HashMap<>();
@Override
public void detach() {
// Stop ui actor
getSystem().stop(uiActor);
super.detach();
}
}