package org.jboss.as.console.client.shared.runtime.charts;
import com.google.gwt.core.client.JsArrayNumber;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Node;
import com.google.gwt.event.logical.shared.AttachEvent;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import org.jboss.as.console.client.shared.runtime.Metric;
import org.jboss.as.console.client.shared.runtime.Sampler;
import org.thechiselgroup.choosel.protovis.client.PV;
import org.thechiselgroup.choosel.protovis.client.PVBulletLayout;
import org.thechiselgroup.choosel.protovis.client.PVMark;
import org.thechiselgroup.choosel.protovis.client.PVPanel;
import org.thechiselgroup.choosel.protovis.client.PVShape;
import org.thechiselgroup.choosel.protovis.client.ProtovisWidget;
import org.thechiselgroup.choosel.protovis.client.jsutil.JsArgs;
import org.thechiselgroup.choosel.protovis.client.jsutil.JsArrayGeneric;
import org.thechiselgroup.choosel.protovis.client.jsutil.JsDoubleFunction;
import org.thechiselgroup.choosel.protovis.client.jsutil.JsFunction;
import org.thechiselgroup.choosel.protovis.client.jsutil.JsStringFunction;
import org.thechiselgroup.choosel.protovis.client.jsutil.JsUtils;
import static org.thechiselgroup.choosel.protovis.client.PVAlignment.*;
/**
* @author Heiko Braun
* @date 8/1/13
*/
public class BulletGraphView implements Sampler {
private final String title;
private final String metricName;
private Column[] columns;
private int ROW_OFFSET = 1;
private HorizontalPanel container;
private PVPanel vis = null;
private Grid grid;
private boolean embeddedUse = false;
@Deprecated
public BulletGraphView(String title, String metricName) {
this.title = title;
this.metricName = metricName;
}
public BulletGraphView(String title, String metricName, boolean embeddedUse) {
this.title = title;
this.metricName = metricName;
this.embeddedUse = embeddedUse;
}
public Sampler setColumns(Column[] columns) {
this.columns = columns;
return this;
}
public static class Bullet {
public String title;
public String subtitle;
public double[] ranges;
public double[] measures;
public double[] markers;
private Bullet(String title, String subtitle, double[] ranges,
double[] measures, double[] markers) {
this.title = title;
this.subtitle = subtitle;
this.ranges = ranges;
this.measures = measures;
this.markers = markers;
}
}
public int getBaseLineIndex() {
int i=0;
boolean didMatch = false;
for(Column c : columns)
{
if(c.isBaseline())
{
didMatch=true;
break;
}
i++;
}
return didMatch ? i : -1;
}
@Override
public Widget asWidget() {
VerticalPanel desc = new VerticalPanel();
desc.addStyleName("metric-container");
if(embeddedUse)
desc.add(new HTML("<h3 class='metric-label-embedded'>" + title + "</h3>"));
else
desc.add(new HTML("<h3 class='metric-label'>" + title + "</h3>"));
container = new HorizontalPanel();
container.setStyleName("fill-layout-width");
container.addStyleName("metric-panel");
grid = new Grid(columns.length, 2);
grid.addStyleName("metric-grid");
// format
for (int row = 0; row < columns.length; ++row) {
grid.getCellFormatter().addStyleName(row, 0, "nominal");
grid.getCellFormatter().addStyleName(row, 1, "numerical");
}
int baselineIndex = getBaseLineIndex();
if(baselineIndex>=0)
grid.getRowFormatter().addStyleName(baselineIndex, "baseline");
// init
for(int i=0; i<columns.length;i++)
{
grid.setText(i, 0, columns[i].label);
grid.setText(i, 1, "0");
}
container.add(grid);
ProtovisWidget graphWidget = new ProtovisWidget();
graphWidget.initPVPanel();
vis = createVisualization(graphWidget);
Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() {
@Override
public void execute() {
renderDefault(); // the 'empty' display
}
});
container.add(graphWidget);
graphWidget.getElement().getParentElement().setAttribute("align", "center");
graphWidget.getElement().getParentElement().setAttribute("width", "80%");
desc.add(container);
return desc;
}
private void renderDefault(){
vis.data(generateDefaultData()).render();
}
private void render(JsArrayGeneric<Bullet> bullets){
vis.data(bullets).render();
}
@Override
public void addSample(Metric metric) {
Double baseline = getBaselineValue(metric);
if(baseline>0.0)
{
final JsArrayGeneric<Bullet> bullets = distinctColumnStrategy(metric);
Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() {
@Override
public void execute() {
render(bullets);
}
});
}
for(int i=0; i<columns.length;i++)
{
grid.setText(i, 0, columns[i].label);
grid.setText(i, 1, metric.get(i));
}
}
private JsArrayGeneric<Bullet> distinctColumnStrategy(Metric metric) {
JsArrayGeneric<Bullet> bullets = JsUtils.createJsArrayGeneric();
Double baseline = getBaselineValue(metric);
int row=ROW_OFFSET;
for(Column c : columns)
{
int dataIndex = row - ROW_OFFSET;
String actualValue = metric.get(dataIndex);
if(null==actualValue)
throw new RuntimeException("Metric value at index "+dataIndex+" is null");
// with comparison column
if(c.isBaseline())
{
// skip the baseline itself
}
else {
Double value = Double.valueOf(actualValue);
String label = c.getLabel();//percentage(baseline, value) +"% "+c.getLabel();
if(c.getComparisonColumn()!=null && baseline<0)
{
throw new RuntimeException("Comparison column specified, but no baseline set!");
}
bullets.push(
new Bullet(
label, metricName,
new double[] {baseline},
new double[] {value},
new double[] {}
)
);
}
row++;
}
return bullets;
}
private Double getBaselineValue(Metric metric) {
int baselineIndex = getBaseLineIndex();
// check if they match
if(baselineIndex>metric.numSamples())
throw new RuntimeException("Illegal baseline index "+baselineIndex+" on number of samples "+metric.numSamples());
return baselineIndex >= 0 ?
Double.valueOf(metric.get(baselineIndex)) : -1;
}
static long percentage(double total, double actual)
{
if(total==0 || actual==0)
return 0;
return Math.round((actual/total)*100);
}
@Override
public void clearSamples() {
renderDefault();
}
@Override
public long numSamples() {
return 1;
}
@Override
public void recycle() {
}
/* bullet graph specs */
private PVPanel createVisualization(final ProtovisWidget graphWidget) {
final PVPanel vis = graphWidget.getPVPanel()
.width(400)
.height(30)
.margin(20)
.left(100) // translate(_,y)
.top(
new JsDoubleFunction() {
public double f(JsArgs args) {
PVMark _this = args.getThis();
return 10 + _this.index() * 60; // translate(x,_)
}
}
);
PVBulletLayout bullet = vis.add(PV.Layout.Bullet()).orient(LEFT)
.ranges(new JsFunction<JsArrayNumber>() {
public JsArrayNumber f(JsArgs args) {
Bullet d = args.getObject();
return JsUtils.toJsArrayNumber(d.ranges);
}
}).measures(new JsFunction<JsArrayNumber>() {
public JsArrayNumber f(JsArgs args) {
Bullet d = args.getObject();
return JsUtils.toJsArrayNumber(d.measures);
}
}).markers(new JsFunction<JsArrayNumber>() {
public JsArrayNumber f(JsArgs args) {
Bullet d = args.getObject();
return JsUtils.toJsArrayNumber(d.markers);
}
});
// workaround for right hand side labels
graphWidget.addAttachHandler(new AttachEvent.Handler() {
@Override
public void onAttachOrDetach(AttachEvent event) {
Element svg = graphWidget.getElement().getFirstChildElement();
if(svg!=null)
{
svg.setAttribute("overflow", "visible");
}
}
});
bullet.strokeStyle("#CFCFCF");
bullet.lineWidth(0.9);
bullet.range().add(PV.Bar).fillStyle("#ffffff");
bullet.measure().add(PV.Bar).fillStyle("#666666");
bullet.marker().add(PV.Dot).shape(PVShape.TRIANGLE).fillStyle("white");
bullet.tick().add(PV.Rule)
.strokeStyle("#CFCFCF")
.anchor(BOTTOM)
.add(PV.Label)
.text(bullet.x().tickFormat());
// title
bullet.anchor(LEFT).add(PV.Label).font("12px sans-serif")
.textAlign(RIGHT).textBaseline(BOTTOM)
.text(new JsStringFunction() {
public String f(JsArgs args) {
Bullet d = args.getObject(0);
return d.title;
}
});
// subtitle
bullet.anchor(LEFT).add(PV.Label).textStyle("#616161").textAlign(RIGHT)
.textBaseline(TOP).text(new JsStringFunction() {
public String f(JsArgs args) {
Bullet d = args.getObject(0);
return d.subtitle;
}
});
// scale
bullet.anchor(RIGHT)
.add(PV.Label)
.textStyle("#616161")
.textAlign(LEFT)
.textBaseline(MIDDLE)
.text(new JsStringFunction() {
public String f(JsArgs args) {
Bullet d = args.getObject(0);
double measures = d.measures[0];
return measures > 0.00 ? String.valueOf(Double.valueOf(d.ranges[0]).longValue()) : "";
}
});
return vis;
}
private JsArrayGeneric<Bullet> generateDefaultData() {
JsArrayGeneric<Bullet> bullets = JsUtils.createJsArrayGeneric();
for(Column col : columns)
{
if(col.isBaseline())
continue;
bullets.push(
new Bullet(
col.getLabel(), metricName,
new double[] { 100 },
new double[] { 0 },
new double[] { }
)
);
}
return bullets;
}
}