FOX/ObjCryst++  1.10.X (development)
wxMultiGraph.cpp
1 /* ObjCryst++ Object-Oriented Crystallographic Library
2  (c) 2005- Vincent Favre-Nicolin vincefn@users.sourceforge.net
3 
4  This program is free software; you can redistribute it and/or modify
5  it under the terms of the GNU General Public License as published by
6  the Free Software Foundation; either version 2 of the License, or
7  (at your option) any later version.
8 
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  GNU General Public License for more details.
13 
14  You should have received a copy of the GNU General Public License
15  along with this program; if not, write to the Free Software
16  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 */
18 
19 #include <iostream>
20 
21 // wx headers, with or without precompilation
22 #include "wx/wxprec.h"
23 #ifdef __BORLANDC__
24  #pragma hdrstop
25 #endif
26 #ifndef WX_PRECOMP
27  #include "wx/wx.h"
28 #endif
29 #include "wx/dcbuffer.h"
30 #include "wx/gdicmn.h"
31 #include "ObjCryst/ObjCryst/General.h"
32 #include "ObjCryst/wxCryst/wxMultiGraph.h"
33 #include "ObjCryst/wxCryst/wxCryst.h"
34 
35 using namespace std;
36 
37 namespace ObjCryst
38 {
39 static const char* swxColourNameList[]={
40 "BLACK",
41 "BLUE",
42 "RED",
43 "GREEN",
44 "BROWN",
45 "CYAN",
46 "ORANGE",
47 "YELLOW",
48 "MAGENTA",
49 "MAROON",
50 "GOLD",
51 "GREY",
52 "PINK",
53 "SALMON",
54 "PURPLE",
55 "DARK GREY",
56 "CORAL",
57 "AQUAMARINE",
58 "VIOLET",
59 
60 "BLUE VIOLET",
61 "CADET BLUE",
62 "CORNFLOWER BLUE",
63 "DARK GREEN",
64 "DARK OLIVE GREEN",
65 "DARK ORCHID",
66 "DARK SLATE BLUE",
67 "DARK SLATE GREY DARK TURQUOISE",
68 "DIM GREY",
69 "FIREBRICK",
70 "FOREST GREEN",
71 "GOLDENROD",
72 "GREEN YELLOW",
73 "INDIAN RED",
74 "KHAKI",
75 "LIGHT BLUE",
76 "LIGHT GREY",
77 "LIGHT STEEL BLUE",
78 "LIME GREEN",
79 "MEDIUM AQUAMARINE",
80 "MEDIUM BLUE",
81 "MEDIUM FOREST GREEN",
82 "MEDIUM GOLDENROD",
83 "MEDIUM ORCHID",
84 "MEDIUM SEA GREEN",
85 "MEDIUM SLATE BLUE",
86 "MEDIUM SPRING GREEN",
87 "MEDIUM TURQUOISE",
88 "MEDIUM VIOLET RED",
89 "MIDNIGHT BLUE",
90 "NAVY",
91 "ORANGE RED",
92 "ORCHID",
93 "PALE GREEN",
94 "PLUM",
95 "SEA GREEN",
96 "SIENNA",
97 "SKY BLUE",
98 "SLATE BLUE",
99 "SPRING GREEN",
100 "STEEL BLUE",
101 "TAN",
102 "THISTLE",
103 "TURQUOISE",
104 "VIOLET RED",
105 "WHEAT",
106 //"WHITE",
107 "YELLOW GREEN."};
108 
110 //
111 // WXMultiGraph
112 //
114 static const long ID_UPDATEUI= WXCRYST_ID();
115 static const long ID_MENU_AUTOSCALE= WXCRYST_ID();
116 
117 BEGIN_EVENT_TABLE(WXMultiGraph, wxWindow)
118  EVT_PAINT( WXMultiGraph::OnPaint)
119  EVT_MOUSE_EVENTS( WXMultiGraph::OnMouse)
120  EVT_CHAR( WXMultiGraph::OnKeyDown)
121  EVT_MOUSEWHEEL( WXMultiGraph::OnMouseWheel)
122  EVT_UPDATE_UI(ID_UPDATEUI, WXMultiGraph::OnUpdateUI)
123  EVT_SIZE( WXMultiGraph::OnSize)
124 END_EVENT_TABLE()
125 
126 
127 WXMultiGraph::WXMultiGraph(wxFrame *frame):
128 wxWindow(frame,-1,wxPoint(-1,-1),wxSize(-1,-1)),
129 mMinX(0.0),mMaxX(1.0),mMinY(0.0),mMaxY(1.0),
130 mLeft(40),mRight(10),mTop(10),mBottom(25),
131 mIsDragging(false),mpParentFrame(frame)
132 {
133  #ifdef VFN_CRYST_MUTEX
134  cout <<"new CrystMutex("<<&mMutexData<<")for WXMultiGraph:"<<this<<endl;
135  #endif
136  mpPopUpMenu=new wxMenu(_T("Graph"));
137  mpPopUpMenu->Append(ID_MENU_AUTOSCALE, _T("&AutoScale"));
138  //mpPopUpMenu->Append(ID_POWDERGRAPH_MENU_TOGGLELABEL, "&Hide Labels");
139 }
140 
141 WXMultiGraph::~WXMultiGraph()
142 {
143  VFN_DEBUG_MESSAGE("WXMultiGraph::~WXMultiGraph():",4)
144  delete mpPopUpMenu;
145  #ifdef VFN_CRYST_MUTEX
146  cout <<"Deleting CrystMutex("<<&mMutexData<<")for WXMultiGraph:"<<this<<endl;
147  #endif
148 }
149 
150 unsigned long WXMultiGraph::AddGraph(const string &name)
151 {
152  wxMutexLocker mlock(mMutexData);
153  long id;
154  for(id=mvData.size();id>=0;id--)
155  if(mvData.end()==mvData.find(id)) break;
156  mvData[id].name=name;
157  mvData[id].xmin=0.0;
158  mvData[id].xmax=-1.0;
159  mvData[id].ymin=0.0;
160  mvData[id].ymax=-1.0;
161  return id;
162 }
163 
164 void WXMultiGraph::SetGraphData
165  (const unsigned long id,const std::valarray<float> &vx,
166  const std::valarray<float> &vy)
167 {
168  bool rescale=false;
169  mMutexData.Lock();
170  if(mvData[id].xmin>mvData[id].xmax)rescale=true;// A new graph has been added
171  mvData[id].vx.resize(vx.size());
172  mvData[id].vy.resize(vx.size());
173  mvData[id].vx=vx;
174  mvData[id].vy=vy;
175  if(vx.size()!=0)
176  {
177  mvData[id].xmin=mvData[id].vx.min();
178  mvData[id].xmax=mvData[id].vx.max();
179  mvData[id].ymin=mvData[id].vy.min();
180  mvData[id].ymax=mvData[id].vy.max();
181  VFN_DEBUG_MESSAGE("WXMultiGraph::SetGraphData():"<<mvData[id].vx.size()<<":"
182  << mvData[id].xmin<<","<< mvData[id].xmax<<","
183  << mvData[id].ymin<<","<< mvData[id].ymax<<",",3)
184  mMutexData.Unlock();
185  if(rescale)
186  this->AutoScale(-1);
187  }
188  else mMutexData.Unlock();
189 }
190 
191 
192 void WXMultiGraph::DeleteGraph(const unsigned long id)
193 {
194  mvData.erase(id);
195 }
196 
197 void WXMultiGraph::OnPaint(wxPaintEvent &event)
198 {
199  wxMutexLocker mlock(mMutexData);
200  if(mvData.size()<1) return;
201  VFN_DEBUG_ENTRY("WXMultiGraph::OnPaint()",4)
202  wxBufferedPaintDC dc(this);
203  this->PrepareDC(dc);
204  mpParentFrame->PrepareDC(dc);
205 
206  dc.DestroyClippingRegion();
207  dc.SetBackground(wxBrush(_T("white"), wxSOLID));
208  dc.Clear();
209 
210  wxString fontInfo;
211  #ifdef __WIN32__
212  dc.SetFont(*wxNORMAL_FONT);
213  #else
214  dc.SetFont(*wxSMALL_FONT);
215  #endif
216  // Get Window Size
217  wxCoord width,height;
218  this->GetSize(&width, &height);
219 
220  // Draw Axis
221  VFN_DEBUG_MESSAGE("WXMultiGraph::OnPaint():Axis",3)
222  {
223  dc.SetPen(*wxBLACK_PEN);
224  dc.SetTextForeground(*wxBLACK);
225  int nbTick=5;//approx.
226  wxCoord tmpW,tmpH;
227  float xs,ys;
228  // X & Y margins.
229  float yStep,xStep,dx,dy;
230  dx=mMaxX-mMinX;
231  dy=mMaxY-mMinY;
232  if(dx<1e-6)dx=1e-6;
233  if(dy<1e-6)dy=1e-6;
234  yStep=pow((float)10,(float)floor(log10(dy/nbTick)));
235  yStep *= floor((dy/yStep+0.1)/nbTick);
236 
237  xStep=pow((float)10,(float)floor(log10(dx/nbTick)));
238  xStep *= floor((dx/xStep+0.1)/nbTick);
239 
240  mLeft=0;
241  for(float y=yStep*ceil(mMinY/yStep);y<mMaxY;y+=yStep)
242  {//get left margin from tick labels
243  fontInfo.Printf(_T("%g"),y);
244  dc.GetTextExtent(fontInfo, &tmpW, &tmpH);
245  if((tmpW+3)>mLeft) mLeft=tmpW+3;
246  }
247 
248  fontInfo.Printf(_T("%g"),xStep);
249  dc.GetTextExtent(fontInfo, &tmpW, &tmpH);
250  mBottom=tmpH*3/2+3;
251 
252  //Y axis
253  dc.DrawLine(mLeft,height-mBottom,mLeft,mTop);
254  VFN_DEBUG_MESSAGE("WXMultiGraph::OnPaint():AxisStep="<<yStep<<","<<mMinY<<","<<mMaxY,3)
255  nbTick=int(dy/yStep);
256  for(int i=0;i<nbTick;i++)
257  {
258  float y=yStep*ceil(mMinY/yStep)+i*yStep;
259  xs=mMinX;
260  ys=y;
261  this->Data2Screen(xs,ys);
262  VFN_DEBUG_MESSAGE("WXMultiGraph::OnPaint():Axis:"<<xs<<","<<ys,3)
263  dc.DrawLine(wxCoord(xs-3),wxCoord(ys),wxCoord(xs+3),wxCoord(ys));
264  fontInfo.Printf(_T("%g"),y);
265  dc.GetTextExtent(fontInfo, &tmpW, &tmpH);
266  dc.DrawText(fontInfo,wxCoord(xs-tmpW-3),wxCoord(ys-tmpH/2));
267  }
268  //X axis
269  dc.DrawLine(mLeft,height-mBottom,width-mRight,height-mBottom);
270  nbTick=int(dx/xStep);
271  for(int i=0;i<nbTick;i++)
272  {
273  float x=xStep*ceil(mMinX/xStep)+i*xStep;
274  xs=x;
275  ys=mMinY;
276  this->Data2Screen(xs,ys);
277  dc.DrawLine(wxCoord(xs),wxCoord(ys-3),wxCoord(xs),wxCoord(ys+3));
278  fontInfo.Printf(_T("%g"),x);
279  dc.GetTextExtent(fontInfo, &tmpW, &tmpH);
280  dc.DrawText(fontInfo,wxCoord(xs-tmpW/2),wxCoord(ys+tmpH/2));
281  }
282  // Axis labels;
283  dc.GetTextExtent(mYLabel, &tmpW, &tmpH);
284  dc.DrawText(mYLabel,wxCoord(0),wxCoord(0));
285  xs=xStep*ceil(mMinX/xStep)+(nbTick-1.5)*xStep;
286  ys=mMinY;
287  this->Data2Screen(xs,ys);
288  dc.GetTextExtent(mXLabel, &tmpW, &tmpH);
289  dc.DrawText(mXLabel,wxCoord(xs-tmpW/2),wxCoord(ys+tmpH/2+3));
290  }
291  // Draw data
292  map<unsigned long, GraphData >::const_iterator pos;
293  long ix=-1,ixdrawn=-1;
294  for(pos=mvData.begin();pos!=mvData.end();++pos)
295  {
296  ix++;
297  VFN_DEBUG_MESSAGE("WXMultiGraph::OnPaint():Data#"<<ix,3)
298  if((pos->second.vx.size()<1)||(pos->second.vx.size()!=pos->second.vy.size())) continue;
299  ixdrawn++;
300  dc.SetPen(wxPen(wxTheColourDatabase->Find(wxString::FromAscii(swxColourNameList[ix])),1,wxSOLID));
301  float x1,y1,x2,y2;
302  x2=pos->second.vx[0];
303  y2=pos->second.vy[0];
304  this->Data2Screen(x2,y2);
305  for(unsigned long i=0;i<pos->second.vx.size();i++)
306  {
307  x1=x2;
308  y1=y2;
309  x2=pos->second.vx[i];
310  y2=pos->second.vy[i];
311  this->Data2Screen(x2,y2);
312  if( ((x1>=mLeft)&&(x1<=(width-mRight))&&(y1>=mBottom)&&(y1<=(height-mTop)))
313  ||((x2>=mLeft)&&(x2<=(width-mRight))&&(y2>=mBottom)&&(y2<=(height-mTop))))
314  dc.DrawLine(wxCoord(x1),wxCoord(y1),wxCoord(x2),wxCoord(y2));
315  }
316  // Print Name
317  dc.SetTextForeground(wxPen(wxTheColourDatabase->Find(wxString::FromAscii(swxColourNameList[ix])),1,wxSOLID).GetColour());
318  wxCoord tmpW,tmpH;
319  fontInfo.Printf(wxString::FromAscii(pos->second.name.c_str()));
320  dc.GetTextExtent(fontInfo, &tmpW, &tmpH);
321  dc.DrawText(fontInfo,wxCoord(width-tmpW-2),wxCoord(tmpH*(ixdrawn)+2));
322  }
323 
324  VFN_DEBUG_EXIT("WXMultiGraph::OnPaint()",4)
325 }
326 
327 void WXMultiGraph::OnMouse(wxMouseEvent &event)
328 {
329  event.Skip();// Make sure the default handler gets the event too
330 
331  if(event.Leaving()) return;// ?
332  wxCoord width,height;
333  this->GetSize(&width, &height);
334  // Write mouse pointer coordinates
335  wxClientDC dc(this);
336  PrepareDC(dc);
337  mpParentFrame->PrepareDC(dc);
338 
339  wxPoint pos=event.GetPosition();
340  float x= float(dc.DeviceToLogicalX(pos.x));
341  float y= float(dc.DeviceToLogicalY(pos.y));
342 
343  if((x>width)||(y>height))
344  {
345  return;
346  }
347  this->Screen2Data(x,y);
348  wxString str;
349  str.Printf(_T("x=%f ,y=%f"),x,y);
350  mpParentFrame->SetStatusText(str);
351 
352  if(event.RightIsDown())
353  {
354  this->PopupMenu(mpPopUpMenu, event.GetX(), event.GetY() );
355  return;
356  }
357  if (event.Dragging() && event.LeftIsDown() && (!mIsDragging))
358  {//Begin zooming
359  mIsDragging=true;
360  mDragX0=x;
361  mDragY0=y;
362  return;
363  }
364  if(event.LeftUp() && mIsDragging)
365  {//Finished zooming !
366  mMutexData.Lock();
367  mIsDragging=false;
368  if(x>mDragX0)
369  {
370  mMinX=mDragX0;
371  mMaxX=x;
372  }
373  else
374  {
375  mMinX=x;
376  mMaxX=mDragX0;
377  }
378  if(y>mDragY0)
379  {
380  mMinY=mDragY0;
381  mMaxY=y;
382  }
383  else
384  {
385  mMinY=y;
386  mMaxY=mDragY0;
387  }
388  if(mMaxX<=mMinX) mMaxX=mMinX+1e-6;
389  if(mMaxY<=mMinY) mMaxY=mMinY+1e-6;
390  mMutexData.Unlock();
391  this->UpdateDisplay();
392  return;
393  }
394  if(false==event.Dragging()) mIsDragging=false;
395 
396  if(event.LeftDClick())
397  {//Reset axis range
398  this->AutoScale();
399  this->UpdateDisplay();
400  return;
401  }
402 }
403 
404 void WXMultiGraph::OnMouseWheel(wxMouseEvent &event)
405 {
406  if(event.GetWheelRotation()>=event.GetWheelDelta())
407  {
408  const REAL range=mMaxX-mMinX;
409  mMaxX += range/8;
410  mMinX += range/8;
411  }
412  if(event.GetWheelRotation()<=(-event.GetWheelDelta()))
413  {
414  const REAL range=mMaxX-mMinX;
415  mMaxX -= range/8;
416  mMinX -= range/8;
417  }
418  this->UpdateDisplay();
419 }
420 
421 void WXMultiGraph::AutoScale(const long id,const bool xmin,const bool xmax,
422  const bool ymin,const bool ymax)
423 {
424  wxMutexLocker mlock(mMutexData);
425  if(mvData.size()==0) return;
426  std::map<unsigned long, GraphData>::const_iterator pos;
427  if(id<0)pos=mvData.end();
428  else pos=mvData.find((unsigned long)id);
429  if(pos==mvData.end())
430  {
431  pos=mvData.begin();
432  if(xmax) mMaxX=pos->second.xmax;
433  if(xmin) mMinX=pos->second.xmin;
434  if(ymax) mMaxY=pos->second.ymax;
435  if(ymin) mMinY=pos->second.ymin;
436  ++pos;
437  for(;pos!=mvData.end();++pos)
438  {
439  if(xmax) if(mMaxX<pos->second.xmax) mMaxX=pos->second.xmax;
440  if(xmin) if(mMinX>pos->second.xmin) mMinX=pos->second.xmin;
441  if(ymax) if(mMaxY<pos->second.ymax) mMaxY=pos->second.ymax;
442  if(ymin) if(mMinY>pos->second.ymin) mMinY=pos->second.ymin;
443  }
444  }
445  else
446  {
447  if(xmax) mMaxX=pos->second.xmax;
448  if(xmin) mMinX=pos->second.xmin;
449  if(ymax) mMaxY=pos->second.ymax;
450  if(ymin) mMinY=pos->second.ymin;
451  }
452  if(mMaxX<=mMinX) mMaxX=mMinX+1e-6;
453  if(mMaxY<=mMinY) mMaxY=mMinY+1e-6;
454  //cout<<"Autoscale to:"<<mMinX<<"->"<<mMaxX<<" , "<<mMinY<<"->"<<mMaxY<<endl;
455 }
456 
457 void WXMultiGraph::OnKeyDown(wxKeyEvent& event)
458 {
459  switch(event.GetKeyCode())
460  {
461  case(WXK_LEFT):
462  {
463  const REAL range=mMaxX-mMinX;
464  mMinX-=range/8;
465  mMaxX-=range/8;
466  break;
467  }
468  case(WXK_RIGHT):
469  {
470  const REAL range=mMaxX-mMinX;
471  mMinX+=range/8;
472  mMaxX+=range/8;
473  break;
474  }
475  case(WXK_UP):
476  {
477  const REAL range=mMaxY-mMinY;
478  mMinY+=range/8;
479  mMaxY+=range/8;
480  break;
481  }
482  case(WXK_DOWN):
483  {
484  const REAL range=mMaxY-mMinY;
485  mMinY-=range/8;
486  mMaxY-=range/8;
487  break;
488  }
489  case(43):// WXK_ADD ?
490  {
491  const REAL range=mMaxX-mMinX;
492  const REAL center=(mMaxX+mMinX)/2;
493  mMinX=center-range/3.0;
494  mMaxX=center+range/3.0;
495  break;
496  }
497  case(45):// WXK_SUBTRACT ?
498  {
499  const REAL range=mMaxX-mMinX;
500  const REAL center=(mMaxX+mMinX)/2;
501  mMinX=center-range*2.0/3.0;
502  mMaxX=center+range*2.0/3.0;
503  break;
504  }
505  case(42):// WXK_MULTIPLY
506  {
507  const REAL range=mMaxY-mMinY;
508  mMaxY=mMinY+range*2.0/3.0;
509  break;
510  }
511  case(47):// WXK_DIVIDE
512  {
513  const REAL range=mMaxY-mMinY;
514  mMaxY=mMinY+range*4.0/3.0;
515  break;
516  }
517  default:
518  {
519  VFN_DEBUG_MESSAGE("WXMultiGraph::OnKeyDown(): no command for key #"<<event.GetKeyCode(),5);
520  }
521  }
522  this->UpdateDisplay();
523  event.Skip();// Make sure the default handler gets the event too
524 }
525 
526 void WXMultiGraph::OnUpdateUI(wxUpdateUIEvent &event)
527 {
528  VFN_DEBUG_MESSAGE("WXMultiGraph::OnUpdateUI()",4)
529  this->Refresh(false);
530  event.Skip();
531 }
532 
533 void WXMultiGraph::OnSize(wxSizeEvent &event)
534 {
535  this->Refresh(false);
536 }
537 void WXMultiGraph::SetXLabel(const wxString &xlabel)
538 {
539  mXLabel=xlabel;
540 }
541 
542 void WXMultiGraph::SetYLabel(const wxString &ylabel)
543 {
544  mYLabel=ylabel;
545 }
546 
547 void WXMultiGraph::UpdateDisplay()
548 {
549  VFN_DEBUG_ENTRY("WXMultiGraph::UpdateDisplay()",4)
550  if(wxThread::IsMain()) this->Refresh(false);
551  else
552  {
553  wxUpdateUIEvent event(ID_UPDATEUI);
554  wxPostEvent(this,event);
555  }
556  VFN_DEBUG_EXIT("WXMultiGraph::UpdateDisplay()",4)
557 }
558 
559 
560 void WXMultiGraph::Screen2Data(float &x,float &y)
561 {
562  wxCoord width,height;
563  this->GetSize(&width, &height);
564 
565  float range=float(width-(mLeft+mRight));
566  if(range<=0) range=1.0;
567  x=(x-mLeft)/range;
568  x=mMinX+x*(mMaxX-mMinX);
569 
570  range=float(height-(mTop+mBottom));
571  if(range<=0) range=1.0;
572  y=(height-mBottom-y)/range;
573  y=mMinY+y*(mMaxY-mMinY);
574 }
575 
576 void WXMultiGraph::Data2Screen(float &x,float &y)
577 {
578  VFN_DEBUG_ENTRY("WXMultiGraph::Data2Screen()"<<x<<","<<y,2)
579  wxCoord width,height;
580  this->GetSize(&width, &height);
581 
582  float range=float(width-(mLeft+mRight));
583  x=(x-mMinX)/(mMaxX-mMinX);
584  x=mLeft+x*range;
585 
586  range=float(height-(mTop+mBottom));
587  y=(y-mMinY)/(mMaxY-mMinY);
588  y=height-mBottom-y*range;
589  VFN_DEBUG_EXIT("WXMultiGraph::Data2Screen()->"<<x<<","<<y,2)
590 }
591 
592 }//namespace
The namespace which includes all objects (crystallographic and algorithmic) in ObjCryst++.
Definition: Atom.cpp:47