1 /*
2  * ===========================
3  * VDK Component Library
4  * Version 0.2
5  * ===========================
6  * Copyright (C) 1998, Mario Motta
7  * Developed by Mario Motta <mmotta@guest.net>
8  * ===========================================
9  * This library is a component of:
10  * VDK Visual Development Kit
11  * Version 0.4.1
12  * Copyright (C) 1998, Mario Motta
13  * Developed by Mario Motta <mmotta@guest.net>
14  * ===========================================
15  * This library is free software; you can redistribute it and/or
16  * modify it under the terms of the GNU Library General Public
17  * License as published by the Free Software Foundation; either
18  * version 2 of the License, or (at your option) any later version.
19  *
20  * This library is distributed in the hope that it will be useful,
21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
23  * Library General Public License for more details.
24  *
25  * You should have received a copy of the GNU Library General Public
26  * License along with this library; if not, write to the Free Software
27  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
28  * 02111-130
29  */
30 
31 #include <vdk/chart.h>
32 #include <cstdlib>
33 #include <cstring>
34 /////////////////////////////////////
35 DEFINE_EVENT_LIST(VDKChart,VDKCanvas);
36 /*
37 captures configure event:
38 recompute chart allocated size
39 and redraw chart
40  */
OnConfigure(VDKObject * sender,GdkEvent * event)41 bool VDKChart::OnConfigure(VDKObject* sender, GdkEvent* event)
42 {
43   if(!gc)
44     gc = gdk_gc_new(pixmap);
45   size = Usize;
46   printf("\nsize:%d,%d",size.x,size.y);
47   fflush(stdout);
48   // patch Bug#262091
49   // axis = ChartAxis(this,size.X(),size.Y());
50   ChartAxis axis_tmp(this, size.X(), size.Y());
51   axis = axis_tmp;
52   axis.Draw();
53   DrawTitle();
54   DrawChart();
55   Redraw();
56   return true;
57 }
58 /*
59  */
OnClick(VDKObject * sender,GdkEvent * event)60 bool VDKChart::OnClick(VDKObject* sender, GdkEvent* event)
61 {
62   if(series.size() <= 0)
63     return true;
64   if(!tip_window)
65     {
66        char buff[64];
67        GdkEventButton *ev = (GdkEventButton*) event;
68        double x = ev->x;
69        double y = ev->y;
70        double sx = (x - xn1 + kx*xv1)/kx;
71        double sy = (y - yn1 + ky*yv1)/ky;
72        sprintf(buff,"%.3f,%.3f",sx,sy);
73        tip_window = gtk_window_new (GTK_WINDOW_POPUP);
74        gtk_window_position(GTK_WINDOW(tip_window),GTK_WIN_POS_MOUSE);
75        GtkWidget* label = gtk_label_new(buff);
76        gtk_container_add(GTK_CONTAINER(tip_window),label);
77        gtk_widget_show(label);
78        gtk_widget_show(tip_window);
79     }
80   return false;
81 }
82 
83 /*
84  */
OnClickRelease(VDKObject * sender,GdkEvent * event)85 bool VDKChart::OnClickRelease(VDKObject* sender, GdkEvent* event)
86 {
87 if(tip_window)
88   {
89     gtk_widget_destroy(tip_window);
90     tip_window = NULL;
91   }
92 return true;
93 }
94 /////////////////////////////////////////////////////////////
95 /*
96 constructor
97  */
VDKChart(VDKForm * owner,int w,int h)98 VDKChart::VDKChart(VDKForm* owner, int w, int h):
99   VDKCanvas(owner,w,h),
100   ChartBorder("ChartBorder",this,20,&VDKChart::SetChartBorder),
101   Title("Title",this,"Untitled"),
102   LabelX("LabelX",this,""),
103   LabelY("LabelX",this,""),
104   LabelXDigits("LabelXDigits",this,2),
105   LabelYDigits("LabelYDigits",this,2)
106 {
107   gc = NULL;
108   tip_window = NULL;
109   EventConnect("configure_event",&VDKChart::OnConfigure);
110   EventConnect("button_press_event",&VDKChart::OnClick);
111   EventConnect("button_release_event",&VDKChart::OnClickRelease);
112   Font(new VDKFont(owner,"helvetica Medium 10"));
113 }
114 
115 /*
116 set chart border
117  */
SetChartBorder(int b)118 void VDKChart::SetChartBorder(int b)
119 {
120 size = Usize;
121 // patch Bug#262091
122 // axis = ChartAxis(this,size.X(),size.Y());
123 ChartAxis axis_tmp(this,size.X(),size.Y());
124 axis = axis_tmp;
125 DrawChart();
126 }
127 /*
128 draw title
129 */
DrawTitle()130 void VDKChart::DrawTitle()
131 {
132   int half_w;
133   VDKUString s = Title;
134   VDKPoint size = Usize;
135   VDKPoint center(size.X()/2,ChartBorder/2);
136   VDKFont* font = Font;
137   half_w = font ? (gdk_string_width (font->AsGdkFont(), (char*) s))/2 : 5;
138   VDKRgb fg = Foreground;
139   if(fg.red >= 0) SetColor(fg);
140   DrawString(center.X()-half_w,center.Y(), (char*) s);
141 }
142 /*
143 destructor:
144 frees series
145 and graphic context
146  */
~VDKChart()147 VDKChart::~VDKChart()
148 {
149 SeriesListIterator li(series);
150 for(;li;li++)
151   delete li.current();
152 }
153 /*
154   add serie to chart, unicity is checked,
155   <s> will substitute actual series
156   if a match is found. Old series will be destroyed
157 */
AddSeries(Series * s)158 void VDKChart::AddSeries(Series* s)
159 {
160 if(series.size() > 0)
161   {
162     SeriesListIterator li(series);
163     for(;li;li++)
164 	if((*li.current() == *s))
165 	  break;
166     if(li)
167       {
168 	Series* p = li.current();
169 	// removes series li.current()
170 	series.remove(p);
171 	delete p;
172       }
173   }
174  series.add(s);
175  ComputeDomainLimits(s);
176  DrawChart();
177 }
178 /*
179 update domain limits:
180 lowest and highest x and y
181  */
ComputeDomainLimits(Series * s)182 void VDKChart::ComputeDomainLimits(Series* s)
183 {
184 if(series.size() == 1)
185   {
186     domainmax.x = s->Max().x;
187     domainmax.y = s->Max().y;
188     domainmin.x = s->Min().x;
189     domainmin.y = s->Min().y;
190   }
191 else
192   {
193     domainmax.x = domainmax.x < s->Max().x ? s->Max().x : domainmax.x;
194     domainmax.y = domainmax.y < s->Max().y ? s->Max().y : domainmax.y;
195     domainmin.x = domainmin.x > s->Min().x ? s->Min().x : domainmin.x;
196     domainmin.y = domainmin.y > s->Min().y ? s->Min().y : domainmin.y;
197   }
198  domainmin.x = domainmin.x == domainmax.x ? 0 : domainmin.x;
199  domainmin.y = domainmin.y == domainmax.y ? 0 : domainmin.y;
200 
201 }
202 /*
203 clear chart, destroy series
204 */
Clear()205 void VDKChart::Clear()
206 {
207   SeriesListIterator li(series);
208   for(;li;li++)
209     delete li.current();
210   series.flush();
211   VDKCanvas::Clear();
212   axis.Draw();
213   DrawTitle();
214   Redraw();
215 }
216 /*
217 actually draw chart:
218 1. Clear chart
219 2. Compute scaling factors
220 3. Calls a virtual drawing function:
221    Plot(Series* s, int n, Coord c) with scaled coordinates.
222    VDKChart::Plot() does nothing.
223    Subclasses should override Plot() in order
224    to draw different chart shapes (lines, bars, etc.)
225 */
226 const int tickL = 8;
DrawChart()227 void VDKChart::DrawChart()
228 {
229   if(series.size() <= 0)
230     return;
231   else
232     {
233       VDKCanvas::Clear();
234       axis.Draw();
235       DrawTitle();
236     }
237   // recompute domain with chart allocated size
238   xn1 = axis.Domain().Origin().X();
239   xn2 = xn1 + axis.Domain().W();
240   xv1 = domainmin.x; xv2 = domainmax.x;
241   //xv1 = xv1 == xv2 ? 0 : xv1;
242   yn1 = axis.Domain().Origin().Y();
243   yn2 = yn1 - axis.Domain().H();
244   yv1 = domainmin.y; yv2 = domainmax.y;
245   //yv1 = yv1 == yv2 ? 0 : yv1;
246   kx  = (xn2-xn1)/(xv2-xv1);
247   ky  = (yn2-yn1)/(yv2-yv1);
248   // iterates over series
249   // n-square algorithm :-(
250   SeriesListIterator li(series);
251   int z;
252   for(z=0;li;li++,z++)
253     {
254       int t;
255       CoordListIterator lc(*li.current());
256       // scales each point
257       for(t = 0;lc;lc++,t++)
258 	{
259 	  VDKPoint p(
260 		     int(xn1+kx*(lc.current().x-xv1)),
261 		     int((yn1+ky*(lc.current().y-yv1)))
262 		     );
263 	  Plot(p,t,li.current());
264 	}
265     }
266   // draw x,y axis ticks
267   DrawTicks();
268   //
269   DrawLabels();
270   // redraw with no expose event
271   Redraw();
272 }
273 /*
274 Draws x,y ticks (major and minor)
275 */
DrawTicks()276 void VDKChart::DrawTicks()
277 {
278   char buff[32];
279   // compute and draws x,y axis min,max value
280   double lminx = xn1+kx*(domainmin.x-xv1);
281   double lminy = yn1+ky*(domainmin.y-yv1);
282   double lmaxx = xn1+kx*(domainmax.x-xv1);
283   double lmaxy = yn1+ky*(domainmax.y-yv1);
284   VDKFont* vdkfont = Font;
285   GdkFont* font = vdkfont->AsGdkFont();
286   int h = font ? font->ascent + font->descent + 1 : 10;
287   int w;
288   // draws x ticks
289   int t;
290   VDKRgb fg = Foreground;
291   if(fg.red >= 0)
292     SetColor(fg);
293   double j,lx,k = (domainmax.x-domainmin.x)/10.0;
294   int x_digits = LabelXDigits;
295   int y_digits = LabelYDigits;
296   for(j = domainmin.x, t=0; j <= domainmax.x; j+=k,t++)
297     {
298       lx = xn1+kx*(j-xv1);
299       if ( (t%2) == 0)
300 	{
301 	  sprintf(buff,"%.*f",x_digits,j);
302 	  w = gdk_string_width (font, buff);
303 	  DrawString(int(lx-w/2),int(lminy+tickL+h),buff);
304 	  // major tick
305 	  DrawLine(int(lx),int(lminy),int(lx),int(lminy+tickL));
306 	}
307       else
308 	// minor tick
309 	DrawLine(int(lx),int(lminy),int(lx),int(lminy+tickL/2));
310     }
311   if(t <= 10)
312     {
313       sprintf(buff,"%.*f",x_digits,domainmax.x);
314       w = gdk_string_width (font, buff);
315       DrawString(int(lmaxx-w/2),int(lminy+tickL+h),buff);
316     }
317   // draws y ticks
318   double ly;
319   k = (domainmax.y-domainmin.y)/10;
320   for(j = domainmin.y, t=0; j <= domainmax.y ; j+=k,t++)
321     {
322       ly = yn1+ky*(j-yv1);
323       if ( (t%2) == 0)
324 	{
325 	  sprintf(buff,"%.*f",y_digits,j);
326 	  w = gdk_string_width (font, buff);
327 	  DrawString(int(lminx-w-tickL),int(ly+h/3),buff);
328 	  // major tick
329 	  DrawLine(int(lminx),int(ly),int(lminx-tickL),int(ly));
330 	}
331       else
332 	// minor tick
333 	DrawLine(int(lminx),int(ly),int(lminx-tickL/2),int(ly));
334     }
335   if(t <= 10)
336     {
337        sprintf(buff,"%.*f",y_digits,domainmax.y);
338        w = gdk_string_width (font, buff);
339        DrawString(int(lminx-w-tickL),int(lmaxy),buff);
340     }
341 }
342 /*
343 draw x and y labels
344 */
DrawLabels()345 void VDKChart::DrawLabels()
346 {
347   // x labels
348   VDKUString s = LabelX;
349   VDKPoint size = Usize;
350   VDKFont* vdkfont = Font;
351   GdkFont* font = vdkfont->AsGdkFont();
352   VDKRgb fg = Foreground;
353   if(fg.red >= 0)
354     SetColor(fg);
355   if(! s.isNull())
356     {
357       VDKPoint center(size.X()/2,
358 		      axis.Domain().Origin().Y() +
359 		      ChartBorder - 5);
360       int half_w = font ? (gdk_string_width (font, (char*) s))/2 : 5;
361       DrawString(center.X()-half_w,center.Y(), (char*) s);
362     }
363   s = LabelY;
364   if(! s.isNull())
365     {
366       int h = font ? font->ascent+font->descent : 10;
367       size_t z = std::strlen((char*) s);
368       int sh = (int)(h*z);
369       char *p = (char*) s;
370       VDKPoint center(axis.Domain().Origin().X() -
371 		      ChartBorder + 5, size.Y()/2-sh/2);
372       size_t t = 0;
373       for(; t < z; t++,p++) DrawText(center.X(),center.Y()+t*h, p,1);
374     }
375 }
376 /*
377 set drawing color.
378 Use this to set color for Plot()
379 function. (see VDKLineChart::Plot())
380 */
SetColor(VDKRgb rgb)381 void VDKChart::SetColor(VDKRgb rgb)
382 {
383   GdkColor *color = (GdkColor*) malloc (sizeof (GdkColor));
384   GdkColormap *colormap = gdk_window_get_colormap (Widget()->window);
385   color->red    = (guint16)(rgb.red << 8);
386   color->green  = (guint16)(rgb.green << 8);
387   color->blue   = (guint16)(rgb.blue << 8);
388   if (!gdk_color_alloc (colormap, color))
389     gdk_color_black (colormap, color);
390   gdk_gc_set_foreground(GC(), color);
391   free(color);
392 }
393 /*
394 set line style
395 Use this to set line style for Plot()
396 function. (see VDKLineChart::Plot())
397 */
SetLineAttributes(gint lineWidth,GdkLineStyle lineStyle,GdkCapStyle capStyle,GdkJoinStyle joinStyle)398 void VDKChart::SetLineAttributes(
399 				 gint lineWidth,
400 				 GdkLineStyle lineStyle,
401 				 GdkCapStyle capStyle,
402 				 GdkJoinStyle joinStyle)
403 {
404 if(gc)
405   gdk_gc_set_line_attributes(gc,
406 			     lineWidth,
407 			     lineStyle,
408 			     capStyle,
409 			     joinStyle);
410 }
411 //////////////////// CHART AXIS /////////////////////////////
412 /*
413 constructor:
414 compute axis origin and chart domain
415 based on chart size and chart border.
416 This will be surely changed by
417 configure event. (see OnConfigure())
418 */
ChartAxis(VDKChart * owner,int w,int h)419 ChartAxis::ChartAxis(VDKChart* owner,int w, int h):
420   owner(owner)
421 {
422 // patch Bug#262091
423 /*
424   domain = VDKRect(owner->ChartBorder,
425 		   h-owner->ChartBorder,
426 		   w-owner->ChartBorder*2,
427 		   h-owner->ChartBorder*2);
428 */
429 VDKRect r(owner->ChartBorder,
430  		   h-owner->ChartBorder,
431  		   w-owner->ChartBorder*2,
432  		   h-owner->ChartBorder*2);
433 domain = r;
434 }
435 /*
436 copy-initializer
437 */
ChartAxis(ChartAxis & a)438 ChartAxis::ChartAxis(ChartAxis& a)
439 {
440   owner = a.owner;
441   domain = a.domain;
442 }
443 /*
444 draws axis on owner canvas
445 */
Draw()446 void ChartAxis::Draw()
447 {
448 if(!owner)
449   return;
450  VDKRgb fg = owner->Foreground;
451  if(fg.red >= 0)
452    owner-> SetColor(fg);
453  owner->DrawLine(domain.Origin().X(),
454 		 domain.Origin().Y(),
455 		 domain.Origin().X(),
456 		 owner->ChartBorder);
457  owner->DrawLine(domain.Origin().X(),
458 		 domain.Origin().Y(),
459 		 domain.Origin().X()+ domain.W(),
460 		 domain.Origin().Y());
461 }
462 
463 /////////////////////// SERIES ///////////////////////
464 /*
465 add a point ot series,
466 computes on the fly
467 max and min x,y
468 */
Add(double x,double y)469 void Series::Add(double x, double y)
470 {
471   if(size() < 1)
472     {
473       max.x = x; max.y = y;
474       min.x = x; min.y = y;
475     }
476   else
477     {
478       max.x = max.x < x ? x : max.x;
479       max.y = max.y < y ? y : max.y;
480       min.x = min.x > x ? x : min.x;
481       min.y = min.y > y ? y : min.y;
482     }
483   Coord c(x,y);
484   CoordList::add(c);
485 }
486 /*
487 add a point array
488  */
Add(double * x,double * y,int n)489 void Series::Add(double* x, double* y, int n)
490 {
491 int i;
492 for(i=0;i<n;i++)
493   Add(x[i],y[i]);
494 }
495 
496 ///////////////////////// LINE CHART //////////////
497 /*
498 Plot a line chart
499  */
Plot(VDKPoint & p,int t,Series * series)500 void VDKLineChart::Plot(VDKPoint& p, int t, Series* series)
501 {
502 static int fx,fy;
503 // each time a new series should be plotted
504 // set canvas color and line attributes.
505 // This happens whenever t == 0
506 if(t == 0)
507   {
508     VDKRgb color = series->Color;
509     SetColor(color);
510     SetLineAttributes(
511 		      series->LineWidth,
512 		      series->LineStyle,
513 		      series->LineCapStyle,
514 		      series->LineJoinStyle);
515     fx = p.X();
516     fy = p.Y();
517   }
518  else
519    {
520      // pixmap is a protected member of VDKCanvas class
521      if(pixmap)
522        gdk_draw_line(pixmap, GC(), fx,fy,p.X(),p.Y());
523      fx = p.X(); fy = p.Y();
524    }
525 }
526 ///////////////////////// SCATTERED CHART //////////////
527 /*
528 Plot a scattered chart
529  */
Plot(VDKPoint & p,int t,Series * series)530 void VDKScatteredChart::Plot(VDKPoint& p, int t, Series* series)
531 {
532 if(t == 0)
533   {
534     VDKRgb color = series->Color;
535     SetColor(color);
536     SetLineAttributes(
537 		      series->LineWidth,
538 		      series->LineStyle,
539 		      series->LineCapStyle,
540 		      series->LineJoinStyle);
541   }
542 gdk_draw_rectangle(pixmap,GC(),TRUE,p.X()-2,p.Y()-2,4,4);
543 }
544 ///////////////////////// BAR CHART //////////////
545 /*
546 Plot a bar chart
547  */
Plot(VDKPoint & p,int t,Series * series)548 void VDKBarChart::Plot(VDKPoint& p, int t, Series* series)
549 {
550   if(t == 0)
551     {
552       VDKRgb color = series->Color;
553       SetColor(color);
554       SetLineAttributes(
555 			series->LineWidth,
556 			series->LineStyle,
557 			series->LineCapStyle,
558 			series->LineJoinStyle);
559     }
560 
561   if(Labels)
562     {
563       char buff[64];
564       VDKFont* vdkfont = Font;
565       GdkFont* font = vdkfont->AsGdkFont();
566       sprintf(buff,"%.1f",(p.Y() - yn1 + ky*yv1)/ky);
567       int w = font ? gdk_string_width (font, buff): 10;
568       gdk_draw_string(pixmap,font,GC(),p.X()- w/2,p.Y()-2,buff);
569     }
570 
571   gdk_draw_rectangle(pixmap,GC(),TRUE,
572 		     p.X()-BarWidth/2,
573 		     p.Y(),
574 		     BarWidth,
575 		     axis.Domain().Origin().Y()-p.Y()
576 		     );
577 }
578 
579 
580 
581 
582 
583 
584 
585