26 #include <cairo/cairo-ps.h> 27 #include <cairo/cairo-pdf.h> 28 #include <cairo/cairo-svg.h> 31 #include "plotOptions.rcd" 34 #include "tensorInterface.rcd" 35 #include "tensorInterface.xcd" 36 #include "tensorVal.rcd" 37 #include "tensorVal.xcd" 38 #include "plotWidget.rcd" 46 using namespace boost;
57 const float boundX[PlotWidget::nBoundsPorts]={-0.46,0.45,-0.49,-0.49, 0.48, 0.48};
59 const float boundY[PlotWidget::nBoundsPorts]={0.49,0.49,0.47,-0.49, 0.47, -0.49};
66 PlotWidget::PlotWidget()
74 legendSide=boundingBox;
77 yvars.resize(2*m_numLines);
78 xvars.resize(m_numLines);
81 void PlotWidget::addPorts()
85 for (; i<nBoundsPorts; ++i)
86 m_ports.emplace_back(make_shared<InputPort>(*
this));
87 for (; i<2*m_numLines+nBoundsPorts; ++i)
88 m_ports.emplace_back(make_shared<MultiWireInputPort>(*
this));
89 for (; i<4*m_numLines+nBoundsPorts; ++i)
90 m_ports.emplace_back(make_shared<InputPort>(*
this));
93 void PlotWidget::draw(cairo_t* cairo)
const 96 const double z=Item::zoomFactor();
97 cairo_scale(cairo,z,z);
98 const double w=iWidth();
102 if (!title.empty()||!xlabel().empty()||!ylabel().empty()||!y1label().empty())
104 cairo_rectangle(cairo,-0.5*w+10,-0.5*h,w-20,h-10);
105 cairo_set_line_width(cairo,1);
109 cairo_translate(cairo,-0.5*w,-0.5*h);
114 const CairoSave cs(cairo);
118 pango.setFontSize(fabs(fy));
120 cairo_set_source_rgb(cairo,0,0,0);
121 cairo_move_to(cairo,0.5*(w-pango.width()), 0);
125 yoffs=pango.height();
133 for (; i<nBoundsPorts; ++i)
136 if (!justDataChanged)
137 m_ports[i]->moveTo(x*z + this->x(), y*z + this->y()+0.5*yoffs);
138 drawTriangle(cairo, x+0.5*w, y+0.5*h+yoffs, palette[(i/2)%palette.size()].colour,
orient[i]);
142 const float xLeft = -0.5*w, dx=w/(2*m_numLines+1);
143 const float dy = h/m_numLines;
145 for (; i<m_numLines+nBoundsPorts; ++i)
147 const float y=0.5*(dy-h) + (i-nBoundsPorts)*dy;
148 if (!justDataChanged)
149 m_ports[i]->moveTo(xLeft*z + this->x(), y*z + this->y()+0.5*yoffs);
150 drawTriangle(cairo, xLeft+0.5*w, y+0.5*h+yoffs, palette[(i-nBoundsPorts)%palette.size()].colour, 0);
154 for (; i<2*m_numLines+nBoundsPorts; ++i)
156 const float y=0.5*(dy-h) + (i-m_numLines-nBoundsPorts)*dy, x=0.5*w;
157 if (!justDataChanged)
158 m_ports[i]->moveTo(x*z + this->x(), y*z + this->y()+0.5*yoffs);
159 drawTriangle(cairo, x+0.5*w, y+0.5*h+yoffs, palette[(i-nBoundsPorts)%palette.size()].colour,
M_PI);
163 const float yBottom=0.5*h;
164 for (; i<4*m_numLines+nBoundsPorts; ++i)
166 const float x=dx-0.5*w + (i-2*m_numLines-nBoundsPorts)*dx;
167 if (!justDataChanged)
168 m_ports[i]->moveTo(x*z + this->x(), yBottom*z + this->y()+0.5*yoffs);
169 drawTriangle(cairo, x+0.5*w, yBottom+0.5*h+yoffs, palette[(i-2*m_numLines-nBoundsPorts)%palette.size()].colour, -0.5*
M_PI);
172 cairo_translate(cairo, portSpace, yoffs);
173 cairo_set_line_width(cairo,1);
174 const double gw=w-2*portSpace, gh=h-portSpace;
176 Plot::draw(cairo,gw,gh);
177 if (mouseFocus && legend)
179 double width,height,x,y;
180 legendSize(x,y,width,height,gw,gh);
183 const double arrowLength=6;
184 y=(h-portSpace)-y+0.5*height;
185 cairo_move_to(cairo,x-arrowLength,y);
186 cairo_rel_line_to(cairo,2*arrowLength,0);
187 cairo_move_to(cairo,x,y-arrowLength);
188 cairo_rel_line_to(cairo,0,2*arrowLength);
190 cairo_move_to(cairo,x,y+0.5*height);
191 cairo_rel_line_to(cairo,0,arrowLength);
199 cairo_rectangle(cairo,x-0.5*width,y-0.5*height,width,height);
205 displayTooltip(cairo,tooltip());
207 drawResizeHandles(cairo);
209 justDataChanged=
false;
211 cairo_new_path(cairo);
212 cairo_rectangle(cairo,-0.5*w,-0.5*h,w,h);
214 if (selected) drawSelected(cairo);
218 void PlotWidget::scalePlot()
222 if (xminVar && xminVar->idx()>-1)
224 if (xIsSecsSinceEpoch && (xminVar->units.empty() || xminVar->units==
Units(
"year")))
227 minx=xminVar->value();
237 if (xmaxVar && xmaxVar->idx()>-1)
239 if (xIsSecsSinceEpoch && (xmaxVar->units.empty() || xmaxVar->units==
Units(
"year")))
242 maxx=xmaxVar->value();
252 if (yminVar && yminVar->idx()>-1)
253 miny=yminVar->value();
256 if (ymaxVar && ymaxVar->idx()>-1)
257 maxy=ymaxVar->value();
261 if (y1minVar && y1minVar->idx()>-1)
262 miny1=y1minVar->value();
265 if (y1maxVar && y1maxVar->idx()>-1)
266 maxy1=y1maxVar->value();
271 if (!justDataChanged)
280 assert(m_ports.size()>=2*m_numLines+nBoundsPorts);
281 for (
auto portNo=nBoundsPorts; portNo<2*m_numLines+nBoundsPorts; ++portNo)
283 if (portNo<m_ports.size())
284 for (
size_t i=0; i<m_ports[portNo]->wires().size(); ++i, ++pen)
286 auto wire=m_ports[portNo]->wires()[i];
287 if (!
wire->tooltip().empty())
289 labelPen(pen,
wire->tooltip());
292 if (
auto from=
wire->from(); !from->item().tooltip().empty())
294 labelPen(pen, from->item().tooltip());
297 if (portNo-nBoundsPorts<yvars.size() && i<yvars[portNo-nBoundsPorts].size())
298 if (
auto v=yvars[portNo-nBoundsPorts][i]; !v->name.empty())
299 labelPen(pen,
uqName(v->name));
310 const double z=Item::zoomFactor();
311 const double gw=iWidth()*z-2*portSpace;
312 double gh=iHeight()*z-portSpace;
313 if (!title.empty()) gh=iHeight()*z-portSpace-
titleHeight;
314 oldLegendLeft=legendLeft*gw+portSpace;
315 oldLegendTop=legendTop*gh;
316 oldLegendFontSz=legendFontSz;
319 void PlotWidget::mouseMove(
float x,
float y)
321 const double z=Item::zoomFactor();
324 const double gw=iWidth()*z-2*portSpace;
325 double gh=iHeight()*z-portSpace;
326 if (!title.empty()) gh=iHeight()*z-portSpace-
titleHeight;
327 const double yoffs=this->y()-(legendTop-0.5)*iHeight()*z;
330 case ClickType::legendMove:
331 legendLeft = (oldLegendLeft + x - clickX-portSpace)/gw;
332 legendTop = (oldLegendTop + clickY - y)/gh;
333 if (!title.empty()) legendTop = (oldLegendTop + clickY - y +
titleHeight)/gh;
335 case ClickType::legendResize:
336 legendFontSz = oldLegendFontSz * (y-yoffs)/(clickY-yoffs);
337 if (!title.empty()) legendFontSz = oldLegendFontSz * (y-yoffs+
titleHeight)/(clickY-yoffs);
342 if (Plot::mouseMove((x-
f.offsetLeft)/
f.childWidth, (
f.childHeight-
f.offsetTop-y)/
f.childHeight,
343 10.0/std::max(
f.childWidth,
f.childHeight)))
350 bool PlotWidget::onMouseOver(
float x,
float y)
352 const double z=Item::zoomFactor();
354 const double dx=x-this->x()+0.5*iWidth()*z-portSpace;
355 const double dy=this->y()-y+0.5*iHeight()*z-portSpace;
356 const double gw=iWidth()*z-2*portSpace;
357 const double gh=iHeight()*z-portSpace-yoffs*z;
358 const double loffx=lh(gw,gh)*!Plot::ylabel.empty(), loffy=lh(gw,gh)*!Plot::xlabel.empty();
359 return Plot::mouseMove((dx-loffx)/gw, (dy-loffy)/gh, 10.0/std::max(gw,gh),formatter);
363 void PlotWidget::requestRedraw()
365 justDataChanged=
true;
369 surface->requestRedraw();
372 bool PlotWidget::redraw(
int x0,
int y0,
int width,
int height)
376 auto sf=RenderNativeWindow::scaleFactor();
377 Plot::draw(surface->cairo(),width/sf,height/sf);
380 return surface.get();
384 void PlotWidget::makeDisplayPlot() {
385 if (
auto g=
group.lock())
386 g->displayPlot=dynamic_pointer_cast<PlotWidget>(g->findItem(*
this));
391 const float invZ=1/Item::zoomFactor();
392 iWidth(abs(x.
x1-x.
x0)*invZ);
393 iHeight(abs(x.
y1-x.
y0)*invZ);
394 Item::moveTo(0.5*(x.
x0+x.
x1), 0.5*(x.
y0+x.
y1));
402 const double z=Item::zoomFactor();
403 for (
auto& p: m_ports)
406 return ClickType::onPort;
409 double legendWidth, legendHeight, lx, ly;
410 legendSize(lx, ly, legendWidth, legendHeight, z*(iWidth()-2*portSpace), z*(iHeight()-portSpace-yoffs));
412 const double xx= x-this->x() + z*(0.5*iWidth()-portSpace) - lx;
413 const double yy= z*(0.5*iHeight()-portSpace)-y+this->y() - ly+legendHeight;
415 if (legend && xx>0 && xx<legendWidth)
417 if (yy>0.2*legendHeight && yy<legendHeight)
418 return ClickType::legendMove;
419 if (yy>-6*z && yy<=0.2*legendHeight)
420 return ClickType::legendResize;
423 if (onResizeHandle(x,y))
return ClickType::onResize;
425 const double dx=x-this->x(), dy=y-this->y();
426 const double w=0.5*iWidth()*z, h=0.5*iHeight()*z;
427 return (abs(dx)<w && abs(dy)<h)?
428 ClickType::onItem: ClickType::outside;
435 size_t PlotWidget::numLines(
size_t n)
446 double PlotWidget::barWidth()
const 448 return accumulate(palette.begin(), palette.end(), 1.0,
449 [](
double acc,
const LineStyle& ls) {
return std::min(acc, ls.barWidth);});
452 double PlotWidget::barWidth(
double w)
454 for (
auto& ls: palette) ls.barWidth=w;
458 void PlotWidget::addPlotPt(
double t)
461 for (
size_t port=0; port<yvars.size(); ++port)
462 for (
auto& yvar: yvars[port])
466 switch (xvars.size())
476 assert(xvars[0] && xvars[0]->idx()>=0);
477 if (xvars[0]->size()>1)
478 throw_error(
"Tensor valued x inputs not supported");
483 if (port < xvars.size() && xvars[port] && xvars[port]->idx()>=0)
485 if (xvars[port]->size()>1)
486 throw_error(
"Tensor valued x inputs not supported");
491 throw_error(
"x input not wired for port "+
to_string(port+1));
500 for (
auto& m: horizontalMarkers)
503 auto eps=1e-4*(maxx-minx);
504 double x[]{minx+eps,miny-eps};
505 double y[]{v->value(),v->value()};
507 assignSide(pen,marker);
508 labelPen(pen++,v->tooltip);
510 for (
auto& m: verticalMarkers)
513 auto eps=1e-4*(maxy-miny);
514 auto value=v->value();
515 if (xIsSecsSinceEpoch && (v->units.empty() || v->units==
Units(
"year")))
517 double x[]{value,value};
518 double y[]{miny+eps,maxy-eps};
520 assignSide(pen,marker);
521 labelPen(pen++,v->tooltip);
527 static const time_duration maxWait=milliseconds(1000);
528 if ((microsec_clock::local_time()-(ptime&)lastAdd) >
531 const ptime timerStart=microsec_clock::local_time();
534 lastAdd=microsec_clock::local_time();
544 std::string
operator()(
const string& label,
double x,
double y)
const 548 r<<label<<
":("<<
str(ptime(date(1970,Jan,1))+microseconds(static_cast<long long>(1E6*x)),format)
555 void PlotWidget::addConstantCurves()
557 std::vector<std::vector<std::pair<double,std::string>>> newXticks;
560 xIsSecsSinceEpoch=
false;
561 for (
auto& yv: yvars)
564 if (i && !i->hypercube().xvectors.empty())
566 const auto& xv=i->hypercube().xvectors[0];
567 if (xv.dimension.type==Dimension::time)
569 xIsSecsSinceEpoch=
true;
575 formatter=defaultFormatter;
577 if (plotType!=automatic)
578 Plot::plotType=plotType;
582 clearPensOnLabelling=
true;
583 const OnStackExit setClearPensOnLabellingFalse([
this]{clearPensOnLabelling=
false;});
585 for (
size_t port=0; port<yvars.size(); ++port)
586 for (
size_t i=0; i<yvars[port].size(); ++i)
589 if (port<m_numLines) noLhsPens=
false;
590 auto& yv=yvars[port][i];
591 if (yv->size()>0) (*yv)[0];
592 auto d=yv->hypercube().dims();
595 if (Plot::plotType==Plot::bar)
601 vector<double> xdefault;
603 if (port<xvars.size() && xvars[port])
605 if (xvars[port]->hypercube().xvectors[0].size()!=d[0])
606 throw error(
"x vector not same length as y vectors");
607 if (xvars[port]->index().empty())
608 x=xvars[port]->begin();
611 xdefault.reserve(d[0]);
612 for (
size_t i=0; i<d[0]; ++i)
613 xdefault.push_back(xvars[port]->atHCIndex(i));
619 xdefault.reserve(d[0]);
620 newXticks.emplace_back();
621 if (yv->hypercube().rank())
623 const auto& xv=yv->hypercube().xvectors[0];
624 assert(xv.size()==d[0]);
625 switch (xv.dimension.type)
627 case Dimension::string:
628 for (
size_t i=0; i<xv.size(); ++i)
630 newXticks.back().emplace_back(i,
str(xv[i]));
631 xdefault.push_back(i);
633 if (plotType==automatic)
634 Plot::plotType=Plot::bar;
636 case Dimension::value:
637 if (xIsSecsSinceEpoch && xv.dimension.units==
"year")
639 for (
const auto& i: xv)
642 if (abs(i.value-
int(i.value))<0.05)
643 newXticks.back().emplace_back(xdefault.back(),
str(
int(i.value)));
646 for (
const auto& i: xv)
647 xdefault.push_back(i.value);
648 if (plotType==automatic)
649 Plot::plotType=Plot::line;
651 case Dimension::time:
653 const string format=xv.timeFormat();
654 formatter=TimeFormatter(xv.dimension.units);
655 for (
const auto& i: xv)
657 const double tv=(i.time-ptime(date(1970,Jan,1))).total_microseconds()*1E-6;
658 newXticks.back().emplace_back(tv,
str(i,format));
659 xdefault.push_back(tv);
662 if (plotType==automatic)
663 Plot::plotType=Plot::line;
668 for (
size_t i=0; i<d[0]; ++i)
669 xdefault.push_back(i);
673 const auto& idx=yv->index();
678 setPen(pen, x, yv->begin(), d[0]);
680 for (
size_t j=0; j<idx.size(); ++j)
681 addPt(pen,x[idx[j]], (*yv)[j]);
691 setPen(pen++, x, yv->begin()+j, d[0]);
694 for (
size_t j=0; j<idx.size(); ++j)
696 auto div=lldiv(idx[j], d[0]);
699 addPt(startPen+div.quot, x[div.rem], (*yv)[j]);
700 if (pen<=startPen+div.quot) pen=startPen+div.quot+1;
704 for (
int j=0; startPen<pen; ++startPen, ++j)
708 for (
size_t i=1; i<yv->hypercube().rank(); ++i)
710 label+=
str(yv->hypercube().xvectors[i][(j/stride)%d[i]])+
" ";
713 assignSide(startPen,port<m_numLines? Side::left: Side::right);
714 labelPen(startPen,
defang(label));
718 justDataChanged=
true;
731 for (
auto& m: horizontalMarkers)
734 auto eps=1e-4*(maxx-minx);
735 addPt(pen,minx+eps,v->value());
736 addPt(pen,maxx-eps,v->value());
737 assignSide(pen,marker);
738 if (!v->tooltip.empty())
739 labelPen(pen++,v->tooltip);
743 for (
auto& m: verticalMarkers)
746 auto eps=1e-4*(maxy-miny);
747 auto value=v->value();
748 if (xIsSecsSinceEpoch && (v->units.empty() || v->units==
Units(
"year")))
750 addPt(pen,value,miny+eps);
751 addPt(pen,value,maxy-eps);
752 assignSide(pen,marker);
753 if (!v->tooltip.empty())
754 labelPen(pen++,v->tooltip);
760 if (newXticks.size()==1)
761 xticks=std::move(newXticks.front());
766 for (
auto& i: newXticks)
772 else if (xticks.empty())
777 for (; j!=i.end(); ++j)
778 if (j->first>=xticks.front().first)
780 xticks.insert(xticks.begin(), i.begin(), j);
782 for (; j!=i.begin(); --j)
783 if ((j-1)->first<=xticks.back().first)
785 xticks.insert(xticks.end(), j, i.end());
792 size_t PlotWidget::startPen(
size_t port)
const 795 for (
size_t p=0; p<std::min(port, yvars.size()); ++p)
796 pen+=yvars[p].size();
801 void PlotWidget::connectVar(
const shared_ptr<VariableValue>&
var,
unsigned port)
804 if (port<nBoundsPorts)
807 case 0: xminVar=
var;
return;
808 case 1: xmaxVar=
var;
return;
809 case 2: yminVar=
var;
return;
810 case 3: ymaxVar=
var;
return;
811 case 4: y1minVar=
var;
return;
812 case 5: y1maxVar=
var;
return;
814 if (port-nBoundsPorts<2*m_numLines)
816 yvars.resize(port-nBoundsPorts+1);
817 yvars[port-nBoundsPorts].push_back(
var);
819 assignSide(startPen(port-nBoundsPorts+1)-1,port-nBoundsPorts<m_numLines? Side::left: Side::right);
821 else if (port-nBoundsPorts<4*m_numLines)
823 xvars.resize(port-nBoundsPorts-2*m_numLines+1);
824 xvars[port-nBoundsPorts-2*m_numLines]=
var;
826 justDataChanged=
false;
830 void PlotWidget::disconnectAllVars()
834 xminVar=xmaxVar=yminVar=ymaxVar=y1minVar=y1maxVar=
nullptr;
837 set<string> PlotWidget::availableMarkers()
const 841 for (
auto g=
group.lock(); g; g=g->group.lock())
842 for (
auto& i: g->items)
843 if (
auto v=i->variableCast())
844 r.insert(
uqName(v->rawName()));
#define M_PI
some useful geometry types, defined from boost::geometry
std::string latexToPango(const char *s)
static const size_t maxNumTensorElementsToPlot
represents rectangular region of a lasso operation
std::string uqName(const std::string &name)
extract unqualified portion of name
string valueId(const string &name)
construct a valueId from fully qualified name @ name should not be canonicalised
arrange for a functional to be called on stack exit
Creation and access to the minskyTCL_obj object, which has code to record whenever Minsky's state cha...
double yearToPTime(double x)
double isfinite(double x)
represents the units (in sense of dimensional analysis) of a variable.
std::string str(T x)
utility function to create a string representation of a numeric type
const Minsky & cminsky()
const version to help in const correctness
static ptime accumulatedBlitTime
void drawTriangle(cairo_t *cairo, double x, double y, const ecolab::cairo::Colour &col, double angle=0)
UnitsExpressionWalker timeUnit
string to_string(CONST84 char *x)
constexpr float portRadius
radius of circle marking ports at zoom=1