Project

General

Profile

Bug #3504 ยป TestGraphApp.cc

Test WtApp showing the WCartesianChart Problem with Firefox (under Linux) - Stefan Ruppert, 07/23/2014 05:56 PM

 
#include <Wt/WApplication>
#include <Wt/WComboBox>
#include <Wt/WContainerWidget>
#include <Wt/WVBoxLayout>
#include <Wt/WHBoxLayout>
#include <Wt/WGroupBox>
#include <Wt/WLabel>
#include <Wt/WText>
#include <Wt/WPushButton>
#include <Wt/WLineEdit>
#include <Wt/WPopupMenu>
#include <Wt/WCssDecorationStyle>
#include <Wt/WStandardItemModel>
#include <Wt/WStandardItem>
#include <Wt/WTableView>
#include <Wt/Chart/WCartesianChart>
#include <Wt/Chart/WAxis>
#include <iostream>

class AttributeGroup;

using namespace Wt;

//#define USE_POINTSERIES 1
#define USE_POINTSERIES 0

class WtApp : public Wt::WApplication
{
public:
WtApp( const Wt::WEnvironment& env);
~WtApp() {}

private:
Wt::WText *createText(const Wt::WString& text);
Wt::WWidget* createGraph();

void vboxLayout();
};

class TransactionResponseTimeSeriesModel : public Wt::WAbstractItemModel
{
public:
TransactionResponseTimeSeriesModel(Wt::WObject *parent = 0)
: WAbstractItemModel(parent)
, tranData_()
, data_()

, rows_(0)
, columns_(0)

, startTime_(0)
, endTime_(0)

, maxrt_(0)
{
tranData_.push_back(TranData("series1", 0, 1, Wt::WColor(255, 0, 0)));
tranData_.push_back(TranData("series2", 1, 1, Wt::WColor(0, 255, 0)));
const int addRows = 2000;

time_t now = time(NULL);
data_[0] = std::vector<Data>();
data_[1] = std::vector<Data>();
for(size_t i=0; i<tranData_.size(); ++i)
{
size_t r=i+1;
for(size_t s=0; s<r*addRows; ++s)
{
int64_t rt = s * addRows / (((r+(s%2))*(7+i)));
data_[i].push_back(Data(now + s, rt));
maxrt_ = std::max(rt, maxrt_);
}
}
rows_ = tranData_.size()*addRows;
columns_ = tranData_.size()*2;
startTime_ = now;
endTime_ = startTime_ + rows_;
}

time_t startTime() const { return startTime_; }
time_t endTime() const { return endTime_; }

void fillDataSeries(std::vector<Wt::Chart::WDataSeries>& dataSeries,
Wt::Chart::SeriesType seriesType,
int& maxLabelLen,
double& ymax)
{
maxLabelLen = 0;
for(size_t i=0; i<tranData_.size(); ++i)
{
const TranData& data = tranData_[i];
maxLabelLen = std::max(maxLabelLen, static_cast<int>(data.name_.value().size()));
int col = data.column_*2;
Wt::Chart::WDataSeries tranSeries(col+1, seriesType);
switch(seriesType)
{
case Wt::Chart::LineSeries:
tranSeries.setPen(Wt::WPen(data.color_));
break;
case Wt::Chart::PointSeries:
tranSeries.setPen(Wt::WPen(data.color_));
tranSeries.setMarker(Wt::Chart::XCrossMarker);
break;
default:
break;
}
tranSeries.setXSeriesColumn(col);
tranSeries.setShadow(Wt::WShadow(1, 1, Wt::WColor(0, 0, 0, 127+64), 1));
dataSeries.push_back(tranSeries);
}
ymax = maxrt_;
}

virtual int columnCount(const Wt::WModelIndex&) const
{
return columns_;
}
virtual int rowCount(const Wt::WModelIndex&) const
{
return rows_;
}
virtual Wt::WModelIndex parent(const Wt::WModelIndex&) const
{
return Wt::WModelIndex();
}
virtual boost::any headerData (int section, Wt::Orientation orientation, int role) const
{
if(role != Wt::DisplayRole)
return boost::any();
int column = section/2;
for(size_t i=0; i< tranData_.size(); ++i)
{
if(tranData_[i].column_ == column)
return boost::any(tranData_[i].name_);
}
return boost::any();
}
virtual boost::any data(const Wt::WModelIndex& idx, int role) const
{
if(!idx.isValid() || role != Wt::DisplayRole)
return boost::any();

const Data& data = *reinterpret_cast<const Data*>(idx.internalPointer());
double value = 0.0;
int rcol = idx.column();
rcol %= 2;
switch(rcol)
{
case 0:
value = data.start_;
break;
case 1:
value = data.rt_;
break;
}
return boost::any(value);
}
virtual Wt::WModelIndex index(int row, int column, const Wt::WModelIndex&) const
{
if(column < columns_)
{
int col = column/2;
DataType::const_iterator it = data_.find(col);
if(it != data_.end())
{
const std::vector<Data>& dv = it->second;
int rows = dv.size();
if(row < rows)
{
const Data& data = dv[row];
return createIndex(row, column, const_cast<void*>(reinterpret_cast<const void*>(&data)));
}
}
}
return Wt::WModelIndex();
}
private:
struct Data
{
Data(time_t start, int64_t rt = 0)
: start_(start) , rt_(rt) { }
time_t start_;
int64_t rt_;
bool operator<(const Data& data) const
{
return start_ < data.start_;
}
};
struct TranData
{
TranData(Wt::WString name = Wt::WString::Empty, int col = 0, int lvl = 1, Wt::WColor color = Wt::WColor())
: name_(name), column_(col), level_(lvl), index_(0), color_(color) {}
Wt::WString name_;
int column_;
int level_;
int index_;
Wt::WColor color_;
};
typedef std::map<int, std::vector<Data> > DataType;
std::vector<TranData> tranData_;
DataType data_;

int rows_;
int columns_;

time_t startTime_;
time_t endTime_;

int64_t maxrt_;
};

class TransactionScatterChart : public Wt::Chart::WCartesianChart
{
public:
TransactionScatterChart(TransactionResponseTimeSeriesModel* model,
Wt::WContainerWidget *parent=0)
: WCartesianChart(Chart::ScatterPlot, parent)
, model_(model)
{
setModel(model_);
setMargin(WLength::Auto, Left | Right);
setPlotAreaPadding(25, Top);
setPlotAreaPadding(30, Right);
setXSeriesColumn(0);
resize(WLength::Auto, WLength::Auto);
Chart::WAxis& xAxis = axis(Chart::XAxis);
Chart::WAxis& yAxis = axis(Chart::YAxis);
yAxis.setMinimum(0.0);
yAxis.setGridLinesEnabled(true);
yAxis.setTitle(WString("RT [{1}]").arg("ms"));
Wt::WFont tf = yAxis.titleFont();
tf.setSize(10);
yAxis.setTitleFont(tf);
xAxis.setScale(Chart::DateTimeScale);
xAxis.setGridLinesEnabled(true);
setLayoutSizeAware(true);
mouseWheel().connect(this, &TransactionScatterChart::zoom);
}

protected:
virtual void modelReset()
{
std::vector<Chart::WDataSeries> dataSeries;
int maxLabelLen;
double ymax = 0.0;

#if USE_POINTSERIES
model_->fillDataSeries(dataSeries, Wt::Chart::PointSeries, maxLabelLen, ymax);
#else
model_->fillDataSeries(dataSeries, Wt::Chart::LineSeries, maxLabelLen, ymax);
#endif

const double width = WLength(maxLabelLen, WLength::FontEm).toPixels(8.0) + 20.0;
const int labelsPerColumn = 6;
const int columns = (dataSeries.size()+labelsPerColumn-1)/labelsPerColumn;

Chart::WAxis& yAxis = axis(Chart::YAxis);
if(ymax > yMax_)
{
int maxLen = log10(ymax)+1;
if(ymax < 100.0)
{
yAxis.setLabelFormat("%.1f");
maxLen += 2;
} else
{
yAxis.setLabelFormat("%.0f");
}
WCartesianChart::setPlotAreaPadding(WLength(maxLen, WLength::FontEm).toPixels(8.0)+10, Left);
yMax_ = ymax;
}
axis(Chart::XAxis).setAutoLimits(Chart::MinimumValue | Chart::MaximumValue);
yAxis.setAutoLimits(Chart::MinimumValue | Chart::MaximumValue);

WCartesianChart::setLegendColumns(std::max(1, columns), std::max(120.0, width));
WCartesianChart::setSeries(dataSeries);
}
void layoutSizeChanged(int width, int height)
{
width_ = width;
WCartesianChart::layoutSizeChanged(width, height);
}

private:
void zoom(const Wt::WMouseEvent& e)
{
double zoomStep = 0.25 * (double) e.wheelDelta();
double width = width_ - plotAreaPadding(Left) - plotAreaPadding(Right);
double weight = (double)(e.widget().x - plotAreaPadding(Left)) / std::max(width, 1.0);
weight = std::min(weight, 1.0);
weight = std::max(weight, 0.0);
//fprintf(stderr, "padding left:%d padding right:%d mouse x:%d width:%f weight:%f\n", plotAreaPadding(Left), plotAreaPadding(Right), e.widget().x, width, weight);

double start = model_->startTime();
double end = model_->endTime();

Chart::WAxis& xAxis = axis(Chart::XAxis);
double interval = xAxis.maximum() - xAxis.minimum();
double zoomAdd = interval * zoomStep;
double newStart = xAxis.minimum() + (zoomAdd * (weight));
double newEnd = xAxis.maximum() - (zoomAdd * (1.0 - weight));

newStart = std::max(start, newStart);
newEnd = std::min(end, newEnd);

//fprintf(stderr, "start:%f end:%f\n", start, end);
//fprintf(stderr, "oldStart:%f, oldEnd:%f\n", xAxis.minimum(), xAxis.maximum());
//fprintf(stderr, "newStart:%f, newEnd:%f diff:%f\n", newStart, newEnd, newEnd - newStart);
//fprintf(stderr, "zoomAdd:%f, zoomstep:%f weight:%f interv:%f\n", zoomAdd, zoomStep, weight, interval);

if(newEnd - newStart > 1.0)
{
xAxis.setRange(newStart, newEnd);
}
}

private:
TransactionResponseTimeSeriesModel* model_;
double yMax_;
int width_;
};



WtApp::WtApp(const Wt::WEnvironment& env)
: Wt::WApplication(env)
{
setCssTheme("polished");

vboxLayout();
}

void WtApp::vboxLayout()
{
Wt::WVBoxLayout* layout = new Wt::WVBoxLayout(root());

root()->setLayout(layout);

// row 0
layout->addWidget(createText("Title"), 0, Wt::AlignTop);


// row 1
layout->addWidget(createText("SubTitle"), 0);

// row 2
Wt::WHBoxLayout* hbox = new Wt::WHBoxLayout();
hbox->addWidget(createText("Selection"), 0, Wt::AlignLeft);
hbox->addWidget(createText("Main"), 1, Wt::AlignJustify);
hbox->addWidget(createText("Details"), 0, Wt::AlignJustify);
hbox->setResizable(1, true);
hbox->setSpacing(4);
layout->addLayout(hbox, 1, Wt::AlignJustify);

// row 3
layout->addWidget(createGraph(), 2);

layout->setResizable(2, true);
}

Wt::WText *WtApp::createText(const Wt::WString& text)
{
Wt::WText *w = new Wt::WText(text);
Wt::WCssDecorationStyle style;
style.setBackgroundColor(Wt::WColor(200,200,200));
w->setDecorationStyle(style);
return w;
}

Wt::WWidget* WtApp::createGraph()
{
TransactionResponseTimeSeriesModel* model = new TransactionResponseTimeSeriesModel();
TransactionScatterChart *graph = new TransactionScatterChart(model);
model->modelReset().emit();
return graph;
}

Wt::WApplication *createApplication(const Wt::WEnvironment& env)
{
Wt::WApplication* myApplication = 0;
myApplication = new WtApp( env );
return myApplication;
}


int main(int argc, char **argv)
{
Wt::WRun(argc, argv, &createApplication);
return 0;
}
    (1-1/1)