GD::Graph は perl モジュールで、perl から gd ライブラリを利用してグラフを生成するためのものです。で、これを解説しようと思ったのですが、マニュアルを読んでも機能が理解できず腹が立ったので、 機能検証プログラムを作ってみました。
実行はこちらから。 ソースは以下のとおり。解説はそのうち (といっても、解説すべきところもあまりないのですが)。
- web 上で操作することで、GD::Graph の全機能を検証できることを目標としています。 しかし現在はまだまだ足りません。
- 組み合わせによっては意味のない項目は自動的に入力不可になります。 ただしまだ完璧ではありません。例えばスキップ幅が 1だと、スキップ開始オフセットの意味がありませんが、 スキップ幅の内容解析はまだできていません。
- Firefox と IE6 で動作確認しました。Javascript 必須です。
- TTF の設定がうまくいかなかったので日本語は出ません。そのうち直します。
- 変な値 (大きすぎる値とか) を入れると gd が落ちて Internal Server Error になります。 GD の円グラフにはデータにマイナスを指定してはならないなどの制限を考慮していない、という利用側の原因はありますが、 GD は異常値に弱すぎという問題もあると考えます。
- 変な値を入れてしまったら、「リセット」ボタンを押下して元に戻してください。
- この環境の GD では、gdGiantFont と gdLargeFont が同じフォントになってしまうようです。
- 以下の機能がまだ未実装です。 logo logo_resize logo_position transparent interlaced valuesclr textclr dclrs borderclrs cycle_clrs accent_treshold x_number_format x_tick_number x_all_ticks two_axes text_space overwrite correct_width values_format line_type_scale markers lg_cols 3d mixed get_hotspot(dataset, point) get_feature_coordinates(feature_name) undef-value
1: #!/usr/local/bin/perl 2: 3: # $Id: graph-maker.cgi,v 1.11 2007/06/21 17:49:11 68user Exp $ 4: # 5: # Written by 68user 6: # http://X68000.q-e-d.net/~68user/ 7: 8: use strict; 9: use Jcode; 10: 11: use HTML::Template; 12: 13: use GD; 14: use GD::Graph; 15: use GD::Graph::colour; 16: 17: use GD::Graph::lines; 18: use GD::Graph::linespoints; 19: use GD::Graph::bars; 20: use GD::Graph::hbars; 21: use GD::Graph::points; 22: use GD::Graph::area; 23: use GD::Graph::pie; 24: 25: my %gd_defs = ( 26: graph_type => { 27: depend_check_only => 1, 28: affect => { 29: 'lines,linespoints,points,pie' => { 30: cumulate => 0, 31: }, 32: 'bars,hbars,area,points,pie' => { 33: line_width => 0, 34: }, 35: 'lines,bars,hbars,area,pie' => { 36: marker_size => 0, 37: }, 38: 'lines,linespoints,bars,hbars,area,points' => { 39: value_font => 0, 40: pie_height => 0, 41: label => 0, 42: labelclr => 0, 43: start_angle => 0, 44: suppress_angle => 0, 45: }, 46: 'lines,linespoints,area,points,pie' => { 47: shadowclr => 0, 48: shadow_depth => 0, 49: bar_spacing => 0, 50: }, 51: 52: 'pie' => { 53: x_axis_font => 0, 54: x_tick_length => 0, 55: x_long_ticks => 0, 56: y_axis_font => 0, 57: y_tick_length => 0, 58: y_long_ticks => 0, 59: x_ticks => 0, 60: zero_axis => 0, 61: zero_axis_only => 0, 62: 63: x_label => 0, 64: x_label_font => 0, 65: x_label_skip => 0, 66: x_label_offset => 0, 67: x_label_position => 0, 68: x_labels_vertical => 0, 69: y_label => 0, 70: y_label_font => 0, 71: y_label_skip => 0, 72: y_label_offset => 0, 73: y_label_position => 0, 74: y_tick_number => 0, 75: no_axes => 0, 76: box_axis => 0, 77: legendclr => 0, 78: legend_placement => 0, 79: legend_spacing => 0, 80: legend_marker_width => 0, 81: legend_marker_height => 0, 82: axis_space => 0, 83: y_max_value => 0, 84: y_min_value => 0, 85: x_plot_values => 0, 86: y_plot_values => 0, 87: show_values => 0, 88: values_vertical => 0, 89: values_space => 0, 90: }, 91: }, 92: }, 93: title => { 94: desc => 'グラフタイトル', 95: default_value => 'this is title', 96: type => 'string', 97: input_size => 10, 98: comment => 'グラフ上部に記述するタイトル文字列', 99: }, 100: title_font => { 101: desc => 'タイトルフォント', 102: shortdesc => 'フォント', 103: type => 'font', 104: default_value => 'gdLargeFont', 105: }, 106: width => { 107: desc => '画像の幅', 108: shortdesc => '幅', 109: type => 'digit', 110: default_value => 400, 111: min_value => 50, 112: max_value => 1000, 113: input_size => 6, 114: comment => '生成するグラフ画像の幅 (グラフの幅ではなく画像の幅)', 115: }, 116: height => { 117: desc => '画像の高さ', 118: shortdesc => '高さ', 119: type => 'digit', 120: default_value => 200, 121: min_value => 50, 122: max_value => 1000, 123: input_size => 6, 124: comment => '生成するグラフ画像の高さ (グラフの高さではなく画像の高さ)', 125: }, 126: t_margin => { 127: desc => '上マージン', 128: type => 'digit', 129: default_value => 0, 130: input_size => 3, 131: }, 132: b_margin => { 133: desc => '下マージン', 134: type => 'digit', 135: default_value => 0, 136: input_size => 3, 137: }, 138: l_margin => { 139: desc => '左マージン', 140: type => 'digit', 141: default_value => 0, 142: input_size => 3, 143: }, 144: r_margin => { 145: desc => '右マージン', 146: type => 'digit', 147: default_value => 0, 148: input_size => 3, 149: }, 150: x_label => { 151: desc => 'X軸ラベル', 152: shortdesc => 'ラベル', 153: type => 'string', 154: default_value => 'X label', 155: input_size => 8, 156: }, 157: y_label => { 158: desc => 'Y軸ラベル', 159: shortdesc => 'ラベル', 160: type => 'string', 161: default_value => 'Y label', 162: input_size => 8, 163: }, 164: x_label_font => { 165: desc => 'X軸ラベルフォント', 166: shortdesc => 'フォント', 167: type => 'font', 168: default_value => 'gdSmallFont', 169: }, 170: y_label_font => { 171: desc => 'Y軸ラベルフォント', 172: shortdesc => 'フォント', 173: type => 'font', 174: default_value => 'gdSmallFont', 175: }, 176: x_axis_font => { 177: desc => 'X軸目盛りフォント', 178: shortdesc => 'フォント', 179: type => 'font', 180: default_value => 'gdSmallFont', 181: comment => 'この例ではグラフ下部の 10, 20, 30 などのフォント', 182: }, 183: y_axis_font => { 184: desc => 'Y軸目盛りフォント', 185: shortdesc => 'フォント', 186: type => 'font', 187: default_value => 'gdSmallFont', 188: comment => 'この例ではグラフ左の 0, 500, 1000 などのフォント', 189: }, 190: x_ticks => { 191: desc => 'X軸目盛り有無', 192: type => 'option', 193: option_values => { 194: 1 => { 195: desc => 'あり', 196: is_default => 1, 197: }, 198: 0 => { 199: desc => 'なし', 200: }, 201: }, 202: affect => { 203: 0 => { 204: x_long_ticks => 0, 205: x_tick_length => 0, 206: }, 207: }, 208: }, 209: x_long_ticks => { 210: desc => 'X軸目盛り線延長', 211: shortdesc => '線延長', 212: type => 'option', 213: option_values => { 214: 1 => { 215: desc => 'する', 216: }, 217: 0 => { 218: desc => 'しない', 219: is_default => 1, 220: }, 221: }, 222: }, 223: y_long_ticks => { 224: desc => 'Y軸目盛り線延長', 225: shortdesc => '線延長', 226: type => 'option', 227: option_values => { 228: 1 => { 229: desc => 'する', 230: }, 231: 0 => { 232: desc => 'しない', 233: is_default => 1, 234: }, 235: }, 236: }, 237: # x_tick_number => { 238: # desc => 'X軸目盛り個数', 239: # type => 'digit', 240: # default_value => 5, 241: # input_size => 3, 242: # }, 243: x_tick_length => { 244: desc => 'X軸目盛り線長', 245: shortdesc => '線長', 246: type => 'digit', 247: default_value => 4, 248: input_size => 2, 249: comment => 'グラフ下部の目盛り部分から上に少し突き出ている線の長さ', 250: }, 251: y_tick_number => { 252: desc => 'Y軸目盛り個数', 253: type => 'digit', 254: default_value => 5, 255: input_size => 2, 256: comment => 'グラフ左の目盛り部分から右に少し突き出ている線の長さ', 257: }, 258: y_tick_length => { 259: desc => 'Y軸目盛り線長', 260: shortdesc => '線長', 261: type => 'digit', 262: default_value => 4, 263: input_size => 2, 264: }, 265: x_label_skip => { 266: desc => 'X軸目盛りスキップ幅', 267: shortdesc => 'スキップ幅', 268: type => 'digit', 269: default_value => 1, 270: input_size => 2, 271: }, 272: x_label_offset => { 273: desc => 'X軸目盛りスキップオフセット', 274: shortdesc => 'スキップ開始オフセット', 275: type => 'digit', 276: default_value => 0, 277: input_size => 2, 278: }, 279: y_label_skip => { 280: desc => 'Y軸目盛りスキップ幅', 281: shortdesc => 'スキップ幅', 282: type => 'digit', 283: default_value => 1, 284: input_size => 2, 285: }, 286: y_label_offset => { 287: desc => 'Y軸目盛りスキップオフセット', 288: shortdesc => 'スキップ開始オフセット', 289: type => 'digit', 290: default_value => 0, 291: input_size => 2, 292: }, 293: x_label_position => { 294: desc => 'X軸ラベル位置', 295: shortdesc => '位置', 296: type => 'realdigit', 297: subdesc => '0〜1.0 の実数', 298: default_value => '0.75', 299: input_size => 5, 300: }, 301: y_label_position => { 302: desc => 'Y軸ラベル位置', 303: shortdesc => '位置', 304: type => 'realdigit', 305: subdesc => '0〜1.0 の実数', 306: default_value => 0.5, 307: input_size => 5, 308: }, 309: x_labels_vertical => { 310: desc => 'X軸ラベル縦横配置', 311: shortdesc => '縦横配置', 312: type => 'option', 313: option_values => { 314: 1 => { 315: desc => '縦', 316: }, 317: 0 => { 318: desc => '横', 319: is_default => 1, 320: }, 321: }, 322: }, 323: box_axis => { 324: desc => '軸囲みタイプ', 325: type => 'option', 326: option_values => { 327: 1 => { 328: desc => '上下左右を囲む', 329: is_default => 1, 330: }, 331: 0 => { 332: desc => '左と下だけ囲む', 333: }, 334: }, 335: }, 336: show_values => { 337: desc => '値表示', 338: type => 'option', 339: option_values => { 340: 1 => { 341: desc => 'あり', 342: is_default => 1, 343: }, 344: 0 => { 345: desc => 'なし', 346: }, 347: }, 348: affect => { 349: 0 => { 350: values_vertical => 0, 351: values_space => 0, 352: }, 353: }, 354: comment => 'グラフの点のそばに値を表示する。', 355: }, 356: values_vertical => { 357: desc => '縦横配置', 358: type => 'option', 359: option_values => { 360: 0 => { 361: desc => '横', 362: is_default => 1, 363: }, 364: 1 => { 365: desc => '縦', 366: }, 367: }, 368: comment => 'true の場合、グラフの点のそばの値表示を縦向きにする。', 369: }, 370: values_space => { 371: desc => '値スペース', 372: type => 'digit', 373: default_value => 4, 374: input_size => 3, 375: comment => 'グラフの点と、そのそばに表示する値の文字列の間隔。ピクセル単位。', 376: }, 377: 378: no_axes => { 379: desc => '軸の有無', 380: type => 'option', 381: option_values => { 382: 1 => { 383: desc => 'なし', 384: }, 385: 'undef' => { 386: desc => 'あり', 387: is_default => 1, 388: }, 389: }, 390: affect => { 391: 1 => { box_axis => 0, }, 392: }, 393: comment => 'グラフの四方を囲む枠線の有無', 394: }, 395: axis_space => { 396: desc => '目盛りフォント/グラフ間余白', 397: type => 'digit', 398: default_value => 4, 399: input_size => 2, 400: comment => '10・20 などの目盛りの文字列と、グラフ枠線との間隔。ピクセル数。', 401: }, 402: zero_axis => { 403: desc => 'Y軸ゼロ目盛り', 404: shortdesc => 'ゼロ目盛り', 405: type => 'option', 406: option_values => { 407: 1 => { 408: desc => 'あり', 409: }, 410: 0 => { 411: desc => 'なし', 412: is_default => 1, 413: }, 414: }, 415: comment => 'Y の値が 0 である位置に線を引く。つまり Y=0 の横棒が引かれる。', 416: }, 417: zero_axis_only => { 418: desc => 'Y軸ゼロ目盛り*のみ*', 419: shortdesc => 'ゼロ目盛りのみ', 420: type => 'option', 421: option_values => { 422: 1 => { 423: desc => 'する', 424: }, 425: 0 => { 426: desc => 'しない', 427: is_default => 1, 428: }, 429: }, 430: affect => { 431: 1 => { 432: zero_axis => 0, 433: }, 434: }, 435: comment => 'Y の値が 0 である位置のみに線を引く。つまり Y=0 の横棒を引き、それ以外の横棒を引かない。', 436: }, 437: bgclr => { 438: desc => 'bgclr(??)', 439: type => 'color', 440: default_value => 'pink', 441: comment => 'よくわからん', 442: }, 443: fgclr => { 444: desc => '軸/目盛り/枠の色', 445: type => 'color', 446: default_value => 'black', 447: }, 448: boxclr => { 449: desc => 'グラフ背景色', 450: type => 'color', 451: default_value => 'white', 452: }, 453: accentclr => { 454: desc => '棒/領域/円グラフ枠色', 455: type => 'color', 456: default_value => 'orange', 457: }, 458: axislabelclr => { 459: desc => '目盛りラベル色', 460: type => 'color', 461: default_value => 'dblue', 462: }, 463: legendclr => { 464: desc => '凡例色', 465: type => 'color', 466: default_value => 'dblue', 467: }, 468: 469: value_font => { 470: desc => '値フォント', 471: type => 'font', 472: default_value => 'gdTinyFont', 473: }, 474: pie_height => { 475: desc => '円グラフ立体高さ', 476: shortdesc => '立体高さ', 477: type => 'digit', 478: input_size => 2, 479: default_value => 30, 480: }, 481: label => { 482: desc => 'ラベル', 483: type => 'string', 484: input_size => 12, 485: default_value => 'this is label', 486: }, 487: labelclr => { 488: desc => 'ラベルの色', 489: type => 'color', 490: default_value => 'black', 491: }, 492: start_angle => { 493: desc => '描画開始角度', 494: subdesc => '0〜360。0:真下 90:左 180:上 270:右', 495: type => 'digit', 496: input_size => 3, 497: default_value => 0, 498: }, 499: suppress_angle => { 500: desc => '値描画省略角度', 501: subdesc => '0〜360。データ割合が少なくてこれ以下の角度なら、値を描画しない', 502: type => 'digit', 503: input_size => 3, 504: default_value => 0, 505: }, 506: shadow_depth => { 507: desc => '影の深さ', 508: type => 'digit', 509: input_size => 2, 510: default_value => 0, 511: }, 512: shadowclr => { 513: desc => '影の色', 514: type => 'color', 515: default_value => 'gray', 516: }, 517: bar_spacing => { 518: desc => '棒グラフ間の空白', 519: type => 'digit', 520: input_size => 2, 521: default_value => 0, 522: }, 523: cumulate => { 524: desc => '積算', 525: type => 'option', 526: option_values => { 527: 1 => { 528: desc => 'する', 529: }, 530: 0 => { 531: desc => 'しない', 532: is_default => 1, 533: }, 534: }, 535: }, 536: line_width => { 537: desc => '線の太さ', 538: type => 'digit', 539: input_size => 2, 540: default_value => 1, 541: }, 542: legend_placement => { 543: desc => '凡例位置', 544: shortdesc => '位置', 545: type => 'option', 546: option_values => { 547: BL => { desc => '下部左' }, 548: BC => { desc => '下部中央', is_default => 1 }, 549: BR => { desc => '下部右' }, 550: RT => { desc => '右上部' }, 551: RC => { desc => '右中央' }, 552: RB => { desc => '右下部' }, 553: }, 554: }, 555: legend_spacing => { 556: desc => '凡例余白', 557: shortdesc => '余白', 558: type => 'digit', 559: input_size => 2, 560: default_value => 4, 561: }, 562: legend_marker_width => { 563: desc => '凡例マーカー幅', 564: shortdesc => 'マーカー幅', 565: type => 'digit', 566: input_size => 2, 567: default_value => 12, 568: }, 569: legend_marker_height => { 570: desc => '凡例マーカー高さ', 571: shortdesc => 'マーカー高さ', 572: type => 'digit', 573: input_size => 2, 574: default_value => 8, 575: }, 576: marker_size => { 577: desc => 'マーカーサイズ', 578: type => 'digit', 579: input_size => 2, 580: default_value => 4, 581: }, 582: y_max_value => { 583: desc => 'Y軸最大値', 584: shortdesc => '最大値', 585: type => 'digit', 586: input_size => 5, 587: default_value => 'undef', 588: comment => 'Y値の最大値以上の値を指定すること (GD の仕様)', 589: 590: }, 591: y_min_value => { 592: desc => 'Y軸最小値', 593: shortdesc => '最小値', 594: type => 'digit', 595: input_size => 5, 596: default_value => 'undef', 597: comment => 'Y値の最小値以下の値を指定すること (GD の仕様)', 598: }, 599: x_plot_values => { 600: desc => 'X軸目盛り値', 601: type => 'option', 602: option_values => { 603: 1 => { 604: desc => '描画する', 605: is_default => 1, 606: }, 607: 0 => { 608: desc => '描画しない', 609: }, 610: }, 611: affect => { 612: 0 => { 613: x_long_ticks => 0, 614: x_tick_length => 0, 615: x_axis_font => 0, 616: x_ticks => 0, 617: x_label_skip => 0, 618: x_label_offset => 0, 619: x_labels_vertical => 0, 620: }, 621: }, 622: }, 623: y_plot_values => { 624: desc => 'Y軸目盛り値', 625: type => 'option', 626: option_values => { 627: 1 => { 628: desc => '描画する', 629: is_default => 1, 630: }, 631: 0 => { 632: desc => '描画しない', 633: }, 634: }, 635: affect => { 636: 0 => { 637: y_axis_font => 0, 638: }, 639: }, 640: }, 641: 642: ); 643: 644: my ($mode, $graph_type, $ref_line_types, $ref_data, $ref_legends, %FORM) = &parse_args; 645: 646: if ( $mode eq 'make_graph' ){ 647: &make_graph(%FORM); 648: } elsif ( $mode eq 'blank' ){ 649: print "Content-type: text/html\n\n"; 650: } else { 651: &make_html(); 652: } 653: 654: exit 0; 655: 656: #------------------------------------------------------- 657: sub make_html { 658: my $rownum = 6; 659: my $colnum = 3; 660: 661: my @row_list; 662: for ( my $row=1 ; $row<=$rownum ; $row++ ){ 663: my @data_column_list; 664: for ( my $col=1 ; $col<=$colnum ; $col++ ){ 665: my %hash; 666: $hash{'column_number'} = $col; 667: $hash{'row_number'} = $row; 668: $hash{'value'} = $col == 1 ? $row*10 : int(rand()*1000)-300; 669: $hash{'nouse_in_pie'} = $col > 2 ? 1 : 0; 670: push(@data_column_list, \%hash); 671: } 672: push(@row_list, 673: { 674: data_column_list => \@data_column_list, 675: }); 676: } 677: 678: my $tmpl_str = <<END; 679: <html lang="ja"> 680: <head> 681: <meta http-equiv="Content-Type" content="text/html; charset=EUC-JP"> 682: <meta http-equiv="Content-Script-Type" content="text/javascript"> 683: <meta http-equiv="Content-Style-Type" content="text/css"> 684: <title>GD::Graph グラフ作成マシーン</title> 685: <style type="text/css"> 686: <!-- 687: td, body { font-size: 80% } 688: input { border: 1px solid #666666; padding: 0px; } 689: select { font-size: 90%; border: 1px solid #666666; padding: 0px; } 690: .desc { color: #993333; } 691: .defsform td { border-bottom: 1px solid #999999 } 692: .defsform tr { margin: 0px; padding: 0px; } 693: .defsform td, th { font-size: 80% } 694: .caption { width: 100%; color: white; font-size: 120%; background-color: #4444bb; } 695: --> 696: </style> 697: </head> 698: <body> 699: <table width="100%"> 700: <tr> 701: <td><h1 style="font-size: 120%; margin: 0px; padding: 0px;">GD::Graph グラフ作成マシーン</h1></td> 702: <td align="right"><a href="../../gd-graph.html">GD::Graph によるグラフ生成 へ戻る</a></td> 703: </tr> 704: </table> 705: 706: <form name="gdform" method="POST" action="@{[ $ENV{'SCRIPT_NAME'} ]}" 707: target="graph_iframe" 708: onSubmit='document.getElementById("graph_iframe_id").width=(this.width.value*1)+40; document.getElementById("graph_iframe_id").height=(this.height.value*1)+40;'> 709: 710: <table width="100%" border=1 cellpadding=0 cellspacing=0> 711: <tr> 712: <td colspan="3"> 713: @{[ &make_form_common() ]} 714: </td> 715: </tr> 716: 717: <tr valign="top"> 718: <td> 719: グラフ種類 720: <select name="graph_type" onChange="depend_check(this); this.form.submit()"> 721: <option value="lines">折れ線</option> 722: <option value="linespoints">点付き折れ線</option> 723: <option value="bars">縦棒グラフ</option> 724: <option value="hbars">横棒グラフ</option> 725: <option value="points">点グラフ</option> 726: <option value="area">折れ線領域グラフ</option> 727: <option value="pie">円グラフ</option> 728: </select> 729: 730: <table border="0" cellpadding="0" cellspacing="0" style="padding: 0px; margin: 0px;"> 731: <tr align="center"> 732: <th>X軸<br><nobr>項目名</nobr></th> 733: <TMPL_LOOP NAME="column_list"> 734: <th>データ<TMPL_VAR NAME="column_number"></th> 735: </TMPL_LOOP> 736: </tr> 737: <TMPL_LOOP NAME="row_list"> 738: <tr align="center"> 739: <TMPL_LOOP NAME="data_column_list"> 740: <td> 741: <input type="text" name="<TMPL_VAR NAME="column_number">,<TMPL_VAR NAME="row_number">" value="<TMPL_VAR NAME="value">" size=4 742: <TMPL_IF NAME="nouse_in_pie">class="nouse_in_pie"</TMPL_IF> 743: onChange="this.form.submit()"> 744: </td> 745: </TMPL_LOOP> 746: </tr> 747: </TMPL_LOOP> 748: 749: <tr> 750: <th><nobr>凡例</nobr></th> 751: <TMPL_LOOP NAME="column_list"> 752: <td><input type="text" name="legend<TMPL_VAR NAME="column_number">" class="nouse_in_pie" value="Legend<TMPL_VAR NAME="column_number">" size=8 onChange="this.form.submit()"></td> 753: </TMPL_LOOP> 754: </tr> 755: 756: <tr> 757: <th><nobr>線タイプ</nobr></th> 758: <TMPL_LOOP NAME="column_list"> 759: <td> 760: <select name="line_type<TMPL_VAR NAME="column_number">" onChange="this.form.submit()" class="use_in_lines_linespoints"> 761: <option value="1">実線</option> 762: <option value="2">破線</option> 763: <option value="3">点線</option> 764: <option value="4">鎖線</option> 765: </select> 766: </td> 767: </TMPL_LOOP> 768: </tr> 769: </table> 770: 771: <p> 772: <input type="hidden" name="mode" value="make_graph"> 773: <input type="submit" value="グラフ作成" style="background-color: #ff9999; font-size: 130%"> 774: <input type="button" value="リセット" onClick="this.form.reset(); all_depend_check(); this.form.submit();"> 775: </p> 776: </td> 777: 778: <td rowspan="3"> 779: @{[ &make_form('basic') ]} 780: @{[ &make_form('type_depend') ]} 781: <br> 782: 783: <iframe src="$ENV{'SCRIPT_NAME'}?mode=blank" name="graph_iframe" id="graph_iframe_id" width="440" height="240"> 784: この部分はインラインフレームを使用しています。 785: </iframe> 786: 787: @{[ &make_form('bottom') ]} 788: </td> 789: 790: <td width="30%"> 791: 機能解説 792: <textarea name="messagearea" cols=30 rows=9 readonly style='border: 1px solid black; font-size: 100%'></textarea> 793: </td> 794: 795: </tr> 796: 797: <tr valign="top"> 798: <td> 799: @{[ &make_form('left') ]} 800: </td> 801: 802: <td> 803: @{[ &make_form('right') ]} 804: </td> 805: </tr> 806: 807: <tr> 808: <td></td> 809: 810: <td></td> 811: </tr> 812: 813: </table> 814: </form> 815: 816: <script type="text/javascript"> 817: <!-- 818: all_depend_check(); 819: document.gdform.submit(); 820: // --> 821: </script> 822: 823: </body> 824: </html> 825: END 826: ; 827: my $tmpl = HTML::Template->new(scalarref => \$tmpl_str); 828: $tmpl->param(column_list => [ map { my %h=(column_number => $_); \%h } (1..$colnum-1) ]); 829: $tmpl->param(row_list => \@row_list); 830: 831: print "Content-type: text/html; charset=EUC-JP\n\n"; 832: print $tmpl->output; 833: } 834: 835: 836: #------------------------------------------------------- 837: sub make_graph { 838: my $graph; 839: if ( $graph_type eq 'lines' ){ $graph = GD::Graph::lines ->new($FORM{'width'}, $FORM{'height'}); } 840: if ( $graph_type eq 'linespoints' ){ $graph = GD::Graph::linespoints ->new($FORM{'width'}, $FORM{'height'}); } 841: if ( $graph_type eq 'bars' ){ $graph = GD::Graph::bars ->new($FORM{'width'}, $FORM{'height'}); } 842: if ( $graph_type eq 'hbars' ){ $graph = GD::Graph::hbars ->new($FORM{'width'}, $FORM{'height'}); } 843: if ( $graph_type eq 'points' ){ $graph = GD::Graph::points ->new($FORM{'width'}, $FORM{'height'}); } 844: if ( $graph_type eq 'area' ){ $graph = GD::Graph::area ->new($FORM{'width'}, $FORM{'height'}); } 845: if ( $graph_type eq 'pie' ){ $graph = GD::Graph::pie ->new($FORM{'width'}, $FORM{'height'}); } 846: 847: $graph->set( title => $FORM{'title'}, 848: x_label => $FORM{'x_label'}, 849: y_label => $FORM{'y_label'}, 850: x_ticks => $FORM{'x_ticks'}, 851: x_label_skip => $FORM{'x_label_skip'}, 852: x_tick_offset => $FORM{'x_tick_offset'}, 853: y_label_skip => $FORM{'y_label_skip'}, 854: y_tick_offset => $FORM{'y_tick_offset'}, 855: x_label_position => $FORM{'x_label_position'}, 856: y_label_position => $FORM{'y_label_position'}, 857: x_labels_vertical => $FORM{'x_labels_vertical'}, 858: box_axis => $FORM{'box_axis'}, 859: no_axes => $FORM{'no_axes'}, 860: t_margin => $FORM{'t_margin'}, 861: b_margin => $FORM{'b_margin'}, 862: l_margin => $FORM{'l_margin'}, 863: r_margin => $FORM{'r_margin'}, 864: x_long_ticks => $FORM{'x_long_ticks'}, 865: x_tick_length => $FORM{'x_tick_length'}, 866: # x_tick_number => $FORM{'x_tick_number'}, 867: # x_tick_number => 'auto', 868: y_long_ticks => $FORM{'y_long_ticks'}, 869: y_tick_length => $FORM{'y_tick_length'}, 870: y_tick_number => $FORM{'y_tick_number'}, 871: bgclr => $FORM{'bgclr'}, 872: fgclr => $FORM{'fgclr'}, 873: boxclr => $FORM{'boxclr'}, 874: accentclr => $FORM{'accentclr'}, 875: axislabelclr => $FORM{'axislabelclr'}, 876: legendclr => $FORM{'legendclr'}, 877: axis_space => $FORM{'axis_space'}, 878: pie_height => $FORM{'pie_height'}, 879: start_angle => $FORM{'start_angle'}, 880: suppress_angle => $FORM{'suppress_angle'}, 881: shadow_depth => $FORM{'shadow_depth'}, 882: shadowclr => $FORM{'shadowclr'}, 883: bar_spacing => $FORM{'bar_spacing'}, 884: zero_axis => $FORM{'zero_axis'}, 885: zero_axis_only => $FORM{'zero_axis_only'}, 886: cumulate => $FORM{'cumulate'}, 887: line_width => $FORM{'line_width'}, 888: line_types => $ref_line_types, 889: legend_placement => $FORM{'legend_placement'}, 890: marker_size => $FORM{'marker_size'}, 891: legend_spacing => $FORM{'legend_spacing'}, 892: legend_marker_width => $FORM{'legend_marker_width'}, 893: legend_marker_height => $FORM{'legend_marker_height'}, 894: y_max_value => $FORM{'y_max_value'}, 895: y_min_value => $FORM{'y_min_value'}, 896: x_plot_values => $FORM{'x_plot_values'}, 897: y_plot_values => $FORM{'y_plot_values'}, 898: values_vertical => $FORM{'values_vertical'}, 899: values_space => $FORM{'values_space'}, 900: # y_number_format => "%04d", 901: ); 902: 903: if ( $graph_type eq 'pie' ){ 904: $graph->set( 905: label => $FORM{'label'}, 906: labelclr => $FORM{'labelclr'}, 907: ); 908: } 909: 910: 911: # my $font_file ='/usr/X11R6/lib/X11/fonts/TrueType/sazanami-gothic.ttf'; 912: # $graph->set_title_font($font_file, 18); 913: 914: 915: $graph->set_title_font(get_font($FORM{'title_font'})); 916: 917: # 意味のないフォントを設定すると GD が落ちることに注意。 918: # 例えば pie にはラベルがないので set_x_label_font をセットすると落ちる。 919: 920: if ( $graph_type eq 'pie' ){ 921: $graph->set_value_font(get_font($FORM{'value_font'})); 922: } else { 923: $graph->set_x_label_font(get_font($FORM{'x_label_font'})); 924: $graph->set_y_label_font(get_font($FORM{'y_label_font'})); 925: $graph->set_x_axis_font(get_font($FORM{'x_axis_font'})); 926: $graph->set_y_axis_font(get_font($FORM{'y_axis_font'})); 927: 928: # 凡例がすべて空文字の場合は GD が落ちるので、事前チェック。 929: foreach my $legend ( @$ref_legends ){ 930: if ( length($legend) > 0 ){ 931: $graph->set_legend(@$ref_legends); 932: last; 933: } 934: } 935: } 936: 937: if ( $FORM{'show_values'} ){ 938: $graph->set(show_values => $ref_data); 939: } 940: my $image = $graph->plot($ref_data) or die "Cannot create image"; 941: binmode STDOUT; 942: print "Content-type: image/png\n\n"; 943: print $image->png(); 944: } 945: 946: #------------------------------------------------------------------------------------ 947: 948: sub parse_args { 949: my $query_string; 950: my $l_mode = ''; 951: my $l_graph_type = ''; 952: my %l_FORM; 953: my @l_data; 954: 955: if ( $ENV{'REQUEST_METHOD'} eq 'POST') { 956: read(STDIN, $query_string, $ENV{CONTENT_LENGTH}); 957: } else { 958: $query_string = $ENV{QUERY_STRING}; 959: } 960: 961: my %line_type_hash; 962: my %data_hash; 963: my %legend_hash; 964: 965: foreach ( split(/&/, $query_string) ) { 966: my ($name, $value) = split(/=/, $_); 967: 968: $value =~ tr/+/ /; 969: $value =~ s/%([0-9a-fA-F][0-9a-fA-F])/pack("C", hex($1))/eg; 970: 971: # name="1,2" という渡し方をしているるので、URL デコードが必要。 972: $name =~ s/%([0-9a-fA-F][0-9a-fA-F])/pack("C", hex($1))/eg; 973: 974: if ( $name eq 'mode' ){ 975: $l_mode = $value; 976: } elsif ( $name eq 'graph_type' ){ 977: $l_graph_type = $value; 978: } elsif ( $name =~ m/^line_type(\d+)$/ ){ 979: $line_type_hash{$1} = $value; 980: } elsif ( $name =~ m/^(\d+),(\d+)/ ){ 981: if ( $value eq 'undef' ){ 982: $data_hash{$1}{$2} = undef; 983: } else { 984: $data_hash{$1}{$2} = $value; 985: } 986: } elsif ( $name =~ m/^legend(\d+)/ ){ 987: $legend_hash{$1} = $value; 988: } else { 989: if ( $value eq 'undef' ){ 990: $l_FORM{$name} = undef; 991: } else { 992: $l_FORM{$name} = $value; 993: } 994: } 995: } 996: 997: my @l_line_types = map { $_ = $line_type_hash{$_} } sort { $a<=>$b } keys %line_type_hash; 998: my @l_legends = map { $_ = $legend_hash{$_} } sort { $a<=>$b } keys %legend_hash; 999: my @l_data; 1000: foreach my $col (sort { $a <=> $b } keys %data_hash ){ 1001: my @data; 1002: foreach my $row (sort { $a <=> $b } keys %{$data_hash{$col}} ){ 1003: push(@data, $data_hash{$col}{$row}); 1004: } 1005: push(@l_data, \@data); 1006: } 1007: 1008: return ($l_mode, $l_graph_type, \@l_line_types, \@l_data, \@l_legends, %l_FORM); 1009: } 1010: 1011: sub get_font { 1012: my ($font_str) = @_; 1013: 1014: if ( $font_str eq 'gdGiantFont' ){ return gdLargeFont; } 1015: if ( $font_str eq 'gdLargeFont' ){ return gdLargeFont; } 1016: if ( $font_str eq 'gdMediumBoldFont' ){ return gdMediumBoldFont; } 1017: if ( $font_str eq 'gdSmallFont' ){ return gdSmallFont; } 1018: if ( $font_str eq 'gdTinyFont' ){ return gdTinyFont; } 1019: } 1020: 1021: sub get_font_list { 1022: my @fonts = ('gdGiantFont', 1023: 'gdLargeFont', 1024: 'gdMediumBoldFont', 1025: 'gdSmallFont', 1026: 'gdTinyFont', 1027: ); 1028: return @fonts; 1029: } 1030: 1031: sub get_colorname_list { 1032: return GD::Graph::colour::colour_list(); 1033: } 1034: sub get_htmlrgb_by_colorname { 1035: my ($colorname) = @_; 1036: my ($r, $g, $b) = GD::Graph::colour::_rgb($colorname); 1037: return sprintf("#%02x%02x%02x", $r, $g, $b); 1038: } 1039: 1040: # "#FF00BB" という文字列を渡し、補色を求める。ただしグレーの補色はグレーになるので、 1041: # 明度差を適当に 1042: sub get_complementary_htmlrgb { 1043: my ($rgb) = @_; 1044: my ($r, $g, $b) = $rgb =~ m/^#(..)(..)(..)/; 1045: my ($comp_r, $comp_g, $comp_b) = (255-hex($r), 255-hex($g), 255-hex($b)); 1046: 1047: if ( abs(( $comp_r + $comp_g + $comp_b ) - ( hex($r) + hex($g) + hex($b) )) < 400 ){ 1048: if ( $comp_r + $comp_g + $comp_b > (255*3)/2 ){ 1049: # RGB 合計が 255段階×RGB3色の半分より大きければ、黒。 1050: $comp_r = $comp_g = $comp_b = 0; 1051: } else { 1052: # そうでなければ、白。 1053: $comp_r = $comp_g = $comp_b = 255; 1054: } 1055: } 1056: return sprintf("#%02x%02x%02x", $comp_r, $comp_g, $comp_b); 1057: } 1058: 1059: #------------------------------------------------------------------------------------ 1060: 1061: sub make_form_common { 1062: my @depend_info; 1063: 1064: foreach my $def (sort keys %gd_defs){ 1065: if ( defined $gd_defs{$def}->{affect} ){ 1066: my %hash; 1067: $hash{'element_name'} = $def; 1068: 1069: my @depend_info_per_element; 1070: foreach my $joined_conds_to_enable ( keys %{$gd_defs{$def}->{affect}} ){ 1071: my %hash2; 1072: my @conds_to_enable; 1073: foreach (split(/,/, $joined_conds_to_enable)){ 1074: push(@conds_to_enable, {cond_value => $_}); 1075: } 1076: my @affected_elements; 1077: foreach (keys %{$gd_defs{$def}->{affect}->{$joined_conds_to_enable}}){ 1078: push(@affected_elements, {affected_element_name => $_}); 1079: } 1080: 1081: $hash2{'conds_to_enable'} = \@conds_to_enable; 1082: $hash2{'affected_elements'} = \@affected_elements; 1083: 1084: push(@depend_info_per_element, \%hash2); 1085: } 1086: $hash{'depend_info_per_element'} = \@depend_info_per_element; 1087: push(@depend_info, \%hash); 1088: } 1089: } 1090: 1091: my $tmpl_str = <<END; 1092: <script type="text/javascript"> 1093: <!-- 1094: function all_depend_check(){ 1095: var selects = document.getElementsByTagName("SELECT"); 1096: for ( var i=0 ; i<selects.length ; i++ ){ 1097: depend_check(selects[i]); 1098: } 1099: var inputs = document.getElementsByTagName("INPUT"); 1100: for ( var i=0 ; i<inputs.length ; i++ ){ 1101: depend_check(inputs[i]); 1102: } 1103: } 1104: function depend_check(element){ 1105: var form = element.form; 1106: <TMPL_LOOP NAME="depend_info"> 1107: if ( element.name == '<TMPL_VAR NAME="element_name">' ){ 1108: <TMPL_LOOP NAME="depend_info_per_element"> 1109: if ( 1110: <TMPL_LOOP NAME="conds_to_enable"> 1111: element.options[element.options.selectedIndex].value == '<TMPL_VAR NAME="cond_value">' 1112: <TMPL_UNLESS __last__> || </TMPL_UNLESS> 1113: </TMPL_LOOP> 1114: ) 1115: { 1116: <TMPL_LOOP NAME="affected_elements"> 1117: form['<TMPL_VAR NAME="affected_element_name">'].disabled = true; 1118: form['<TMPL_VAR NAME="affected_element_name">'].style.backgroundColor = '#d6d6d6'; 1119: </TMPL_LOOP> 1120: } else { 1121: <TMPL_LOOP NAME="affected_elements"> 1122: form['<TMPL_VAR NAME="affected_element_name">'].disabled = false; 1123: form['<TMPL_VAR NAME="affected_element_name">'].style.backgroundColor = 'white'; 1124: </TMPL_LOOP> 1125: } 1126: </TMPL_LOOP> 1127: } 1128: </TMPL_LOOP> 1129: 1130: var inputs = document.getElementsByTagName("INPUT"); 1131: var selects = document.getElementsByTagName("SELECT"); 1132: for ( var i=0 ; i < (inputs.length + selects.length) ; i++ ){ 1133: var e; 1134: if ( i < inputs.length ){ 1135: e = inputs[i]; 1136: } else { 1137: e = selects[i-inputs.length]; 1138: } 1139: if ( e.className == 'nouse_in_pie' ){ 1140: if ( form.graph_type.value == 'pie' ){ 1141: e.disabled = true; 1142: e.style.backgroundColor = '#d6d6d6'; 1143: } else { 1144: e.disabled = false; 1145: e.style.backgroundColor = 'white'; 1146: } 1147: } else if ( e.className == 'use_in_lines_linespoints' ){ 1148: if ( form.graph_type.value == 'lines' || form.graph_type.value == 'linespoints' ){ 1149: e.disabled = false; 1150: e.style.backgroundColor = 'white'; 1151: } else { 1152: e.disabled = true; 1153: e.style.backgroundColor = '#d6d6d6'; 1154: } 1155: } 1156: } 1157: } 1158: // --> 1159: </script> 1160: 1161: END 1162: ; 1163: 1164: my $tmpl = HTML::Template->new(scalarref => \$tmpl_str, 1165: loop_context_vars => 1); 1166: $tmpl->param(depend_info => \@depend_info); 1167: return $tmpl->output; 1168: } 1169: 1170: #------------------------------------------------------------------------------------------ 1171: sub make_form { 1172: my ($form_position) = @_; 1173: 1174: my $form = ''; 1175: 1176: my %def_map = ( 1177: type_depend => { 1178: caption => 'グラフ種類固有情報', 1179: content => [ 1180: [ 1181: 'group:円グラフ', 1182: 'value_font', 1183: 'pie_height', 1184: 'label', 1185: 'labelclr', 1186: 'start_angle', 1187: 'suppress_angle', 1188: 'BR', 1189: ], 1190: [ 1191: 'group:線グラフ', 1192: 'line_width', 1193: 'BR', 1194: ], 1195: [ 1196: 'group:棒グラフ', 1197: 'shadowclr', 1198: 'shadow_depth', 1199: 'bar_spacing', 1200: 'BR', 1201: ], 1202: ], 1203: }, 1204: bottom => { 1205: caption => 'X軸関連', 1206: content => [ 1207: [ 1208: 'group:目盛り', 1209: 'axis_space', 1210: 'BR', 1211: ], 1212: [ 1213: 'group:X軸ラベル', 1214: 'x_label', 1215: 'x_label_font', 1216: 'x_label_position', 1217: 'BR', 1218: ], 1219: [ 1220: 'group:X軸目盛り', 1221: 'x_axis_font', 1222: 'x_tick_length', 1223: 'x_long_ticks', 1224: 'x_ticks', 1225: 'x_label_skip', 1226: 'x_label_offset', 1227: 'x_labels_vertical', 1228: 'x_plot_values', 1229: 'BR', 1230: ], 1231: ], 1232: }, 1233: left => { 1234: caption => 'Y軸関連', 1235: content => [ 1236: [ 1237: 'group:Y軸ラベル', 1238: 'y_label', 1239: 'y_label_font', 1240: 'y_label_position', 1241: 'BR', 1242: ], 1243: [ 1244: 'group:Y軸目盛り', 1245: 'y_axis_font', 1246: 'y_tick_length', 1247: 'y_long_ticks', 1248: 'zero_axis', 1249: 'zero_axis_only', 1250: 'y_label_skip', 1251: 'y_label_offset', 1252: 'y_plot_values', 1253: 'BR', 1254: ], 1255: [ 1256: 'group:Y軸値', 1257: 'y_max_value', 1258: 'y_min_value', 1259: ], 1260: ], 1261: }, 1262: basic => { 1263: caption => '基本', 1264: content => [ 1265: [ 1266: 'group:画像', 1267: 'width', 1268: 'height', 1269: 'BR', 1270: ], 1271: [ 1272: 'group:タイトル', 1273: 'title', 1274: 'title_font', 1275: 'BR', 1276: ], 1277: ], 1278: }, 1279: 1280: right => { 1281: caption => 'X・Y軸共通', 1282: content => [ 1283: [ 1284: 'group:値', 1285: 'show_values', 1286: 'values_vertical', 1287: 'values_space', 1288: 'BR', 1289: ], 1290: [ 1291: 'group:軸', 1292: 'no_axes', 1293: 'box_axis', 1294: 'BR', 1295: ], 1296: ['group:マージン', 1297: 't_margin', 1298: 'b_margin', 1299: 'l_margin', 1300: 'r_margin', 1301: 'BR', 1302: ], 1303: [ 1304: 'group:色', 1305: 'bgclr', 1306: 'fgclr', 1307: 'boxclr', 1308: 'accentclr', 1309: 'axislabelclr', 1310: 'legendclr', 1311: 'BR', 1312: ], 1313: [ 1314: 'group:凡例', 1315: 'legend_placement', 1316: 'legend_spacing', 1317: 'legend_marker_width', 1318: 'legend_marker_height', 1319: ], 1320: [ 1321: 'group:その他', 1322: 'cumulate', 1323: 'marker_size', 1324: 'y_tick_number', 1325: ], 1326: ], 1327: }, 1328: ); 1329: 1330: # %def_map 記述漏れ対策。 1331: my %defs_for_check = %gd_defs; 1332: 1333: my @current_defs = @{$def_map{$form_position}->{'content'}}; 1334: my $caption = $def_map{$form_position}->{'caption'}; 1335: 1336: my @defs_ordered; 1337: foreach my $def (@current_defs){ 1338: if ( ref($def) eq 'ARRAY' ){ 1339: foreach my $def2 (@$def){ 1340: push(@defs_ordered, $def2); 1341: delete $defs_for_check{$def2}; 1342: } 1343: } else { 1344: push(@defs_ordered, $def); 1345: delete $defs_for_check{$def}; 1346: } 1347: } 1348: 1349: # @def_map 記述漏れの定義情報を追加。 1350: if ( scalar(keys %defs_for_check) > 0 ){ 1351: # push(@defs_ordered, 'group:未整理'); 1352: foreach my $def (keys %defs_for_check){ 1353: # push(@defs_ordered, $def); 1354: } 1355: } 1356: 1357: 1358: $form .= qq(<div class="caption">$caption</div><table width=100% cellspacing=0 cellpadding=0 border=0 class="defsform"><tr><td>\n); 1359: 1360: foreach my $def (@defs_ordered){ 1361: if ( $def eq 'BR' ){ 1362: $form .= "</td></tr><tr><td>\n"; 1363: next; 1364: } 1365: if ( $def =~ m/^group:(.*)/ ){ 1366: $form .= "▼$1\n"; 1367: next; 1368: } 1369: if ( defined $gd_defs{$def}->{'depend_check_only'} && $gd_defs{$def}->{'depend_check_only'} ){ 1370: # 依存関係チェックのみなのでスキップ 1371: next; 1372: } 1373: 1374: my $desc = $gd_defs{$def}->{'desc'}; 1375: my $shortdesc = $gd_defs{$def}->{'shortdesc'}; 1376: my $subdesc = $gd_defs{$def}->{'subdesc'}; 1377: my $type = $gd_defs{$def}->{'type'}; 1378: my $comment = $gd_defs{$def}->{'comment'}; 1379: my $input_name = $def; 1380: my $input_type; 1381: my $input_size = $gd_defs{$def}->{'input_size'}; 1382: my @option_value_list; 1383: my @option_desc_list; 1384: if ( $type eq 'string' || $type eq 'digit' || $type eq 'realdigit' ){ 1385: $input_type="TEXT"; 1386: } elsif ( $type eq 'font' || $type eq 'option' || $type eq 'color' ){ 1387: $input_type="SELECT"; 1388: 1389: if ( $type eq 'font' ){ 1390: @option_value_list = &get_font_list(); 1391: @option_desc_list = &get_font_list(); 1392: } elsif ( $type eq 'option' ){ 1393: @option_value_list = keys %{$gd_defs{$def}->{'option_values'}}; 1394: foreach (@option_value_list){ 1395: push(@option_desc_list, $gd_defs{$def}->{'option_values'}->{$_}->{'desc'}); 1396: } 1397: } elsif ( $type eq 'color' ){ 1398: @option_value_list = &get_colorname_list(); 1399: @option_desc_list = &get_colorname_list(); 1400: } 1401: } 1402: my $input_default_value = defined $gd_defs{$def}->{'default_value'} ? $gd_defs{$def}->{'default_value'} : ''; 1403: 1404: $form.= sprintf(qq(<nobr><span class="desc" title="%s" onMouseOver="document.gdform.messagearea.value='%s'+unescape('%%0D%%0A')+'%s';">%s</span>), 1405: $def, 1406: "$desc $def", 1407: "$comment", 1408: defined $shortdesc ? $shortdesc : $desc, 1409: ); 1410: 1411: if ( $input_type eq 'SELECT' ){ 1412: $form .= qq(<select name="$input_name" onChange="depend_check(this); this.form.submit()">\n); 1413: for ( my $i=0 ; $i<@option_value_list ; $i++ ){ 1414: my $value = $option_value_list[$i]; 1415: my $desc = $option_desc_list[$i]; 1416: my $selected_str = ''; 1417: 1418: if ( $gd_defs{$def}->{'option_values'}->{$value}->{'is_default'} || 1419: $value eq $gd_defs{$def}->{'default_value'} ){ 1420: $selected_str = 'SELECTED'; 1421: } 1422: if ( $type eq 'color' ){ 1423: $form .= sprintf(" <option value='$value' $selected_str style='background-color: %s; color: %s'>$desc</option>\n", 1424: get_htmlrgb_by_colorname($value), 1425: get_complementary_htmlrgb(get_htmlrgb_by_colorname($value))); 1426: } else { 1427: $form .= " <option value='$value' $selected_str>$desc</option>\n"; 1428: } 1429: } 1430: $form .= qq(</select>\n); 1431: } else { 1432: $form .= sprintf(qq(<input type="%s" name="%s" value="%s" %s onChange="this.form.submit()">\n), 1433: $input_type, 1434: $input_name, 1435: $input_default_value, 1436: defined $input_size ? "size='$input_size'" : ''); 1437: } 1438: $form .= qq(</nobr>\n); 1439: 1440: if ( defined $subdesc ){ 1441: $form .= qq(<span style="font-size: 80%">($subdesc)</span>\n); 1442: } 1443: } 1444: 1445: $form .= qq(</td></tr></table>\n); 1446: 1447: return $form; 1448: }