Minsky: 3.17.0
ravelWrap.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 
20 #include "minsky.h"
21 #include "cairoItems.h"
22 #include "ravelWrap.h"
23 #include "selection.h"
24 #include "dimension.h"
25 #include "minskyTensorOps.h"
26 #include "pango.h"
27 
28 #include "capiRenderer.xcd"
29 #include "dimension.rcd"
30 #include "dynamicRavelCAPI.rcd"
31 #include "dynamicRavelCAPI.xcd"
32 #include "handleLockInfo.rcd"
33 #include "handleLockInfo.xcd"
34 #include "hypercube.rcd"
35 #include "hypercube.xcd"
36 #include "itemT.rcd"
37 #include "nobble.h"
38 #include "ravelState.xcd"
39 #include "ravelState.rcd"
40 #include "ravelWrap.rcd"
41 #include "ravelWrap.xcd"
42 #include "xvector.rcd"
43 #include "minskyCairoRenderer.h"
44 #include "minsky_epilogue.h"
45 
46 
47 #include <string>
48 #include <cmath>
49 using namespace std;
50 
51 
52 namespace minsky
53 {
54  unsigned RavelLockGroup::nextColour=1;
55  SVGRenderer Ravel::svgRenderer;
56 
57  namespace
58  {
60  typedef ravel::HandleSort::Order HandleSort;
61  }
62 
63  Ravel::Ravel(): popup(*this)
64  {
65  if (!wrappedRavel)
66  {
67  tooltip("https://ravelation.net");
68  detailedText(wrappedRavel.lastError());
69  }
70  if (minsky().model->findAny(&GroupItems::items, [](const ItemPtr& i){return i->ravelCast();}))
71  return; // early return if at least 1 ravel already present
72  m_editorMode=true; // first ravel is in editor mode
73  // set an intial 3 axis ravel to give an indication of what it is about
74  wrappedRavel.addHandle("Year",{"1990","1991","1992"});
75  wrappedRavel.addHandle("Gender",{"Male","Female"});
76  wrappedRavel.addHandle("Country",{"Australia","UK","USA"});
77  wrappedRavel.setOutputHandleIds({0,2});
78  }
79 
80  void Ravel::draw(cairo_t* cairo) const
81  {
82  const double z=zoomFactor(), r=m_editorMode? 1.1*z*wrappedRavel.radius(): 30*z;
83  if (flipped)
84  {
85  m_ports[0]->moveTo(x()-1.1*r, y());
86  m_ports[1]->moveTo(x()+1.1*r, y());
87  drawTriangle(cairo,m_ports[1]->x()-x(),m_ports[1]->y()-y(),{0,0,0,1},M_PI);
88  }
89  else
90  {
91  m_ports[0]->moveTo(x()+1.1*r, y());
92  m_ports[1]->moveTo(x()-1.1*r, y());
93  drawTriangle(cairo,m_ports[1]->x()-x(),m_ports[1]->y()-y(),{0,0,0,1},0);
94  }
95  if (mouseFocus)
96  {
97  drawPorts(cairo);
98  displayTooltip(cairo,tooltip().empty()? explanation: tooltip());
99  // Resize handles always visible on mousefocus. For ticket 92.
100  if (m_editorMode) drawResizeHandles(cairo);
101  }
102  cairo_rectangle(cairo,-r,-r,2*r,2*r);
103  cairo_rectangle(cairo,-1.1*r,-1.1*r,2.2*r,2.2*r);
104  cairo_stroke_preserve(cairo);
105  if (onBorder || lockGroup)
106  { // shadow the border when mouse is over it
107  const cairo::CairoSave cs(cairo);
108  cairo::Colour c{1,1,1,0};
109  if (lockGroup)
110  c=palette[ lockGroup->colour() % paletteSz ];
111  c.r*=0.5; c.g*=0.5; c.b*=0.5;
112  c.a=onBorder? 0.5:0.3;
113  cairo_set_source_rgba(cairo,c.r,c.g,c.b,c.a);
114  cairo_set_fill_rule(cairo,CAIRO_FILL_RULE_EVEN_ODD);
115  cairo_fill_preserve(cairo);
116  }
117 
118  cairo_clip(cairo);
119 
120  {
121  const cairo::CairoSave cs(cairo);
122  cairo_rectangle(cairo,-r,-r,2*r,2*r);
123  cairo_clip(cairo);
124  if (m_editorMode)
125  {
126  cairo_scale(cairo,z,z);
127  CairoRenderer cr(cairo);
128  wrappedRavel.render(cr);
129  }
130  else
131  {
132  cairo_translate(cairo,-r,-r);
133  svgRenderer.render(cairo,2*r,2*r);
134  }
135  }
136  if (selected) drawSelected(cairo);
137  }
138 
139  void Ravel::resize(const LassoBox& b)
140  {
141  wrappedRavel.rescale(0.5*std::max(fabs(b.x0-b.x1),fabs(b.y0-b.y1))/(1.21*zoomFactor()));
142  moveTo(0.5*(b.x0+b.x1), 0.5*(b.y0+b.y1));
143  bb.update(*this);
144  }
145 
146  bool Ravel::inItem(float xx, float yy) const
147  {
148  if (m_editorMode)
149  {
150  const float r=1.1*zoomFactor()*wrappedRavel.radius();
151  return std::abs(xx-x())<=r && std::abs(yy-y())<=r;
152  }
153  return false;
154  }
155 
156  void Ravel::onMouseDown(float xx, float yy)
157  {
158  const double invZ=1/zoomFactor();
159  wrappedRavel.onMouseDown((xx-x())*invZ,(yy-y())*invZ);
160  }
161 
162  void Ravel::onMouseUp(float xx, float yy)
163  {
164  const double invZ=1/zoomFactor();
165  wrappedRavel.onMouseUp((xx-x())*invZ,(yy-y())*invZ);
168  }
169  bool Ravel::onMouseMotion(float xx, float yy)
170  {
171  const double invZ=1/zoomFactor();
172  return wrappedRavel.onMouseMotion((xx-x())*invZ,(yy-y())*invZ);
173  }
174 
175  bool Ravel::onMouseOver(float xx, float yy)
176  {
177  const double invZ=1/zoomFactor();
178  return wrappedRavel.onMouseOver((xx-x())*invZ,(yy-y())*invZ);
179  }
180 
181  Hypercube Ravel::hypercube() const
182  {
183  auto outHandles=wrappedRavel.outputHandleIds();
184  Hypercube hc;
185  auto& xv=hc.xvectors;
186  for (auto h: outHandles)
187  {
188  auto labels=wrappedRavel.sliceLabels(h);
189  xv.emplace_back(handleDescription(h));
190  if (auto dim=axisDimensions.find(xv.back().name);
191  dim!=axisDimensions.end())
192  xv.back().dimension=dim->second;
193  else if (auto dim=cminsky().dimensions.find(xv.back().name);
194  dim!=cminsky().dimensions.end())
195  xv.back().dimension=dim->second;
196  // else otherwise dimension is a string (default type)
197  for (auto& i: labels)
198  xv.back().push_back(i);
199  }
200  return hc;
201  }
202 
203  void Ravel::populateHypercube(const Hypercube& hc)
204  {
205  if (!wrappedRavel) return;
206  auto state=initState.empty()? getState(): initState;
207  const bool redistribute=!initState.empty();
208  initState.clear();
209  wrappedRavel.populateFromHypercube(hc);
210  if (state.empty())
211  {
212  setRank(hc.rank());
213  }
214  else
215  {
216  applyState(state);
217  if (redistribute) wrappedRavel.redistributeHandles();
218  }
219 #ifndef NDEBUG
220  if (wrappedRavel && state.empty())
221  {
222  auto d=hc.dims();
223  assert(d.size()==wrappedRavel.rank());
224  auto outputHandles=wrappedRavel.outputHandleIds();
225  for (size_t i=0; i<d.size(); ++i)
226  assert(d[i]==numSliceLabels(outputHandles[i]));
227  }
228 #endif
229  }
230 
231 
232  void Ravel::setRank(unsigned rank)
233  {
234  vector<size_t> ids;
235  for (size_t i=0; i<rank; ++i) ids.push_back(i);
236  wrappedRavel.setOutputHandleIds(ids);
237  }
238 
240  {
241  wrappedRavel.adjustSlicer(n);
244  }
245 
246  bool Ravel::onKeyPress(int keySym, const std::string& utf8, int state)
247  {
248  switch (keySym)
249  {
250  case 0xff52: case 0xff53: //Right, Up
251  adjustSlicer(1);
252  break;
253  case 0xff51: case 0xff54: //Left, Down
254  adjustSlicer(-1);
255  break;
256  default:
257  return false;
258  }
259  minsky().requestReset();
260  return true;
261  }
262 
263  void Ravel::collapseAllHandles(bool collapse)
264  {
265  auto state=getState();
266  for (auto& h: state.handleStates)
267  if (collapse && !h.collapsed)
268  {
269  h.reductionOp=m_nextReduction;
270  h.collapsed=true;
271  }
272  else if (!collapse && h.collapsed)
273  h.collapsed=false;
274  applyState(state);
275  minsky().requestReset();
276  }
277 
278 
280  {
281  const int h=wrappedRavel.selectedHandle();
282  if (h>=0)
283  {
284  auto state=wrappedRavel.getHandleState(h);
285  return state.displayFilterCaliper;
286  }
287  return false;
288  }
289 
291  {
292  const int h=wrappedRavel.selectedHandle();
293  if (h>=0)
294  wrappedRavel.displayFilterCaliper(h,x);
295  return x;
296  }
297 
298  vector<string> Ravel::allSliceLabels() const
299  {
300  return wrappedRavel.allSliceLabels(wrappedRavel.selectedHandle(),ravel::HandleSort::forward);
301  }
302 
303  vector<string> Ravel::allSliceLabelsAxis(int axis) const
304  {
305  return wrappedRavel.allSliceLabels(axis,ravel::HandleSort::forward);
306  }
307 
308  vector<string> Ravel::pickedSliceLabels(int axis) const
309  {return wrappedRavel.sliceLabels(axis);}
310 
311  vector<string> Ravel::pickedSliceLabels() const
312  {return pickedSliceLabels(wrappedRavel.selectedHandle());}
313 
314  void Ravel::pickSliceLabels(int axis, const vector<string>& pick)
315  {
316  if (axis>=0 && axis<int(numHandles()))
317  {
318  vector<size_t> customOrder, currentOrder=wrappedRavel.currentPermutation(axis);
319  auto allLabels=wrappedRavel.allSliceLabels(axis, ravel::HandleSort::none);
320  map<string,size_t> idxMap; // map index positions
321  for (size_t i=0; i<allLabels.size(); ++i)
322  idxMap[allLabels[i]]=i;
323  set<string> picked(pick.begin(), pick.end());
324  for (auto i: currentOrder)
325  {
326  auto pickedIter=picked.find(allLabels[i]);
327  if (pickedIter==picked.end()) continue;
328  picked.erase(pickedIter);
329  customOrder.push_back(i);
330  }
331  // add remaining picked labels to end of permutation
332  for (auto& i: picked)
333  {
334  auto j=idxMap.find(i);
335  if (j!=idxMap.end())
336  customOrder.push_back(j->second);
337  }
338  assert(!customOrder.empty());
339  wrappedRavel.applyCustomPermutation(axis,customOrder);
341  minsky().requestReset();
342  }
343  }
344 
345  Dimension Ravel::dimension(int handle) const
346  {
347  Dimension dim;
348  auto dimitr=cminsky().dimensions.find(handleDescription(handle));
349  if (dimitr!=cminsky().dimensions.end())
350  dim=dimitr->second;
351  return dim;
352  }
353 
354  ravel::HandleSort::Order Ravel::sortOrder() const
355  {
356  const int h=wrappedRavel.selectedHandle();
357  if (h>=0)
358  {
359  auto state=wrappedRavel.getHandleState(h);
360  return state.order;
361  }
362  return ravel::HandleSort::none;
363  }
364 
365  ravel::HandleSort::Order Ravel::setSortOrder(ravel::HandleSort::Order x)
366  {
367  setHandleSortOrder(x, wrappedRavel.selectedHandle());
368  return x;
369  }
370 
372  {
373  if (wrappedRavel.rank()==1)
374  {
375  const int outputHandleId=wrappedRavel.outputHandleIds()[0];
376  auto hs=wrappedRavel.getHandleState(outputHandleId);
377  switch (hs.order)
378  {
379  case ravel::HandleSort::dynamicForward:
380  case ravel::HandleSort::dynamicReverse:
381  {
382  auto calipers=wrappedRavel.getCaliperPositions(outputHandleId);
383  // sortByValue of the whole range of the dimension
384  wrappedRavel.displayFilterCaliper(outputHandleId,false);
385  sortByValue(hs.order);
386  wrappedRavel.displayFilterCaliper(outputHandleId,hs.displayFilterCaliper);
387  wrappedRavel.setCaliperPositions(outputHandleId,calipers.first,calipers.second);
388  }
389  break;
390  default:
391  break;
392  }
393  }
394  }
395 
396  ravel::HandleSort::Order Ravel::setHandleSortOrder(ravel::HandleSort::Order order, int handle)
397  {
398  if (handle>=0)
399  {
400  const Dimension dim=dimension(handle);
401  wrappedRavel.orderLabels(handle,order);
402  }
403  return order;
404  }
405 
407  {
408  if (wrappedRavel.rank()!=1) return false;
409  auto ids=wrappedRavel.outputHandleIds();
410  return size_t(wrappedRavel.selectedHandle())==ids[0];
411  }
412 
413  void Ravel::sortByValue(ravel::HandleSort::Order dir)
414  {
415  if (wrappedRavel.rank()!=1) return;
416  try {minsky().requestReset();} catch (...) {throw runtime_error("Cannot sort handle at the moment");}
417  auto vv=m_ports[1]->getVariableValue();
418  if (!vv)
419  throw runtime_error("Cannot sort handle at the moment");
420  wrappedRavel.sortByValue(vv, dir);
421  }
422 
423 
424  string Ravel::description() const
425  {
426  return handleDescription(wrappedRavel.selectedHandle());
427  }
428 
429  void Ravel::setDescription(const string& description)
430  {
431  wrappedRavel.setHandleDescription(wrappedRavel.selectedHandle(),description);
432  }
433 
434  Dimension::Type Ravel::dimensionType() const
435  {
436  return dimensionType(selectedHandle());
437  }
438 
439  Dimension::Type Ravel::dimensionType(int handleIndex) const
440  {
441  auto descr=handleDescription(handleIndex);
442 
443  if (auto i=axisDimensions.find(descr); i!=axisDimensions.end())
444  return i->second.type;
445 
446  if (auto i=cminsky().dimensions.find(descr); i!=cminsky().dimensions.end())
447  return i->second.type;
448 
449  return Dimension::string;
450  }
451 
452  std::string Ravel::dimensionUnitsFormat() const
453  {
455  }
456 
457  std::string Ravel::dimensionUnitsFormat(int handleIndex) const
458  {
459  auto descr=handleDescription(handleIndex);
460  if (descr.empty()) return "";
461  auto i=axisDimensions.find(descr);
462  if (i!=axisDimensions.end())
463  return i->second.units;
464  i=cminsky().dimensions.find(descr);
465  if (i!=cminsky().dimensions.end())
466  return i->second.units;
467  return "";
468  }
469 
471  void Ravel::setDimension(Dimension::Type type,const std::string& units)
472  {
474  }
475 
477  void Ravel::setDimension(int handleIndex, Dimension::Type type,const std::string& units)
478  {
479  auto descr=handleDescription(handleIndex);
480  if (descr.empty()) return;
481  auto i=cminsky().dimensions.find(descr);
482  const Dimension d{type,units};
483  if (i!=cminsky().dimensions.end())
484  {
485  if (type!=i->second.type)
486  throw error("type mismatch with global dimension");
487  }
488  else
489  {
490  minsky().dimensions[descr]=d;
492  }
493  axisDimensions[descr]=d;
494  }
495 
496 
497  void Ravel::exportAsCSV(const string& filename, bool tabular) const
498  {
499  if (!m_ports.empty())
500  if (auto vv=m_ports[0]->getVariableValue())
501  {
502  vv->exportAsCSV(filename, wrappedRavel.description(), tabular);
503  return;
504  }
505 
506  // if no variable value attached, create one
508  const TensorsFromPort tp(make_shared<EvalCommon>());
511  // TODO: add some comment lines, such as source of data
512  v.exportAsCSV(filename, wrappedRavel.description(), tabular);
513  }
514 
515  Units Ravel::units(bool check) const
516  {
517  Units inputUnits=m_ports[1]->units(check);
518  if (inputUnits.empty()) return inputUnits;
519  size_t multiplier=1;
520  // at this stage, gross up exponents by the handle size of each
521  // reduced by product handles
522  for (size_t h=0; h<numHandles(); ++h)
523  {
524  auto state=wrappedRavel.getHandleState(h);
525  if (state.collapsed && state.reductionOp==ravel::Op::prod)
526  multiplier*=numSliceLabels(h);
527  }
528  if (multiplier>1)
529  for (auto& u: inputUnits)
530  u.second*=multiplier;
531  return inputUnits;
532  }
533 
534  void Ravel::applyState(const ravel::RavelState& state)
535  {
536  if (!wrappedRavel) {
537  initState=state;
538  return;
539  }
540  auto r=wrappedRavel.radius();
541  wrappedRavel.setRavelState(state);
542  if (state.radius!=r) // only need to update bounding box if radius changes
544  }
545 
546 
547  void Ravel::displayDelayedTooltip(float xx, float yy)
548  {
549  if (wrappedRavel.rank()==0)
550  explanation="load CSV data from\ncontext menu";
551  else
552  {
553  explanation=wrappedRavel.explain(xx-x(),yy-y());
554  // line break every 5 words
555  int spCnt=0;
556  for (auto& c: explanation)
557  if (isspace(c) && ++spCnt % 5 == 0)
558  c='\n';
559  }
560  }
561 
563  {
564  if (lockGroup)
565  lockGroup->removeFromGroup(*this);
566  lockGroup.reset();
567  }
568 
570  {
571  if (lockGroup) lockGroup->broadcast(*this);
572  }
573 
574  vector<unsigned> Ravel::lockGroupColours()
575  {
576  set<unsigned> r;
577  cminsky().model->recursiveDo(&GroupItems::items, [&r](Items&, Items::iterator i) {
578  if (auto ravel=(*i)->ravelCast(); ravel && ravel->lockGroup)
579  r.insert(ravel->lockGroup->colour());
580  return false;
581  });
582  return {r.begin(),r.end()};
583  }
584 
585  void Ravel::joinLockGroup(unsigned colour)
586  {
587  cminsky().model->recursiveDo(&GroupItems::items, [this,colour](Items&, Items::iterator i) {
588  if (auto ravel=(*i)->ravelCast(); ravel && ravel->lockGroup && ravel->lockGroup->colour()==colour)
589  {
590  leaveLockGroup();
591  auto ravelPtr=dynamic_pointer_cast<Ravel>(itemPtrFromThis());
592  if (ravelPtr)
593  {
594  lockGroup=ravel->lockGroup;
595  lockGroup->addRavel(ravelPtr);
596  return true;
597  }
598  }
599  return false;
600  });
601  }
602 
604  {
605  if (!m_ravels.empty())
606  if (auto r=m_ravels.front().lock())
607  broadcast(*r);
608  }
609 
610  void RavelLockGroup::broadcast(const Ravel& ravel)
611  {
612  vector<shared_ptr<Ravel>> lockedRavels;
613  size_t ravelIdx=m_ravels.size();
614  for (auto& i: m_ravels)
615  {
616  lockedRavels.push_back(i.lock());
617  if (lockedRavels.back().get()==&ravel)
618  ravelIdx=lockedRavels.size()-1;
619  }
620  if (ravelIdx==m_ravels.size()) return; // not in lock group
621 
622  auto sourceState=ravel.getState();
623 
624  if (handleLockInfo.empty()) // default is all handles are locked
625  {
626  for (auto& i: m_ravels)
627  if (auto r=i.lock())
628  {
629  sourceState.radius=r->radius(); // preserve radius
630  r->applyState(sourceState);
631  }
632  return;
633  }
634 
635  // reorder source handle states according to handleLockInfo
636  vector<const ravel::HandleState*> sourceHandleStates;
637  set<string> handlesAdded;
638  for (auto& i: handleLockInfo)
639  {
640  auto hs=find_if(sourceState.handleStates.begin(), sourceState.handleStates.end(),
641  [&](const ravel::HandleState& hs){return hs.description==i.handleNames[ravelIdx];});
642  if (hs!=sourceState.handleStates.end())
643  {
644  sourceHandleStates.emplace_back(&*hs);
645  if (!handlesAdded.insert(hs->description).second)
646  throw runtime_error("Multiple locks found on handle "+hs->description);
647  }
648  else
649  sourceHandleStates.emplace_back(nullptr);
650  }
651 
652  assert(sourceHandleStates.size()==handleLockInfo.size());
653 
654  for (size_t ri=0; ri<m_ravels.size(); ++ri)
655  if (auto r=m_ravels[ri].lock())
656  {
657  if (r.get()==&ravel) continue;
658  auto state=r->getState();
659  set<string> outputHandles(state.outputHandles.begin(), state.outputHandles.end());
660  for (size_t i=0; i<handleLockInfo.size(); ++i)
661  {
662  if (!sourceHandleStates[i]) continue;
663  auto& sourceHandleState=*sourceHandleStates[i];
664 
665  auto& hlInfo=handleLockInfo[i];
666  auto handleState=find_if(state.handleStates.begin(), state.handleStates.end(),
667  [&](ravel::HandleState& s){return s.description==hlInfo.handleNames[ri];});
668  if (handleState!=state.handleStates.end())
669  {
670  if (hlInfo.slicer)
671  {
672  handleState->sliceLabel=sourceHandleState.sliceLabel;
673  if (find(sourceState.outputHandles.begin(), sourceState.outputHandles.end(), sourceHandleState.description)!=sourceState.outputHandles.end())
674  // is an output handle
675  outputHandles.insert(handleState->description);
676  else
677  outputHandles.erase(handleState->description);
678  }
679  if (hlInfo.orientation)
680  {
681  handleState->x=sourceHandleState.x;
682  handleState->y=sourceHandleState.y;
683  handleState->collapsed=sourceHandleState.collapsed;
684  handleState->reductionOp=sourceHandleState.reductionOp;
685  }
686  if (hlInfo.calipers)
687  {
688  handleState->displayFilterCaliper=sourceHandleState.displayFilterCaliper;
689  handleState->minLabel=sourceHandleState.minLabel;
690  handleState->maxLabel=sourceHandleState.maxLabel;
691  }
692  if (hlInfo.order)
693  {
694  handleState->order=sourceHandleState.order;
695  handleState->customOrder=sourceHandleState.customOrder;
696  }
697  }
698  }
699  // reorder output handle list according to the source ravel output order.
700  state.outputHandles.clear();
701  for (auto& i: sourceState.outputHandles)
702  {
703  auto o=outputHandles.find(i);
704  if (o!=outputHandles.end())
705  {
706  state.outputHandles.push_back(i);
707  outputHandles.erase(o);
708  }
709  }
710  // add remaining handles in order.
711  state.outputHandles.insert(state.outputHandles.end(), outputHandles.begin(), outputHandles.end());
712  r->applyState(state);
713  }
714  }
715 
717  {
718  vector<set<string>> checkHandleNames(m_ravels.size());
719  for (auto& hl: handleLockInfo)
720  {
721  if (hl.handleNames.size()!=m_ravels.size())
722  throw runtime_error("Insufficient data on line");
723  for (size_t i=0; i<hl.handleNames.size(); ++i)
724  {
725  auto& nm=hl.handleNames[i];
726  // non-breaking space 0xa0 not treated as space by isspace
727  if (find_if(nm.begin(), nm.end(), [](unsigned char i){return !isspace(i)&&i!=0xa0;})==nm.end())
728  continue; // disregard wholly white space strings
729  if (!checkHandleNames[i].insert(nm).second) // check for duplicated handle names in a column
730  throw runtime_error("duplicate handle name "+nm);
731  }
732  }
733  }
734 
735 
736  vector<string> RavelLockGroup::allLockHandles() const
737  {
738  set<string> handles;
739  for (auto& rr: m_ravels)
740  if (auto r=rr.lock())
741  {
742  auto state=r->getState();
743  for (auto& h: state.handleStates)
744  handles.insert(h.description);
745  }
746  return {handles.begin(), handles.end()};
747  }
748 
749  std::vector<std::string> RavelLockGroup::ravelNames() const
750  {
751  std::vector<std::string> r;
752  int cnt=0;
753  for (auto& i: m_ravels)
754  if (auto rr=i.lock())
755  r.emplace_back(rr->tooltip().empty()? to_string(cnt++): rr->tooltip());
756  else
757  r.emplace_back("<invalid>");
758  return r;
759  }
760 
761  std::vector<std::string> RavelLockGroup::handleNames(size_t ravel_idx) const
762  {
763  if (auto rr=m_ravels[ravel_idx].lock())
764  return rr->handleNames();
765  return {};
766  }
767 
768  void RavelLockGroup::setLockHandles(const std::vector<std::string>& handles)
769  {
770  handleLockInfo.clear();
771  std::vector<std::set<std::string>> handleNames;
772  for (auto& rp: m_ravels)
773  if (auto r=rp.lock())
774  {
775  auto names=r->handleNames();
776  handleNames.emplace_back(names.begin(), names.end());
777  }
778  else
779  handleNames.emplace_back();
780 
781  for (auto& h: handles)
782  {
783  handleLockInfo.emplace_back();
784  for (size_t i=0; i<m_ravels.size(); ++i)
785  handleLockInfo.back().handleNames.push_back(handleNames[i].contains(h)? h: "");
786  }
787  }
788 
789  void RavelLockGroup::addRavel(const std::weak_ptr<Ravel>& ravel)
790  {
791  m_ravels.push_back(ravel);
792  for (auto& i: handleLockInfo)
793  i.handleNames.resize(m_ravels.size());
794  addHandleInfo(m_ravels.back());
795  }
796 
797  void RavelLockGroup::addHandleInfo(const std::weak_ptr<Ravel>& ravel)
798  {
799  auto ravelIdx=&ravel-m_ravels.data();
800  assert(ravelIdx>=0);
801  if (ravelIdx<0 || size_t(ravelIdx)>=m_ravels.size()) return;
802  if (auto r=ravel.lock())
803  {
804  auto names=r->handleNames();
805  set<string> handleNames(names.begin(), names.end());
806  if (names.size()!=handleNames.size())
807  r->throw_error("Ambiguous handle names");
808  // add corresponding handles to what's already there
809  for (auto& hli: handleLockInfo)
810  {
811  assert(hli.handleNames.size()==m_ravels.size());
812  const set<string> lockNames(hli.handleNames.begin(), hli.handleNames.end());
813  for (auto& l: lockNames)
814  {
815  auto hn=handleNames.find(l);
816  if (hn!=handleNames.end())
817  {
818  hli.handleNames[ravelIdx]=l;
819  handleNames.erase(hn);
820  }
821  }
822  }
823  // now add in any extras
824  for (auto& h: handleNames)
825  {
826  handleLockInfo.emplace_back();
827  handleLockInfo.back().handleNames.resize(m_ravels.size());
828  handleLockInfo.back().handleNames[ravelIdx]=h;
829  }
830  }
831  }
832 
834  {
835  auto found=find_if(m_ravels.begin(), m_ravels.end(),
836  [&](const weak_ptr<Ravel>& i){
837  if (auto r=i.lock())
838  return r.get()==&ravel;
839  return false;
840  });
841  if (found==m_ravels.end()) return;
842  for (auto& i: handleLockInfo)
843  i.handleNames.erase(i.handleNames.begin()+(found-m_ravels.begin()));
844  m_ravels.erase(found);
845  if (m_ravels.size()==1)
846  if (auto r=m_ravels[0].lock())
847  r->lockGroup.reset(); // this may delete this, so should be last
848  }
849 
850  bool RavelPopup::redraw(int x0, int y0, int width, int height)
851  {
852  if (!surface.get()) return false;
853  this->width=width; this->height=height;
854  const ecolab::cairo::CairoSave cs(surface->cairo());
855  cairo_translate(surface->cairo(),0.5*width,0.5*height);
856  auto z=0.4*min(width,height)/ravel.wrappedRavel.radius();
857  scale=1/z;
858  cairo_scale(surface->cairo(), z,z);
859  CairoRenderer cr(surface->cairo());
860  ravel.wrappedRavel.render(cr);
861  return true;
862  }
863 
864  float RavelPopup::localX(float x) const
865  {return scale*(x-0.5*width);}
866  float RavelPopup::localY(float y) const
867  {return scale*(y-0.5*height);}
868 
869  void RavelPopup::mouseDown(float x, float y) {
870  ravel.wrappedRavel.onMouseDown(localX(x),localY(y));
871  requestRedraw();
872  }
873  void RavelPopup::mouseUp(float x, float y) {
874  ravel.wrappedRavel.onMouseUp(localX(x),localY(y));
875  requestRedraw();
876  }
877  void RavelPopup::mouseMove(float x, float y) {
878  ravel.wrappedRavel.onMouseMotion(localX(x),localY(y));
879  requestRedraw();
880  }
881  void RavelPopup::mouseOver(float x, float y) {
882  ravel.wrappedRavel.onMouseOver(localX(x),localY(y));
884  minsky().requestReset();
885  requestRedraw();
886  }
888  ravel.wrappedRavel.onMouseLeave();
889  requestRedraw();
890  }
891 
893  {
894  auto r=ravel.onKeyPress(args.keySym,args.utf8,args.state);
895  if (r) requestRedraw();
896  return r;
897  }
898 
899 }
900 
901 
903 CLASSDESC_ACCESS_EXPLICIT_INSTANTIATION(ravel::RavelState);
#define M_PI
some useful geometry types, defined from boost::geometry
Definition: geometry.h:29
void collapseAllHandles(bool collapse=true)
collapse all handles (applying nextReduction op where appropriate)
Definition: ravelWrap.cc:263
std::vector< std::string > handleNames(size_t ravel_idx) const
return the handle descriptions of of ravel ravel_idx in ravels
Definition: ravelWrap.cc:761
int selectedHandle() const
current handle mouse is over, or -1 if none
Definition: ravelWrap.h:134
void setDescription(const std::string &)
Definition: ravelWrap.cc:429
void addRavel(const std::weak_ptr< Ravel > &ravel)
Definition: ravelWrap.cc:789
void mouseUp(float x, float y) override
Definition: ravelWrap.cc:873
void leaveLockGroup()
Definition: ravelWrap.cc:562
void pickSliceLabels(int axis, const std::vector< std::string > &pick)
pick (selected) pick labels
Definition: ravelWrap.cc:314
void setLockHandles(const std::vector< std::string > &handles)
set handlesToLock to the handles in handles
Definition: ravelWrap.cc:768
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...
void onMouseDown(float x, float y) override
respond to mouse down events
Definition: ravelWrap.cc:156
void drawPorts(cairo_t *cairo) const
Definition: item.cc:294
std::vector< std::weak_ptr< Ravel > > m_ravels
Definition: ravelWrap.h:229
void mouseMove(float x, float y) override
Definition: ravelWrap.cc:877
bool handleSortableByValue() const
Definition: ravelWrap.cc:406
const Hypercube & hypercube() const override
void requestReset()
Definition: minsky.cc:466
bool inItem(float x, float y) const override
Definition: ravelWrap.cc:146
ravel::RavelState getState() const
get the current state of the Ravel
Definition: ravelWrap.h:206
virtual void displayTooltip(cairo_t *, const std::string &) const
display tooltip text, eg on mouseover
Definition: item.cc:398
std::vector< std::string > dimensions() const
return dimension names of tensor object attached to input if binary op, then the union of dimension n...
Definition: operation.cc:348
static std::vector< double, CIVITA_ALLOCATOR< double > > stockVars
vector of variables that are integrated via Runge-Kutta. These variables label the columns of the God...
void setDimension(Dimension::Type type, const std::string &units)
Definition: ravelWrap.cc:471
bool flipped
Definition: ravelWrap.h:85
shared_ptr< EvalCommon > ev
virtual float x() const
Definition: item.cc:107
void adjustSlicer(int)
adjust currently selected handle&#39;s slicer
Definition: ravelWrap.cc:239
void onMouseUp(float x, float y) override
respond to mouse up events
Definition: ravelWrap.cc:162
virtual float y() const
Definition: item.cc:114
float localX(float x) const
Definition: ravelWrap.cc:864
void applyState(const ravel::RavelState &state)
apply the state to the Ravel, leaving data, slicelabels etc unchanged
Definition: ravelWrap.cc:534
represents rectangular region of a lasso operation
Definition: lasso.h:28
STL namespace.
void broadcastStateToLockGroup() const
Definition: ravelWrap.cc:569
std::vector< std::string > allSliceLabels() const
returns all slice labels along the selected handle, in specified order
Definition: ravelWrap.cc:298
ravel::HandleSort::Order HandleSort
Definition: ravelWrap.cc:60
static SVGRenderer svgRenderer
SVG icon to display when not in editor mode.
Definition: ravelWrap.h:83
std::shared_ptr< Item > ItemPtr
Definition: item.h:55
void mouseOver(float x, float y)
Definition: ravelWrap.cc:881
ravel::RavelState initState
used entirely to defer persisted state data until after first load from a variable ...
Definition: ravelWrap.h:73
void addHandleInfo(const std::weak_ptr< Ravel > &ravel)
add ravel&#39;s handles to handleLockInfo, for a ravel stashed in m_ravels
Definition: ravelWrap.cc:797
ravel::HandleSort::Order setSortOrder(ravel::HandleSort::Order)
the handle sorting order for currently selected handle
Definition: ravelWrap.cc:365
Dimension dimension(int handle) const
dimension details associated with handle
Definition: ravelWrap.cc:345
std::vector< std::string > pickedSliceLabels() const
returns just the picked slice labels along the handle
Definition: ravelWrap.cc:311
void sortByValue(ravel::HandleSort::Order dir)
Sort handle by value. Only applicable for rank 1 ravels.
Definition: ravelWrap.cc:413
Hypercube hypercube() const
return hypercube corresponding to the current Ravel state
Definition: ravelWrap.cc:181
bool onMouseMotion(float x, float y) override
respond to mouse motion events with button pressed
Definition: ravelWrap.cc:169
BoundingBox bb
canvas bounding box.
Definition: item.h:192
virtual std::string const & tooltip() const
Definition: noteBase.h:36
std::shared_ptr< ITensor > create(const ItemPtr &, const TensorsFromPort &tp={})
create a tensor representation of the expression rooted at op. If expression doesn&#39;t contain any refe...
ravel::HandleSort::Order sortOrder() const
the handle sorting order for currently selected handle
Definition: ravelWrap.cc:354
virtual float zoomFactor() const
Definition: item.cc:121
void update(const Item &x)
Definition: item.cc:46
void broadcast(const Ravel &ravel)
broadcast state from ravel to the lock group
Definition: ravelWrap.cc:610
void removeFromGroup(const Ravel &)
Definition: ravelWrap.cc:833
void joinLockGroup(unsigned)
Definition: ravelWrap.cc:585
Dimension::Type dimensionType() const
get/set dimension attributes of selected handle, or handle at given index
Definition: ravelWrap.cc:434
static void drawSelected(cairo_t *cairo)
Definition: item.cc:308
std::vector< ItemPtr > Items
Definition: item.h:360
bool onKeyPress(int, const std::string &, int) override
respond to key press events
Definition: ravelWrap.cc:246
bool m_editorMode
indicate whether icon is in editor mode or icon mode
Definition: ravelWrap.h:66
bool displayFilterCaliper() const
enable/disable calipers on currently selected handle
Definition: ravelWrap.cc:279
std::string description() const
Definition: ravelWrap.cc:424
static constexpr float h
Definition: operationBase.h:53
represents the units (in sense of dimensional analysis) of a variable.
Definition: units.h:34
ItemPtr itemPtrFromThis() const
return a shared_ptr to this
Definition: item.cc:447
std::vector< std::string > allSliceLabelsAxis(int axis) const
returns all slice labels along an axis(dimension) identified by its number
Definition: ravelWrap.cc:303
std::vector< std::string > allLockHandles() const
populate handlesToLock by all handles present in the lock group
Definition: ravelWrap.cc:736
bool onBorder
true to indicate mouse hovering over border
Definition: item.h:173
bool setDisplayFilterCaliper(bool x)
Definition: ravelWrap.cc:290
const Minsky & cminsky()
const version to help in const correctness
Definition: minsky.h:538
std::vector< std::string > ravelNames() const
return tooltips of the ravels in this lockGroup
Definition: ravelWrap.cc:749
void validateLockHandleInfo()
checks handleLockInfo for non repeated handles, etc
Definition: ravelWrap.cc:716
void mouseDown(float x, float y) override
Definition: ravelWrap.cc:869
bool redraw(int x0, int y0, int width, int height) override
Definition: ravelWrap.cc:850
std::string handleDescription(int handle) const
return the description field for handle handle.
Definition: ravelWrap.h:189
std::shared_ptr< RavelLockGroup > lockGroup
group of ravels that move syncronously
Definition: ravelWrap.h:103
void draw(cairo_t *cairo) const override
draw this item into a cairo context
Definition: ravelWrap.cc:80
ItemPortVector m_ports
Definition: item.h:154
std::string axis
axis selector in tensor operations
Definition: operationBase.h:90
bool selected
true if selected for cut, copy or group operation
Definition: noteBase.h:32
CLASSDESC_ACCESS_EXPLICIT_INSTANTIATION(minsky::Ravel)
ravel::HandleSort::Order setHandleSortOrder(ravel::HandleSort::Order, int handle)
set a given handle sort order
Definition: ravelWrap.cc:396
static std::vector< unsigned > lockGroupColours()
Definition: ravelWrap.cc:574
void displayDelayedTooltip(float x, float y) override
enable extended tooltip help message appropriate for mouse at (x,y)
Definition: ravelWrap.cc:547
static std::vector< double, CIVITA_ALLOCATOR< double > > flowVars
variables defined as a simple function of the stock variables, also known as lhs variables. These variables appear in the body of the Godley table
size_t numSliceLabels(size_t axis) const
number of slice labels along axis axis
Definition: ravelWrap.h:122
ravel::Op::ReductionOp m_nextReduction
Definition: ravelWrap.h:81
float x0
Definition: lasso.h:30
std::vector< HandleLockInfo > handleLockInfo
Definition: ravelWrap.h:240
bool mouseFocus
true if target of a mouseover
Definition: noteBase.h:31
TensorOpFactory tensorOpFactory
float y1
Definition: lasso.h:30
float x1
Definition: lasso.h:30
void drawTriangle(cairo_t *cairo, double x, double y, const ecolab::cairo::Colour &col, double angle=0)
void moveTo(float x, float y)
Definition: item.cc:256
void updateBoundingBox() override
Definition: item.h:198
bool keyPress(const EventInterface::KeyPressArgs &) override
handle key press over current itemFocus,
Definition: ravelWrap.cc:892
void initialBroadcast()
broadcast first ravel&#39;s state to the remainder
Definition: ravelWrap.cc:603
static constexpr float r
Definition: operationBase.h:53
std::string explanation
Definition: ravelWrap.h:68
GroupPtr model
Definition: minsky.h:242
float y0
Definition: lasso.h:30
void populateHypercube(const Hypercube &)
Definition: ravelWrap.cc:203
void exportAsCSV(const std::string &filename, bool tabular) const
export the plotted data as a CSV file
Definition: ravelWrap.cc:497
Minsky & minsky()
global minsky object
Definition: addon.cc:545
void resize(const LassoBox &) override
resize this item on the canvas
Definition: ravelWrap.cc:139
float localY(float y) const
Definition: ravelWrap.cc:866
void imposeDimensions()
Definition: minsky.cc:364
ravel::Op::ReductionOp ReductionOp
Definition: ravelWrap.cc:59
Dimensions axisDimensions
local override of axis dimensionality
Definition: ravelWrap.h:100
Units units(bool) const override
compute the dimensional units
Definition: ravelWrap.cc:515
unsigned numHandles() const
Definition: ravelWrap.h:120
void drawResizeHandles(cairo_t *cairo) const override
Definition: item.cc:355
void resortHandleIfDynamic()
Definition: ravelWrap.cc:371
bool onMouseOver(float x, float y) override
respond to mouse motion events (hover) without button pressed
Definition: ravelWrap.cc:175
void setRank(unsigned r)
adjust output dimensions to first r handles
Definition: ravelWrap.cc:232
std::string dimensionUnitsFormat() const
get/set dimension attributes of selected handle, or handle at given index
Definition: ravelWrap.cc:452
Dimensions dimensions
Definition: minsky.h:189
void render(cairo_t *, double width, double height) const
render SVG into region of size width height
Definition: SVGItem.cc:84
virtual std::string const & detailedText() const
Definition: noteBase.h:34
ravelCAPI::Ravel wrappedRavel
Definition: ravelWrap.h:80