1#
2#  tkextlib/tcllib/plotchart.rb
3#                               by Hidetoshi NAGAI (nagai@ai.kyutech.ac.jp)
4#
5#   * Part of tcllib extension
6#   * Simple plotting and charting package
7#
8# (The following is the original description of the library.)
9#
10# Plotchart is a Tcl-only package that focuses on the easy creation of
11# xy-plots, barcharts and other common types of graphical presentations.
12# The emphasis is on ease of use, rather than flexibility. The procedures
13# that create a plot use the entire canvas window, making the layout of the
14# plot completely automatic.
15#
16# This results in the creation of an xy-plot in, say, ten lines of code:
17# --------------------------------------------------------------------
18#    package require Plotchart
19#
20#    canvas .c -background white -width 400 -height 200
21#    pack   .c -fill both
22#
23#    #
24#    # Create the plot with its x- and y-axes
25#    #
26#    set s [::Plotchart::createXYPlot .c {0.0 100.0 10.0} {0.0 100.0 20.0}]
27#
28#    foreach {x y} {0.0 32.0 10.0 50.0 25.0 60.0 78.0 11.0 } {
29#        $s plot series1 $x $y
30#    }
31#
32#    $s title "Data series"
33# --------------------------------------------------------------------
34#
35# A drawback of the package might be that it does not do any data management.
36# So if the canvas that holds the plot is to be resized, the whole plot must
37# be redrawn. The advantage, though, is that it offers a number of plot and
38# chart types:
39#
40#    * XY-plots like the one shown above with any number of data series.
41#    * Stripcharts, a kind of XY-plots where the horizontal axis is adjusted
42#      automatically. The result is a kind of sliding window on the data
43#      series.
44#    * Polar plots, where the coordinates are polar instead of cartesian.
45#    * Isometric plots, where the scale of the coordinates in the two
46#      directions is always the same, i.e. a circle in world coordinates
47#      appears as a circle on the screen.
48#      You can zoom in and out, as well as pan with these plots (Note: this
49#      works best if no axes are drawn, the zooming and panning routines do
50#      not distinguish the axes), using the mouse buttons with the control
51#      key and the arrow keys with the control key.
52#    * Piecharts, with automatic scaling to indicate the proportions.
53#    * Barcharts, with either vertical or horizontal bars, stacked bars or
54#      bars side by side.
55#    * Timecharts, where bars indicate a time period and milestones or other
56#      important moments in time are represented by triangles.
57#    * 3D plots (both for displaying surfaces and 3D bars)
58#
59
60require 'tk'
61require 'tkextlib/tcllib.rb'
62
63# TkPackage.require('Plotchart', '0.9')
64# TkPackage.require('Plotchart', '1.1')
65# TkPackage.require('Plotchart', '1.6.3')
66TkPackage.require('Plotchart')
67
68module Tk
69  module Tcllib
70    module Plotchart
71      PACKAGE_NAME = 'Plotchart'.freeze
72      def self.package_name
73        PACKAGE_NAME
74      end
75
76      def self.package_version
77        begin
78          TkPackage.require('Plotchart')
79        rescue
80          ''
81        end
82      end
83    end
84  end
85end
86
87module Tk::Tcllib::Plotchart
88  extend TkCore
89  ############################
90  def self.view_port(w, *args) # args := pxmin, pymin, pxmax, pymax
91    tk_call_without_enc('::Plotchart::viewPort', w.path, *(args.flatten))
92  end
93
94  def self.world_coordinates(w, *args) # args := xmin, ymin, xmax, ymax
95    tk_call_without_enc('::Plotchart::worldCoordinates',
96                        w.path, *(args.flatten))
97  end
98
99  def self.world_3D_coordinates(w, *args)
100    # args := xmin, ymin, zmin, xmax, ymax, zmax
101    tk_call_without_enc('::Plotchart::world3DCoordinates',
102                        w.path, *(args.flatten))
103  end
104
105  def self.coords_to_pixel(w, x, y)
106    list(tk_call_without_enc('::Plotchart::coordsToPixel', w.path, x, y))
107  end
108
109  def self.coords_3D_to_pixel(w, x, y, z)
110    list(tk_call_without_enc('::Plotchart::coords3DToPixel', w.path, x, y, z))
111  end
112
113  def self.plotconfig(*args)
114    case args.length
115    when 0, 1, 2
116      # 0: (no args) --> list of chat types
117      # 1: charttype --> list of components
118      # 2: charttype, component --> list of properties
119      simplelist(tk_call('::Plotchart::plotconfig', *args))
120    when 3
121      # 3: charttype, component, property --> current value
122      tk_call('::Plotchart::plotconfig', *args)
123    else
124      # 4: charttype, component, property, value : set new value
125      # 5+: Error on Tcl/Tk
126      tk_call('::Plotchart::plotconfig', *args)
127      nil
128    end
129  end
130
131  def self.plotpack(w, dir, *plots)
132    tk_call_without_enc('::Plotchart::plotpack', w.path, dir, *plots)
133    w
134  end
135
136  def self.polar_coordinates(w, radmax)
137    tk_call_without_enc('::Plotchart::polarCoordinates', w.path, radmax)
138  end
139
140  def self.polar_to_pixel(w, rad, phi)
141    list(tk_call_without_enc('::Plotchart::polarToPixel', w.path, rad, phi))
142  end
143
144  def self.pixel_to_coords(w, x, y)
145    list(tk_call_without_enc('::Plotchart::coordsToPixel', w.path, x, y))
146  end
147
148  def self.determine_scale(*args) # (xmin, xmax, inverted=false)
149    tk_call_without_enc('::Plotchart::determineScale', *args)
150  end
151
152  def self.set_zoom_pan(w)
153    tk_call_without_enc('::Plotchart::setZoomPan', w.path)
154  end
155
156  ############################
157  module ChartMethod
158    include TkCore
159
160    def title(str)
161      tk_call_without_enc(@chart, 'title', _get_eval_enc_str(str))
162      self
163    end
164
165    def save_plot(filename)
166      tk_call_without_enc(@chart, 'saveplot', filename)
167      self
168    end
169
170    def xtext(str)
171      tk_call_without_enc(@chart, 'xtext', _get_eval_enc_str(str))
172      self
173    end
174
175    def ytext(str)
176      tk_call_without_enc(@chart, 'ytext', _get_eval_enc_str(str))
177      self
178    end
179
180    def xconfig(key, value=None)
181      if key.kind_of?(Hash)
182        tk_call_without_enc(@chart, 'xconfig', *hash_kv(key, true))
183      else
184        tk_call(@chart, 'xconfig', "-#{key}",value)
185      end
186      self
187    end
188
189    def yconfig(key, value=None)
190      if key.kind_of?(Hash)
191        tk_call_without_enc(@chart, 'yconfig', *hash_kv(key, true))
192      else
193        tk_call(@chart, 'yconfig', "-#{key}", value)
194      end
195      self
196    end
197
198    def background(part, color_or_image, dir)
199      tk_call_without_enc(@chart, 'background',
200                          part, color_or_image, dir)
201      self
202    end
203
204    def xticklines(color=None)
205      tk_call(@chart, 'xticklines', color)
206      self
207    end
208
209    def yticklines(color=None)
210      tk_call(@chart, 'yticklines', color)
211      self
212    end
213
214    def legendconfig(key, value=None)
215      if key.kind_of?(Hash)
216        tk_call_without_enc(@chart, 'legendconfig', *hash_kv(key, true))
217      else
218        tk_call(@chart, 'legendconfig', "-#{key}", value)
219      end
220      self
221    end
222
223    def legend(series, text)
224      tk_call_without_enc(@chart, 'legend',
225                          _get_eval_enc_str(series), _get_eval_enc_str(text))
226      self
227    end
228
229    def balloon(*args) # args => (x, y, text, dir) or ([x, y], text, dir)
230      if args[0].kind_of?(Array)
231        # args => ([x, y], text, dir)
232        x, y = args.shift
233      else
234        # args => (x, y, text, dir)
235        x = args.shift
236        y = args.shift
237      end
238
239      text, dir = args
240
241      tk_call_without_enc(@chart, 'balloon', x, y,
242                          _get_eval_enc_str(text), dir)
243      self
244    end
245
246    def balloonconfig(key, value=None)
247      if key.kind_of?(Hash)
248        tk_call_without_enc(@chart, 'balloonconfig', *hash_kv(key, true))
249      else
250        tk_call(@chart, 'balloonconfig', "-#{key}", value)
251      end
252    end
253
254    def plaintext(*args) # args => (x, y, text, dir) or ([x, y], text, dir)
255      if args[0].kind_of?(Array)
256        # args => ([x, y], text, dir)
257        x, y = args.shift
258      else
259        # args => (x, y, text, dir)
260        x = args.shift
261        y = args.shift
262      end
263
264      text, dir = args
265
266      tk_call_without_enc(@chart, 'plaintext', x, y,
267                          _get_eval_enc_str(text), dir)
268      self
269    end
270
271    ############################
272
273    def view_port(*args) # args := pxmin, pymin, pxmax, pymax
274      tk_call_without_enc('::Plotchart::viewPort', @path, *(args.flatten))
275      self
276    end
277
278    def world_coordinates(*args) # args := xmin, ymin, xmax, ymax
279      tk_call_without_enc('::Plotchart::worldCoordinates',
280                          @path, *(args.flatten))
281      self
282    end
283
284    def world_3D_coordinates(*args)
285      # args := xmin, ymin, zmin, xmax, ymax, zmax
286      tk_call_without_enc('::Plotchart::world3DCoordinates',
287                          @path, *(args.flatten))
288      self
289    end
290
291    def coords_to_pixel(x, y)
292      list(tk_call_without_enc('::Plotchart::coordsToPixel', @path, x, y))
293    end
294
295    def coords_3D_to_pixel(x, y, z)
296      list(tk_call_without_enc('::Plotchart::coords3DToPixel', @path, x, y, z))
297    end
298
299    def plotpack(dir, *plots)
300      tk_call_without_enc('::Plotchart::plotpack', @path, dir, *plots)
301      self
302    end
303
304    def polar_coordinates(radmax)
305      tk_call_without_enc('::Plotchart::polarCoordinates', @path, radmax)
306      self
307    end
308
309    def polar_to_pixel(rad, phi)
310      list(tk_call_without_enc('::Plotchart::polarToPixel', @path, rad, phi))
311    end
312
313    def pixel_to_coords(x, y)
314      list(tk_call_without_enc('::Plotchart::coordsToPixel', @path, x, y))
315    end
316
317    def determine_scale(xmax, ymax)
318      tk_call_without_enc('::Plotchart::determineScale', @path, xmax, ymax)
319      self
320    end
321
322    def set_zoom_pan()
323      tk_call_without_enc('::Plotchart::setZoomPan', @path)
324      self
325    end
326  end
327
328  ############################
329  class XYPlot < Tk::Canvas
330    include ChartMethod
331
332    TkCommandNames = [
333      'canvas'.freeze,
334      '::Plotchart::createXYPlot'.freeze
335    ].freeze
336
337    def initialize(*args) # args := ([parent,] xaxis, yaxis [, keys])
338                          # xaxis := Array of [minimum, maximum, stepsize]
339                          # yaxis := Array of [minimum, maximum, stepsize]
340      if args[0].kind_of?(Array)
341        @xaxis = args.shift
342        @yaxis = args.shift
343
344        super(*args) # create canvas widget
345      else
346        parent = args.shift
347
348        @xaxis = args.shift
349        @yaxis = args.shift
350
351        if parent.kind_of?(Tk::Canvas)
352          @path = parent.path
353        else
354          super(parent, *args) # create canvas widget
355        end
356      end
357
358      @chart = _create_chart
359    end
360
361    def _create_chart
362      p self.class::TkCommandNames[1] if $DEBUG
363      tk_call_without_enc(self.class::TkCommandNames[1], @path,
364                          array2tk_list(@xaxis), array2tk_list(@yaxis))
365    end
366    private :_create_chart
367
368    def __destroy_hook__
369      Tk::Tcllib::Plotchart::PlotSeries::SeriesID_TBL.mutex.synchronize{
370        Tk::Tcllib::Plotchart::PlotSeries::SeriesID_TBL.delete(@path)
371      }
372    end
373
374    def plot(series, x, y)
375      tk_call_without_enc(@chart, 'plot', _get_eval_enc_str(series), x, y)
376      self
377    end
378
379    def contourlines(xcrd, ycrd, vals, clss=None)
380      xcrd = array2tk_list(xcrd) if xcrd.kind_of?(Array)
381      ycrd = array2tk_list(ycrd) if ycrd.kind_of?(Array)
382      vals = array2tk_list(vals) if vals.kind_of?(Array)
383      clss = array2tk_list(clss) if clss.kind_of?(Array)
384
385      tk_call(@chart, 'contourlines', xcrd, ycrd, vals, clss)
386      self
387    end
388
389    def contourfill(xcrd, ycrd, vals, clss=None)
390      xcrd = array2tk_list(xcrd) if xcrd.kind_of?(Array)
391      ycrd = array2tk_list(ycrd) if ycrd.kind_of?(Array)
392      vals = array2tk_list(vals) if vals.kind_of?(Array)
393      clss = array2tk_list(clss) if clss.kind_of?(Array)
394
395      tk_call(@chart, 'contourfill', xcrd, ycrd, vals, clss)
396      self
397    end
398
399    def contourbox(xcrd, ycrd, vals, clss=None)
400      xcrd = array2tk_list(xcrd) if xcrd.kind_of?(Array)
401      ycrd = array2tk_list(ycrd) if ycrd.kind_of?(Array)
402      vals = array2tk_list(vals) if vals.kind_of?(Array)
403      clss = array2tk_list(clss) if clss.kind_of?(Array)
404
405      tk_call(@chart, 'contourbox', xcrd, ycrd, vals, clss)
406      self
407    end
408
409    def color_map(colors)
410      colors = array2tk_list(colors) if colors.kind_of?(Array)
411
412      tk_call_without_enc(@chart, 'colorMap', colors)
413      self
414    end
415
416    def grid_cells(xcrd, ycrd)
417      xcrd = array2tk_list(xcrd) if xcrd.kind_of?(Array)
418      ycrd = array2tk_list(ycrd) if ycrd.kind_of?(Array)
419
420      tk_call_without_enc(@chart, 'grid', xcrd, ycrd)
421      self
422    end
423
424    def dataconfig(series, key, value=None)
425      if key.kind_of?(Hash)
426        tk_call_without_enc(@chart, 'dataconfig', series, *hash_kv(key, true))
427      else
428        tk_call(@chart, 'dataconfig', series, "-#{key}", value)
429      end
430    end
431
432    def rescale(xscale, yscale) # xscale|yscale => [newmin, newmax, newstep]
433      tk_call_without_enc(@chart, 'rescale', xscale, yscale)
434      self
435    end
436
437    def trend(series, xcrd, ycrd)
438      tk_call_without_enc(@chart, 'trend',
439                          _get_eval_enc_str(series), xcrd, ycrd)
440      self
441    end
442
443    def rchart(series, xcrd, ycrd)
444      tk_call_without_enc(@chart, 'rchart',
445                          _get_eval_enc_str(series), xcrd, ycrd)
446      self
447    end
448
449    def interval(series, xcrd, ymin, ymax, ycenter=None)
450      tk_call(@chart, 'interval', series, xcrd, ymin, ymax, ycenter)
451      self
452    end
453
454    def box_and_whiskers(series, xcrd, ycrd)
455      tk_call_without_enc(@chart, 'box-and-whiskers',
456                          _get_eval_enc_str(series), xcrd, ycrd)
457      self
458    end
459    alias box_whiskers box_and_whiskers
460
461    def vectorconfig(series, key, value=None)
462      if key.kind_of?(Hash)
463        tk_call_without_enc(@chart, 'vectorconfig',
464                            _get_eval_enc_str(series), *hash_kv(key, true))
465      else
466        tk_call(@chart, 'vectorconfig', series, "-#{key}", value)
467      end
468      self
469    end
470
471    def vector(series, xcrd, ycrd, ucmp, vcmp)
472      tk_call_without_enc(@chart, 'vector', _get_eval_enc_str(series),
473                          xcrd, ycrd, ucmp, vcmp)
474      self
475    end
476
477    def dotconfig(series, key, value=None)
478      if key.kind_of?(Hash)
479        tk_call_without_enc(@chart, 'dotconfig',
480                            _get_eval_enc_str(series), *hash_kv(key, true))
481      else
482        tk_call(@chart, 'dotconfig', series, "-#{key}", value)
483      end
484      self
485    end
486
487    def dot(series, xcrd, ycrd, value)
488      tk_call_without_enc(@chart, 'dot', _get_eval_enc_str(series),
489                          xcrd, ycrd, value)
490      self
491    end
492  end
493
494  ############################
495  class Stripchart < XYPlot
496    TkCommandNames = [
497      'canvas'.freeze,
498      '::Plotchart::createStripchart'.freeze
499    ].freeze
500  end
501
502  ############################
503  class TXPlot < XYPlot
504    TkCommandNames = [
505      'canvas'.freeze,
506      '::Plotchart::createTXPlot'.freeze
507    ].freeze
508  end
509
510  ############################
511  class XLogYPlot < XYPlot
512    TkCommandNames = [
513      'canvas'.freeze,
514      '::Plotchart::createXLogYPlot'.freeze
515    ].freeze
516  end
517
518  ############################
519  class Histogram < XYPlot
520    TkCommandNames = [
521      'canvas'.freeze,
522      '::Plotchart::createHistgram'.freeze
523    ].freeze
524  end
525
526  ############################
527  class PolarPlot < Tk::Canvas
528    include ChartMethod
529
530    TkCommandNames = [
531      'canvas'.freeze,
532      '::Plotchart::createPolarplot'.freeze
533    ].freeze
534
535    def initialize(*args) # args := ([parent,] radius_data [, keys])
536                          # radius_data := Array of [maximum_radius, stepsize]
537      if args[0].kind_of?(Array)
538        @radius_data = args.shift
539
540        super(*args) # create canvas widget
541      else
542        parent = args.shift
543
544        @radius_data = args.shift
545
546        if parent.kind_of?(Tk::Canvas)
547          @path = parent.path
548        else
549          super(parent, *args) # create canvas widget
550        end
551      end
552
553      @chart = _create_chart
554    end
555
556    def _create_chart
557      p self.class::TkCommandNames[1] if $DEBUG
558      tk_call_without_enc(self.class::TkCommandNames[1], @path,
559                          array2tk_list(@radius_data))
560    end
561    private :_create_chart
562
563    def __destroy_hook__
564      Tk::Tcllib::Plotchart::PlotSeries::SeriesID_TBL.mutex.synchronize{
565        Tk::Tcllib::Plotchart::PlotSeries::SeriesID_TBL.delete(@path)
566      }
567    end
568
569    def plot(series, radius, angle)
570      tk_call_without_enc(@chart, 'plot', _get_eval_enc_str(series),
571                          radius, angle)
572      self
573    end
574
575    def dataconfig(series, key, value=None)
576      if key.kind_of?(Hash)
577        tk_call_without_enc(@chart, 'dataconfig', _get_eval_enc_str(series),
578                            *hash_kv(key, true))
579      else
580        tk_call(@chart, 'dataconfig', series, "-#{key}", value)
581      end
582    end
583  end
584  Polarplot = PolarPlot
585
586  ############################
587  class IsometricPlot < Tk::Canvas
588    include ChartMethod
589
590    TkCommandNames = [
591      'canvas'.freeze,
592      '::Plotchart::createIsometricPlot'.freeze
593    ].freeze
594
595    def initialize(*args) # args := ([parent,] xaxis, yaxis, [, step] [, keys])
596                          # xaxis := Array of [minimum, maximum]
597                          # yaxis := Array of [minimum, maximum]
598                          # step := Float of stepsize | "noaxes" | :noaxes
599      if args[0].kind_of?(Array)
600        @xaxis = args.shift
601        @yaxis = args.shift
602
603        if args[0].kind_of?(Hash)
604          @stepsize = :noaxes
605        else
606          @stepsize = args.shift
607        end
608
609        super(*args) # create canvas widget
610      else
611        parent = args.shift
612
613        @xaxis = args.shift
614        @yaxis = args.shift
615
616        if args[0].kind_of?(Hash)
617          @stepsize = :noaxes
618        else
619          @stepsize = args.shift
620        end
621
622        if parent.kind_of?(Tk::Canvas)
623          @path = parent.path
624        else
625          super(parent, *args) # create canvas widget
626        end
627      end
628
629      @chart = _create_chart
630    end
631
632    def _create_chart
633      p self.class::TkCommandNames[1] if $DEBUG
634      tk_call_without_enc(self.class::TkCommandNames[1], @path,
635                          array2tk_list(@xaxis), array2tk_list(@yaxis),
636                          @stepsize)
637    end
638    private :_create_chart
639
640    def plot(type, *args)
641      self.__send__("plot_#{type.to_s.tr('-', '_')}", *args)
642    end
643
644    def plot_rectangle(*args) # args := x1, y1, x2, y2, color
645      tk_call_without_enc(@chart, 'plot', 'rectangle', *(args.flatten))
646      self
647    end
648
649    def plot_filled_rectangle(*args) # args := x1, y1, x2, y2, color
650      tk_call_without_enc(@chart, 'plot', 'filled-rectangle', *(args.flatten))
651      self
652    end
653
654    def plot_circle(*args) # args := xc, yc, radius, color
655      tk_call_without_enc(@chart, 'plot', 'circle', *(args.flatten))
656      self
657    end
658
659    def plot_filled_circle(*args) # args := xc, yc, radius, color
660      tk_call_without_enc(@chart, 'plot', 'filled-circle', *(args.flatten))
661      self
662    end
663  end
664  Isometricplot = IsometricPlot
665
666  ############################
667  class Plot3D < Tk::Canvas
668    include ChartMethod
669
670    TkCommandNames = [
671      'canvas'.freeze,
672      '::Plotchart::create3DPlot'.freeze
673    ].freeze
674
675    def initialize(*args) # args := ([parent,] xaxis, yaxis, zaxis [, keys])
676                          # xaxis := Array of [minimum, maximum, stepsize]
677                          # yaxis := Array of [minimum, maximum, stepsize]
678                          # zaxis := Array of [minimum, maximum, stepsize]
679      if args[0].kind_of?(Array)
680        @xaxis = args.shift
681        @yaxis = args.shift
682        @zaxis = args.shift
683
684        super(*args) # create canvas widget
685      else
686        parent = args.shift
687
688        @xaxis = args.shift
689        @yaxis = args.shift
690        @zaxis = args.shift
691
692        if parent.kind_of?(Tk::Canvas)
693          @path = parent.path
694        else
695          super(parent, *args) # create canvas widget
696        end
697      end
698
699      @chart = _create_chart
700    end
701
702    def _create_chart
703      p self.class::TkCommandNames[1] if $DEBUG
704      tk_call_without_enc(self.class::TkCommandNames[1], @path,
705                          array2tk_list(@xaxis),
706                          array2tk_list(@yaxis),
707                          array2tk_list(@zaxis))
708    end
709    private :_create_chart
710
711    def plot_function(cmd=Proc.new)
712      Tk.ip_eval("proc #{@path}_#{@chart} {x y} {#{install_cmd(cmd)} $x $y}")
713      tk_call_without_enc(@chart, 'plotfunc', "#{@path}_#{@chart}")
714      self
715    end
716
717    def plot_funcont(conts, cmd=Proc.new)
718      conts = array2tk_list(conts) if conts.kind_of?(Array)
719      Tk.ip_eval("proc #{@path}_#{@chart} {x y} {#{install_cmd(cmd)} $x $y}")
720      tk_call_without_enc(@chart, 'plotfuncont', "#{@path}_#{@chart}", conts)
721      self
722    end
723
724    def grid_size(nxcells, nycells)
725      tk_call_without_enc(@chart, 'gridsize', nxcells, nycells)
726      self
727    end
728
729    def plot_line(dat, color)
730      # dat has to be provided as a 2 level array.
731      # 1st level contains rows, drawn in y-direction,
732      # and each row is an array whose elements are drawn in x-direction,
733      # for the columns.
734      tk_call_without_enc(@chart, 'plotline', dat, color)
735      self
736    end
737
738    def plot_data(dat)
739      # dat has to be provided as a 2 level array.
740      # 1st level contains rows, drawn in y-direction,
741      # and each row is an array whose elements are drawn in x-direction,
742      # for the columns.
743      tk_call_without_enc(@chart, 'plotdata', dat)
744      self
745    end
746
747    def zconfig(key, value=None)
748      if key.kind_of?(Hash)
749        tk_call_without_enc(@chart, 'zconfig', *hash_kv(key, true))
750      else
751        tk_call(@chart, 'zconfig', "-#{key}", value)
752      end
753      self
754    end
755
756    def colour(fill, border)
757      # configure the colours to use for polygon borders and inner area
758      tk_call_without_enc(@chart, 'colour', fill, border)
759      self
760    end
761    alias colours colour
762    alias colors  colour
763    alias color   colour
764  end
765
766  ############################
767  class Barchart3D < Tk::Canvas
768    include ChartMethod
769
770    TkCommandNames = [
771      'canvas'.freeze,
772      '::Plotchart::create3DBarchart'.freeze
773    ].freeze
774
775    def initialize(*args) # args := ([parent,] yaxis, nobars [, keys])
776                          # yaxis  := Array of [minimum, maximum, stepsize]
777                          # nobars := number of bars
778      if args[0].kind_of?(Array)
779        @yaxis = args.shift
780        @nobars = args.shift
781
782        super(*args) # create canvas widget
783      else
784        parent = args.shift
785
786        @yaxis = args.shift
787        @nobars = args.shift
788
789        if parent.kind_of?(Tk::Canvas)
790          @path = parent.path
791        else
792          super(parent, *args) # create canvas widget
793        end
794      end
795
796      @chart = _create_chart
797    end
798
799    def _create_chart
800      p self.class::TkCommandNames[1] if $DEBUG
801      tk_call_without_enc(self.class::TkCommandNames[1], @path,
802                          array2tk_list(@yaxis), @nobars)
803    end
804    private :_create_chart
805
806    def plot(label, yvalue, color)
807      tk_call_without_enc(@chart, 'plot', _get_eval_enc_str(label),
808                          _get_eval_enc_str(yvalue), color)
809      self
810    end
811
812    def config(key, value=None)
813      if key.kind_of?(Hash)
814        tk_call_without_enc(@chart, 'config', *hash_kv(key, true))
815      else
816        tk_call(@chart, 'config', "-#{key}", value)
817      end
818      self
819    end
820  end
821
822  ############################
823  class RibbonChart3D < Tk::Canvas
824    include ChartMethod
825
826    TkCommandNames = [
827      'canvas'.freeze,
828      '::Plotchart::create3DRibbonChart'.freeze
829    ].freeze
830
831    def initialize(*args) # args := ([parent,] names, yaxis, zaxis [, keys])
832                          # names := Array of the series
833                          # yaxis := Array of [minimum, maximum, stepsize]
834                          # zaxis := Array of [minimum, maximum, stepsize]
835      if args[0].kind_of?(Array)
836        @names = args.shift
837        @yaxis = args.shift
838        @zaxis = args.shift
839
840        super(*args) # create canvas widget
841      else
842        parent = args.shift
843
844        @names = args.shift
845        @yaxis = args.shift
846        @zaxis = args.shift
847
848        if parent.kind_of?(Tk::Canvas)
849          @path = parent.path
850        else
851          super(parent, *args) # create canvas widget
852        end
853      end
854
855      @chart = _create_chart
856    end
857
858    def _create_chart
859      p self.class::TkCommandNames[1] if $DEBUG
860      tk_call_without_enc(self.class::TkCommandNames[1], @path,
861                          array2tk_list(@names),
862                          array2tk_list(@yaxis),
863                          array2tk_list(@zaxis))
864    end
865    private :_create_chart
866
867    def line(*args) # xypairs, color
868      color = args.pop # last argument is a color
869      xypairs = TkComm.slice_ary(args.flatten, 2) # regenerate xypairs
870      tk_call_without_enc(@chart, 'line', xypairs, color)
871      self
872    end
873
874    def area(*args) # xypairs, color
875      color = args.pop # last argument is a color
876      xypairs = TkComm.slice_ary(args.flatten, 2) # regenerate xypairs
877      tk_call_without_enc(@chart, 'area', xypairs, color)
878      self
879    end
880
881    def zconfig(key, value=None)
882      if key.kind_of?(Hash)
883        tk_call_without_enc(@chart, 'zconfig', *hash_kv(key, true))
884      else
885        tk_call(@chart, 'zconfig',"-#{key}", value)
886      end
887      self
888    end
889  end
890
891
892  ############################
893  class Piechart < Tk::Canvas
894    include ChartMethod
895
896    TkCommandNames = [
897      'canvas'.freeze,
898      '::Plotchart::createPiechart'.freeze
899    ].freeze
900
901    def initialize(*args) # args := ([parent] [, keys])
902      if args[0].kind_of?(Tk::Canvas)
903        parent = args.shift
904        @path = parent.path
905      else
906        super(*args) # create canvas widget
907      end
908      @chart = _create_chart
909    end
910
911    def _create_chart
912      p self.class::TkCommandNames[1] if $DEBUG
913      tk_call_without_enc(self.class::TkCommandNames[1], @path)
914    end
915    private :_create_chart
916
917    def plot(*dat)  # argument is a list of [label, value]
918      tk_call(@chart, 'plot', dat.flatten)
919      self
920    end
921
922    def colours(*list)
923      tk_call_without_enc(@chart, 'colours', *list)
924      self
925    end
926    alias colors  colours
927  end
928
929
930  ############################
931  class Radialchart < Tk::Canvas
932    include ChartMethod
933
934    TkCommandNames = [
935      'canvas'.freeze,
936      '::Plotchart::createRadialchart'.freeze
937    ].freeze
938
939    def initialize(*args) # args := ([parent,] names, scale, style [, keys])
940                          # radius_data := Array of [maximum_radius, stepsize]
941      if args[0].kind_of?(Array)
942        @names = args.shift
943        @scale = args.shift
944        @style = args.shift
945
946        super(*args) # create canvas widget
947      else
948        parent = args.shift
949
950        @names = args.shift
951        @scale = args.shift
952        @style = args.shift
953
954        if parent.kind_of?(Tk::Canvas)
955          @path = parent.path
956        else
957          super(parent, *args) # create canvas widget
958        end
959      end
960
961      @chart = _create_chart
962    end
963
964    def _create_chart
965      p self.class::TkCommandNames[1] if $DEBUG
966      tk_call_without_enc(self.class::TkCommandNames[1], @path,
967                          array2tk_list(@names), @scale, @style)
968    end
969    private :_create_chart
970
971    def __destroy_hook__
972      Tk::Tcllib::Plotchart::PlotSeries::SeriesID_TBL.mutex.synchronize{
973        Tk::Tcllib::Plotchart::PlotSeries::SeriesID_TBL.delete(@path)
974      }
975    end
976
977    def plot(data, color, thickness)
978      tk_call_without_enc(@chart, 'plot', _get_eval_enc_str(data),
979                          color, thickness)
980      self
981    end
982
983    def colours(*list)
984      tk_call_without_enc(@chart, 'colours', *list)
985      self
986    end
987    alias colors  colours
988  end
989
990  ############################
991  class Barchart < Tk::Canvas
992    include ChartMethod
993
994    TkCommandNames = [
995      'canvas'.freeze,
996      '::Plotchart::createBarchart'.freeze
997    ].freeze
998
999    def initialize(*args)
1000      # args := ([parent,] xlabels, ylabels [, series] [, keys])
1001      # xlabels, ylabels := labels | axis ( depend on normal or horizontal )
1002      # labels := Array of [label, label, ...]
1003      #   (It determines the number of bars that will be plotted per series.)
1004      # axis := Array of [minimum, maximum, stepsize]
1005      # series := Integer number of data series | 'stacked' | :stacked
1006      if args[0].kind_of?(Array)
1007        @xlabels = args.shift
1008        @ylabels  = args.shift
1009
1010        if args[0].kind_of?(Hash)
1011          @series_size = :stacked
1012        else
1013          @series_size  = args.shift
1014        end
1015
1016        super(*args) # create canvas widget
1017      else
1018        parent = args.shift
1019
1020        @xlabels = args.shift
1021        @ylabels = args.shift
1022
1023        if args[0].kind_of?(Hash)
1024          @series_size = :stacked
1025        else
1026          @series_size  = args.shift
1027        end
1028
1029        if parent.kind_of?(Tk::Canvas)
1030          @path = parent.path
1031        else
1032          super(parent, *args) # create canvas widget
1033        end
1034      end
1035
1036      @chart = _create_chart
1037    end
1038
1039    def _create_chart
1040      p self.class::TkCommandNames[1] if $DEBUG
1041      tk_call_without_enc(self.class::TkCommandNames[1], @path,
1042                          array2tk_list(@xlabels), array2tk_list(@ylabels),
1043                          @series_size)
1044    end
1045    private :_create_chart
1046
1047    def __destroy_hook__
1048      Tk::Tcllib::Plotchart::PlotSeries::SeriesID_TBL.mutex.synchronize{
1049        Tk::Tcllib::Plotchart::PlotSeries::SeriesID_TBL.delete(@path)
1050      }
1051    end
1052
1053    def plot(series, dat, col=None)
1054      tk_call(@chart, 'plot', series, dat, col)
1055      self
1056    end
1057
1058    def colours(*cols)
1059      # set the colours to be used
1060      tk_call(@chart, 'colours', *cols)
1061      self
1062    end
1063    alias colour colours
1064    alias colors colours
1065    alias color  colours
1066  end
1067
1068  ############################
1069  class HorizontalBarchart < Barchart
1070    TkCommandNames = [
1071      'canvas'.freeze,
1072      '::Plotchart::createHorizontalBarchart'.freeze
1073    ].freeze
1074  end
1075
1076  ############################
1077  class Boxplot < Tk::Canvas
1078    include ChartMethod
1079
1080    TkCommandNames = [
1081      'canvas'.freeze,
1082      '::Plotchart::createBoxplot'.freeze
1083    ].freeze
1084
1085    def initialize(*args) # args := ([parent,] xaxis, ylabels [, keys])
1086                          # xaxis := Array of [minimum, maximum, stepsize]
1087                          # yaxis := List of labels for the y-axis
1088      if args[0].kind_of?(Array)
1089        @xaxis   = args.shift
1090        @ylabels = args.shift
1091
1092        super(*args) # create canvas widget
1093      else
1094        parent = args.shift
1095
1096        @xaxis   = args.shift
1097        @ylabels = args.shift
1098
1099        if parent.kind_of?(Tk::Canvas)
1100          @path = parent.path
1101        else
1102          super(parent, *args) # create canvas widget
1103        end
1104      end
1105
1106      @chart = _create_chart
1107    end
1108
1109    def _create_chart
1110      p self.class::TkCommandNames[1] if $DEBUG
1111      tk_call_without_enc(self.class::TkCommandNames[1], @path,
1112                          array2tk_list(@xaxis), array2tk_list(@ylabels))
1113    end
1114    private :_create_chart
1115
1116    def __destroy_hook__
1117      Tk::Tcllib::Plotchart::PlotSeries::SeriesID_TBL.mutex.synchronize{
1118        Tk::Tcllib::Plotchart::PlotSeries::SeriesID_TBL.delete(@path)
1119      }
1120    end
1121
1122    def plot(label, *values)
1123      tk_call(@chart, 'plot', label, values.flatten)
1124      self
1125    end
1126  end
1127
1128  ############################
1129  class RightAxis < Tk::Canvas
1130    include ChartMethod
1131
1132    TkCommandNames = [
1133      'canvas'.freeze,
1134      '::Plotchart::createRightAxis'.freeze
1135    ].freeze
1136
1137    def initialize(*args) # args := ([parent,] yaxis [, keys])
1138                          # yaxis := Array of [minimum, maximum, stepsize]
1139      if args[0].kind_of?(Array)
1140        @yaxis   = args.shift
1141
1142        super(*args) # create canvas widget
1143      else
1144        parent = args.shift
1145
1146        @yaxis   = args.shift
1147
1148        if parent.kind_of?(Tk::Canvas)
1149          @path = parent.path
1150        else
1151          super(parent, *args) # create canvas widget
1152        end
1153      end
1154
1155      @chart = _create_chart
1156    end
1157
1158    def _create_chart
1159      p self.class::TkCommandNames[1] if $DEBUG
1160      tk_call_without_enc(self.class::TkCommandNames[1], @path,
1161                          array2tk_list(@yaxis))
1162    end
1163    private :_create_chart
1164
1165    def __destroy_hook__
1166      Tk::Tcllib::Plotchart::PlotSeries::SeriesID_TBL.mutex.synchronize{
1167        Tk::Tcllib::Plotchart::PlotSeries::SeriesID_TBL.delete(@path)
1168      }
1169    end
1170  end
1171
1172  ############################
1173  class Timechart < Tk::Canvas
1174    include ChartMethod
1175
1176    TkCommandNames = [
1177      'canvas'.freeze,
1178      '::Plotchart::createTimechart'.freeze
1179    ].freeze
1180
1181    def initialize(*args)
1182      # args := ([parent,] time_begin, time_end, items [, keys])
1183      # time_begin := String of time format (e.g. "1 january 2004")
1184      # time_end   := String of time format (e.g. "1 january 2004")
1185      # items := Expected/maximum number of items
1186      #          ( This determines the vertical spacing. )
1187      if args[0].kind_of?(String)
1188        @time_begin = args.shift
1189        @time_end   = args.shift
1190        @items      = args.shift
1191
1192        super(*args) # create canvas widget
1193      else
1194        parent = args.shift
1195
1196        @time_begin = args.shift
1197        @time_end   = args.shift
1198        @items      = args.shift
1199
1200        if parent.kind_of?(Tk::Canvas)
1201          @path = parent.path
1202        else
1203          super(parent, *args) # create canvas widget
1204        end
1205      end
1206
1207      @chart = _create_chart
1208    end
1209
1210    def _create_chart
1211      p self.class::TkCommandNames[1] if $DEBUG
1212      tk_call_without_enc(self.class::TkCommandNames[1], @path,
1213                          @time_begin, @time_end, @items)
1214    end
1215    private :_create_chart
1216
1217    def period(txt, time_begin, time_end, col=None)
1218      tk_call(@chart, 'period', txt, time_begin, time_end, col)
1219      self
1220    end
1221
1222    def milestone(txt, time, col=None)
1223      tk_call(@chart, 'milestone', txt, time, col)
1224      self
1225    end
1226
1227    def vertline(txt, time)
1228      tk_call(@chart, 'vertline', txt, time)
1229      self
1230    end
1231
1232    def hscroll=(scr)
1233      tk_call_without_enc(@chart, 'hscroll', scr)
1234      scr
1235    end
1236    def hscroll(scr)
1237      tk_call_without_enc(@chart, 'hscroll', scr)
1238      self
1239    end
1240
1241    def vscroll=(scr)
1242      tk_call_without_enc(@chart, 'vscroll', scr)
1243      scr
1244    end
1245    def vscroll(scr)
1246      tk_call_without_enc(@chart, 'vscroll', scr)
1247      self
1248    end
1249  end
1250
1251  ############################
1252  class Ganttchart < Tk::Canvas
1253    include ChartMethod
1254
1255    TkCommandNames = [
1256      'canvas'.freeze,
1257      '::Plotchart::createGanttchart'.freeze
1258    ].freeze
1259
1260    def initialize(*args)
1261      # args := ([parent,] time_begin, time_end, items [, text_width] [, keys])
1262      # time_begin := String of time format (e.g. "1 january 2004")
1263      # time_end   := String of time format (e.g. "1 january 2004")
1264      # args := Expected/maximum number of items
1265      #          ( This determines the vertical spacing. ),
1266      #         Expected/maximum width of items,
1267      #         Option Hash ( { key=>value, ... } )
1268      if args[0].kind_of?(String)
1269        @time_begin = args.shift
1270        @time_end   = args.shift
1271        @args  = args
1272
1273        super(*args) # create canvas widget
1274      else
1275        parent = args.shift
1276
1277        @time_begin = args.shift
1278        @time_end   = args.shift
1279        @args  = args
1280
1281        if parent.kind_of?(Tk::Canvas)
1282          @path = parent.path
1283        else
1284          super(parent, *args) # create canvas widget
1285        end
1286      end
1287
1288      @chart = _create_chart
1289    end
1290
1291    def _create_chart
1292      p self.class::TkCommandNames[1] if $DEBUG
1293      tk_call(self.class::TkCommandNames[1], @path,
1294              @time_begin, @time_end, *args)
1295    end
1296    private :_create_chart
1297
1298    def task(txt, time_begin, time_end, completed=0.0)
1299      list(tk_call(@chart, 'task', txt, time_begin, time_end,
1300                   completed)).collect!{|id|
1301        TkcItem.id2obj(self, id)
1302      }
1303    end
1304
1305    def milestone(txt, time, col=None)
1306      tk_call(@chart, 'milestone', txt, time, col)
1307      self
1308    end
1309
1310    def vertline(txt, time)
1311      tk_call(@chart, 'vertline', txt, time)
1312      self
1313    end
1314
1315    def connect(from_task, to_task)
1316      from_task = array2tk_list(from_task) if from_task.kind_of?(Array)
1317      to_task   = array2tk_list(to_task)   if to_task.kind_of?(Array)
1318
1319      tk_call(@chart, 'connect', from_task, to_task)
1320      self
1321    end
1322
1323    def summary(txt, tasks)
1324      tasks = array2tk_list(tasks) if tasks.kind_of?(Array)
1325      tk_call(@chart, 'summary', tasks)
1326      self
1327    end
1328
1329    def color_of_part(keyword, newcolor)
1330      tk_call(@chart, 'color', keyword, newcolor)
1331      self
1332    end
1333
1334    def font_of_part(keyword, newfont)
1335      tk_call(@chart, 'font', keyword, newfont)
1336      self
1337    end
1338
1339    def hscroll=(scr)
1340      tk_call_without_enc(@chart, 'hscroll', scr)
1341      scr
1342    end
1343    def hscroll(scr)
1344      tk_call_without_enc(@chart, 'hscroll', scr)
1345      self
1346    end
1347
1348    def vscroll=(scr)
1349      tk_call_without_enc(@chart, 'vscroll', scr)
1350      scr
1351    end
1352    def vscroll(scr)
1353      tk_call_without_enc(@chart, 'vscroll', scr)
1354      self
1355    end
1356  end
1357
1358  ############################
1359  class PlotSeries < TkObject
1360    SeriesID_TBL = TkCore::INTERP.create_table
1361
1362    (Series_ID = ['series'.freeze, TkUtil.untrust('00000')]).instance_eval{
1363      @mutex = Mutex.new
1364      def mutex; @mutex; end
1365      freeze
1366    }
1367    TkCore::INTERP.init_ip_env{
1368      SeriesID_TBL.mutex.synchronize{ SeriesID_TBL.clear }
1369    }
1370
1371    def self.id2obj(chart, id)
1372      path = chart.path
1373      SeriesID_TBL.mutex.synchronize{
1374        if SeriesID_TBL[path]
1375          SeriesID_TBL[path][id]? SeriesID_TBL[path][id]: id
1376        else
1377          id
1378        end
1379      }
1380    end
1381
1382    def initialize(chart, keys=nil)
1383      @parent = @chart_obj = chart
1384      @ppath = @chart_obj.path
1385      Series_ID.mutex.synchronize{
1386        @path = @series = @id = Series_ID.join(TkCore::INTERP._ip_id_)
1387        Series_ID[1].succ!
1388      }
1389      SeriesID_TBL.mutex.synchronize{
1390        SeriesID_TBL[@ppath] ||= {}
1391        SeriesID_TBL[@ppath][@id] = self
1392      }
1393      dataconfig(keys) if keys.kind_of?(Hash)
1394    end
1395
1396    def plot(*args)
1397      @chart_obj.plot(@series, *args)
1398    end
1399
1400    def dataconfig(key, value=None)
1401      @chart_obj.dataconfig(@series, key, value)
1402    end
1403  end
1404end
1405