This repository was archived by the owner on Aug 31, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 225
Expand file tree
/
Copy pathgraph.lcb
More file actions
1323 lines (1084 loc) · 39.9 KB
/
graph.lcb
File metadata and controls
1323 lines (1084 loc) · 39.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
Copyright (C) 2015-2016 LiveCode Ltd.
This file is part of LiveCode.
LiveCode is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License v3 as published by the Free
Software Foundation.
LiveCode is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with LiveCode. If not see <http://www.gnu.org/licenses/>. */
/**
A configurable line graph widget.
The graph has labelled axes and allows multiple lines to be displayed.
- Load comma-delimited data into the graph with the <graphData> property
- Set the <graphColors> to control the color of each line in the graph
- Call out a specific point on the graph by setting the
<hilitedCoordinates>
References: graphData(property), graphColors(property), hilitedCoordinates(property)
*/
widget com.livecode.widget.linegraph
--
-- dependency declarations
use com.livecode.canvas
use com.livecode.widget
use com.livecode.engine
use com.livecode.library.iconsvg
use com.livecode.library.widgetutils
--
-- adding metadata to ensure the extension displays correctly in LiveCode
metadata author is "LiveCode"
metadata version is "1.2.0"
metadata title is "Line Graph"
metadata preferredSize is "300,175"
metadata svgicon is "M50.6,27.2h-2.2v-3.3l0.8-0.5l-0.2-0.3h2.9v-1h-3.5v-5h3.5v-1h-3.5v-5h3.5v-1h-3.5v-5h3.5v-1h-3.5V1.8h-1v2.4h-10V1.8h-1v2.4h-10V1.8h-1v2.4h-10V1.8h-1v2.4H5V2.5C5,1.1,3.9,0,2.5,0S0,1.1,0,2.5v29.7h50.6c1.4,0,2.5-1.1,2.5-2.5C53.1,28.4,52,27.2,50.6,27.2z M47.4,27.2h-10v-4.1h8l1.2,1.9l0.8-0.5V27.2z M15.4,15.2l1.4,0.9h-1.4V15.2z M14.4,17.1v5h-4.2l3.6-5H14.4z M15.4,17.1h2.9l7.1,4.6v0.4h-10V17.1z M26.4,17.1h0.1l-0.1,0.2V17.1z M25.4,18.1l-1.6-1h1.6V18.1z M26.4,22l4-4.9h6v5h-10V22z M37.4,17.1h4.2l3.2,5h-7.4V17.1z M37.4,16.1v-5h0.4l3.2,5H37.4z M36.4,10.1h-0.1l0.1-0.2V10.1z M36.4,11.1v5h-5.1l4.2-5H36.4z M27.4,16.1h-1v-5h5.1L27.4,16.1z M25.4,16.1h-3.1l-6.9-4.5v-0.5h10V16.1z M14.4,11.3l-3.5,4.9H5v-5h9.4V11.3z M10.1,17.1l-3.6,5H5v-5H10.1z M5.7,23.1l-0.7,1v-1H5.7z M9.5,23.1h4.9v4.1H6.5L9.5,23.1z M15.4,23.1h10v4.1h-10V23.1z M26.4,23.1h10v4.1h-10V23.1z M47.4,20.7l-2.3-3.6h2.3V20.7z M47.4,16.1h-2.9l-3.2-5h6.1V16.1z M47.4,10.1h-6.7l-3.2-5h9.9V10.1z M36.4,5.1v0.1l-4,4.9h-6v-5H36.4z M25.4,5.1v5h-10v-5H25.4z M14.4,5.1v5H5v-5H14.4z"
--
-- property declarations
/**
Syntax:
set the graphData of <widget> to <pData>
get the graphData of <widget>
Summary: The data displayed by the graph.
Value(String): A line delimited list of the data for each point on the x-axis.
Example:
local tVar
put "mon,1000,300,0\ntue,720,340,123\nwed,889,400,80\nthu,800,360,10\nfri,1000,301,234\nsat,964,400,200\nsun,1000,500,0" into tVar
replace "\n" with return in tVar
set the graphData of widget "My Graph" to tVar
Description:
The <graphData> is the data to be displayed by the graph widget.
Each line of the <graphData> should be a a comma delimited list. The
first item of each line is the label for that point on the x-axis.
The second and following items are the values for each series of data
to plot.
*/
property graphData get getData set setData
metadata graphData.editor is "com.livecode.pi.text"
metadata graphData.default is "mon,1000,300,0\ntue,720,340,123\nwed,889,400,80\nthu,800,360,10\nfri,1000,301,234\nsat,964,400,200\nsun,1000,500,0"
metadata graphData.label is "Data (CSV)"
/**
Syntax:
set the graphColors of <widget> to <pColors>
get the graphColors of <widget>
Summary: The colors for drawing lines on the graph
Value(String): A line-delimited list of the colors for each line on the graph
Example:
local tVar
put "255,0,155,255\n155,100,255,255\n100,255,100,255" into tVar
replace "\n" with return in tVar
set the graphColors of widget "My Graph" to tVar
Description:
The <graphColors> are the colors of each line in the graph widget.
Each line of the <graphColors> should be a comma delimited list with
four items. The items are the red, green, blue, and alpha (opacity)
components of the line color.
*/
property graphColors get getColors set setColors
metadata graphColors.editor is "com.livecode.pi.editorList"
metadata graphColors.subeditor is "com.livecode.pi.color"
metadata graphColors.delimiter is "\n"
metadata graphColors.default is "255,0,155,255\n155,100,255,255\n100,255,100,255"
metadata graphColors.label is "Line colors"
metadata graphColors.section is "Colors"
/**
Summary: Whether horizontal grid lines are displayed
Syntax:
set the graphXLines of <widget> to { true | false }
get the graphXLines of <widget>
Value (boolean): True if grid lines should be shown; false otherwise
Description:
If true, the graph widget will draw horizontal grid lines on the
graph. The number and spacing of the grid lines is determined
automatically.
Related: graphYLines (property)
*/
property graphXLines get mGridHShow set setXLinesVisibility
metadata graphXLines.default is "true"
metadata graphXLines.label is "Horizontal grid lines "
/**
Summary: Whether vertical grid lines are displayed
Syntax:
set the graphYLines of <widget> to { true | false }
get the graphYLines of <widget>
Value (boolean): True if grid lines should be shown; false otherwise
Description:
If true, the graph widget will draw vertical grid lines on the graph.
The number and spacing of the grid lines is determined automatically.
Related: graphXLines (property)
*/
property graphYLines get mGridVShow set setYLinesVisibility
metadata graphYLines.default is "true"
metadata graphYLines.label is "Vertical grid lines"
/**
Summary: Whether chart lines are displayed
Syntax:
set the showLines of <widget> to { true | false }
get the showLines of <widget>
Value (boolean): True if lines should be shown; false otherwise
Description:
If true, the graph widget will draw graph lines on the graph.
Related: markerStyles (property), markerScale (property)
*/
property showLines get mLinesShow set SetLineVisibility
metadata showLines.default is "true"
metadata showLines.label is "Show graph lines"
metadata showLines.section is "Basic"
/**
Summary: Set the marker style for each data series
Syntax:
set the markerStyles of <widget> to { <string> | empty }
get the markerStyles of <widget>
Summary: The marker styles for drawing vertices on the graph
Value(String): A line-delimited list of the marker styles for each line
on the graph
Description:
The <markerStyles> are the marker styles of each line in the graph
widget.
If set to empty then no markers will be shown and
showLines will be set to true. Available marker styles may be one of:
- "filled circle"
- "filled square"
- "filled diamond"
- "circle"
- "square"
- "diamond"
- any icon name from the svg icon library
By default the markerStyles will repeat in the following order:
- "filled circle"
- "filled square"
- "filled diamond"
- "circle"
- "square"
- "diamond"
Related: showLines (property), markerScale (property)
*/
property markerStyles get GetMarkerStyles set SetMarkerStyles
metadata markerStyles.default is "filled circle\nfilled square\nfilled diamond"
metadata markerStyles.editor is "com.livecode.pi.text"
metadata markerStyles.label is "Marker styles"
/**
Summary: A scale factor to apply to markers
Syntax:
set the markerScale of <widget> to <real>
get the markerScale of <widget>
Value (real): A scale factor to apply to markers
Description:
The default scale factor is 1 which corresponds to an 8x8 point marker.
Related: markerStyles (property), showLines (property)
*/
property markerScale get mMarkerScale set SetMarkerScale
metadata markerScale.default is "1"
metadata markerScale.editor is "com.livecode.pi.number"
metadata markerScale.step is "0.05"
metadata markerScale.min is "0"
metadata markerScale.max is "5"
metadata markerScale.label is "Marker scale"
/**
Summary: The coordinates of a highlighted point on the graph
Syntax:
set the hilitedCoordinates of <widget> to { <point> | empty }
get the hilitedCoordinates of <widget>
Value(String): The comma-delimited coordinates of a point, or empty.
Description:
If the <hilitedCoordinates> of the graph widget is not empty, then the
widget will highlight the specified coordinates with a dot and dashed
horizontal and vertical lines.
>*Note:* If the x-axis values in the graph data are non-numeric,
>then the x-value of the point set must match one of those values.
Related: hilitedCoordinatesColor (property)
*/
property hilitedCoordinates get GetHilitedCoordinates set SetHilitedCoordinates
metadata hilitedCoordinates.default is ""
metadata hilitedCoordinates.label is "Hilited coordinates"
/**
Summary: The color for drawing the highlighted point
Syntax:
set the hilitedCoordinatesColor of <widget> to <color>
get the hilitedCoordinatesColor of <widget>
Value(String): Any string which is a valid color, including RGBA values.
Description:
If the widget currently has some <hilitedCoordinates|highlighted
coordinates> set, the <hilitedCoordinatesColor> determines the color
used to draw the highlighting dot and lines.
Related: hilitedCoordinates (property)
*/
property hilitedCoordinatesColor get mHilitedCoordinatesColor set SetHilitedCoordinatesColor
metadata hilitedCoordinatesColor.default is "0,0,0,255"
metadata hilitedCoordinatesColor.label is "Hilited coordinates color"
metadata hilitedCoordinatesColor.editor is "com.livecode.pi.colorWithAlpha"
metadata hilitedCoordinatesColor.section is "Colors"
-- private instance variables
private variable mData as List
private variable mGridHShow as Boolean
private variable mGridVShow as Boolean
private variable mLegendShow as Boolean
private variable mLabelXShow as Boolean
private variable mLabelYShow as Boolean
private variable mVerticesShow as Boolean
private variable mLinesShow as Boolean
private variable mMarkerStyles as List
private variable mMarkerScale as Real
private variable mYMax as Number
private variable mYMin as Number
private variable mXMax as optional Number
private variable mXMin as optional Number
private variable mVPointCount as Integer
private variable mHPointCount as Integer
private variable mGridHWidth as Real
private variable mGridVHeight as Real
private variable mLegendWidth as Real
private variable mGridRect as Rectangle
private variable mColors as List
private variable mMaxYLabelWidth as Real
private variable mYLabelTopOverhang as Real
private variable mYLabelBottomOverhang as Real
private variable mMaxXLabelHeight as Real
private variable mXLabelLeftOverhang as Real
private variable mXLabelRightOverhang as Real
private variable mXLabelRows as Number
// Stores the numerical increment of the steps in the Y axis
private variable mYIncrement as Real
// Paths
private variable mBackgroundGridPath as Path
private variable mBackgroundAxisPath as Path
--
private variable mWidth as Real
private variable mHeight as Real
private variable mRecalculateAll as Boolean
private variable mRecalculateGrid as Boolean
private variable mHilitedCoordinates as List
private variable mHilitedCoordinatesColor as String
-- this handler is called when the widget is created
public handler OnCreate() returns nothing
put [["mon","tue","wed","thu","fri","sat","sun"],[1000,720,889,800,1000,964,1000],[300,340,400,360,301,400,nothing],[0,123,80,10,234,200,0]] into mData
put [stringToColor("255,0,155"),stringToColor("155,100,255"),stringToColor("100,255,100")] into mColors
put true into mGridHShow
put true into mGridVShow
put true into mLegendShow
put true into mLabelXShow
put true into mLabelYShow
put true into mLinesShow
put true into mVerticesShow
put [] into mMarkerStyles
put 1 into mMarkerScale
put my height into mHeight
put my width into mWidth
put [] into mHilitedCoordinates
put "0,0,0,255" into mHilitedCoordinatesColor
put true into mRecalculateAll
EnsureMarkerStyles()
end handler
----------
-- this handler is called whenever LiveCode needs to redraw the widget
public handler OnPaint() returns nothing
-- Set the font prior to doing text measurements
set the font of this canvas to my font
set the paint of this canvas to getPaint("axis","stroke")
if mRecalculateAll then
calculateAll()
else if mRecalculateGrid then
calculateGrid()
end if
if mLabelYShow then
drawYLabels()
end if
if mLabelXShow then
drawXLabels()
end if
if mLegendShow then
drawLegend()
end if
drawGraph()
drawHilited()
end handler
-- Marshal data that needs to be saved into an array
public handler OnSave(out rProperties as Array)
put {} into rProperties
put getData() into rProperties["graphData"]
put getColors() into rProperties["graphColors"]
put mGridHShow into rProperties["graphXLines"]
put mGridVShow into rProperties["graphYLines"]
put GetHilitedCoordinates() into rProperties["hilitedCoordinates"]
put mHilitedCoordinatesColor into rProperties["hilitedCoordinatesColor"]
put mVerticesShow into rProperties["showMarkers"]
put mMarkerStyles into rProperties["markerStyles"]
put mLinesShow into rProperties["showLines"]
put mMarkerScale into rProperties["markerScale"]
end handler
-- Process saved data array and re-set properties accordingly
public handler OnLoad(in pProperties as Array)
if "graphData" is among the keys of pProperties then
setData(pProperties["graphData"])
end if
if "graphColors" is among the keys of pProperties then
setColors(pProperties["graphColors"])
end if
if "graphXLines" is among the keys of pProperties then
setXLinesVisibility(pProperties["graphXLines"])
end if
if "graphYLines" is among the keys of pProperties then
setYLinesVisibility(pProperties["graphYLines"])
end if
if "hilitedCoordinates" is among the keys of pProperties then
SetHilitedCoordinates(pProperties["hilitedCoordinates"])
end if
if "hilitedCoordinatesColor" is among the keys of pProperties then
SetHilitedCoordinatesColor(pProperties["hilitedCoordinatesColor"])
end if
if "showLines" is among the keys of pProperties then
SetLineVisibility(pProperties["showLines"])
end if
if "showMarkers" is among the keys of pProperties then
put pProperties["showMarkers"] into mVerticesShow
end if
if "markerStyles" is among the keys of pProperties then
put pProperties["markerStyles"] into mMarkerStyles
end if
if "markerScale" is among the keys of pProperties then
put pProperties["markerScale"] into mMarkerScale
end if
EnsureMarkerStyles()
end handler
----------
----------
-- this handler sets the graphData property
public handler setData(in pData as String) returns nothing
variable tData as List
variable tNewData as List
put the empty list into tNewData
if pData is empty then
throw "Invalid graph data"
end if
split pData by newline into tData
variable tCount as Number
variable tGraphLineData as String
variable tSplitGraphLineData as List
// Count the longer data points
variable tMax as Integer
put 0 into tMax
repeat for each element tGraphLineData in tData
split tGraphLineData by "," into tSplitGraphLineData
if the number of elements in tSplitGraphLineData > tMax then
put the number of elements in tSplitGraphLineData into tMax
end if
end repeat
repeat with tCount from 1 up to tMax
push the empty list onto tNewData
end repeat
repeat for each element tGraphLineData in tData
split tGraphLineData by "," into tSplitGraphLineData
repeat with tCount from 1 up to tMax
if the number of elements in tSplitGraphLineData >= tCount then
if tCount is 1 then
push element tCount of tSplitGraphLineData onto element 1 of tNewData
else
parse element tCount of tSplitGraphLineData as number
if the result is not nothing then
push the result onto element tCount of tNewData
else
push 0 onto element tCount of tNewData
end if
end if
else
push nothing onto element tCount of tNewData
end if
end repeat
end repeat
put tNewData into mData
put [] into mHilitedCoordinates
put true into mRecalculateAll
EnsureMarkerStyles()
redraw all
end handler
----------
----------
-- this handler sets the graphColors property
public handler setColors(in tColors as String) returns nothing
if tColors is empty then
put the empty list into mColors
else
variable tStringColorList as List
variable tColorList as List
put the empty list into tColorList
split tColors by newline into tStringColorList
variable tColor as String
repeat for each element tColor in tStringColorList
push stringToColor(tColor) onto back of tColorList
end repeat
put tColorList into mColors
end if
redraw all
end handler
----------
-- this handler sets whether the x grid lines are visible
public handler setXLinesVisibility(in pShowXGridLines as Boolean) returns nothing
put pShowXGridLines into mGridHShow
redraw all
end handler
public handler setYLinesVisibility(in pShowYGridLines as Boolean) returns nothing
put pShowYGridLines into mGridVShow
redraw all
end handler
public handler SetLineVisibility(in pShowLines as Boolean) returns nothing
put pShowLines into mLinesShow
if not pShowLines then
put true into mVerticesShow
EnsureMarkerStyles()
end if
redraw all
end handler
public handler GetMarkerStyles() returns String
if mMarkerStyles is [] then
return ""
end if
variable tMarkerStyles as String
combine mMarkerStyles with newline into tMarkerStyles
return tMarkerStyles
end handler
public handler SetMarkerStyles(in pMarkerStyles as String) returns nothing
if pMarkerStyles is "" then
put true into mLinesShow
put [] into mMarkerStyles
put false into mVerticesShow
else
split pMarkerStyles by newline into mMarkerStyles
put true into mVerticesShow
EnsureMarkerStyles()
end if
redraw all
end handler
public handler SetMarkerScale(in pMarkerScale as Real) returns nothing
put pMarkerScale into mMarkerScale
redraw all
end handler
public handler EnsureMarkerStyles() returns nothing
if mVerticesShow and the number of elements in mMarkerStyles < the number of elements in mData - 1 then
variable tStyles as List
put ["filled circle", "filled square", "filled diamond", "circle", "square", "diamond"] into tStyles
variable tCount as Integer
repeat with tCount from the number of elements in mMarkerStyles + 1 up to the number of elements in mData - 1
push tStyles[(tCount - 1) mod 6 + 1] onto back of mMarkerStyles
end repeat
end if
end handler
public handler GetHilitedCoordinates() returns String
if mHilitedCoordinates is [] then
return ""
end if
variable tCoordList
put mHilitedCoordinates into tCoordList
combine tCoordList with "," into tCoordList
return tCoordList
end handler
public handler SetHilitedCoordinates(in pCoordinates as String) returns nothing
variable tCoordList
put [] into tCoordList
if pCoordinates is not "" then
split pCoordinates by "," into tCoordList
if the number of elements in tCoordList is not 2 then
throw "Invalid coordinates:" && pCoordinates
return
end if
calculateMinMaxes()
-- If x-coords are non-numeric, the hilited one must be in the list of points
if mXMin is nothing and not tCoordList[1] is in mData[1] then
throw "Invalid value for x-coordinate- can't interpolate non-numeric values:" && pCoordinates
return
end if
end if
if tCoordList is not mHilitedCoordinates then
put tCoordList into mHilitedCoordinates
put true into mRecalculateAll
redraw all
end if
end handler
public handler SetHilitedCoordinatesColor(in pColor as String)
if pColor is not mHilitedCoordinatesColor then
put pColor into mHilitedCoordinatesColor
redraw all
end if
end handler
----------
-- this handler gets the graphColors property, returning the graphColors as a list of colors
public handler getColors() returns String
// Work out how many colors there should be
variable tMax as Integer
put the number of elements in mData - 1 into tMax
variable tColors as String
variable tColor as Color
variable tColorStringList as List
variable tCount as Integer
put the empty list into tColorStringList
repeat with tCount from 1 up to tMax
if the number of elements in mColors >= tCount then
put element tCount of mColors into tColor
push colorToString(tColor, false) onto back of tColorStringList
else
push "0,0,0,255" onto back of tColorStringList
push stringToColor("0,0,0,255") onto back of mColors
end if
end repeat
combine tColorStringList with newline into tColors
return tColors
end handler
----------
----------
-- this handler gets the graphData property, returning a string
public handler getData() returns String
// Convert mData in a form that can be combined for output
variable tData as List
put the empty list into tData
variable tCount as Integer
variable tCount2 as Integer
variable tMax as Integer
variable tLineData as List
put the empty list into tLineData
put the number of elements in element 1 of mData into tMax
repeat with tCount from 1 up to the number of elements in mData
put element tCount of mData into tLineData
repeat with tCount2 from 1 up to tMax
if tCount is 1 then
push the empty list onto back of tData
push element tCount2 of tLineData onto element tCount2 of tData
else
if the number of elements in tLineData >= tCount2 then
if element tCount2 of tLineData is not nothing then
variable tString as String
put stripZeros((element tCount2 of tLineData) formatted as string) into tString
push tString onto back of element tCount2 of tData
else
push "" onto back of element tCount2 of tData
end if
else
push "" onto back of element tCount2 of tData
end if
end if
end repeat
end repeat
// Flatten second level list
variable tFinalList as List
variable tFlatList as String
put the empty list into tFinalList
put the empty string into tFlatList
repeat with tCount from 1 up to the number of elements in tData
combine element tCount of tData with "," into tFlatList
push tFlatList onto tFinalList
end repeat
// Combine the list for final output
variable tReturnString as String
combine tFinalList with newline into tReturnString
return tReturnString
end handler
----------
----------
-- this handler returns a paint
public handler getPaint(pLocation, pType) returns Paint
if pLocation is "grid" then
if pType is "fill" then
return solid paint with stringToColor("248,248,248")
else if pType is "stroke" then
return solid paint with stringToColor("0,0,0,50")
end if
else if pLocation is "axis" and pType is "stroke" then
return solid paint with stringToColor("0,0,0,150")
end if
return solid paint with stringToColor("255,100,200")
end handler
-- Call calculateAll if the data changes
private handler calculateAll()
-- Currently the widget makes the assumption that the horizontal points are
-- evenly spaced.
put the number of elements in (element 1 of mData) -1 into mHPointCount
-- Use the same number of points for the y-axis
put mHPointCount into mVPointCount
calculateMinMaxes()
-- Set the y axis point increment
put (mYMax - mYMin) / mVPointCount into mYIncrement
-- Calculate geomentry of any elements that affect the grid space
calculateGrid()
put false into mRecalculateAll
end handler
private handler calculateMinMaxes() returns nothing
variable tYMax as optional Number
variable tYMin as optional Number
variable tXMax as optional Number
variable tXMin as optional Number
variable tLineNumber as Integer
variable tMax as Number
variable tMin as Number
repeat with tLineNumber from 2 up to the number of elements in mData
put listMax(mData[tLineNumber], false) into tMax
if tYMax is nothing then
put tMax into tYMax
else
put the maximum of tMax and tYMax into tYMax
end if
put listMin(mData[tLineNumber], false) into tMin
if tYMin is nothing then
put tMin into tYMin
else
put the minimum of tMin and tYMin into tYMin
end if
end repeat
-- No data
if tYMax is nothing or tYMin is nothing then
put 0 into tYMax
put 0 into tYMin
end if
put tYMax into mYMax
put tYMin into mYMin
put listMin(mData[1], true) into mXMin
put listMax(mData[1], true) into mXMax
if mHilitedCoordinates is not [] then
variable tNumber as Number
if mXMin is not nothing then
put mHilitedCoordinates[1] parsed as number into tNumber
if tNumber < mXMin then
put tNumber into mXMin
else if tNumber > mXMax then
put tNumber into mXMax
end if
end if
put mHilitedCoordinates[2] parsed as number into tNumber
if tNumber < mYMin then
put tNumber into mYMin
else if tNumber > mYMax then
put tNumber into mYMax
end if
end if
end handler
private handler LabelRect(in pLabel as any) returns Rectangle
variable tString as String
if pLabel is a string then
put pLabel into tString
else
put pLabel formatted as string into tString
end if
variable tThisRect as Rectangle
measure tString on this canvas into tThisRect
return tThisRect
end handler
private handler calculateXOverhangs() returns nothing
variable tLeftOverhang as Real
put 0 into tLeftOverhang
variable tRightOverhang as Real
put 0 into tRightOverhang
if mLabelXShow then
put the width of LabelRect(the head of mData[1]) / 2 into tLeftOverhang
put the width of LabelRect(the tail of mData[1]) / 2 into tRightOverhang
end if
put tLeftOverhang into mXLabelLeftOverhang
put tRightOverhang into mXLabelRightOverhang
end handler
private handler calculateYLabelGeometry() returns nothing
variable tMaxWidth as Real
put 0 into tMaxWidth
variable tTopOverhang as Real
put 0 into tTopOverhang
variable tBottomOverhang as Real
put 0 into tBottomOverhang
if mLabelYShow then
variable tNumber as Real
variable tString as String
variable tTextBounds as Rectangle
variable tCount as Integer
repeat with tCount from 1 up to mVPointCount + 1
put mYMin + mYIncrement * (tCount - 1) into tNumber
put LabelRect(tNumber) into tTextBounds
if the width of tTextBounds > tMaxWidth then
put the width of tTextBounds into tMaxWidth
end if
if tCount is 1 then
put the height of tTextBounds / 2 into tBottomOverhang
else if tCount is mVPointCount + 1 then
put the height of tTextBounds / 2 into tTopOverhang
end if
end repeat
end if
put tMaxWidth into mMaxYLabelWidth
put tBottomOverhang into mYLabelBottomOverhang
put tTopOverhang into mYLabelTopOverhang
end handler
private handler calculateGridWidth()
variable tPadding as Real
put gridPadding() into tPadding
variable tYLabelNeed as Real
if mLabelYShow then
put mMaxYLabelWidth + tPadding into tYLabelNeed
end if
variable tLabelSpaceLeft as Real
put the maximum of mXLabelLeftOverhang and tYLabelNeed into tLabelSpaceLeft
-- There will be padding either side in addition to the specified padding
variable tGridWidth as Real
put mWidth - tLabelSpaceLeft - 2 * tPadding into tGridWidth
put tGridWidth / mHPointCount into mGridHWidth
end handler
private handler calculateXLabelGeometry() returns nothing
variable tMaxHeight as Real
put 0 into tMaxHeight
variable tXLabelRows as Number
put 0 into tXLabelRows
if mLabelXShow then
put 1 into tXLabelRows
variable tXLabels as List
put mData[1] into tXLabels
variable tString as String
variable tThisRect as Rectangle
variable tOverhang as Number
variable tLastOverhang as Number
variable tLabel
variable tCount as Number
put 0 into tCount
// Try arranging in one row
repeat for each element tLabel in tXLabels
add 1 to tCount
put LabelRect(tLabel) into tThisRect
put the width of tThisRect / 2 into tOverhang
if tCount is 1 then
put the height of tThisRect into tMaxHeight
else
-- If two consecutive labels don't fit,
-- we'll put them onto two lines.
if tOverhang + tLastOverhang > mGridHWidth then
put 2 into tXLabelRows
end if
put the maximum of the height of tThisRect and tMaxHeight into tMaxHeight
end if
put tOverhang into tLastOverhang
end repeat
end if
put tMaxHeight into mMaxXLabelHeight
put tXLabelRows into mXLabelRows
end handler
private handler calculateGridRect() returns nothing
variable tPadding as Real
put gridPadding() into tPadding
variable tNeededTop as Real
put mYLabelTopOverhang into tNeededTop
variable tNeededRight as Real
put mXLabelRightOverhang into tNeededRight
updateGridRect(rectangle [gridLeft(), tPadding + tNeededTop, mWidth - tPadding - tNeededRight, gridBottom()])
end handler
constant kMinPadding is 5
constant kPaddingRatio is 0.02
private handler gridPadding() returns Real
variable tPadding as Real
put the maximum of mHeight * kPaddingRatio and kMinPadding into tPadding
return tPadding
end handler
private handler gridLeft() returns Real
variable tNeededLeft as Real
put the maximum of mMaxYLabelWidth and mXLabelLeftOverhang into tNeededLeft
return tNeededLeft + gridPadding()
end handler
private handler gridBottom() returns Real
variable tNeededBottom as Real
variable tPadding as Real
put gridPadding() into tPadding
variable tXLabelsSpace as Real
put mXLabelRows * (mMaxXLabelHeight + tPadding) into tXLabelsSpace
put the maximum of tXLabelsSpace and mYLabelBottomOverhang into tNeededBottom
return mHeight - (tNeededBottom + tPadding)
end handler
-- this handler updates the grid rectangle
private handler updateGridRect(in tRect as Rectangle) returns nothing
put tRect into mGridRect
put the height of mGridRect / mVPointCount into mGridVHeight
put the width of mGridRect / mHPointCount into mGridHWidth
end handler
-- Call if the width changes, or if show/hide of either axis' labels changes
private handler calculateGrid()
-- First calculate the size of any x-axis label overhangs
calculateXOverhangs()
-- Next calculate the geometry (especially max width) of the y-axis labels
calculateYLabelGeometry()
-- Now we know how much horizontal space the grid has, we can work
-- out the spacing of the vertical grid lines
calculateGridWidth()
-- Now we can work out how to fit the x-axis labels correctly, so they don't overlap
calculateXLabelGeometry()
-- Now we know how much vertical space the grid has, we can work
-- out the rest of the grid geometry.
calculateGridRect()
put false into mRecalculateGrid
end handler
-- this handler is called when the geometry of the widget is changed
public handler OnGeometryChanged()
if my height is not mHeight or my width is not mWidth then
put my height into mHeight
put my width into mWidth
put true into mRecalculateGrid
end if
end handler