%\iffalse % makeindex -s gglo.ist -o bargraph-js.gls bargraph-js.glo % makeindex -s gind.ist -o bargraph-js.ind bargraph-js.idx %<*copyright> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% bargraph-js.sty package, %% %% Copyright (C) 2019 %% %% dpstory@uakron.edu %% %% %% %% This program can redistributed and/or modified under %% %% the terms of the LaTeX Project Public License %% %% Distributed from CTAN archives in directory %% %% macros/latex/base/lppl.txt; either version 1.2 of the %% %% License, or (at your option) any later version. %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % %\NeedsTeXFormat{LaTeX2e}[1997/12/01] %\ProvidesPackage{bargraph-js} % [2019/04/07 v0.8 bargraph-js: Create bar graphs using form fields and JavaScript] %<*driver> \documentclass{ltxdoc} \usepackage{xcolor} \usepackage[colorlinks,hyperindex=false,bookmarksopen=false]{hyperref} \usepackage{bargraph-js} %\pdfstringdefDisableCommands{\let\\\textbackslash} \EnableCrossrefs \CodelineIndex \RecordChanges \gdef\brpr#1{\texttt{\char123\relax#1\char125\relax}} \let\darg\brpr \let\env\texttt \let\opt\texttt \let\app\textsf \let\pkg\textsf \def\visispace{\symbol{32}} \def\ameta#1{\ensuremath{\langle\textit{\texttt{#1}}\rangle}} \def\meta#1{\textsl{\texttt{#1}}} \def\SUB#1{\ensuremath{{}_{\mbox{\scriptsize\ttfamily#1}}}} %\def\cs#1{\texttt{\bslash#1}} \DeclareRobustCommand{\tmspace}[3]{% \ifmmode\mskip#1#2\else\kern#1#3\fi\relax} \renewcommand{\,}{\tmspace+\thinmuskip{.1667em}} \let\thinspace\, \renewcommand{\!}{\tmspace-\thinmuskip{.1667em}} \let\negthinspace\! \renewcommand{\:}{\tmspace+\medmuskip{.2222em}} \let\medspace\: \newcommand{\negmedspace}{\tmspace-\medmuskip{.2222em}} \renewcommand{\;}{\tmspace+\thickmuskip{.2777em}} \let\thickspace\; \newcommand{\negthickspace}{\tmspace-\thickmuskip{.2777em}} \makeatletter \renewcommand{\paragraph} {\@startsection{paragraph}{4}{0pt}{6pt}{-3pt} {\normalfont\normalsize\bfseries}} \renewenvironment{quote}[1][] {\def\@rgi{#1}\ifx\@rgi\@empty \let\rghtm\@empty\else\def\rghtm{\rightmargin\leftmargin}\fi \list{}{\rghtm} %{\rightmargin\leftmargin}% \item\relax} {\endlist} \makeatother \InputIfFileExists{aebdocfmt.def}{\PackageInfo{bargraph-js}{Inputting aebdocfmt.def}} {\def\IndexOpt{\DescribeMacro}\def\IndexKey{\DescribeMacro}\let\setupFullwidth\relax \PackageInfo{bargraph-js}{aebdocfmt.def cannot be found}} \begin{document} \def\CMD#1{\textbackslash#1} \GetFileInfo{bargraph-js.sty} \title{\textsf{bargraph-js}: Create dynamic bar graphs using form fields and JavaScript} \author{D. P. Story\\ Email: \texttt{dpstory@acrotex.net}} \date{processed \today} \maketitle \tableofcontents \let\Email\texttt \DocInput{bargraph-js.dtx} \IfFileExists{\jobname.ind}{\newpage\setupFullwidth\par\PrintIndex}{\paragraph*{Index} The index goes here.\\Execute \texttt{makeindex -s gind.ist -o bargraph-js.ind bargraph-js.idx}\\on the command line and recompile \texttt{bargraph-js.dtx}.} \IfFileExists{\jobname.gls}{\PrintChanges}{\paragraph*{Change History} The list of changes goes here.\\Execute \texttt{makeindex -s gglo.ist -o bargraph-js.gls bargraph-js.glo}\\on the command line and recompile \texttt{bargraph-js.dtx}.} \end{document} % % \fi % \MakeShortVerb{|} % % \InputIfFileExists{aebdonotindex.def}{\PackageInfo{web}{Inputting aebdonotindex.def}} % {\PackageInfo{web}{cannot find aebdonotindex.def}} % % \begin{macrocode} %<*package> \RequirePackage{xkeyval} % \end{macrocode} % \section{Introduction} % Through {\LaTeX} markup, a document author can create a PDF with form fields. % \app{Adobe Acrobat Reader DC} (as well as the \app{Acrobat} application itself) allows you to change % the properties of this field through the JavaScript engine; in particular, the dimensions % of the bounding rectangles of the form fields can be changed. Changes to form fields can be saved as well. % % An {Acro\negthinspace\TeX} user (GBH) wanted to create a bar graph % reflecting the responses of his students. This package is a generalization % of that solution. % % A bar graph is based on qualitative (or categorical) data. The raw data % is classified by some criterion and and a numerical value is assigned (often times the value is a count, % but need not be). Totals for each category are % recorded in the form of a bar graph. Graphs may be vertically or % horizontally oriented. In this package, we address such problems as (1) % creation and coloring of the bar graph; (2) scaling; and (3) labeling. % Conceptually, more than one bar graph can be placed, one superimposed on % top the other. % \section{Options and requirements} % We only have one option, \IndexOpt{dynamic}\opt{dynamic}, this brings in some JavaScript to support % the creation of bar graphs with no explicit use of \cs{barfor} commands; this is % used in creating histograms for discrete probability distributions. % \begin{macrocode} \DeclareOptionX{dynamic}{\def\bgjs@importDynamic {\InputIfFileExists{dynam-js.def}{}{\PackageWarning{bargraph-js} {The file dynam-js.def cannot be found}}}} \let\bgjs@importDynamic\relax \ProcessOptionsX\relax \AtEndOfPackage{\bgjs@importDynamic} \RequirePackage{xcolor} \RequirePackage{eforms}[2019/05/24] \edef\bgjs@restoreCats{\catcode`\noexpand\"=\the\catcode`\"\relax} \@makeother\" % \end{macrocode} % An initial scale factor. The scale factor is dynamically reset, as needed. However, when a bar graph % is cleared, \DescribeMacro\scaleFactorDef\cs{scaleFactorDef} is used. % \begin{macrocode} \def\scaleFactorDef#1{\def\sc@leF@ctorDef{#1}} \scaleFactorDef{2} % \end{macrocode} % % \section{The \texttt{bargraphenv} and \texttt{bargraph} environments} % A bar graph is built within an \env{bargraphenv} environment that controls it. % \begin{environment}{bargraphenv} % A \env{bargraph} environment (defined later) is contained within the \env{bargraphenv} % environment. The \env{bargraphenv} has a number of key-values to set. % \paragraph*{Key-values for \env{bargraphenv}} \leavevmode\IndexKey{width}\texttt{width} is the width of the underlying % \texttt{minipage}; similarly, \IndexKey{height}\texttt{height} is its height. % \begin{macrocode} \define@key{bgrphenv}{width}[\linewidth]{\setlength{\@tempdima}{#1}% \ifxetex\else\addtolength{\@tempdima}{2bp}\fi \edef\bgrphenv@width{\the\@tempdima}\@tempdima=.99626\@tempdima \edef\bgrphenv@widthbp{\strip@pt\@tempdima}} \define@key{bgrphenv}{height}[2in]{\setlength{\@tempdima}{#1}% \edef\bgrphenv@height{\the\@tempdima}\@tempdima=.99626\@tempdima \edef\bgrphenv@heightbp{\strip@pt\@tempdima}} % \end{macrocode} % A switch, a counter, and a token register used by this package. % \begin{macrocode} \newif\ifhorizontalbars \horizontalbarsfalse \newcount\cntbars \cntbars=0 \newtoks\bgtoks % \end{macrocode} % \leavevmode\IndexKey{o}\hskip-\marginparsep\texttt{=\ameta{{\upshape{horiz\string|vert}}}} % When \texttt{o=vert}, the default, the bars of the bargraph % increase/decrease vertically (height changes); while for % \texttt{o=horiz}, they increase/decrease horizontally (width changes). % \begin{macrocode} \define@choicekey{bgrphenv}{o}[\val\nr]{horiz,vert}{% \ifcase\nr\relax \horizontalbarstrue\or \horizontalbarsfalse\else \horizontalbarsfalse\fi} % \end{macrocode} % \leavevmode\IndexKey{origin}\hskip-\marginparsep\texttt{=\ameta{\upshape0\string|.5}} Sets the origin of the bar graph. % When \texttt{origin=0}, the baseline of the bars is the horizontal axis (\texttt{o=vert}) or the vertical % axis (\texttt{o=horiz}); when \texttt{origin=.5}, the baseline of the bars is on a line positioned half-way % up the vertical axis (\texttt{o=vert}) or half-way across the horizontal axis (\texttt{o=horiz}). % \begin{macrocode} \define@choicekey+{bgrphenv}{origin}{0,.5}[0]{\def\bgjs@origin{#1}} {\PackageWarning{bargraph-js}{Bad choice for origin, permissible values are 0 and .5.\MessageBreak Will use a value of 0. Try again}} \def\bgjs@origin{0} % \end{macrocode} % \leavevmode\IndexKey{showaxis}\hskip-\marginparsep\texttt{=\ameta{\upshape{true\string|false}}} % If true, a horizontal or vertical axis is drawn, depending on the setting of the \texttt{o} key. % \begin{macrocode} \newif\if@bgshowaxis \@bgshowaxisfalse % dps23 \catcode`\%=14\relax \define@boolkey{bgrphenv}[@bg]{showaxis}[true]{} % \end{macrocode} % The following \env{defineJS} environment defines \cs{hided@t@Fmt}. It declares % some vital data for the current \env{bargraphenv} environment. In this environment % we make exclamation point (\texttt!) the escape, and the comment character as \texttt{\%}. % \begin{macrocode} \begin{defineJS}[\catcode`\!=0\relax]{\hided@t@Fmt} if (typeof dataForEnv=="undefined") var dataForEnv=new Object; dataForEnv["!p(1)"]=new Object; dataForEnv["!p(1)"].width=!bgrphenv@widthbp; dataForEnv["!p(1)"].height=!bgrphenv@heightbp; dataForEnv["!p(1)"].horiz=!ifhorizontalbars(true)!else(false)!fi; dataForEnv["!p(1)"].sf=!sc@leF@ctorDef; dataForEnv["!p(1)"].bgs=[!bg@list]; dataForEnv["!p(1)"].origin=!bgjs@origin; var _nO=!bgjs@origin; var r=event.target.rect; if (dataForEnv["!p(1)"].horiz){ var _w1=_nO*dataForEnv["!p(1)"].width; dataForEnv["!p(1)"].maxDim=(_nO==0)?!% !bgrphenv@widthbp:Math.min(_w1,!bgrphenv@widthbp-_w1); dataForEnv["!p(1)"].baseline=r[0]+_w1; } else { var _h1=_nO*dataForEnv["!p(1)"].height; dataForEnv["!p(1)"].maxDim=(_nO==0)?!% !bgrphenv@heightbp:Math.min(_h1,!bgrphenv@heightbp-_h1); dataForEnv["!p(1)"].baseline=r[3]+_h1; } dataForEnv["!p(1)"].values=new Object; \end{defineJS} % \end{macrocode} % This text field holds various information about the \env{bargraphenv} named \texttt{\#1}. % In this definition, we say |\cmd{\let\%\defjsLB}|; the format code is passed using % \cs{hided@t@Fmt}, which is defined by a \env{defineJS} environment above. We also pass % a parameter (|\bgrphenv@name=|\ameta{bgenv-name}) to \cs{hided@t@Fmt} through the \cs{bParams}/\cs{eParams} mechanism. % \begin{macrocode} \def\internalD@t@Hidden#1{% \llap{\textField[\cmd{\bParams{#1}\eParams\let\%\defjsLB} \autoCenter{n}\BC{}\BG{}\S{S}\textSize{0}\Ff{\FfReadOnly} \AA{\AAFormat{\hided@t@Fmt}}]{internalData.#1}{2bp}{2bp}}} % \end{macrocode} % \DescribeEnv{bargraphenv}\hskip-\marginparsep\texttt{[\ameta{KV-pairs}]\darg{\ameta{env-name}}} % The \env{bargraphenv} simply encloses the \env{bargraph} environment in a % \env{minipage} of dimensions specified by the key-values. % \begin{macrocode} \def\bg@vs#1{\setlength\@tempdima{#1}\vskip\@tempdima} \def\bg@hs#1{\setlength\@tempdima{#1}\hskip\@tempdima} % \end{macrocode} % \DescribeMacro\oBgEnvs is an internal command that will hold a comma-delimited list % of all \env{bargraphenv}s in the document. % \begin{macrocode} \let\oBgEnvs\@gobble \newif\ifisbgenv\isbgenvfalse \let\barNum\ef@Zero \def\bg@warning{\PackageWarningNoLine{bargraph-js} {At least one more compile is required}\global\let\bg@warning\@empty} \newenvironment{bargraphenv}[2][]{\offinterlineskip % \end{macrocode} % Within the \env{bargraphenv}, we define \DescribeMacro\vs\cs{vs} and \DescribeMacro\hs\cs{hs} to position objects % vertically and horizontally. % These are local definitions. Syntax: \cs{vs\darg{\ameta{dimen}}} and \cs{hs\darg{\ameta{dimen}}}. % \begin{macrocode} \let\vs\bg@vs\let\hs\bg@hs\global\isbgenvtrue \@ifundefined{envname@#2}{\global\@namedef{envname@#2}{#2}} {\PackageWarning{bargraph-js} {The name '#2' has been used in an earlier\MessageBreak bargraphenv environment, please choose another\MessageBreak name for the bargraphenv environment that\MessageBreak appears}}% \g@addto@macro\oBgEnvs{,"#2":\dl@lBrace\dl@rBrace}% \def\bgrphenv@name{#2}\setkeys{bgrphenv}{width,height,#1}% \ifhorizontalbars \@tempdima\bgrphenv@width\relax \@tempdima=\bgjs@origin\@tempdima \edef\bgjs@setorigin{\the\@tempdima}% \else % vertical \@tempdima\bgrphenv@height\relax \@tempdima=\bgjs@origin\@tempdima \edef\bgjs@setorigin{\the\@tempdima}% \fi \@tempdima=.99626\@tempdima \edef\bgrphenv@setoriginbp{\strip@pt\@tempdima}% \let\isdynamic\relax\let\bg@list\@gobble \global\let\barNum\ef@Zero \begin{minipage}[b][\bgrphenv@height][b]{\bgrphenv@width}\hfuzz4pt} {\hfill\end{minipage}\leavevmode \ifx\txtBgValues\relax\else \@ifundefined{OBgEnvs}{\bg@warning}{\txtBgValues \global\let\txtBgValues\relax}\fi \internalD@t@Hidden{\bgrphenv@name}} % \end{macrocode} % \end{environment} % \begin{environment}{bargraph}\hskip-\marginparsep\texttt{[\ameta{KV-pairs}]\darg{\ameta{bar-name}}} % The description of an individual bar graph. This environment is contained % within the \env{bargraphenv} environment. The content of the environment % \emph{must consist exclusively} of the commands % \cs{barfor\darg{\ameta{bar-name}}} and \cs{cmd\darg{\ameta{tex-cmds}}}. % These pairs of tokens are collected and stacked up in their natural order. % The \env{bargraph} environment has number of key-values to set % % \paragraph*{Key-values for \env{bargraph}} The environment has several key-value pairs.\medskip % % \noindent\IndexKey{nbars}\hskip-\marginparsep\texttt{=\ameta{pos-int}} % \texttt{nbars} is the number of bars in this environment. % \begin{macrocode} \define@key{bargraph}{nbars}[0]{\gdef\nbars{#1}} \def\nb@rsDef{0} \def\nbars{0} % \end{macrocode} % \leavevmode\IndexKey{gap}\hskip-\marginparsep\texttt{=\ameta{pos-num}} % \texttt{gap} is the gap between bars; it is a positive number (\emph{no dimensions}). % It is interpreted as printer points (bp, big points). % \begin{macrocode} \define@key{bargraph}{gap}[0]{\gdef\bargap{#1bp}} \def\b@rgapDef{0} \def\bargap{0bp} % in bp units % \end{macrocode} % \leavevmode\IndexKey{bardimen}\hskip-\marginparsep\texttt{=\ameta{pos-num}} % is the width/height of the bars (measured in bp points). When % \texttt{o=vert}, then the value of \texttt{bardimen} is the width of each bar; % when \texttt{o=horiz}, the value of \texttt{bardimen} is the height of each bar. % The value \ameta{pos-num} is a dimensionless number. % \begin{macrocode} \define@key{bargraph}{bardimen}{\gdef\bardimen{#1bp}} \def\b@rdimenDef{20} \def\bardimen{20bp} % in bp units % \end{macrocode} % \cs{bgjs@seto} begins the process of gathering the command pairs % \cs{barfor\darg{\ameta{name}}} and \cs{cmd\darg{\ameta{cmds}}}. % \begin{macrocode} \def\bgjs@seto{\cntbars\@ne \ifx\barNum\ef@Zero\relax \global\let\barNum\ef@One \toks@={\ifhorizontalbars\else\ifxetex\else \hskip2bp\fi\fi}\else\toks@={}\fi\bgjs@gettype} % \end{macrocode} % \cs{bgjs@gettype} continues \cs{bgjs@seto}, it determines if this is a dynamic \env{bargraph} environment or not % \begin{macrocode} \def\bgjs@gettype{\@ifnextchar\relax {\bgjs@dynamic}{\bgjs@@getbartoks}} \def\expbarfor#1\@nil{#1} % \end{macrocode} % The \cs{bgjs@dynamic} command continues \cs{bgjs@gettype} in the first case. It is the case where JavaScript does it % all. We create only an initial bar, whose name is `\texttt{x\ameta{bar-name}.bgnoname@\ameta{env}}'. % \begin{macrocode} \def\bgjs@dynamic{\let\b@rforCommon\dyb@rforCommon \edef\bgrph@name{x\bgrph@name}\leavevmode \rlap{\barfor{bgnoname}}} % \end{macrocode} % The \cs{bgjs@@getbartoks} command continues \cs{bgjs@gettype} in the % second case. We detect two types of tokens: % \cs{barfor\darg{\ameta{arg}}} and \cs{cmd\darg{\ameta{arg}}}. If this % former, we jump to \cs{bgjs@@getbarfor}, otherwise, we jump to % \cs{bgjs@@getcmd}. % \begin{macrocode} \def\bgjs@@getbartoks{\@ifnextchar\barfor {\bgjs@@getbarfor}{\@ifnextchar\cmd {\bgjs@@getcmd}{\relax}}}% % \end{macrocode} % The \cs{bgjs@@getbarfor} command handles \cs{barfor} tokens, processing depends on % \texttt{o=\ameta{\upshape{vert\string|horiz}}}. % \begin{macrocode} \def\hmrk{\hskip\bgjs@setorigin\relax} \def\bgjs@@getbarfor\barfor#1{% \ifhorizontalbars % o=horiz \toks@=\expandafter{\the\toks@\leavevmode \rlap{\hmrk\bgjs@adj\barfor{#1}}}% \ifnum\cntbars=\nbars\relax \toks@=\expandafter{\the\toks@\vcgBdry[0bp]}\else \toks@=\expandafter{\the\toks@\vcgBdry[\bargap]}\fi \else % o=vert \toks@=\expandafter{\the\toks@ \raisebox{\bgjs@setorigin+\bgjs@adj}{\barfor{#1}}}% \ifnum\cntbars=\nbars\relax \toks@=\expandafter{\the\toks@\cgBdry[0bp]}\else \toks@=\expandafter{\the\toks@\cgBdry[\bargap]}\fi \fi \advance\cntbars\@ne \ifnum\cntbars>\nbars \def\bgjs@next{\@ifnextchar\cmd{\bgjs@@getbartoks} {\let\\\expbarfor\the\toks@}}% \else \def\bgjs@next{\bgjs@@getbartoks}\fi \unskip\bgjs@next} % \end{macrocode} % The \cs{bgjs@@getcmd} command handles \cs{barfor} tokens. Again, the processing depends on % \texttt{o=\ameta{\upshape{vert\string|horiz}}}. % \begin{macrocode} \def\bgjs@@getcmd\cmd#1{% \toks@=\expandafter{\the\toks@#1}% \ifhorizontalbars % o=horiz \ifnum\cntbars=\nbars\relax \toks@=\expandafter{\the\toks@}\else \toks@=\expandafter{\the\toks@}\fi \else % o=vert \ifnum\cntbars=\@ne \toks@=\expandafter{\the\toks@}\else \toks@=\expandafter{\the\toks@}\fi \fi \ifnum\cntbars>\nbars \def\bgjs@next{\@ifnextchar\cmd{\bgjs@@getbartoks} {\let\\\expbarfor\the\toks@}}\else \def\bgjs@next{\bgjs@@getbartoks}\fi \bgjs@next} % \end{macrocode} % Here we finally define the \DescribeEnv{bargraph}\env{bargraph} environment; it records its name, % processes its key-values, and expands \cs{bgjs@seto}. % \begin{macrocode} \newenvironment{bargraph}[2][]{\def\bgrph@name{#2}% \g@addto@macro\bg@list{,"#2"}\setkeys{bargraph}{nbars=\nb@rsDef, bardimen=\b@rdimenDef,gap=\b@rgapDef,#1}% \edef\bg@sign{\ifxetex-\else+\fi}% \edef\bgjs@adj{\ifhorizontalbars \ifxetex\else\hskip2bp\fi\else+\ifxetex0pt\else2bp\fi\fi}% \if@bgshowaxis\ifhorizontalbars\else\leavevmode \smash{\makebox[0pt][l]{\raisebox{\bgjs@setorigin\bg@sign.5pt}% {\rule{\bgrphenv@width}{1pt}}}}\fi\fi \bgjs@seto}{\if@bgshowaxis\ifhorizontalbars \smash{\rlap{\setlength{\@tempdima} {\bgjs@setorigin\bg@sign.5pt}% \hskip\@tempdima\rule{1pt}{\bgrphenv@height}}}\fi\fi} % \end{macrocode} % \end{environment} %\paragraph*{Saving the bar data.} We need to save the value of each bar so that % when the user re-opens the document, the bar graphs will work as designed, % including the optimization. We initialize this text field using \cs{OBgEnvs}. % \begin{macrocode} \def\txtBgValues{%\leavevmode \llap{\textField[\S{S}\H{N}\BG{}\BC{}\F{\FHidden}% \autoCenter{n}\DV{(\{\OBgEnvs\})}\V{(\{\OBgEnvs\})} ]{bgValues}{2bp}{2bp}}\llap{\textField[\S{S}\H{N}\BG{}\BC{} \F{\FHidden}\autoCenter{n}\DV{}\V{}]{barLabeling}{2bp}{2bp}}% } % \end{macrocode} % The definition of \DescribeMacro\OBgEnvs\cs{OBgEnvs} is written the auxiliary % file and is based in \DescribeMacro\oBgEnvs\cs{oBgEnvs}, which is an ongoing comma-delimited list of % all names of \env{bargraphenvs} in the document. % \begin{macrocode} \gdef\bg@wrtBbValues{\immediate\write\@auxout{\string \gdef\string\OBgEnvs{\oBgEnvs}}} % \end{macrocode} % At the end of the document, we issued \cs{wrtBgValues}, which defines the command % \cs{OBgEnvs}. The command is a list of all \env{bargraphenv}s in the document and is used % to build a JavaScript object that will hold the values of each bar in the document. % \begin{macrocode} \AtEndDocument{\ifisbgenv\bg@wrtBbValues\fi} % \end{macrocode} % \section{Designing the bars} % The bars themselves are pushbuttons (created by \cs{pushButton} of \pkg{eforms}). The problem is to pass % any appearance changes to the \env{bargraph} environment. We pass the bars in two ways: % (1) key-values that are common to all bars; (2) key-values that are individual to a bar. % \begin{macro}{\barforCommon}\leavevmode % \hskip-\marginparsep\texttt{\,\darg{\ameta{KV-pairs}}} % Pass \pkg{eforms} key-values that will be applied to all bars in this environment. % \begin{macrocode} \def\barforCommon#1{\def\b@rforCommon{#1}} % \end{macrocode} % Below are the default common key-value properties for a bar. % \begin{macrocode} \barforCommon{\S{S}\H{N}\BG{}\BC{}\F{\FHidden}\autoCenter{n}} % \end{macrocode} % \leavevmode\DescribeMacro{\dybarforCommon} % \hskip-\marginparsep\texttt{\,\darg{\ameta{KV-pairs}}} % The properties set by \cs{dybarforCommon} are the properties of the bars when the bars are dynamically generated. Bar % are generated % dynamically when there are no \cs{barfor} commands in the \env{bargraph} environment. % \begin{macrocode} \def\dybarforCommon#1{\def\dyb@rforCommon{#1}} \dybarforCommon{\S{S}\H{N}\BG{}\BC{}\F{\FHidden}} % \end{macrocode} % \end{macro} % \begin{macro}{\presetsbarfor} % \leavevmode % \hskip-\marginparsep\texttt{\,\darg{\ameta{bar-name}}\darg{\ameta{name}}\darg{\ameta{KV-pairs}}} % Pass customizing key-values to an individual bar. A bar (as defined by \cs{barfor}, defined below) % takes a \ameta{name} argument, which is used to reference that bar. For example, to color the % bar \ameta{name}, we say \cs{presetbarfor\darg{\ameta{bar-name}}\darg{\ameta{name}}\darg{\cs{BG\darg{red}}}}. % \begin{macrocode} \def\presetsbarfor#1#2#3{\protectedKeys{#1.#2@\bgrphenv@name}{#3}} %\toks@={}\bgjs@gettwo#3\bgjs@stop\relax % \expandafter\edef\csname #1.#2@\bgrphenv@name\endcsname{\the\toks@}} %\def\bgjs@stop{\relax}\def\bgjs@relax{\relax} % \end{macrocode} % For this preset, we need to fully expand, while protecting the key-values. We store % key-value pairs in \cs{toks@} but we place \cs{noexpand} front of the key; for example % \cs{BG\darg{red}} is stored as \cs{noexpand\cs{BG}\darg{red}}. % \begin{macrocode} %\def\bgjs@gettwo#1#2{\ifx#1\bgjs@stop\else % \toks@=\expandafter{\the\toks@\noexpand#1{#2}}\expandafter % \bgjs@gettwo\fi} \def\presetsb@rfor#1{\@nameuse{\bgrph@name.#1@\bgrphenv@name}} % \end{macrocode} % \end{macro} % \begin{macro}{\barfor}\hskip-\marginparsep\texttt{\,\darg{\ameta{name}}} % Within the \env{bargraph} environment, place the named \cs{barfor} command. % \begin{macrocode} \def\barfor#1{\ifhorizontalbars\def\bgenv@angle{0}\else \def\bgenv@angle{90}\fi \@ifundefined{\bgrph@name.#1@\bgrphenv@name} {\let\@presets\@gobble}{\let\@presets\presetsb@rfor}% \pushButton[\presets{\b@rforCommon\R{\bgenv@angle}} % \end{macrocode} % We use \cs{epresets} key, new to \pkg{eforms} (2019/01/22). % \begin{macrocode} \epresets{\@presets{#1}} % \end{macrocode} % The field name of the \cs{pushButton} for the \cs{barfor} command is % \begin{quote}\texttt{\ameta{bar-name}.\ameta{name}@\ameta{env-name}}\end{quote} % \changes{v0.7}{2019/04/07}{Change field name of \string\cs{barfor}} % We change the field name of \cs{barfor} to % \begin{quote}\texttt{\ameta{env-name}@\ameta{bar-name}.\ameta{name}}\end{quote} % In this way, the same \ameta{name} can be used in more than one environments. % \begin{macrocode} ]{\bgrphenv@name @\bgrph@name.#1}{\bardimen}{\bardimen}} % \end{macrocode} %\DescribeMacro\getBarName\leavevmode\hskip-\marginparsep % \texttt{\darg{\ameta{env-name}}\darg{\ameta{bar-name}}\darg{\ameta{name}}} is the field name of the corresponding % to the bar identified by its arguments. The second argument is assumed to be dynamic, not literal. If the second % argument is a literal, then you can say \cs{getBarName\darg{\ameta{bar-name}}\darg{"myBar"}\darg{\ameta{name}}}; i.e., % enclosed it in quotation marks. % \begin{macrocode} \def\getBarName(#1,#2,#3){#1+"@"+#2+"."+#3} % \end{macrocode} % \end{macro} % \section{Inputting count data into a bar} % \begin{macro}{\inputFor}\hskip-\marginparsep\texttt{\,\darg{\ameta{env}}\darg{\ameta{bar}}\darg{\ameta{name}}} % Use \cs{inputFor} to create a text field to input count data. We need to set up % a correspondence between \cs{inputFor} and a particular bar. The key-value options are passed to this % field using \cs{presetinputfor}; its argument uses \ameta{bar} and \ameta{name}, which are used for labeling purposes % (see the default definition of \cs{presetinptfor}). % \begin{macrocode} \def\inputFor#1#2#3{% env, bar, name \textField[\cmd{\bParams{#1}{#2.#3}\eParams} \presets{\priorpresetinputfor{#1}{#2.#3}} \presets{\presetinputfor{#1}{#2.#3}} % \end{macrocode} % This package uses a lot of calculation events, we try to reduce the number of events processed by % registering the current input field as an active graph. All the subsequent calculations are made only % if the active graph (\texttt{updateBG.activegraph}) matches the \texttt{\ameta{env}.\ameta{bar}@\ameta{bgenv}}. % \begin{macrocode} \AAonfocus{updateBG.activegraph="#1@#2.#3";}]% % \end{macrocode} % The field name of this \cs{textField} shall be % \begin{quote}\texttt{\ameta{env-name}.\ameta{bar-name}@\ameta{name}}\end{quote} % \begin{macrocode} {#1.#2@#3}} % \end{macrocode} % \end{macro} % Below is the default definition of \DescribeMacro\presetinputfor\cs{presetinputfor}, it defines calculate, keystroke, % format, % and validate events. In this setup, we accept only natural numbers (or count data). We call the % \pkg{bargraph-js}-defined JavaScript function \texttt{updateGG} (update bar graph). This command % can be redefined. \cs{presetinputfor} takes one argument, which is internally pass as \texttt{\ameta{env-name}.\ameta{bar-name}}, % as seen above in the definition of \cs{inputFor}. % \begin{macrocode} \def\priorpresetinputfor#1#2{\AddAAcalculate{% updateBG.env=getEnvName(event.targetName);\r updateBG(event.value,"#1@#2",\usebarlabel);\r} \AAcalculate{;} } \newcommand{\presetinputfor}[2]{% \AAkeystroke{AFNumber_Keystroke(0, 0, 0, 0, "", true);} \AAformat{AFNumber_Format(0, 0, 0, 0, "", true);} \AAvalidate{AFRange_Validate(true, 0, false, 0);} } % \end{macrocode} % Manually re-scale all bar graphs in a particular bar graph environment, % using both \DescribeMacro\displaysfFor\cs{displaysfFor}, which both displays % the current scale factor and allows input for a new scale factor, and % a push button \DescribeMacro\manualsfFor\cs{manualsfFor} to re-scale. For each % of these commands, the first argument is optional arguments to pass to the % form field, the second argument is the name of the \env{bargraphenv} to be re-scaled. % \begin{macrocode} \begin{defineJS}{\dsfForKeyStr} if(event.willCommit) { var v=event.value; try { v=eval(v) } catch(e){}; if(isNaN(v)) { app.beep(0); app.alert("Enter a nonnegative number") event.rc=false; } } \end{defineJS} \newcommand{\displaysfFor}[2][]{% \textField[\DV{2}\V{2}\TU{Current scale factor} \AAkeystroke{\dsfForKeyStr} \AAvalidate{AFRange_Validate(true, 0, false);} #1]{txtRescale.#2}% } \begin{defineJS}[\catcode`\!=0\relax]{\msfForMU} var f=this.getField("txtRescale.!p(1)"); var v=eval(f.value); if (v>0){ dataForEnv["!p(1)"].sf=v; rescaleBargraph("!p(1)"); } \end{defineJS} \newcommand{\manualsfFor}[2][]{% \pushButton[\cmd{\bParams{#2}\eParams} \AAmouseup{\msfForMU}#1]{btnRescale.#2}% } % \end{macrocode} % %\subsection{A labeling scheme} % Labeling by typesetting the label is possible, but we also provide labeling through % Acrobat forms tool tips property. We have two approaches:\par\medskip\noindent % \DescribeMacro\barLabelsTU\hskip-\marginparsep\texttt{\,\darg{\ameta{string}\string|\ameta{function}}} % When the argument is a \ameta{string}, the label becomes % `\texttt{\ameta{string}\,\ameta{env}.\ameta{bar},\,Value:\,value}', % and with it is a \ameta{function}, the function must return a string representing the label. The argument of % \cs{barLabelsTU} % is used when the bar has a tool tip already defined. In this case, the form of the tool tip % is a string which incorporates the token \texttt{@v@}; this token will be replaced by the value of the bar. % \begin{macrocode} \def\barLabelsTU#1{\def\usebarlabel{#1}\ifx\usebarlabel\@empty \def\usebarlabel{""}\fi} \barLabelsTU{""} % \end{macrocode} % \leavevmode\DescribeMacro\barLabelsNoTU % \hskip-\marginparsep\texttt{\,\darg{\ameta{string}\string|\ameta{function}}} % When the \cs{barFor} command has no associated tool tip (\cs{TU}), the value of \cs{barLabelsNoTU} is used. % When the argument is a string the tokens \texttt{\#1} and \texttt{\#2} are used as placeholders for % \texttt{\ameta{env}.\env{bar}} and \texttt{\ameta{value}}, respectively. When it is a \ameta{function}, % the function returns a string based on its argument of \texttt{fld} (usually \texttt{\ameta{env-name}.\ameta{bar-name}}) and % \texttt{v} (the \texttt{\ameta{value}}. % \begin{macrocode} \def\barLabelsNoTU#1{\def\@rgi{#1}\ifx\@empty \edef\barLabelsNoTUJS{\barLabelsNoTUJSDef}\else \def\barLabelsNoTUJS{#1}\fi} \def\barLabelsNoTUJSDef{o.barname+": "+o.bar+", Value: "+o.value} \expandafter\barLabelsNoTU\expandafter{\barLabelsNoTUJSDef} \def\simpleBarLabels(#1,#2){"Bar for "+#1+", Value: "+#2;} % \end{macrocode} % \leavevmode\DescribeMacro\labelFld\hskip-\marginparsep % \texttt{[\ameta{form-opts}]\darg{\ameta{caption}}\darg{\ameta{bg-name}.\ameta{bar-name}}\darg{\ameta{width}}\darg{\ameta{height}}}\\ % The command creates a text field that is used to label horizontal or vertical bars. For vertical bars % use the key \cs{R\darg{90}} in the \ameta{form-opts}. % \begin{macrocode} \newcommand\labelFld[4][]{\textField[\Ff{\FfReadOnly}\F{\FHidden} \BC{}\BG{}#1\autoCenter{n}\DV{#2}\V{#2}]{#3@\bgrphenv@name}{#4}} % \end{macrocode} % \subsection{Saving and restoring critical data} % \textbf{Restore critical data with an open action.} % If the first page is opened, and \texttt{oBarLabeling} needs % to be restored, we call \texttt{gbRestoreData()}. % \begin{macrocode} \begin{defineJS}[\catcode`\!=0\relax]{\bgOpenAction} if(oBarLabeling.needsRestore) { var bgTO=app.setTimeOut("bgRestoreData();!% app.clearTimeOut(bgTO);!% oBarLabeling.needsRestore=false",1000); } \end{defineJS} \begingroup\@makeother\%\let\%\defjsLB \thisPageAction{\JS{\bgOpenAction}}{} \endgroup % \end{macrocode} % \textbf{Saving critical data.} We save the object \texttt{oBarLabeling} as a string to % the hidden field \texttt{barLabeling}. % \begin{macrocode} \begin{willSave} bgSaveData(); \end{willSave} % \end{macrocode} % \section{Document JavaScript} % JavaScript that supports the creation of bar graphs. % \begin{macrocode} \def\barDefColor{color.blue} % \end{macrocode} % \leavevmode\DescribeMacro\populateCommaData\hskip-\marginparsep\texttt{(env,bar,str[,validate])} is % a convenience command to access the JavaScript function \texttt{populateCommaData}, which is defined in Section~\ref{s:labelcd} % \begin{macrocode} \newcommand{\populateCommaData}{% populateCommaData.usebarlabel=\usebarlabel;\r populateCommaData } \begin{insDLJS*}{bgjs} \begin{newsegment}{AeB: bargraph-js} /* Document Level JavaScript bargraph-js Package D. P. Story copyright \the\year */ \end{newsegment} % \end{macrocode} % \leavevmode\IndexJS{updateBG}\hskip-\marginparsep\texttt{(v,fld,o)} % function is designed for bar graph with explicit \cs{barFor} commands % inserted by the document author. % \begin{macrocode} \begin{newsegment}{Bar Graph of preset bars} var _scaleFactorDef=\sc@leF@ctorDef; var oBarLabeling = new Object; oBarLabeling.needsRestore=true; function updateBG (v,fld,o) { % \end{macrocode} % If \texttt{updateBG.activegraph} is different from \texttt{fld}, we exit, don't do any calculations. % The form of \texttt{fld} is \texttt{\ameta{env-name}@\ameta{bar-name}.\ameta{name}}. % \begin{macrocode} if (typeof v=="undefined") return; if (updateBG.activegraph!=fld) return; var useDefLabeling=(typeof o=="string"); var env=updateBG.env; var w=dataForEnv[env].width; var h=dataForEnv[env].height; var baseline=dataForEnv[env].baseline var isHoriz=dataForEnv[env].horiz; var bRescaleNeeded=false; var f=this.getField(fld); var cachedValue=getBarValue(env,fld); saveBarValue(env,fld,v); // dps13 bRescaleNeeded=getNewScaleFactor(env,v); var sf=dataForEnv[env].sf; sf=(sf<0)?-sf:sf; var r=f.rect; % \end{macrocode} % \textbf{Calculating the new rectangle.} % \texttt{cachedValue} is the previous value of the bar, and \texttt{v} is the new % value. There are several cases: horizontal versus vertical; \texttt{v<0} versus % \texttt{v>=0}; and whether the rectangle is changing direction (neg to pos) or not. % \begin{macrocode} if(isHoriz) { // dps01 if (v<0) { if (cachedValue<0) // no change in sign r[0]=r[2]+(v*sf); else { // change from pos to neg r[2]=r[0]; r[0]=r[2]+(v*sf); } } else { if (cachedValue<0) { // from neg to pos r[0]=r[2]; r[2]=r[0]+(v*sf); } else // from pos to pos r[2]=r[0]+(v*sf); } } else { // vertical if (v<0) { // dps01 if (cachedValue<0) { // no change in sign r[3]=r[1]+(v*sf); } else { // switching from pos to neg r[1]=r[3]; r[3]=r[1]+(v*sf); } } else { if (cachedValue<0) // change in sign: neg to pos r[3]=r[1]+(v*sf); else // no change r[1]=r[3]+(v*sf); } } f.rect=r; % \end{macrocode} % \textbf{Applying a label to current bar.} If a field has an empty \cs{TU} property, % we save this in \texttt{oBarLabeling}, if not already saved. If the field has an % empty \cs{TU} field, we record this with a \texttt{-1}. % \begin{macrocode} if (typeof oBarLabeling[fld]=="undefined") oBarLabeling[fld]=(f.userName!="")?f.userName:-1; % \end{macrocode} % If this field has no \cs{TU} property, we use the default, as expressed through % \texttt{barLabelsDef}. % \begin{macrocode} if (oBarLabeling[fld]==-1)f.userName=barLabelsDef(fld,v); else { % \end{macrocode} % If this field has a nonempty \cs{TU} property, we either use \texttt{simpleBarLabels}, % passing a formatting string with it, or we pass the function name with is arguments. % If the \texttt{o} argument is not passed, there is no change to the labeling. % \begin{macrocode} if (typeof o!="undefined") f.userName=(useDefLabeling)?((o=="")?% (simpleBarLabels(fld,v,oBarLabeling[fld])):% %(simpleBarLabels(fld,v,f.userName)):% (simpleBarLabels(fld,v,o))):o(fld,v); } % \end{macrocode} % \textbf{Default fill color.} If the bar does not have an associated fill color, % we apply the default color of \cs{barDefColor} % \begin{macrocode} f.fillColor=(color.equal(f.fillColor,color.transparent))?% \barDefColor:f.fillColor; % \end{macrocode} % Make the bar visible (initially they are hidden) % \begin{macrocode} f.display=display.visible; if(bRescaleNeeded) { % \end{macrocode} % If we must re-scale, we cannot allow the user to enter any more data % until the re-scaling is finished; otherwise, data is occasionally lost. % \begin{macrocode} var g=this.getField(env); if (g!=null)g.readonly=true; app.setTimeOut("rescaleBargraph(\""+env+"\")",5); } } % \end{macrocode} % \leavevmode\IndexJS{barLabelsDef}\hskip-\marginparsep\texttt{(fld,v)} is the default function for % labeling the bars using the tooltip property of a form field. % inserted by the document author. % \begin{macrocode} function parseFld(fld,v) { var pos=fld.indexOf("@"); var env=fld.substring(0,pos); fld=fld.substring(pos+1); pos=fld.indexOf("."); var barname=fld.substring(0,pos); var bar=fld.substring(pos+1); parseFld.env=env; parseFld.barname=barname; parseFld.bar=bar; parseFld.value=v; } function barLabelsDef(fld,v){ parseFld(fld,v); var o=parseFld; var strOrFnc=\barLabelsNoTUJS; return ((typeof strOrFnc=="string")?strOrFnc:strOrFnc(fld,v)); } function simpleBarLabels(fld,v){ % parseFld(fld,v); % var o=parseFld; if (arguments.length==2) % return o.barname + ": "+o.bar+", Value: "+v; return customBarLabels(fld,v); else { parseFld(fld,v); var s=arguments[2]; return replaceLblVars(s); } } % \end{macrocode} % \leavevmode\IndexJS{getNewScaleFactor}\hskip-\marginparsep\texttt{(env,v)} % is a utility function for calculating the scale factor of the graph. % \begin{macrocode} function getNewScaleFactor(env,v) { var w=dataForEnv[env].width; var h=dataForEnv[env].height; var maxDimen=dataForEnv[env].maxDim; var nO=dataForEnv[env].origin; var sf=dataForEnv[env].sf; var isHoriz=dataForEnv[env].horiz; v=(v<0)?-v:v; if(isHoriz) { maxDimen=(nO==0)?w:maxDimen; if( sf*v > maxDimen ) { sf=maxDimen/v; dataForEnv[env].sf=sf; return true; } } else { maxDimen=(nO==0)?h:maxDimen; if ( sf*v > maxDimen ) { sf=maxDimen/v; dataForEnv[env].sf=sf; return true; } } return false; } % \end{macrocode} % This function converts the field name for a \texttt{\string\inputFor} field % to the field name of corresponding bar. % \begin{macrocode} // .@ --> @. function getBarName(name) { var pos=name.indexOf("."); var env=name.substring(0,pos); name=name.substring(pos+1); return env+"@"+name.replace(/@/,"."); } function getEnvName(name) { var pos=name.indexOf("."); var env=name.substring(0,pos); return env; } % \end{macrocode} % \leavevmode\IndexJS{rescaleBargraph}\hskip-\marginparsep\texttt{(env)} % touches each field causing the calculation fields to activate. % Here, \texttt{env} is the name of the \texttt{bargraphenv} that encloses % the active bar graph. % \begin{macrocode} function rescaleBargraph(env) { var oInputFor=this.getField(env); this.delay=true; // date input by the \\inputFor if (oInputFor!=null) { var a=oInputFor.getArray() for (var i=0; i maxDim ) { maxDim=thisDim; maxFldName=a[i].name; } } if (maxFldName=="") return; // get name of input field env.bar@name var v=getBarValue(env,maxFldName); // now rescale all bars v=(v<0)?-v:v; // dps01 if (v !=0) { sf=w/v; // if v !=0 dataForEnv[env].sf=sf; var g=this.getField(env); % g.strokeColor=color.red; if (g!=null)g.readonly=true; app.setTimeOut("rescaleBargraph(\""+env+"\")",5); } } else { // if vertical bars var maxDim=0; maxFldName=""; for (var i=0; i< a.length; i++) { if (a[i].display==display.hidden) continue; var thisDim=a[i].rect[1]-a[i].rect[3]; thisDim=(thisDim<0)?-thisDim:thisDim; // dps01 if ( thisDim > maxDim ) { maxDim=thisDim; maxFldName=a[i].name; } } if (maxFldName=="") return; var v=getBarValue(env,maxFldName); // dps13 if ( typeof v == "undefined" ) return; // now rescale all bars v=(v<0)?-v:v; // dps01 if (v !=0) { sf=h/v; dataForEnv[env].sf=sf; var g=this.getField(env); if (g!=null)g.readonly=true; % g.strokeColor=color.red; app.setTimeOut("rescaleBargraph(\""+env+"\")",5); } } } } % \end{macrocode} % \leavevmode\IndexJS{resetBargraphs}\hskip-\marginparsep\texttt{(\ameta{various})} % resets the bar graphs and the input fields. The unspecified argument % is a list of names for \env{bargraphenv} and \env{bargraph} names. % \begin{macrocode} function resetBargraphs() { this.calculate=false; this.resetForm(arguments); for (var n=0; n