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

 
1
#include <Wt/WApplication>
2
#include <Wt/WComboBox>
3
#include <Wt/WContainerWidget>
4
#include <Wt/WVBoxLayout>
5
#include <Wt/WHBoxLayout>
6
#include <Wt/WGroupBox>
7
#include <Wt/WLabel>
8
#include <Wt/WText>
9
#include <Wt/WPushButton>
10
#include <Wt/WLineEdit>
11
#include <Wt/WPopupMenu>
12
#include <Wt/WCssDecorationStyle>
13
#include <Wt/WStandardItemModel>
14
#include <Wt/WStandardItem>
15
#include <Wt/WTableView>
16
#include <Wt/Chart/WCartesianChart>
17
#include <Wt/Chart/WAxis>
18
#include <iostream>
19

    
20
class AttributeGroup;
21

    
22
using namespace Wt;
23

    
24
//#define USE_POINTSERIES 1
25
#define USE_POINTSERIES 0
26

    
27
class WtApp : public Wt::WApplication
28
{
29
   public:
30
      WtApp( const Wt::WEnvironment& env);
31
      ~WtApp() {}
32

    
33
   private:
34
      Wt::WText *createText(const Wt::WString& text);
35
      Wt::WWidget* createGraph();
36

    
37
      void vboxLayout();
38
};
39

    
40
class TransactionResponseTimeSeriesModel : public Wt::WAbstractItemModel
41
{
42
   public:	 
43
      TransactionResponseTimeSeriesModel(Wt::WObject *parent = 0)
44
	 : WAbstractItemModel(parent)
45
	 , tranData_()
46
	 , data_()
47

    
48
	 , rows_(0)
49
	 , columns_(0)
50

    
51
	 , startTime_(0)
52
	 , endTime_(0)
53

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

    
60
	 time_t now = time(NULL);
61
	 data_[0] = std::vector<Data>();
62
	 data_[1] = std::vector<Data>();
63
	 for(size_t i=0; i<tranData_.size(); ++i)
64
	 {
65
	    size_t r=i+1;
66
	    for(size_t s=0; s<r*addRows; ++s)
67
	    {
68
	       int64_t rt = s * addRows / (((r+(s%2))*(7+i)));
69
	       data_[i].push_back(Data(now + s, rt));
70
	       maxrt_ = std::max(rt, maxrt_);
71
	    }
72
	 }
73
	 rows_ = tranData_.size()*addRows;
74
	 columns_ = tranData_.size()*2;
75
	 startTime_ = now;
76
	 endTime_ = startTime_ + rows_;
77
      }
78

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

    
82
      void fillDataSeries(std::vector<Wt::Chart::WDataSeries>& dataSeries,
83
			  Wt::Chart::SeriesType seriesType,
84
			  int& maxLabelLen,
85
			  double& ymax)
86
      {
87
	 maxLabelLen = 0;
88
	 for(size_t i=0; i<tranData_.size(); ++i)
89
	 {
90
	    const TranData& data = tranData_[i];
91
	    maxLabelLen = std::max(maxLabelLen, static_cast<int>(data.name_.value().size()));
92
	    int col = data.column_*2;
93
	    Wt::Chart::WDataSeries tranSeries(col+1, seriesType);
94
	    switch(seriesType)
95
	    {
96
	    case Wt::Chart::LineSeries:
97
	       tranSeries.setPen(Wt::WPen(data.color_));
98
	       break;
99
	    case Wt::Chart::PointSeries:
100
	       tranSeries.setPen(Wt::WPen(data.color_));
101
	       tranSeries.setMarker(Wt::Chart::XCrossMarker);
102
	       break;
103
	    default:
104
	       break;
105
	    }
106
	    tranSeries.setXSeriesColumn(col);
107
	    tranSeries.setShadow(Wt::WShadow(1, 1, Wt::WColor(0, 0, 0, 127+64), 1));
108
	    dataSeries.push_back(tranSeries);
109
	 }
110
	 ymax = maxrt_;
111
      }
112

    
113
      virtual int columnCount(const Wt::WModelIndex&) const
114
      {
115
	 return columns_;
116
      }
117
      virtual int rowCount(const Wt::WModelIndex&) const
118
      {
119
	 return rows_;
120
      }
121
      virtual Wt::WModelIndex parent(const Wt::WModelIndex&) const
122
      {
123
	 return Wt::WModelIndex();
124
      }
125
      virtual boost::any headerData (int section, Wt::Orientation orientation, int role) const
126
      {
127
	 if(role != Wt::DisplayRole)
128
	    return boost::any();
129
	 int column = section/2;
130
	 for(size_t i=0; i< tranData_.size(); ++i)
131
	 {
132
	    if(tranData_[i].column_ == column)
133
	       return boost::any(tranData_[i].name_);
134
	 }
135
	 return boost::any();
136
      }
137
      virtual boost::any data(const Wt::WModelIndex& idx, int role) const
138
      {
139
	 if(!idx.isValid() || role != Wt::DisplayRole)
140
	    return boost::any();
141

    
142
	 const Data& data = *reinterpret_cast<const Data*>(idx.internalPointer());
143
	 double value = 0.0;
144
	 int rcol = idx.column();
145
	 rcol %= 2;
146
	 switch(rcol)
147
	 {
148
	 case 0:
149
	    value = data.start_;
150
	    break;
151
	 case 1:
152
	    value = data.rt_;
153
	    break;
154
	 }
155
	 return boost::any(value);
156
      }
157
      virtual Wt::WModelIndex index(int row, int column, const Wt::WModelIndex&) const
158
      {
159
	 if(column < columns_)
160
	 {
161
	    int col = column/2;
162
	    DataType::const_iterator it = data_.find(col);
163
	    if(it != data_.end())
164
	    {
165
	       const std::vector<Data>& dv = it->second;
166
	       int rows = dv.size();
167
	       if(row < rows)
168
	       {
169
		  const Data& data = dv[row];
170
		  return createIndex(row, column, const_cast<void*>(reinterpret_cast<const void*>(&data)));
171
	       }
172
	    }
173
	 }
174
	 return Wt::WModelIndex();
175
      }
176
   private:
177
      struct Data
178
      {
179
	    Data(time_t start, int64_t rt = 0)
180
	       : start_(start) , rt_(rt) { }
181
	    time_t start_;
182
	    int64_t rt_;
183
	    bool operator<(const Data& data) const
184
	    {
185
	       return start_ < data.start_;
186
	    }
187
      };
188
      struct TranData
189
      {
190
	    TranData(Wt::WString name = Wt::WString::Empty, int col = 0, int lvl = 1, Wt::WColor color = Wt::WColor())
191
	       : name_(name), column_(col), level_(lvl), index_(0), color_(color) {}
192
	    Wt::WString name_;
193
	    int column_;
194
	    int level_;
195
	    int index_;
196
	    Wt::WColor color_;
197
      };
198
      typedef std::map<int, std::vector<Data> > DataType;
199
      std::vector<TranData> tranData_;
200
      DataType data_;
201

    
202
      int rows_;
203
      int columns_;
204

    
205
      time_t startTime_;
206
      time_t endTime_;
207

    
208
      int64_t maxrt_;
209
};
210

    
211
class TransactionScatterChart : public Wt::Chart::WCartesianChart
212
{
213
   public:
214
      TransactionScatterChart(TransactionResponseTimeSeriesModel* model,
215
			      Wt::WContainerWidget *parent=0)
216
	 : WCartesianChart(Chart::ScatterPlot, parent)
217
	 , model_(model)
218
      {
219
	 setModel(model_);
220
	 setMargin(WLength::Auto, Left | Right);
221
	 setPlotAreaPadding(25, Top);
222
	 setPlotAreaPadding(30, Right);
223
	 setXSeriesColumn(0);
224
	 resize(WLength::Auto, WLength::Auto);
225
	 Chart::WAxis& xAxis = axis(Chart::XAxis);
226
	 Chart::WAxis& yAxis = axis(Chart::YAxis);
227
	 yAxis.setMinimum(0.0);
228
	 yAxis.setGridLinesEnabled(true);
229
	 yAxis.setTitle(WString("RT [{1}]").arg("ms"));
230
	 Wt::WFont tf = yAxis.titleFont();
231
	 tf.setSize(10);
232
	 yAxis.setTitleFont(tf);
233
	 xAxis.setScale(Chart::DateTimeScale);
234
	 xAxis.setGridLinesEnabled(true);
235
	 setLayoutSizeAware(true);
236
	 mouseWheel().connect(this, &TransactionScatterChart::zoom);
237
      }
238

    
239
   protected:
240
      virtual void modelReset()
241
      {
242
	 std::vector<Chart::WDataSeries> dataSeries;
243
	 int maxLabelLen;
244
	 double ymax = 0.0;
245

    
246
#if USE_POINTSERIES
247
	 model_->fillDataSeries(dataSeries, Wt::Chart::PointSeries, maxLabelLen, ymax);
248
#else
249
	 model_->fillDataSeries(dataSeries, Wt::Chart::LineSeries, maxLabelLen, ymax);
250
#endif
251

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

    
256
	 Chart::WAxis& yAxis = axis(Chart::YAxis);
257
	 if(ymax > yMax_)
258
	 {
259
	    int maxLen = log10(ymax)+1;
260
	    if(ymax < 100.0)
261
	    {
262
	       yAxis.setLabelFormat("%.1f");
263
	       maxLen += 2;
264
	    } else
265
	    {
266
	       yAxis.setLabelFormat("%.0f");
267
	    }
268
	    WCartesianChart::setPlotAreaPadding(WLength(maxLen, WLength::FontEm).toPixels(8.0)+10, Left);
269
	    yMax_ = ymax;
270
	 }
271
	 axis(Chart::XAxis).setAutoLimits(Chart::MinimumValue | Chart::MaximumValue);
272
	 yAxis.setAutoLimits(Chart::MinimumValue | Chart::MaximumValue);
273

    
274
	 WCartesianChart::setLegendColumns(std::max(1, columns), std::max(120.0, width));
275
	 WCartesianChart::setSeries(dataSeries);
276
      }
277
      void layoutSizeChanged(int width, int height)
278
      {
279
	 width_ = width;
280
	 WCartesianChart::layoutSizeChanged(width, height);
281
      }
282

    
283
   private:
284
      void zoom(const Wt::WMouseEvent& e)
285
      {
286
	 double zoomStep = 0.25 * (double) e.wheelDelta();
287
	 double width = width_ - plotAreaPadding(Left) - plotAreaPadding(Right);
288
	 double weight = (double)(e.widget().x - plotAreaPadding(Left)) / std::max(width, 1.0);
289
	 weight = std::min(weight, 1.0);
290
	 weight = std::max(weight, 0.0);
291
	 //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);
292

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

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

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

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

    
310
	 if(newEnd - newStart > 1.0)
311
	 {
312
	    xAxis.setRange(newStart, newEnd);
313
	 }
314
      }
315

    
316
   private:
317
      TransactionResponseTimeSeriesModel* model_;
318
      double yMax_;
319
      int width_;
320
};
321

    
322

    
323

    
324
WtApp::WtApp(const Wt::WEnvironment& env)
325
   : Wt::WApplication(env)
326
{
327
   setCssTheme("polished");
328

    
329
   vboxLayout();
330
}
331

    
332
void WtApp::vboxLayout()
333
{
334
   Wt::WVBoxLayout* layout = new Wt::WVBoxLayout(root());
335

    
336
   root()->setLayout(layout);
337

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

    
341

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

    
345
   // row 2
346
   Wt::WHBoxLayout* hbox  = new Wt::WHBoxLayout();
347
   hbox->addWidget(createText("Selection"), 0, Wt::AlignLeft);
348
   hbox->addWidget(createText("Main"), 1, Wt::AlignJustify);
349
   hbox->addWidget(createText("Details"), 0, Wt::AlignJustify);
350
   hbox->setResizable(1, true);
351
   hbox->setSpacing(4);
352
   layout->addLayout(hbox, 1, Wt::AlignJustify);
353

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

    
357
   layout->setResizable(2, true);
358
}
359

    
360
Wt::WText *WtApp::createText(const Wt::WString& text)
361
{
362
   Wt::WText *w = new Wt::WText(text);
363
   Wt::WCssDecorationStyle style;
364
   style.setBackgroundColor(Wt::WColor(200,200,200));
365
   w->setDecorationStyle(style);
366
   return w;
367
}
368

    
369
Wt::WWidget* WtApp::createGraph()
370
{
371
   TransactionResponseTimeSeriesModel* model = new TransactionResponseTimeSeriesModel();
372
   TransactionScatterChart *graph = new TransactionScatterChart(model);
373
   model->modelReset().emit();
374
   return graph;
375
}
376

    
377
Wt::WApplication *createApplication(const Wt::WEnvironment& env)
378
{
379
   Wt::WApplication* myApplication = 0;
380
   myApplication = new WtApp( env );
381
   return myApplication;
382
}
383

    
384

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