Minsky
sheet.cc
Go to the documentation of this file.
1 /*
2  @copyright Steve Keen 2018
3  @author Russell Standish
4  This file is part of Minsky.
5 
6  Minsky is free software: you can redistribute it and/or modify it
7  under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  Minsky is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with Minsky. If not, see <http://www.gnu.org/licenses/>.
18 */
19 #include "sheet.h"
20 #include "str.h"
21 #include "selection.h"
22 #include "lasso.h"
23 #include "minsky.h"
24 #include "plotWidget.h"
25 #include <cairo_base.h>
26 #include <pango.h>
27 
28 #include "itemT.rcd"
29 #include "sheet.rcd"
30 #include "minskyCairoRenderer.h"
31 #include "ravelWrap.h"
32 #include "tensorOp.h"
33 #include "minsky_epilogue.h"
34 
35 using namespace minsky;
36 using namespace ecolab;
37 using namespace std;
38 
39 // width of draggable border
40 const float border=10;
41 // proportion of width/height to centre Ravel popout
42 const double ravelOffset=0.1;
43 
45 {
46  m_ports.emplace_back(make_shared<InputPort>(*this));
47  iWidth(100);
48  iHeight(100);
49 }
50 
51 double Sheet::ravelSize() const
52 {
53  return 0.25*std::min(m_width,m_height)*zoomFactor();
54 }
55 
56 double Sheet::ravelX(double xx) const
57 {
58  return (xx+(0.5+ravelOffset)*zoomFactor()*m_width-x())*inputRavel.radius()/(zoomFactor()*ravelSize());
59 }
60 
61 double Sheet::ravelY(double yy) const
62 {
63  return (yy+(0.5+ravelOffset)*zoomFactor()*m_height-y())*inputRavel.radius()/(zoomFactor()*ravelSize());
64 }
65 
66 bool Sheet::onResizeHandle(float xx, float yy) const
67 {
68  const float dx=xx-x(), dy=yy-y();
69  const float w=0.5*m_width*zoomFactor(), h=0.5*m_height*zoomFactor();
70  return fabs(dx)>=w-resizeHandleSize() && fabs(dx)<=w+resizeHandleSize() &&
71  fabs(dy)>=h-resizeHandleSize() && fabs(dy)<=h+resizeHandleSize() &&
72  (!inputRavel || dx>0 || dy>0);
73 }
74 
75 
76 void Sheet::drawResizeHandles(cairo_t* cairo) const
77 {
78  auto sf=resizeHandleSize();
79  const float w=0.5f*m_width*zoomFactor(), h=0.5f*m_height*zoomFactor();
80  if (!showRavel)
81  drawResizeHandle(cairo,-w,-h,sf,0);
82  drawResizeHandle(cairo,w,-h,sf,0.5*M_PI);
83  drawResizeHandle(cairo,w,h,sf,0);
84  drawResizeHandle(cairo,-w,h,sf,0.5*M_PI);
85  cairo_stroke(cairo);
86 }
87 
88 
89 bool Sheet::onRavelButton(float xx, float yy) const
90 {
91  const float dx=xx-x(), dy=yy-y();
92  const float w=0.5*m_width*zoomFactor(), h=0.5*m_height*zoomFactor(), b=border*zoomFactor();
93  return inputRavel && dx>=-w && dx<=b-w && dy>=-h && dy<=b-h;
94 }
95 
96 bool Sheet::inRavel(float xx, float yy) const
97 {
98  auto dx=xx-x(), dy=yy-y();
99  const float w=0.5*m_width*zoomFactor(), h=0.5*m_height*zoomFactor();
100  return showRavel && inputRavel && (dx<w || dy<h) &&
101  fabs(ravelX(xx))<1.1*inputRavel.radius() && fabs(ravelY(yy))<1.1*inputRavel.radius();
102 }
103 
104 bool Sheet::inItem(float xx, float yy) const
105 {
106  const double z=zoomFactor();
107  const double w=0.5*m_width*z, h=0.5*m_height*z, b=border*z;
108  return (abs(xx-x())<w-b && abs(yy-y())<h-b) || onRavelButton(xx,yy) || inRavel(xx,yy);
109 }
110 
111 ClickType::Type Sheet::clickType(float x, float y) const
112 {
113  if (onResizeHandle(x,y)) return ClickType::onResize;
114  if (inItem(x,y)) return ClickType::inItem;
115  const double dx=fabs(x-this->x()), dy=fabs(y-this->y());
116  const double w=0.5*m_width*zoomFactor(), h=0.5*m_height*zoomFactor();
117  if (dx < w && dy < h)
118  return ClickType::onItem;
119  return ClickType::outside;
120 }
121 
122 std::vector<Point> Sheet::corners() const
123 {
124  const float w=0.5*m_width*zoomFactor(), h=0.5*m_height*zoomFactor();
125  return {{x()-w,y()-h},{x()+w,y()-h},{x()-w,y()+h},{x()+w,y()+h}};
126 }
127 
128 bool Sheet::contains(float x, float y) const
129 {
130  return Item::contains(x,y) || inRavel(x,y);
131 }
132 
134 {
135  if (scrollOffset+scrollDelta<scrollMax)
136  {
137  scrollOffset+=scrollDelta;
138  setSliceIndicator();
139  return true;
140  }
141  return false;
142 }
143 
145 {
146  if (scrollOffset>scrollDelta)
147  {
148  scrollOffset-=scrollDelta;
149  setSliceIndicator();
150  return true;
151  }
152  return false;
153 }
154 
155 bool Sheet::onKeyPress(int keySym, const std::string& utf8, int state)
156 {
157  switch (keySym)
158  {
159  case 0xff52: case 0xff53: case 0xff55: //Right, Up
160  return scrollUp();
161  case 0xff51: case 0xff54: case 0xff56://Left, Down
162  return scrollDown();
163  default:
164  return false;
165  }
166 }
167 
168 namespace {
169  string formattedStr(const XVector& xv, size_t index)
170  {
171  return str(xv[index], minsky::minsky().dimensions[xv.name].units);
172  }
173 }
174 
176 {
177  if (!value || value->rank()<=2) return;
178  auto idx=value->hypercube().splitIndex(scrollOffset);
179  sliceIndicator=formattedStr(value->hypercube().xvectors[2], idx[2]);
180  for (size_t i=3; i<idx.size(); ++i)
181  sliceIndicator+=" | "+formattedStr(value->hypercube().xvectors[i], idx[i]);
182 }
183 
184 namespace
185 {
187  {
188  size_t startRow, numRows, tailStartRow;
189  double rowHeight;
190  ElisionRowChecker(ShowSlice slice, double height, double rowHeight, size_t size): rowHeight(rowHeight) {
191  switch (slice)
192  {
193  default: // just to shut up the "maybe uninitialised" checker.
194  case ShowSlice::head:
195  tailStartRow=startRow=0;
196  numRows=std::min(size, size_t(height/rowHeight+1));
197  break;
199  startRow=0;
200  numRows=std::min(size, size_t(0.5*height/rowHeight-1));
201  tailStartRow=size-numRows;
202  if (numRows<size && 2*numRows*rowHeight>height)
203  tailStartRow++;
204  break;
205  case ShowSlice::tail:
206  numRows=std::min(size, size_t(height/rowHeight-1));
207  tailStartRow=startRow=size-numRows;
208  break;
209  }
210  }
211  bool operator()(size_t& row, double& y) const {
212  if (row==startRow+numRows && row<tailStartRow)
213  {
214  row=tailStartRow; // for middle elision
215  y+=rowHeight;
216  }
217  return row>=tailStartRow+numRows;
218  }
219  };
220 }
221 
223 {
224  if (m_ports[0] && (value=m_ports[0]->getVariableValue()) && showRavel && inputRavel )
225  {
226  const bool wasEmpty=inputRavel.numHandles()==0;
227  inputRavel.populateFromHypercube(value->hypercube());
228  for (size_t i=0; i<inputRavel.numHandles(); ++i)
229  inputRavel.displayFilterCaliper(i,true);
230  if (wasEmpty)
231  switch (value->rank())
232  {
233  case 0: break;
234  case 1:
235  inputRavel.setOutputHandleIds({0});
236  break;
237  default:
238  inputRavel.setOutputHandleIds({0,1});
239  break;
240  }
241 
242  if (value->rank()>0)
243  {
244  value=inputRavel.hyperSlice(value);
245  if (value->rank()>=2)
246  { // swap first and second axes
247  auto& xv=value->hypercube().xvectors;
248  auto pivot=make_shared<civita::Pivot>();
249  pivot->setArgument(value,{});
250  pivot->setOrientation(vector<string>{xv[1].name,xv[0].name});
251  value=std::move(pivot);
252  }
253  }
254  }
255  if (value && value->rank()>2)
256  {
257  const size_t delta=value->hypercube().xvectors[0].size()*value->hypercube().xvectors[1].size();
258  if (delta!=scrollDelta)
259  {
260  scrollDelta=delta;
261  scrollOffset=0;
262  }
263  }
264  else
265  scrollOffset=scrollDelta=0;
266  scrollMax=value? value->size(): 1;
267  setSliceIndicator();
268 }
269 
270 
271 namespace {
273  struct ClippedPango: public Pango
274  {
275  double m_width, m_height;
276  ClippedPango(cairo_t* cairo): Pango(cairo) {
277  setText(str(-std::numeric_limits<double>::max()));
278  m_width=5+width();
279  m_height=height();
280  }
281  void show() {
282  const cairo::CairoSave cs(cairoContext());
283  double x,y;
284  cairo_get_current_point(cairoContext(),&x,&y);
285  cairo_rectangle(cairoContext(),x,y,m_width,m_height);
286  cairo_clip(cairoContext());
287  cairo_move_to(cairoContext(),x,y);
288  Pango::show();
289  }
290  };
291 }
292 
293 void Sheet::draw(cairo_t* cairo) const
294 {
295  auto z=zoomFactor();
296  m_ports[0]->moveTo(x()-0.5*m_width*z,y());
297  if (mouseFocus)
298  {
299  drawPorts(cairo);
300  displayTooltip(cairo,tooltip());
301  // Resize handles always visible on mousefocus. For ticket 92.
302  drawResizeHandles(cairo);
303  }
304 
305  cairo_scale(cairo,z,z);
306 
307  cairo_rectangle(cairo,-0.5*m_width+border,-0.5*m_height+border,m_width-2*border,m_height-2*border);
308  cairo_stroke_preserve(cairo);
309  cairo_rectangle(cairo,-0.5*m_width,-0.5*m_height,m_width,m_height);
310  cairo_stroke_preserve(cairo);
311  // draw border
312  if (onBorder)
313  { // shadow the border when mouse is over it
314  const cairo::CairoSave cs(cairo);
315  cairo_set_source_rgba(cairo,0.5,0.5,0.5,0.5);
316  cairo_set_fill_rule(cairo,CAIRO_FILL_RULE_EVEN_ODD);
317  cairo_fill(cairo);
318  }
319  cairo_new_path(cairo);
320  cairo_rectangle(cairo,-0.5*m_width+border,-0.5*m_height+border,m_width-2*border,m_height-2*border);
321  cairo_clip(cairo);
322 
323  if (selected) drawSelected(cairo);
324 
325  static const regex pangoMarkup("<[^<>]*>");
326  smatch match;
327 
328  try
329  {
330  if (!value || !m_ports[0] || m_ports[0]->numWires()==0) return;
331  Pango pango(cairo);
332  pango.setFontSize(12.0);
333  double x0=-0.5*m_width+border, y0=-0.5*m_height+border;
334  const size_t vertDim=value->rank()>1? 1: 0, horizDim=0;
335 
336  // force evaluate first data item, in case cachedTensorOp updates hypercube
337  if (value->size()>0)
338  (*value)[0];
339 
340  if (value->hypercube().rank()==0)
341  {
342  cairo_move_to(cairo,x0,y0);
343  pango.setMarkup(str((*value)[0]));
344  pango.show();
345  }
346  else
347  {
348  if (value->size()!=scrollMax) minsky().requestReset(); // fix up slice indicator
349 
350  if (!value->hypercube().xvectors[vertDim].name.empty())
351  {
352  const cairo::CairoSave cs(cairo);
353  pango.setMarkup(value->hypercube().xvectors[vertDim].name);
354  x0+=pango.height();
355  cairo_move_to(cairo,x0, -0.5*pango.width());
356  pango.angle=0.5*M_PI;
357  pango.show();
358  pango.angle=0;
359  { // draw vertical grid line
360  const cairo::CairoSave cs(cairo);
361  cairo_set_source_rgba(cairo,0,0,0,0.5);
362  cairo_move_to(cairo,x0,-0.5*m_height);
363  cairo_line_to(cairo,x0,0.5*m_height);
364  cairo_stroke(cairo);
365  }
366  }
367 
368  pango.setMarkup("9999");
369 
370  ClippedPango cpango(cairo);
371  const double rowHeight=cpango.m_height;
372  const double colWidth=cpango.m_width;
373 
374  double x=x0, y=y0; // make sure row labels are aligned with corresponding values. for ticket 1281
375  string format=value->hypercube().xvectors[vertDim].dimension.units;
376 
377  if (value->hypercube().rank()>=2)
378  {
379  y+=2*rowHeight; // allow room for header row
380  if (value->hypercube().rank()>2) // allow room for slice indicator
381  {
382  y+=rowHeight;
383  const cairo::CairoSave cs(cairo);
384  cpango.setMarkup(sliceIndicator);
385  cairo_move_to(cairo,0.5*(x0+colWidth+0.5*m_width-cpango.width()), y0);
386  y0+=cpango.height();
387  cpango.show();
388  }
389  if (!value->hypercube().xvectors[horizDim].name.empty())
390  {
391  const cairo::CairoSave cs(cairo);
392  cpango.setMarkup(value->hypercube().xvectors[horizDim].name);
393  cairo_move_to(cairo,0.5*(x0+colWidth+0.5*m_width-cpango.width()), y0);
394  y0+=cpango.height();
395  cpango.show();
396  }
397 
398  { // draw horizontal grid lines
399  const cairo::CairoSave cs(cairo);
400  cairo_set_source_rgba(cairo,0,0,0,0.5);
401  cairo_move_to(cairo,-0.5*m_width,y0-2.5);
402  cairo_line_to(cairo,0.5*m_width,y0-2.5);
403  cairo_stroke(cairo);
404  cairo_move_to(cairo,x,y0+rowHeight-2.5);
405  cairo_line_to(cairo,0.5*m_width,y0+rowHeight-2.5);
406  cairo_stroke(cairo);
407 
408  }
409  }
410 
411  auto dims=value->hypercube().dims();
412  double dataHeight=m_height;
413  switch (dims.size())
414  {
415  case 0: case 1: break;
416  case 2: dataHeight-=2*(rowHeight+3); break;
417  default: dataHeight-=3*(rowHeight+3); break;
418  }
419  const ElisionRowChecker adjustRowAndFinish(showRowSlice,dataHeight,rowHeight,dims[vertDim]);
420 
421  // draw in label column
422  auto& xv=value->hypercube().xvectors[vertDim];
423  for (size_t i=adjustRowAndFinish.startRow; i<xv.size(); ++i)
424  {
425  if (adjustRowAndFinish(i,y)) break;
426  cairo_move_to(cairo,x,y);
427  auto sliceLabel=trimWS(str(xv[i],format));
428  if (regex_search(sliceLabel,match,pangoMarkup))
429  cpango.setMarkup(sliceLabel);
430  else
431  cpango.setText(sliceLabel);
432  cpango.show();
433  y+=rowHeight;
434  }
435  y=y0;
436  x+=colWidth;
437  if (value->hypercube().rank()==1)
438  {
439  { // draw vertical grid line
440  const cairo::CairoSave cs(cairo);
441  cairo_set_source_rgba(cairo,0,0,0,0.5);
442  cairo_move_to(cairo,x,-0.5*m_height);
443  cairo_line_to(cairo,x,0.5*m_height);
444  cairo_stroke(cairo);
445  }
446  for (size_t i=adjustRowAndFinish.startRow; i<dims[vertDim]; ++i)
447  {
448  if (adjustRowAndFinish(i,y)) break;
449  cairo_move_to(cairo,x,y);
450  auto v=value->atHCIndex(i);
451  if (!std::isnan(v))
452  {
453  cpango.setMarkup(str(v));
454  cpango.show();
455  }
456  y+=rowHeight;
457  }
458  }
459  else
460  {
461  format=value->hypercube().xvectors[horizDim].dimension.units;
462  const ElisionRowChecker adjustColAndFinish(showColSlice,m_width-colWidth,colWidth,dims[horizDim]);
463  for (size_t i=adjustColAndFinish.startRow; i<dims[horizDim]; ++i)
464  {
465  if (adjustColAndFinish(i,x)) break;
466  y=y0;
467  {
468  const cairo::CairoSave cs(cairo);
469  cairo_rectangle(cairo,x,y,colWidth,rowHeight);
470  cairo_clip(cairo);
471  cairo_move_to(cairo,x,y);
472  auto sliceLabel=trimWS(str(value->hypercube().xvectors[horizDim][i],format));
473  if (regex_search(sliceLabel,match,pangoMarkup))
474  cpango.setMarkup(sliceLabel);
475  else
476  cpango.setText(sliceLabel);
477  cpango.show();
478  }
479  { // draw vertical grid line
480  const cairo::CairoSave cs(cairo);
481  cairo_set_source_rgba(cairo,0,0,0,0.5);
482  cairo_move_to(cairo,x-2.5,y0);
483  cairo_line_to(cairo,x-2.5,0.5*m_height);
484  cairo_stroke(cairo);
485  }
486  for (size_t j=adjustRowAndFinish.startRow; j<dims[vertDim]; ++j)
487  {
488  if (adjustRowAndFinish(j,y)) break;
489  y+=rowHeight;
490  cairo_move_to(cairo,x,y);
491  assert(horizDim==0);
492  auto v=value->atHCIndex(i+j*dims[0]+scrollOffset);
493  if (!std::isnan(v))
494  {
495  cpango.setText(str(v));
496  cpango.show();
497  }
498  }
499  x+=colWidth;
500  if (x>0.5*m_width) break;
501  }
502  }
503  // draw grid
504  {
505  const cairo::CairoSave cs(cairo);
506  cairo_set_source_rgba(cairo,0,0,0,0.2);
507  for (y=y0+0.8*rowHeight; y<0.5*m_height; y+=2*rowHeight)
508  {
509  cairo_rectangle(cairo,x0,y,m_width,rowHeight);
510  cairo_fill(cairo);
511  }
512  }
513 
514  }
515  }
516  catch (...) {/* exception most likely invalid variable value */}
517  cairo_reset_clip(cairo);
518  cairo_rectangle(cairo,-0.5*m_width,-0.5*m_height,m_width,m_height);
519  cairo_clip(cairo);
520 }
521 
522 void Sheet::exportAsCSV(const string& filename, bool tabular) const
523 {
524  if (!value)
525  throw_error("input not defined");
526  VariableValue vv(VariableType::flow); vv=*value;
527  vv.exportAsCSV(filename,"",tabular);
528 }
529 
#define M_PI
some useful geometry types, defined from boost::geometry
Definition: geometry.h:29
bool inItem(float, float) const override
offset for scrolling through higher ranked inputs
Definition: sheet.cc:104
bool inRavel(float, float) const
offset for scrolling through higher ranked inputs
Definition: sheet.cc:96
void exportAsCSV(const std::string &filename, bool tabular) const
export the plotted data as a CSV file
Definition: sheet.cc:522
void exportAsCSV(const std::string &filename, const std::string &comment="", bool tabular=false) const
export this to a CSV file. If comment is non-empty, it is written as the first line of the file...
const float border
Definition: sheet.cc:40
void requestReset()
Definition: minsky.cc:467
bool onKeyPress(int keySym, const std::string &utf8, int state) override
offset for scrolling through higher ranked inputs
Definition: sheet.cc:155
STL namespace.
double ravelX(double xx) const
ravel coordinate from screen coordinate
Definition: sheet.cc:56
double ravelSize() const
size of ravel in screen coordinates
Definition: sheet.cc:51
Sheet()
offset for scrolling through higher ranked inputs
Definition: sheet.cc:44
ShowSlice
Definition: showSlice.h:25
void computeValue()
calculates the input value
Definition: sheet.cc:222
ElisionRowChecker(ShowSlice slice, double height, double rowHeight, size_t size)
Definition: sheet.cc:190
Creation and access to the minskyTCL_obj object, which has code to record whenever Minsky&#39;s state cha...
Definition: constMap.h:22
std::vector< Point > corners() const override
offset for scrolling through higher ranked inputs
Definition: sheet.cc:122
std::string trimWS(const std::string &s)
Definition: str.h:49
ClickType::Type clickType(float x, float y) const override
offset for scrolling through higher ranked inputs
Definition: sheet.cc:111
bool onRavelButton(float, float) const
offset for scrolling through higher ranked inputs
Definition: sheet.cc:89
double ravelY(double yy) const
offset for scrolling through higher ranked inputs
Definition: sheet.cc:61
std::string str(T x)
utility function to create a string representation of a numeric type
Definition: str.h:33
virtual bool contains(float xx, float yy) const
Definition: item.h:196
const double ravelOffset
Definition: sheet.cc:42
A pango that clips text to a standard area suitable for numbers.
Definition: sheet.cc:273
void drawResizeHandles(cairo_t *cairo) const override
offset for scrolling through higher ranked inputs
Definition: sheet.cc:76
bool onResizeHandle(float x, float y) const override
offset for scrolling through higher ranked inputs
Definition: sheet.cc:66
string formattedStr(const XVector &xv, size_t index)
Definition: sheet.cc:169
CLASSDESC_ACCESS_EXPLICIT_INSTANTIATION(minsky::Sheet)
void setSliceIndicator()
offset for scrolling through higher ranked inputs
Definition: sheet.cc:175
void draw(cairo_t *cairo) const override
offset for scrolling through higher ranked inputs
Definition: sheet.cc:293
Minsky & minsky()
global minsky object
Definition: minskyTCL.cc:51
bool scrollUp()
offset for scrolling through higher ranked inputs
Definition: sheet.cc:133
bool contains(float x, float y) const override
offset for scrolling through higher ranked inputs
Definition: sheet.cc:128
bool scrollDown()
offset for scrolling through higher ranked inputs
Definition: sheet.cc:144
bool operator()(size_t &row, double &y) const
Definition: sheet.cc:211