Minsky
phillipsDiagram.cc
Go to the documentation of this file.
1 /*
2  @copyright Steve Keen 2023
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 "cairoItems.h"
21 #include "phillipsDiagram.h"
22 #include "phillipsDiagram.rcd"
23 #include "phillipsDiagram.xcd"
24 #include "minsky.h"
25 #include "minsky_epilogue.h"
26 using ecolab::cairo::CairoSave;
27 
28 namespace minsky
29 {
30  std::map<Units, double> PhillipsFlow::maxFlow;
31  std::map<Units, double> PhillipsStock::maxStock;
32 
33  void PhillipsFlow::draw(cairo_t* cairo)
34  {
35  const CairoSave cs(cairo);
36  const double value=this->value();
37  double& maxV=maxFlow[units()];
38  if (abs(value)>maxV) maxV=abs(value);
39  double lineWidth=1;
40  if (maxV>0)
41  {
42  const double lw=5*abs(value)/maxV;
43  lineWidth=std::max(1.0, lw);
44  static const double dashLength=3;
45  if (lw<1)
46  cairo_set_dash(cairo,&dashLength,1,0);
47  }
48  cairo_set_line_width(cairo, lineWidth);
49  Wire::draw(cairo,value>=0);
50  }
51 
52  void PhillipsStock::draw(cairo_t* cairo) const
53  {
54  StockVar::draw(cairo);
55  // colocate input and output ports on the input side
56  m_ports[0]->moveTo(m_ports[1]->x(), m_ports[1]->y());
57  auto maxV=maxStock[units()];
58  if (maxV>0)
59  {
60  const CairoSave cs(cairo);
61  auto w=width()*zoomFactor();
62  auto h=height()*zoomFactor();
63  auto f=value()/maxV;
64  if (f>=0)
65  {
66  cairo_set_source_rgba(cairo,0,0,1,0.3);
67  cairo_rectangle(cairo,-0.6*w,0.5*h,1.2*w,-f*h);
68  }
69  else
70  {
71  cairo_set_source_rgba(cairo,1,0,0,0.3);
72  cairo_rectangle(cairo,-0.6*w,-0.5*h,1.2*w,-f*h);
73  }
74  cairo_fill(cairo);
75  }
76  }
77 
78 
79  bool PhillipsDiagram::redraw(int, int, int width, int height)
80  {
81  if (!surface.get()) return false;
82  auto cairo=surface->cairo();
83  const CairoSave cs(cairo);
84  cairo_translate(cairo,x,y);
85  for (auto& i: stocks)
86  {
87  const CairoSave cs(cairo);
88  cairo_identity_matrix(cairo);
89  cairo_translate(cairo,i.second.x()+x, i.second.y()+y);
90  i.second.draw(cairo);
91  }
92  for (auto& i: flows)
93  i.second.draw(cairo);
94  return true;
95  }
96 
98  {
99  decltype(stocks) newStocks;
100  decltype(flows) newFlows;
101  cminsky().model->recursiveDo
102  (&GroupItems::items, [&](const Items&,Items::const_iterator it) {
103  if (auto g=dynamic_cast<GodleyIcon*>(it->get())) {
104  for (auto& v: g->stockVars())
105  {
106  auto newStock=newStocks.emplace(v->valueId(), static_cast<Variable<VariableType::stock>&>(*v)).first;
107  auto oldStock=stocks.find(v->valueId());
108  if (oldStock!=stocks.end())
109  {
110  newStock->second.moveTo(oldStock->second.x(), oldStock->second.y());
111  newStock->second.rotation(oldStock->second.rotation());
112  }
113  }
114  for (unsigned r=1; r<g->table.rows(); ++r) {
115  if (g->table.initialConditionRow(r)) continue;
116  map<string, vector<pair<FlowCoef, string>>> sources, destinations;
117  for (size_t c=1; c<g->table.cols(); c++)
118  {
119  const FlowCoef fc(g->table.cell(r,c));
120  if (fc.coef)
121  {
122  auto payload=make_pair(FlowCoef(fc.coef,g->table.cell(0,c)),g->table.cell(r,0));
123  if ((fc.coef>0 && !g->table.signConventionReversed(c))
124  || (fc.coef<0 && g->table.signConventionReversed(c)))
125  sources[fc.name].emplace_back(payload);
126  else
127  destinations[fc.name].emplace_back(payload);
128  }
129  }
130  for (auto& i: sources)
131  for (auto& [s,sd]: i.second)
132  for (auto& [d,description]: destinations[i.first])
133  {
134  auto ss=s, dd=d;
135  if (s.coef*d.coef<0) swap(ss,dd);
136  auto& source=newStocks[g->valueId(ss.name)];
137  auto& dest=newStocks[g->valueId(dd.name)];
138  auto flow=newFlows.emplace(make_pair(g->valueId(dd.name),g->valueId(ss.name)), PhillipsFlow(dest.ports(0), source.ports(1))).first;
139  flow->second.addTerm(abs(s.coef*d.coef), i.first);
140  flow->second.tooltip((!flow->second.tooltip().empty()?";":"")+description);
141  if (auto oldFlow=flows.find(flow->first); oldFlow!=flows.end())
142  flow->second.coords(oldFlow->second.coords());
143  }
144  }
145  }
146  return false;
147  });
148 
149  // now layout the diagram
150  if (newStocks.empty())
151  {
152  stocks.clear();
153  flows.clear();
154  minsky().pushHistory();
155  return;
156  }
157 
158  double angle=0, delta=2*M_PI/newStocks.size();
159 
160 
161  auto h=newStocks.begin()->second.height();
162  auto maxW=newStocks.begin()->second.width();
163  for (auto& i: newStocks) maxW=max(maxW,i.second.width());
164  // calculate radius to ensure vars do not overlap
165  auto r=h/delta + 0.5*maxW;
166 
167  for (auto& i: newStocks)
168  {
169  if (!stocks.contains(i.first))
170  {
171  i.second.moveTo(r*(cos(angle)+1)+maxW+50,r*(sin(angle)+1)+maxW+50);
172  i.second.rotation(angle*180.0/M_PI);
173  }
174  angle+=delta;
175  }
176 
177  flows.swap(newFlows);
178  stocks.swap(newStocks);
179  minsky().pushHistory();
180  }
181 
182  void PhillipsDiagram::mouseDown(float x, float y)
183  {
184  if (stockBeingRotated) return;
185  x-=this->x; y-=this->y;
186  for (auto& i: stocks)
187  if (i.second.contains(x,y))
188  {
189  stockBeingMoved=&i.second;
190  return;
191  }
192 
193  for (auto& i: flows)
194  if (i.second.near(x,y))
195  {
196  flowBeingEdited=&i.second;
197  i.second.mouseFocus=true;
198  handleSelected=i.second.nearestHandle(x,y);
199  return;
200  }
201  }
202 
203  void PhillipsDiagram::mouseUp(float x, float y)
204  {
205  mouseMove(x,y);
206  minsky().pushHistory();
207  stockBeingMoved=nullptr;
208  stockBeingRotated=nullptr;
209  flowBeingEdited=nullptr;
210  }
211 
212  void PhillipsDiagram::mouseMove(float x, float y)
213  {
214  x-=this->x; y-=this->y;
215  if (stockBeingRotated)
216  {
218  requestRedraw();
219  return;
220  }
221  if (stockBeingMoved)
222  {
224  requestRedraw();
225  return;
226  }
227  if (flowBeingEdited)
228  {
230  requestRedraw();
231  return;
232  }
233  for (auto& i: flows)
234  {
235  const bool mf=i.second.near(x,y);
236  if (mf!=i.second.mouseFocus)
237  {
238  i.second.mouseFocus=mf;
239  requestRedraw();
240  }
241  }
242  }
243 
244  void PhillipsDiagram::startRotatingItem(float x, float y)
245  {
246  x-=this->x; y-=this->y;
247  for (auto& i: stocks)
248  if (i.second.contains(x,y))
249  {
250  stockBeingRotated=&i.second;
252  requestRedraw();
253  return;
254  }
255  }
256 
257 
258 }
#define M_PI
some useful geometry types, defined from boost::geometry
Definition: geometry.h:29
function f
Definition: canvas.m:1
PhillipsFlow * flowBeingEdited
Units units() const
Definition: variable.h:195
Expr sin(const Expr &x)
Definition: expr.h:131
Expr cos(const Expr &x)
Definition: expr.h:137
virtual float x() const
Definition: item.cc:107
bool pushHistory()
push current model state onto history if it differs from previous
Definition: minsky.cc:1265
virtual float y() const
Definition: item.cc:114
void draw(cairo_t *cairo) const override
draw this item into a cairo context
virtual double value() const override
< set the initial value for this variable
Definition: variable.cc:332
PhillipsStock * stockBeingRotated
static std::map< Units, double > maxFlow
static std::map< Units, double > maxStock
void mouseMove(float x, float y) override
float zoomFactor() const override
Definition: variable.cc:150
Creation and access to the minskyTCL_obj object, which has code to record whenever Minsky&#39;s state cha...
Definition: constMap.h:22
float width() const
Definition: item.h:242
bool redraw(int, int, int width, int height) override
std::vector< ItemPtr > Items
Definition: item.h:366
void draw(cairo_t *)
void mouseDown(float x, float y) override
std::map< std::pair< std::string, std::string >, PhillipsFlow > flows
std::map< std::string, PhillipsStock > stocks
boost::geometry::model::d2::point_xy< float > Point
Definition: geometry.h:34
const Minsky & cminsky()
const version to help in const correctness
Definition: minsky.h:549
double value() const
ItemPortVector m_ports
Definition: item.h:156
represents a numerical coefficient times a variable (a "flow")
Definition: flowCoef.h:27
void init() override
populate phillips diagram from Godley tables in model
PhillipsStock * stockBeingMoved
float height() const
Definition: item.h:243
void startRotatingItem(float x, float y)
bool mouseFocus
true if target of a mouseover
Definition: noteBase.h:31
Exclude< Point > rotateOrigin
void draw(cairo_t *cairo, bool reverseArrow=false) const
draw this item into a cairo context
Definition: wire.cc:374
void moveTo(float x, float y)
Definition: item.cc:256
CLASSDESC_ACCESS_EXPLICIT_INSTANTIATION(minsky::PhillipsDiagram)
void mouseUp(float x, float y) override
GroupPtr model
Definition: minsky.h:255
void draw(cairo_t *) const override
Definition: variable.cc:704
Minsky & minsky()
global minsky object
Definition: minskyTCL.cc:51
void editHandle(unsigned position, float x, float y)
Definition: wire.cc:674
float y
position for panning