#' Creates charts
#'
#' @param chart.type The name of plot to create, e.g \code{\link[flipStandardCharts]{Area}}, \code{\link[flipStandardCharts]{Bar}}, \code{\link[flipStandardCharts]{BarPictograph}}, \code{\link[flipStandardCharts]{Bean}}, \code{\link[flipStandardCharts]{Box}}, \code{\link[flipStandardCharts]{Column}}, \code{\link[flipStandardCharts]{Density}}, \code{\link[flipStandardCharts]{Donut}}, \code{\link[flipStandardCharts]{GeographicMap}}, \code{\link[flipStandardCharts]{Heat}}, \code{\link[flipStandardCharts]{Histogram}}, \code{\link[flipStandardCharts]{Line}}, \code{\link[flipStandardCharts]{Palm}}, \code{\link[flipStandardCharts]{Pie}}, \code{\link[flipStandardCharts]{Pyramid}}, \code{\link[flipStandardCharts]{Radar}}, \code{\link[flipStandardCharts]{Scatter}}, \code{\link[flipStandardCharts]{Stream}}, \code{\link[flipStandardCharts]{TimeSeries}}, \code{\link[flipStandardCharts]{Venn}}, \code{\link[flipStandardCharts]{Violin}}. Spaces will automatically be stripped.
#' @param x The data to be plotted.
#' @param small.multiples Logical; Whether each series should be shown in its own panel. When this is true, parameters from \code{\link[flipStandardCharts]{SmallMultiples}} can be used (e.g. \code{nrows}, \code{x.order}, \code{share.axes}, \code{average.show}, \code{average.color}, \code{panel.title.show}).
#' @param multi.color.series Logical; Indicates whether multiple colors will be shown in a Bar or Column chart with a single series. By default this is off and different colors are used to distinguish between different series. However, when chart.type is "Pyramid", then \code{multi.color.series} is always \code{true}.
#' @param font.units One of "px" or "pt"
#' @param annotation.list a list of annotations to add to the chart, based on statistics in the input data.
#' @param signif.show Logical; whether to show significance tests (arrows or column comparisons).
#' @param signif.column.comparisons Logical; whether to show comparisons (letters) in data labels.
#' @param ... Arguments to the function \code{chart.type}. See documentation for specific chart types or see details below.
#' @param warn.if.no.match Logical; If TRUE, a warning is shown if any arugments are not matched.
#' @param append.data Logical; If TRUE, extra information is appended to the chart object which is used for exporting. These are appended as attributes
#' \describe{\item{ChartData}{This is the data passed to the charting functions after tidying by \code{PrepareData}. It is used for both Excel and Powerpoint exporting.}
#' \item{ChartSettings}{This a named list so that powerpoint chart can use the same visualization settings. Information about the structure of this list is \href{https://wiki.q-researchsoftware.com/wiki/PptChartSettings}{here}.}
#' \item{ChartWarning}{This attribute is set if the chart cannot be properly exported to Excel. It is set to a string which is shown as a warning message on export in Displayr.}
#' \item{ChartType}{This attribute is actually always set, even if \code{append.data} if not true. It is the name of the chart type as used in PowerPoint and can vary depending on the visualization settings used.}}
#' @details Parameters passed to the charting functions are listed below:
#' \describe{
#' \itemize{\code{type} }{ For chart types \code{Area}, \code{Bar} and \code{Column}, this can be set to \code{Stacked} to show cumulative values.}
#' \itemize{\code{grid.show} }{ Logical; whether to show grid lines.}
#' \itemize{\code{opacity} }{ Opacity of bars as an alpha value (0 to 1).}
#' \itemize{\code{colors} }{ Character; a vector containing one or more colors specified as hex codes.}
#' \itemize{\code{fit.type} }{ Character; type of line of best fit. Can be one of "None", "Linear", "LOESS",
#' "Friedman's super smoother" or "Cubic spline". This option is only valid for charts Area, Bar, Column, Line, Scatter, Pyramid.}
#' \itemize{\code{fit.ignore.last} }{ Logical; whether to ignore the last data point in the fit.}
#' \itemize{\code{fit.line.type} }{ Character; One of "solid", "dot", "dash", "dotdash", or length of dash "2px", "5px".}
#' \itemize{\code{fit.line.colors} }{ Character; a vector containing one or more colors specified as hex codes.}
#' \itemize{\code{fit.line.width} }{ Numeric; Line width of line of best fit.}
#' \itemize{\code{fit.line.name} }{ Character; Name of the line of best fit, which will appear in the hovertext.}
#' \itemize{\code{fit.line.opacity} }{ Opacity of trend line as an alpha value (0 to 1).}
#' \itemize{\code{fit.CI.show} }{ Show 95\% confidence interval.}
#' \itemize{\code{fit.CI.opacity} }{ Opacity of confidence interval ribbon as an alpha value (0 to 1).}
#' \itemize{\code{fit.CI.colors} }{ Character; a vector containing one or more colors specified as hex codes.}
#' \itemize{\code{title} }{ Character; chart title.}
#' \itemize{\code{title.font.family} }{ Character; title font family. Can be "Arial Black",
#' "Arial", "Comic Sans MS", "Courier New", "Georgia", "Impact",
#' "Lucida Console", "Lucida Sans Unicode", "Marlett", "Symbol", "Tahoma",
#' "Times New Roman", "Trebuchet MS", "Verdana", "Webdings".}
#' \itemize{\code{title.font.color} }{ Title font color as a named color in character format (e.g. "black") or hex code.}
#' \itemize{\code{title.font.size} }{ Integer; Title font size; default = 10.}
#' \itemize{\code{subtitle} }{ Character.}
#' \itemize{\code{subtitle.font.color} }{ subtitle font color as a named color in character format (e.g. "black") or hex code.}
#' \itemize{\code{subtitle.font.family} }{ Character; subtitle font family.}
#' \itemize{\code{subtitle.font.size} }{ Integer; subtitle font size.}
#' \itemize{\code{footer} }{ Character to show below the chart.}
#' \itemize{\code{footer.font.color} }{ footer font color as a named color in character format (e.g. "black") or hex code.}
#' \itemize{\code{footer.font.family} }{ Character; footer font family.}
#' \itemize{\code{footer.font.size} }{ Integer; footer font size.}
#' \itemize{\code{footer.wrap} }{ Logical; whether the footer text should be wrapped.}
#' \itemize{\code{footer.wrap.nchar} }{ Number of characters (approximately) in each
#' line of the footer when \code{footer.wrap} \code{TRUE}.}
#' \itemize{\code{background.fill.color} }{ Background color in character format (e.g. "black").}
#' \itemize{\code{legend.show} }{ Logical; show the legend.}
#' \itemize{\code{legend.wrap} }{ Logical; whether the legend text should be wrapped.}
#' \itemize{\code{legend.wrap.nchar} }{ Number of characters (approximately) in each
#' line of the legend when \code{legend.wrap} \code{TRUE}.}
#' \itemize{\code{legend.fill.color} }{ Legend fill color as a named color in character format
#' (e.g. "black") or an rgb value (e.g. rgb(0, 0, 0, maxColorValue = 255)).}
#' \itemize{\code{legend.fill.opacity} }{ Legend fill opacity as an alpha value (0 to 1).}
#' \itemize{\code{legend.ascending} }{ Logical; TRUE for ascending, FALSE for descending.
#' By default, we set it to to FALSE if the chart is stacked and TRUE otherwise.}
#' \itemize{\code{legend.border.color} }{ Legend border color as a named color in character
#' format (e.g. "black") or an rgb value (e.g. rgb(0, 0, 0, maxColorValue = 255)).}
#' \itemize{\code{legend.border.line.width} }{ Width in pixels of the border
#' around the legend. 0 = no border.}
#' \itemize{\code{legend.position.x} }{ A numeric controlling the position of the legend. Values range from -0.5 (left) to 1.5 (right).}
#' \itemize{\code{legend.position.y} }{ A numeric controlling the position of the legend. Values range from 0 (bottom) to 1 (top).}
#' \itemize{\code{legend.font.color} }{ Legend font color as a named color in character format (e.g. "black") or hex code.}
#' \itemize{\code{legend.font.family} }{ Character; legend font family.}
#' \itemize{\code{legend.font.size} }{ Integer; Legend font size.}
#' \itemize{\code{legend.width} }{ Only used in a TimeSeries chart. Integer; Width (in pixels) of the textbox containing the legend.}
#' \itemize{\code{values.title} }{ Character; title of the axis showing the independent values, i.e. y-axis (in a column or area chart) or x-axis (in a bar chart). This will override dim names read from the input table.}
#' \itemize{\code{values.title.font.color} }{ values-axis title font color as a named color in character format (e.g. "black") or hex code.}
#' \itemize{\code{values.title.font.family} }{ Character; values-axis title font family.}
#' \itemize{\code{values.title.font.size} }{ Integer; values-axis title font size.}
#' \itemize{\code{values.line.width} }{ values-axis line width in pixels (0 = no line).}
#' \itemize{\code{values.line.color} }{ values-axis line color as a named color in character format.
#' (e.g. "black") or an rgb value (e.g. rgb(0, 0, 0, maxColorValue = 255)).}
#' \itemize{\code{values.tick.mark.length} }{ Length of tick marks in pixels. Ticks are only shown when \code{y.line.width > 0}.}
#' \itemize{\code{values.bounds.minimum} }{ Minimum of range for plotting; NULL = no manual range set.}
#' \itemize{\code{values.bounds.maximum} }{ Maximum of range for plotting; NULL = no manual range set.}
#' \itemize{\code{values.tick.distance} }{ Distance between tick marks. Requires that \code{values.bounds.minimum} and \code{values.bounds.maximum} have been set.}
#' \itemize{\code{values.number.ticks} }{ Only used in Stream. The total number of ticks on the y-axis.}
#' \itemize{\code{values.zero} }{ Whether the values-axis should include zero. If false, the range will be determined from the input data.}
#' \itemize{\code{values.zero.line.width} }{ Width in pixels of zero line;.}
#' \itemize{\code{values.zero.line.color} }{ Color of horizontal zero line as a named.
#' color in character format (e.g. "black") or an rgb value (e.g. rgb(0, 0, 0, maxColorValue = 255)).}
#' \itemize{\code{values.data.reversed} }{ Logical; whether to reverse values-axis or not.}
#' \itemize{\code{values.grid.width} }{ Width of values-grid lines in pixels; 0 = no line.}
#' \itemize{\code{values.grid.color} }{ Color of values-grid lines as a named color in character.
#' format (e.g. "black") or an rgb value (e.g. rgb(0, 0, 0, maxColorValue = 255)).}
#' \itemize{\code{values.tick.show} }{ Whether to display the values-axis tick labels.}
#' \itemize{\code{values.tick.suffix} }{ values-axis tick label suffix.}
#' \itemize{\code{values.tick.prefix} }{ values-axis tick label prefix.}
#' \itemize{\code{values.tick.format} }{ A string representing a d3 formatting code. See \url{https://github.com/d3/d3/blob/master/API.md#number-formats-d3-format}.}
#' \itemize{\code{values.hovertext.format} }{ A string representing a d3 formatting code. See \url{https://github.com/d3/d3/blob/master/API.md#number-formats-d3-format}.}
#' \itemize{\code{values.tick.angle} }{ values-axis tick label angle in degrees 90 = vertical; 0 = horizontal.}
#' \itemize{\code{values.tick.font.color} }{ values-axis tick label font color as a named color in character format (e.g. "black") or hex code.}
#' \itemize{\code{values.tick.font.family} }{ Character; values-axis tick label font family.}
#' \itemize{\code{values.tick.font.size} }{ Integer; values-axis tick label font size.}
#' \itemize{\code{categories.title} }{ Character; title of the axis showing the dependent values, i.e. x-axis (in a column or area chart) or y-axis (in a bar chart). This will override dim names read from the input table.}
#' \itemize{\code{categories.title.font.color} }{ categories-axis title font color as a named color in character format (e.g. "black") or hex code.}
#' \itemize{\code{categories.title.font.family} }{ Character; categories-axis title font family.}
#' \itemize{\code{categories.title.font.size} }{ Integer; categories-axis title font size.}
#' \itemize{\code{categories.line.width} }{ categories-axis line in pixels, 0 = no line.}
#' \itemize{\code{categories.line.color} }{ categories-axis line color as a named color in character format (e.g. "black") or an rgb value (e.g. rgb(0, 0, 0, macategories.olorValue = 255)).}
#' \itemize{\code{categories.tick.marks} }{ Character; whether and where to show tick marks on the categories.axis. Can be "outside", "inside", "none".}
#' \itemize{\code{categories.tick.mark.length} }{ Length of tick marks in pixels.}
#' \itemize{\code{categories.tick.units} }{ Only used in Stream. "Automatic", "Number", "Day", "Month" or "Year".}
#' \itemize{\code{categories.tick.interval} }{ Only used in Stream. The frequency of ticks on the x-axis. Where the data crosses multiple years, re-starts at each year.}
#' \itemize{\code{categories.bounds.minimum} }{ Minimum of range for plotting; NULL = no manual range set. Must be less than categories.bounds.maximum.}
#' \itemize{\code{categories.bounds.maximum} }{ Maximum of range for plotting; NULL = no manual range set. Must be greater than categories.bounds.minimum.}
#' \itemize{\code{categories.tick.distance} }{ Tick mark distance in categories.axis units between minimum and maximum for plotting; NULL = no manual range set.}
#' \itemize{\code{categories.zero} }{ Whether the categories-axis should include zero. If false, the range will be determined from the input data.}
#' \itemize{\code{categories.zero.line.width} }{ Width in pixels of zero line.}
#' \itemize{\code{categories.zero.line.color} }{ Color of horizontal zero (origin) line as a named color in character format (e.g. "black") or hex code.}
#' \itemize{\code{categories.data.reversed} }{ Logical; whether to reverse categories-axis or not.}
#' \itemize{\code{categories.grid.width} }{ Width of y-grid lines in pixels; 0 = no line.}
#' \itemize{\code{categories.grid.color} }{ Color of y-grid lines as a named color in character format (e.g. "black") or hex code.}
#' \itemize{\code{categories.tick.show} }{ Whether to display the categories-axis tick labels.}
#' \itemize{\code{categories.tick.suffix} }{ categories-axis tick label suffix.}
#' \itemize{\code{categories.tick.prefix} }{ categories-axis tick label prefix.}
#' \itemize{\code{categories.tick.format} }{ A string representing a d3 formatting code. See \url{https://github.com/mbostock/d3/wiki/Formatting#numbers}.}
#' \itemize{\code{categories.hovertext.format} }{ A string representing a d3 formatting code. See \url{https://github.com/mbostock/d3/wiki/Formatting#numbers}.}
#' \itemize{\code{categories.tick.angle} }{ categories-axis tick label angle in degrees. 90 = vertical; 0 = horizontal.}
#' \itemize{\code{categories.tick.font.color} }{ categories-axis tick label font color as a named color in character format (e.g. "black") or hex code.}
#' \itemize{\code{categories.tick.font.family} }{ Character; categories-axis tick label font family.}
#' \itemize{\code{categories.tick.font.size} }{ Integer; categories-axis tick label font size.}
#' \itemize{\code{categories.tick.label.wrap} }{ Logical; whether to wrap long labels on the categories-axis.}
#' \itemize{\code{categories.tick.label.wrap.nchar} }{ Integer; number of characters in each line when \code{label.wrap} is \code{TRUE}.}
#' \itemize{\code{tooltip.show} }{ Logical; whether to show a tooltip on hover.}
#' \itemize{\code{modebar.show} }{ Logical; whether to show the zoom menu buttons or not.}
#' \itemize{\code{global.font.family} }{ Character; font family for all occurrences of any
#' font attribute for the chart unless specified individually.}
#' \itemize{\code{global.font.color} }{ Global font color as a named color in character format (e.g. "black") or hex code.}
#' \itemize{\code{data.label.show} }{ Logical; whether to show data labels.}
#' \itemize{\code{data.label.font.family} }{ Character; font family for data label.}
#' \itemize{\code{data.label.font.size} }{ Integer; Font size for data label.px.}
#' \itemize{\code{data.label.font.color} }{ Font color as a named color in character format (e.g. "black") or hex code.}
#' \itemize{\code{data.label.format} }{ A string representing a d3 formatting code. See \url{https://github.com/mbostock/d3/wiki/Formatting#numbers}.}
#' \itemize{\code{data.label.prefix} }{ Character; prefix for data values.}
#' \itemize{\code{data.label.suffix} }{ Character; suffix for data values.}
#' \itemize{\code{data.label.threshold} }{ The proportion of the total range below which data labels should not be displayed. Only applicable for pie, bar and column charts.}
#' \itemize{\code{margin.top} }{ Margin between plot area and the top of the graphic in pixels.}
#' \itemize{\code{margin.bottom} }{ Margin between plot area and the bottom of the graphic in pixels.}
#' \itemize{\code{margin.left} }{ Margin between plot area and the left of the graphic in pixels.}
#' \itemize{\code{margin.right} }{ Margin between plot area and the right of the graphic in pixels.}
#' \itemize{\code{margin.inner.pad} }{ Padding in pixels between plot proper and axis lines.}
#' \itemize{\code{background.fill.opacity} }{ Background opacity as an alpha value (0 to 1).}
#' \itemize{\code{charting.area.fill.color} }{ Charting area background color as a named color in character format (e.g. "black") or hex code.}
#' \itemize{\code{charting.area.fill.opacity} }{ Charting area background opacity as an alpha value (0 to 1).}
#' \itemize{\code{line.thickness} }{ Only used in Scatter, Line Area or TimeSeries charts. Controls the line thickness in pixels.}
#' \itemize{\code{line.colors} }{ Character; a vctor containing one or more colors specified as hex codes.}
#' \itemize{\code{line.thickness} }{ Opacity of series lines as an alpha value (0 to 1).}
#' \itemize{\code{marker.show} }{ Only used in Scatter, Line or Area charts. Can be "none", "automatic" or a vector referencing the plotly symbol dictionary using either numerics or strings.}
#' \itemize{\code{marker.colors} }{ Only works if \code{marker.show} is true. Character; a vector containing one or more colors specified as hex codes.}
#' \itemize{\code{marker.opacity} }{ Only works if \code{marker.show} is true. Controls ppacity for markers as an alpha value (0 to 1).}
#' \itemize{\code{marker.size} }{ Only works if \code{marker.show} is true. Controls size in pixels of marker.}
#' \itemize{\code{marker.border.width} }{ Width in pixels of border/line around series bars; 0 is no line.}
#' \itemize{\code{marker.border.colors} }{ Character; a vector containing one or more colors specified as hex codes.}
#' \itemize{\code{marker.border.opacity} }{ Opacity of border around bars as an alpha value (0 to 1).}
#' \itemize{\code{bar.gap} }{ Only used in charts Bar, Column and Pyramid. Chart proportion between each bar or column if using bar or column charts, or between each cluster of bars or columns.}
#' \itemize{\code{weights} }{ Only used in Bean, Box, Density, Histogram, Violin plots or Venn diagrams. An optional \code{\link{list}}, where each element is a vector containing weights corresponding to the values of \code{x}, or, a vector where the weights is assumed applicable for each element in \code{x}.}
#' \itemize{\code{vertical} }{ Only used in Bean, Box, Density, Histogram or Violin plots Display the densities vertically.}
#' \itemize{\code{show.mean} }{ Only used in Violin plots. Displays the mean of the data.}
#' \itemize{\code{mean.color} }{ Defaults to "white".}
#' \itemize{\code{show.median} }{ Only used in Violin plots. Displays the median of the data.}
#' \itemize{\code{median.color} }{ Defaults to "black".}
#' \itemize{\code{show.quartiles} }{ Only used in Violin plots. Displays the quartiles of the data.}
#' \itemize{\code{quartile.color} }{ Defaults to "black".}
#' \itemize{\code{show.range} }{ Only used in Violin plots. Displays the range of the data.}
#' \itemize{\code{range.color} }{ Defaults to "black".}
#' \itemize{\code{show.values} }{ Only used in Box, Density or Histogram (always true in Bean plots). Produces a rug plot of individual values.}
#' \itemize{\code{values.color} }{ Defaults to "Green".}
#' \itemize{\code{show.density} }{ Only used in Violin plots. Show the left or top (if \code{vertical} is FALSE) of the violin plot.}
#' \itemize{\code{density.color} }{ Only used in Bean, Box, Density, Histogram or Violin plots. Defaults to "Green".}
#' \itemize{\code{bw} }{ The smoothing bandwidth to be used when creating a Density, Bean, or Violin plot. This defaults to \code{"nrd0"}, whereas \code{"SJ"} may often be superior (see \code{\link{density}}). The default is to \code{"nrd0"} as \code{"SJ"} fails with trivial categorical cases.}
#' \itemize{\code{adjust} }{ A scaling factor for the bandwidth when creating a Density, Bean, or Violin plot. E.g., a value of 0.5 sets the bandwidth to have of that computed using \code{bw}.}
#' \itemize{\code{kernel} }{ The kernel used when when creating a Density, Bean, or Violin plot. One of "gaussian" (the default), "epanechnikov", "rectangular", "triangular", "biweight", "cosine", "optcosine".}
#' \itemize{\code{n} }{ The number of equally-spaced points at which the density is to be estimated when creating a Density, Bean, or Violin plot. If greater than 512, it is rounded to a power of 2 (see \code{link{density}})}.
#' \itemize{\code{from} }{ The left-most point of the grid used when creating a Density, Bean, or Violin plot.}
#' \itemize{\code{to} }{ The right-most point of the grid used when creating a Density, Bean, or Violin plot.}
#' \itemize{\code{cut} }{ By default, the values of \code{from} and \code{to} are \code{cut} bandwidths beyond the extremes of the data.}
#' \itemize{\code{automatic.lower.density} }{ When \code{TRUE}, which is the default, \code{from} is set to the lowest value in the data.}
#' \itemize{\code{histogram.cumulative} }{ Only used in Histogram plots. Plots the cumulative histogram, if set to TRUE.}
#' \itemize{\code{histogram.counts} }{ Only used in Histogram plots. Displays the counts in tooltips of a histogram, rather than the proportions.}
#' \itemize{\code{maximum.bins} }{ Only used in Histograph plots. The maximum number of bins of the histogram. If \code{NULL}, this is generated automatically.}
#' \itemize{\code{box.points} }{ How outliers are displayed boxplots. \code{"All"} plots all the points. \code{"Suspected outliers"} plots points between 1.5 and 3 IQR from the 1st and 3rd quartile with un-filled circles. \code{"Outliers"} does not plot points between 1.5 and 3 IQR from the 1st and 3rd quartiles.}
#' \itemize{\code{country} }{ Only used in GeographicMap. Character string optionally stating the country that the states are from, if \code{map.type} is \code{"states"}.}
#' \itemize{\code{high.resolution} }{ Only used in GeographicMap. Specifically request a high resolution map. Otherwise the resolution of the map is chosen automatically based on the resolution required for the requested countries or regions.}
#' \itemize{\code{show.missing.regions} }{ Only used in GeographicMap. Logical; Whether to plot regions not included in \code{x} with values of \code{NA}. Used only when \code{mapping.package} is \code{"leaflet"}.}
#' \itemize{\code{treat.NA.as.0} }{ Only used in GeographicMap. Plots any \code{NA} values in the data and any geographical entities without data as having a zero value.}
#' \itemize{\code{ocean.color} }{ Only used in GeographicMap. The color used for oceans (or background).}
#' \itemize{\code{mapping.package} }{ Only used in GeographicMap. Either \code{"leaflet"} (better graphics, wider range of
#' maps) or \code{"plotly"} (faster).}
#' \itemize{\code{background} }{ Only used in GeographicMap. If \code{"mapping.package"} is \code{"leaflet"}, add a background
#' tile from opensteetmaps.}
#' \itemize{\code{zip.country} }{ Only used in GeographicMap. One of \code{"Automatic"}, \code{"USA"}, \code{"UK"} or \code{"Australia"}. If \code{"Automatic"} an attempt is made to infer the country from the data.}
#' \itemize{\code{sort.rows} }{ Only used in Heat. Whether to sort rows by their averages or link as a dendrogram. Options are \code{"None"}, \code{"Sort by averages (ascending)"}, \code{"Sort by averages (descending)"} and \code{"Dendrogram"}.}
#' \itemize{\code{sort.columns} }{ Only used in Heat. Whether to sort columns by their averages or link as a dendrogram. Options are \code{"None"}, \code{"Sort by averages (ascending)"}, \code{"Sort by averages (descending)"} and \code{"Dendrogram"}.}
#' \itemize{\code{standardization} }{ Only used in Heat. Whether to standardize the shading of rows or columns. Options are \code{"None"}, \code{"Standardize rows"} and \code{"Standardize columns"}.}
#' \itemize{\code{left.columns} }{ Only used in Heat. An optional list of vectors or matrices to be appended to the left of the heatmap.}
#' \itemize{\code{left.column.headings} }{ Only used in Heat. An optional comma separated string containing headings for \code{left.columns}. If not supplied, colnames of the items in \code{left.columns} are used.}
#' \itemize{\code{right.columns} }{ Only used in Heat. An optional list of vectors or matrices to be appended to the right of the heatmap.}
#' \itemize{\code{right.column.headings} }{ Only used in Heat. An optional comma separated string containing headings for
#' \code{right.columns}. If not supplied, colnames of the items in \code{right.columns} are used.}
#' \itemize{\code{pie.subslice.colors} }{ A vector containing hex value colors for the outer ring of the pie chart. If not supplied will default to the same colors as the inner ring.}
#' \itemize{\code{pie.subslice.colors.repeat} }{ Logical; if, when a grouped pie chart is displayed, the colors of the subslices should repeat by group, or be different throughout; defaults to TRUE.}
#' \itemize{\code{pie.groups.font.family} }{ Character; font family for group labels.}
#' \itemize{\code{pie.groups.font.size} }{ Font size for group labels.}
#' \itemize{\code{pie.groups.font.color} }{ Font color as a named color or hex code.}
#' \itemize{\code{pie.data.threshold} }{ Labels with values smaller than the theshold are not shown.}
#' \itemize{\code{pie.border.color} }{ A single color for space around pie and between segments.}
#' \itemize{\code{pie.inner.radius} }{ The size of the inner radius of pie and donut charts as a proportion out of 100. Defaults to 70.}
#' \itemize{\code{scatter.x.column} }{ When \code{x} is a dataframe or matrix, the index of the column (1-based) which contains the x-coordinate data in a scatterplot.}
#' \itemize{\code{scatter.y.column} }{ When \code{x} is a dataframe or matrix, the index of the column (1-based) which contains the y-coordinate data in a scatterplot. This is ignored if \code{scatter.y} is provided instead.}
#' \itemize{\code{scatter.sizes.column} }{ When \code{x} is a dataframe or matrix, the index of the column (1-based) which contains \code{scatter.sizes} data. This is ignored if \code{scatter.sizes} is provided instead.}
#' \itemize{\code{scatter.colors.column} }{ When \code{x} is a dataframe or matrix, the index of the column (1-based) which contains \code{scatter.colors} data. This is ignored if \code{scatter.colors} is provided instead.}
#' \itemize{\code{scatter.groups.column} }{ Only used for small multiples (otherwise use \code{scatter.colors.column} with \code{scatter.colors.as.categorical = TRUE}). The index of the column of \code{x} which is used to aggregate the data for small multiples. By default this is the last column in \code{x}.}
#' \itemize{\code{scatter.labels} }{ Optional vector for labelling scatter points. This should be the same length as the number of observations in x and y. If not provided, the rownames of \code{x} will be used instead.}
#' \itemize{\code{scatter.labels.name} }{ Character; Used for labelling subtitles and footers if \code{scatter.labels} is provided}.
#' \itemize{\code{scatter.sizes} }{ Numeric vector determining of the size of each observation. These can alternatively be provided as a column in \code{x}.}
#' \itemize{\code{scatter.sizes.name} }{ Character; Used for labelling footers and legends if \code{scatter.sizes} is provided.}
#' \itemize{\code{scatter.colors} }{ Numeric, character, or categorical vector determining the color of each observation. These can alternatively be provided as a column in \code{x}.}
#' \itemize{\code{scatter.colors.name} }{ Character; Used for labelling footers if \code{scatter.colors} is provided.}
#' \itemize{\code{scatter.colors.as.categorical} }{ Logical; Whether to treat colors as a categorical groups, or a numeric scale..}
#' \itemize{\code{scatter.labels.as.hovertext} }{ Logical; if TRUE, labels are shown has hovers; otherwise, as a labeled scatterplot.}
#' \itemize{\code{scatter.sizes.as.diameter} }{ Whether to show the points with diameter (instead of area, which is the default) proportional to the sizes variable. Cannot be used if \code{scatter.labels.as.hovertext = FALSE}.}
#' \itemize{\code{scatter.max.labels} }{ Integer; the maximum number of labels to show on a Labeled Scatterplot, i.e. when \code{scatter.labels.as.hovertext = FALSE}.}
#' \itemize{\code{trend.lines} }{ Logical; indicating whether to plot trend lines for multiple tables when \code{scatter.labels.as.hovertext = FALSE}.}
#' \itemize{\code{logos} }{ Optional list of images to be used to label scatterplot instead of the row names. This should be input as a comma-seperated list of URLs. Only used when \code{scatter.labels.as.hovertext = FALSE}.}
#' \itemize{\code{logo.size} }{ Numeric controlling the size of the logos.}
#' \itemize{\code{range.bars} }{ Only used in TimeSeries. Logical; whether the data consists of a single series with low, value, high in the columns, or multiple series.}
#' \itemize{\code{window.start} }{ Only used in TimeSeries. The number of days before the end of the data series to start the range selector window.}
#' \itemize{\code{nrows} }{ Only used for small multiples. Integer; Number of rows to arrange the charts in.}
#' \itemize{\code{x.order} }{ Only used for small multiples. A vector containing the list index of the columns in the order which they are to be shown.}
#' \itemize{\code{share.axes} }{ Only used for small multiples. Force range of the plot to be the same across all panels.}
#' \itemize{\code{average.show} }{ Only used for small multiples. Logical; whether to show a second series in each panel containing the data averaged across all series.}
#' \itemize{\code{average.color} }{ Only used for small multiples. The color in which the average series should be displayed.}
#' \itemize{\code{panel.title.show} }{ Only used for small multiples. Logical; whether to show a title for each panel.}
#' \itemize{\code{panel.title.font.family} }{ Only used for small multiples. Font family of panel titles.}
#' \itemize{\code{panel.title.font.color} }{ Only used for small multiples. Font color of panel titles as hex code.}
#' \itemize{\code{panel.title.font.size} }{ Only used for small multiples. Font size of panel titles.}
#' \itemize{\code{panel.title.wrap} }{ Only used for small multiples. Logical; whether the panel title should be wrapped.}
#' \itemize{\code{panel.title.wrap.nchar} }{ Only used for small multiples. Number of characters (approximately) in each line
#' of the panel title when \code{panel.title.wordwrap} \code{TRUE}.}
#' \itemize{\code{pad.top} }{ Only used for small multiples. Numeric in [0,1]; Spacing above chart (between panels).}
#' \itemize{\code{pad.bottom} }{ Only used for small multiples. Numeric in [0,1]; Spacing below chart (between panels).}
#' \itemize{\code{pad.left} }{ Only used for small multiples. Numeric in [0,1]; Spacing to the left of chart (between panels).}
#' \itemize{\code{pad.right} }{ Only used for small multiples. Numeric in [0,1]; Spacing to the right chart (between panels).}
#' }
#' @importFrom methods formalArgs
#' @importFrom flipStandardCharts ErrorIfNotEnoughData
#' @importFrom flipChartBasics StripAlphaChannel
#' @return A chart object that can be printed. Most often, a plotly object.
#' @export
#' @examples
#' x <- matrix(1:21, 7, 3, dimnames = list(letters[1:7], LETTERS[1:3]))
#' CChart("Column", x, colors = c("red", "green", "blue"), categories.title = "Categories")
#' CChart("Bar", x, type = "Stacked", colors = grey(1:3/3), categories.title = "Categories")
#' CChart("Area", x, small.multiples = TRUE, colors = rainbow(3), categories.title = "Categories")
CChart <- function(chart.type, x, small.multiples = FALSE,
multi.color.series = FALSE, font.units = "px",
annotation.list = NULL, signif.show = FALSE,
signif.column.comparisons = FALSE,
..., warn.if.no.match = TRUE, append.data = FALSE)
{
if (chart.type %in% c("Venn"))
ErrorIfNotEnoughData(x, require.tidy = FALSE)
if (chart.type == "Funnel")
chart.type <- "Pyramid"
if (multi.color.series && chart.type %in% c("Bar", "Column"))
chart.type <- paste0(chart.type, "MultiColor")
user.args <- if (small.multiples) list(chart.type = chart.type, ...)
else list(...)
if (chart.type == "CombinedScatter" && !small.multiples)
user.args$scatter.groups.column <- NULL
# Extract info about signif data so we can remove it later
signif.data.names <- NULL
if (!is.null(attr(x, "signif.annotations")))
signif.data.names <- unique(sapply(attr(x, "signif.annotations"), function(x) x$data))
if (signif.show)
{
colcmp.font <- list(
family = user.args$data.label.font.family,
color = user.args$data.label.font.color,
size = pt2px(user.args$data.label.font.size))
if (!isTRUE(user.args$data.label.show))
{
colcmp.font <- list(
family = user.args$global.font.family,
color = user.args$global.font.color,
size = pt2px(user.args$global.font.size))
# If data label show is false, then other annotations are not shown
user.args$data.label.show <- TRUE
annotation.list <- list(
list(
type = "Hide",
data = "",
threstype = "above threshold",
threshold = "-Inf"),
list(type = "Hide",
data = "",
threstype = "which are missing")
)
}
annot.len <- length(annotation.list)
if (signif.column.comparisons)
{
x.ndim <- length(dim(x))
x.data.names <- dimnames(x)[[x.ndim]]
if ("Column Comparisons" %in% x.data.names)
{
# Note that even if data labels are not shown, default font settings
# are passed to CChart, i.e. https://github.com/Displayr/Plugins/blob/master/src/Standard%20R/Visualization/Area/Area.R#L400
annotation.list[[annot.len + 1]] <- list(
type = "Text - after data label",
data = "Column Comparisons",
threstype = "above threshold",
threshold = "",
format = "",
prefix = " ",
suffix = "",
color = colcmp.font$color,
size = colcmp.font$size,
font.family = colcmp.font$family
)
}
} else if (!is.null(attr(x, "signif.annotations")))
{
# Add arrow annotations which have been attached as an
# attribute to x in PrepareData
# Only show arrows if not showing column comparisons
new.len <- length(attr(x, "signif.annotations"))
for (j in 1:new.len)
annotation.list[[annot.len + j]] <- attr(x, "signif.annotations")[[j]]
attr(x, "signif.annotations") <- NULL
}
}
user.args$annotation.list <- annotation.list
chart.function <- gsub(" ", "", chart.type) # spaces always removed
fun.and.pars <- getFunctionAndParameters(chart.function, small.multiples)
if (tolower(font.units) %in% c("pt", "points"))
user.args <- scaleFontSizes(user.args)
# This needs to be called before categories/values is converted
# into x/y axis but after font sizes have been converted to pixels
if (append.data)
{
chart.settings <- getPPTSettings(chart.type, user.args, x)
categories.title <- user.args$categories.title
values.title <- user.args$values.title
}
user.args <- substituteAxisNames(chart.function, user.args)
arguments <- substituteArgumentNames(fun.and.pars$parameters.o, user.args, warn.if.no.match)
args <- paste0("c(list(", fun.and.pars$parameter.1, " = x), arguments)")
if (!append.data)
return(do.call(fun.and.pars$chart.function, eval(parse(text = args))))
result <- do.call(fun.and.pars$chart.function, eval(parse(text = args)))
chart.warning <- attr(result, "ChartWarning")
result <- addLabels(result, chart.type, user.args$title, categories.title, values.title, user.args$data.label.format)
chart.settings <- updateChartSettingsWithLabels(chart.settings, attr(result, "ChartLabels"), attr(result, "CustomPoints"))
if (isScatter(chart.type))
{
# Convert data after the charting function has been applied
chart.warning <- paste(chart.warning,
scatterAxisWarning(x, user.args)) # set warning before data conversion
x <- convertChartDataToNumeric(x)
chart.settings <- setScatterAxesBounds(chart.settings, x)
# Specify data label font color for labeledscatter + numeric scale colors + default font color
# In all other cases, ChartLabels from flipStandardCharts does not need modification
custom.points <- chart.settings$TemplateSeries[[1]]$CustomPoints
if (#isTRUE(chart.settings$TemplateSeries[[1]]$ShowDataLabels) &&
!isFALSE(user.args$data.label.font.autocolor) &&
!isTRUE(user.args$scatter.colors.as.categorical) &&
!is.null(custom.points) && !is.null(custom.points[[1]]$Marker$BackgroundColor))
{
annot.pts <- attr(result, "ChartLabels")$SeriesLabels[[1]]
if (!is.null(annot.pts))
annot.pts <- annot.pts$CustomPoints
tmp.pts <- chart.settings$TemplateSeries[[1]]$CustomPoints
tmp.lbs <- vector(mode = "list", length = length(tmp.pts))
k <- 1
for (ii in 1:length(tmp.pts))
{
tmp.lbs[[ii]] <- list(Index = tmp.pts[[ii]]$Index,
Font = list(color = StripAlphaChannel(tmp.pts[[ii]]$Marker$BackgroundColor)))
if (k <= length(annot.pts) && annot.pts[[k]]$Index == tmp.lbs[[ii]]$Index)
{
tmp.lbs[[ii]]$Segments <- annot.pts[[k]]$Segments
k <- k + 1
}
}
if (any(sapply(tmp.lbs, Negate(is.null))))
attr(result, "ChartLabels")$SeriesLabels[[1]]$CustomPoints <- tmp.lbs
}
}
result <- addChartWarning(result, chart.warning, chart.type, small.multiples, user.args)
# Remove null elements that causes PPT errors
for (i in 1:length(chart.settings$TemplateSeries))
chart.settings$TemplateSeries[[i]] <- Filter(Negate(is.null), chart.settings$TemplateSeries[[i]])
# Append data used for exporting to PPT/Excel
# Exception is for StackedColumnWithAnnot that handles this itself
if (is.null(attr(result, "ChartData")))
attr(result, "ChartData") <- removeSignifAndCharData(x, signif.data.names)
class(result) <- c(class(result), "visualization-selector")
attr(result, "ChartSettings") <- chart.settings
attr(result, "footerhtml") <- attr(x, "footerhtml", exact = TRUE)
result
}
removeSignifAndCharData <- function(x, rm.stats)
{
if (length(dim(x)) < 3)
return(x)
# Remove statistics that are of type character
all.stats <- dimnames(x)[[3]]
char.stats <- c("Column Names", "Columns Compared", "Column Comparisons")
if (any(all.stats %in% char.stats))
rm.stats <- c(rm.stats, intersect(all.stats, char.stats))
if (is.null(rm.stats) && is.numeric(x))
return(x)
# Because of the way abind is used in PrepareData
# we can assume significance data is all in the 3rd dimension
new.dat <- NULL
primary.stat <- NULL
if (length(all.stats) > length(rm.stats) + 1) {
keep.stats <- setdiff(all.stats, rm.stats)
new.dat <- x[,,keep.stats, drop = FALSE]
} else {
primary.stat <- all.stats[1]
new.dat <- x[,,1, drop = TRUE]
if (NCOL(x) > 1 && NCOL(new.dat) == 1)
new.dat <- t(new.dat)
attr(new.dat, "statistic") <- primary.stat
}
if (is.character(new.dat)) {
if (is.null(new.dat))
new.dat <- x
new.dim <- dim(new.dat)
new.dnames <- dimnames(new.dat)
if (is.null(new.dim)) {
# handle 1-d vector case
new.names <- names(new.dat)
new.dat <- as.numeric(new.dat)
names(new.dat) <- new.names
}
else
new.dat <- array(suppressWarnings(as.numeric(new.dat)),
dim = new.dim, dimnames = new.dnames)
if (!is.null(primary.stat))
attr(new.dat, "statistic") <- primary.stat
}
return(CopyAttributes(new.dat, x))
}
addLabels <- function(x, chart.type, chart.title, categories.title, values.title, data.label.format)
{
chart.labels <- attr(x, "ChartLabels") # for plotly standard charts this will be a list
if (is.null(chart.labels))
chart.labels <- list()
if (any(nzchar(chart.title)))
chart.labels$ChartTitle <- chart.title
if (any(nzchar(categories.title)))
chart.labels$PrimaryAxisTitle <- categories.title
if (any(nzchar(values.title)))
chart.labels$ValueAxisTitle <- values.title
if (!isScatter(chart.type) && any(nzchar(data.label.format)))
{
numformat <- convertToPPTNumFormat(data.label.format)
if (!is.null(numformat) && length(chart.labels$SeriesLabels) > 0)
{
for (i in 1:length(chart.labels$SeriesLabels))
chart.labels$SeriesLabels[[i]]$NumberingFormat <- numformat
} else if (!is.null(numformat))
chart.labels$SeriesLabels[[1]]$NumberingFormat <- numformat
}
if (length(chart.labels) == 0)
chart.labels <- NULL
attr(x, "ChartLabels") <- chart.labels
return(x)
}
updateChartSettingsWithLabels <- function(chart.settings, chart.labels, custom.points)
{
if (!is.null(chart.labels))
{
if (!is.null(chart.labels$PrimaryAxisTitle))
chart.settings$PrimaryAxis$ShowTitle <- TRUE
if (!is.null(chart.labels$ValueAxisTitle))
chart.settings$ValueAxis$ShowTitle <- TRUE
}
# Set ShowDataLabels to false so that ChartLabels will be used
if (!is.null(chart.labels) && !is.null(chart.labels$SeriesLabels))
{
n <- length(chart.settings$TemplateSeries)
for (i in 1:n)
chart.settings$TemplateSeries[[i]]$ShowDataLabels <- FALSE
}
# Update ChartSettings to incorporate annotation info from flipStandardCharts
# that is stored in the CustomPoints attribute
# Currently this is only used to add annotation marker borders in CombinedScatter
if (!is.null(custom.points) && any(sapply(custom.points, Negate(is.null))))
{
n.series <- min(length(chart.settings$TemplateSeries), length(custom.points))
for (i in 1:n.series)
{
if (length(custom.points[[i]]) == 0)
next
k <- 1
for (j in 1:length(custom.points[[i]]))
{
if (is.null(custom.points[[i]][[j]]))
next
tmp.index <- custom.points[[i]][[j]]$Index
while (k <= length(chart.settings$TemplateSeries[[i]]$CustomPoints) &&
chart.settings$TemplateSeries[[i]]$CustomPoints[[k]]$Index != tmp.index)
k <- k + 1
if (k <= length(chart.settings$TemplateSeries[[i]]$CustomPoints) &&
chart.settings$TemplateSeries[[i]]$CustomPoints[[k]]$Index == tmp.index)
{
chart.settings$TemplateSeries[[i]]$CustomPoints[[k]]$Marker <- c(
chart.settings$TemplateSeries[[i]]$CustomPoints[[k]]$Marker, custom.points[[i]][[j]][-1])
has.dup <- duplicated(chart.settings$TemplateSeries[[i]]$CustomPoints[[k]]$Marker)
if (any(has.dup))
chart.settings$TemplateSeries[[i]]$CustomPoints[[k]]$Marker <-
chart.settings$TemplateSeries[[i]]$CustomPoints[[k]]$Marker[-which(has.dup)]
# Make sure marker color is defined otherwise opacity tends to be lost
if (is.null(chart.settings$TemplateSeries[[i]]$CustomPoints[[k]]$Marker$BackgroundColor))
{
chart.settings$TemplateSeries[[i]]$CustomPoints[[k]]$Marker$BackgroundColor <-
chart.settings$TemplateSeries[[i]]$BackgroundColor
}
k <- k + 1
next
}
# If no match then append to end
k <- length(chart.settings$TemplateSeries[[i]]$CustomPoints) + 1
chart.settings$TemplateSeries[[i]]$CustomPoints[[k]] <- list(
Index = tmp.index, Marker = custom.points[[i]][[j]][-1])
# Make sure marker color is defined otherwise opacity tends to be lost
if (is.null(chart.settings$TemplateSeries[[i]]$CustomPoints[[k]]$Marker$BackgroundColor))
{
chart.settings$TemplateSeries[[i]]$CustomPoints[[k]]$Marker$BackgroundColor <-
chart.settings$TemplateSeries[[i]]$Marker$BackgroundColor
}
k <- k + 1
}
}
}
return(chart.settings)
}
addChartWarning <- function(x, warnings, chart.type, small.multiples, user.args)
{
export.type <- attr(x, "ChartType")
msg <- ""
if (chart.type %in% c("Palm", "Stream", "Venn", "Pyramid", "Bar Pictograph"))
msg <- paste0("This visualization is of type '", chart.type,
"' which is not supported by PowerPoint. ")
else if (export.type %in% c("Sunburst", "Histogram", "Filled Map", "Box & Whisker"))
{
tmp.type <- chart.type
if (tmp.type == "Pie")
tmp.type <- "2-dimensional Pie"
msg <- paste0("This visualization is a ", tmp.type,
" chart which cannot be exported to PowerPoint.")
# The charts in the last condition have chart types supported by powerpoint 2016,
# however they cannot be handled by the API for exporting used by Displayr
}
more.unsupported <- c()
if (small.multiples)
more.unsupported <- c(more.unsupported, "Small multiples")
if (!is.null(user.args$fit.type) && user.args$fit.type != "None")
more.unsupported <- c(more.unsupported, "Trend lines")
if (isTRUE(user.args$trend.lines))
more.unsupported <- c(more.unsupported, "Lines with arrows")
if (any(nzchar(user.args$logos)))
more.unsupported <- c(more.unsupported, "Logos")
if (any(nzchar(user.args$subtitle)))
more.unsupported <- c(more.unsupported, "Subtitle")
if (any(nzchar(user.args$footer)))
more.unsupported <- c(more.unsupported, "Footer")
if (length(more.unsupported) > 0)
warnings <- paste0(warnings, paste(more.unsupported, collapse = ", "),
" are not supported by PowerPoint.")
# Only add once
if (nzchar(msg) || any(nzchar(warnings)))
attr(x, "ChartWarning") <- paste(msg, warnings,
"It will be exported to PowerPoint as an image.",
"Set 'PowerPoint Export > Format' to 'Microsoft Chart' and select a",
"supported chart type or set the export format to 'Image' to",
"suppress this warning.", collapse = "")
return(x)
}
scatterAxisWarning <- function(data, user.args)
{
msg <- NULL
if (!is.matrix(data) || !is.data.frame(data))
return (msg)
.isValidIndex <- function(i) {return (!is.null(i) && !is.na(i) && i > 0 &&
i <= NCOL(data))}
ind.x <- user.args$scatter.x.column
ind.y <- user.args$scatter.y.column
if (.isValidIndex(ind.x) && !is.numeric(data[,ind.x]))
msg <- "Powerpoint only supports numeric axes in scatterplots."
else if (.isValidIndex(ind.y) && !is.numeric(data[,ind.y]))
msg <- "Powerpoint only supports numeric axes in scatterplots."
return (msg)
}
getPPTSettings <- function(chart.type, args, data)
{
# Opacity is by default set to NULL in the javascript code
tmp.opacity <- args$opacity
tmp.is.stacked <- isTRUE(args$type == "Stacked")
if (is.null(tmp.opacity))
{
if (chart.type %in% c("Area", "Radar", "Palm") && !tmp.is.stacked)
tmp.opacity <- 0.4
else if (isScatter(chart.type) && isTRUE(attr(data, "scatter.variable.indices")["sizes"] <= NCOL(data)))
tmp.opacity <- 0.4
else
tmp.opacity <- 1.0
}
tmp.n <- length(args$colors)
if (tmp.n == 0)
{
tmp.n <- NROW(data)
args$colors <- ChartColors(tmp.n)
}
tmp.line.style <- "None"
if (chart.type %in% c("Donut", "Pie", "Palm"))
tmp.line.style <- "Solid"
else if (chart.type %in% c("Line", "Radar", "Time Series"))
tmp.line.style <- if (is.null(args$line.type)) "Solid" else args$line.type
else if (!is.null(args$marker.border.opacity))
tmp.line.style <- "Solid"
tmp.line.thickness <- 1
if (chart.type %in% c("Line", "Radar", "Time Series"))
tmp.line.thickness <- as.numeric(ConvertCommaSeparatedStringToVector(args$line.thickness))
else if (chart.type %in% c("Pie", "Donut"))
tmp.line.thickness <- 1
else if (!is.null(args$marker.border.opacity))
tmp.line.thickness <- args$marker.border.width
tmp.line.thickness <- rep(px2pt(tmp.line.thickness), length = tmp.n)
tmp.line.color <- args$colors
if (chart.type %in% c("Pie", "Donut"))
tmp.line.color <- args$pie.border.color
else if (!is.null(args$marker.border.opacity))
tmp.line.color <- getHexCode(args$marker.border.color, args$marker.border.opacity)
if (is.null(tmp.line.color) || all(is.na(tmp.line.color)))
tmp.line.color <- "#FFFFFF"
tmp.line.color <- rep(tmp.line.color, length = tmp.n)
tmp.data.label.show <- isTRUE(args$data.label.show)
tmp.data.label.show.category.labels <- FALSE
if (isScatter(chart.type) && !isTRUE(args$scatter.labels.as.hovertext))
tmp.data.label.show <- TRUE
if (chart.type %in% c("Donut", "Pie"))
{
tmp.data.label.show <- TRUE
tmp.data.label.show.category.labels <- TRUE
}
if (chart.type == "Radar" && tmp.data.label.show)
tmp.data.label.show.category.labels <- TRUE
if (chart.type == "Bar Pictograph" && isTRUE(args$data.label.position != "No"))
tmp.data.label.show <- TRUE
# DataLabelsPosition not supported for Area Chart
tmp.data.label.position <- "BestFit"
if (chart.type == "Column" && tmp.is.stacked && !isTRUE(args$data.label.centered))
tmp.data.label.position <- "InsideEnd"
else if (chart.type %in% c("Donut", "Pie"))
tmp.data.label.position <- "OutsideEnd"
else if (isScatter(chart.type))
tmp.data.label.position <- "Center"
# Behaviour of 'Automatically' set data label font colors
# change depending on the chart type
tmp.data.label.font.color <- ConvertCommaSeparatedStringToVector(args$data.label.font.color)
if (chart.type %in% c("Line", "Scatter", "Bubble", "CombinedScatter") &&
isTRUE(args$data.label.font.autocolor))
tmp.data.label.font.color <- args$colors
else if (tmp.is.stacked && isTRUE(args$data.label.font.autocolor))
{
if (chart.type == "Area")
tmp.data.label.font.color <- c(autoFontColor(args$colors[-1]), args$global.font.color)
else
tmp.data.label.font.color <- autoFontColor(args$colors)
}
if (length(tmp.data.label.font.color) < length(args$colors))
tmp.data.label.font.color <- rep(tmp.data.label.font.color, length = tmp.n)
# Initialise series-specific parameters
if (isDistribution(chart.type))
{
series.settings <- list(list(
ShowDataLabels = FALSE,
BackgroundColor = args$density.color))
} else if (isScatter(chart.type) && !isTRUE(args$scatter.colors.as.categorical))
{
# When scatterplots use colors as a numerical scale
# we can assume a single template series
series.settings <- list(list(
CustomPoints = getColorsAsNumericScale(data, args$colors, tmp.opacity, args$marker.size),
Marker = list(Size = args$marker.size, OutlineStyle = "None"),
ShowDataLabels = tmp.data.label.show,
DataLabelsPosition = "Center",
DataLabelsFont = list(family = args$data.label.font.family,
size = px2pt(args$data.label.font.size),
color = tmp.data.label.font.color[1]),
OutlineStyle = "None"))
} else if (chart.type %in% c("BarMultiColor", "ColumnMultiColor",
"Pyramid", "Bar Pictograph"))
{
# Multi-color series is implemented as a single series
# with many CustomPoints
tmp.colors <- list()
for (i in seq_along(args$colors))
tmp.colors[[i]] <- list(BackgroundColor = getHexCode(args$colors[i], tmp.opacity),
Index = i - 1)
series.settings <- list(list(
CustomPoints = tmp.colors,
ShowDataLabels = tmp.data.label.show,
DataLabelsFont = list(family = args$data.label.font.family,
size = px2pt(args$data.label.font.size),
color = tmp.data.label.font.color[1]),
DataLabelsPosition = tmp.data.label.position,
OutlineColor = tmp.line.color[1], # style is none if no border color defined
OutlineWidth = tmp.line.thickness[1],
OutlineStyle = tmp.line.style))
} else
series.settings <- lapply(1:length(args$colors),
function(i) {list(
BackgroundColor = getHexCode(args$colors[i], tmp.opacity),
ShowDataLabels = tmp.data.label.show,
ShowCategoryNames = tmp.data.label.show.category.labels,
DataLabelsFont = list(family = args$data.label.font.family,
size = px2pt(args$data.label.font.size),
color = tmp.data.label.font.color[i]),
DataLabelsPosition = tmp.data.label.position,
OutlineColor = tmp.line.color[i],
OutlineWidth = tmp.line.thickness[i],
OutlineStyle = tmp.line.style)})
tmp.n <- length(series.settings)
if ((isScatter(chart.type) && isTRUE(args$scatter.colors.as.categorical)) || chart.type == "Line")
for (i in 1:tmp.n)
series.settings[[i]]$Marker = list(Size = args$marker.size,
OutlineStyle = "None",
BackgroundColor = getHexCode(args$colors[i], tmp.opacity))
# Initialise return output
res <- list()
if (tmp.n > 0)
res$TemplateSeries = series.settings
if (isFALSE(args$legend.show) || isTRUE(args$legend.show == "Hide") || tmp.n == 1)
res$ShowLegend <- FALSE
if (isTRUE(args$legend.show == "Show"))
res$ShowLegend <- TRUE
legend.position <- "Right"
if (isTRUE(args$legend.orientation == "Horizontal"))
legend.position <- "Bottom"
if (isTRUE(args$legend.y.position < 0.1))
legend.position <- "Bottom"
if (isTRUE(args$legend.y.position > 0.9))
legend.position <- "Top"
if (isTRUE(args$legend.x.position < 0.1))
legend.position <- "Left"
if (isTRUE(args$legend.x.position > 0.9))
legend.position <- "Right"
res$Legend = list(Font = list(color = args$legend.font.color,
family = args$legend.font.family, size = px2pt(args$legend.font.size)),
Position = legend.position)
if (isTRUE(nchar(args$background.fill.color) > 0) &&
args$background.fill.color != "transparent")
res$BackgroundColor <- getHexCode(args$background.fill.color, args$background.fill.opacity)
# Chart and Axis titles always seem to be ignored
# Waiting on RS-7208
res$ShowChartTitle = any(nzchar(args$title))
res$ChartTitleFont = list(color = args$title.font.color, family = args$title.font.family,
size = px2pt(args$title.font.size))
if (!chart.type %in% c("Pie", "Donut"))
{
res$PrimaryAxis = list(LabelsFont = list(color = args$categories.tick.font.color,
family = args$categories.tick.font.family,
size = px2pt(args$categories.tick.font.size)),
ShowTitle = any(nzchar(args$categories.title)),
TitleFont = list(color = args$categories.title.font.color,
family = args$categories.title.font.family,
size = px2pt(args$categories.title.font.size)),
NumberFormat = convertToPPTNumFormat(args$categories.tick.format),
AxisLine = list(Color = args$categories.line.color,
Width = px2pt(args$categories.line.width),
Style = if (isTRUE(args$categories.line.width == 0)) "None" else "Solid"),
MajorGridLine = list(Color = args$categories.grid.color,
Width = px2pt(args$categories.grid.width),
Style = if (isTRUE(args$categories.grid.width == 0)) "None" else "Solid"),
RotateLabels = isTRUE(args$categories.tick.angle == 90),
LabelPosition = "Low")
if (any(nzchar(args$categories.bounds.maximum)))
res$PrimaryAxis$Maximum <- args$categories.bounds.maximum
if (any(nzchar(args$categories.bounds.minimum)))
res$PrimaryAxis$Minimum <- args$categories.bounds.minimum
res$ValueAxis = list(LabelsFont = list(color = args$values.tick.font.color,
family = args$values.tick.font.family, size = px2pt(args$values.tick.font.size)),
ShowTitle = any(nzchar(args$values.title)),
TitleFont = list(color = args$values.title.font.color,
family = args$values.title.font.family, size = px2pt(args$values.title.font.size)),
NumberFormat = convertToPPTNumFormat(args$values.tick.format),
AxisLine = list(Color = args$values.line.color,
Width = px2pt(args$values.line.width),
Style = if (isTRUE(args$values.line.width == 0)) "None" else "Solid"),
MajorGridLine = list(Color = args$values.grid.color,
Width = px2pt(args$values.grid.width),
Style = if (isTRUE(args$values.grid.width == 0)) "None" else "Solid"))
if (any(nzchar(args$values.bounds.maximum)))
res$ValueAxis$Maximum <- args$values.bounds.maximum
if (any(nzchar(args$values.bounds.minimum)))
res$ValueAxis$Minimum <- args$values.bounds.minimum
# We don't want to manually set axis label position
# if they are not shown
if (!is.null(args$values.axis.show) && args$values.axis.show == FALSE)
res$ValueAxis$LabelPosition <- "None"
if (!is.null(args$categories.axis.show) && args$categories.axis.show == FALSE)
res$PrimaryAxis$LabelPosition <- "None"
}
# Chart-specfic parameters
if (grepl("StackedColumn", chart.type) && isTRUE(args$values.zero))
{
# PPT doesn't have a concept of the zero line so use a workaround
res$ValueAxis$Crosses <- "AutoZero"
res$PrimaryAxis$AxisLine$Style <- "Solid"
res$PrimaryAxis$AxisLine$Width <- px2pt(args$values.zero.line.width)
res$PrimaryAxis$AxisLine$Color <- args$values.zero.line.color
}
if (chart.type %in% "Donut")
res$HoleSize = args$pie.inner.radius
if (chart.type %in% c("Donut", "Pie"))
res$FirstSliceAngle <- 270
if (chart.type %in% c("Bar", "Column", "Pyramid", "BarMultiColor", "ColumnMultiColor"))
{
if (!tmp.is.stacked && NCOL(data) > 1 && !is.null(args$bar.group.gap))
{
# Overlap is capped at 100 and GapWidth at 500 in PPT
tmp.gap = min(1.0, args$bar.group.gap / (1 - args$bar.group.gap))
res$Overlap = tmp.gap * -100
res$GapWidth = min(5.0, (NCOL(data) - 1) * (args$bar.gap / (1 - args$bar.gap) + tmp.gap)) * 100
} else
res$GapWidth = min(5.0, args$bar.gap / (1 - args$bar.gap)) * 100
}
if (chart.type == "Line")
res$Smooth = isTRUE(args$shape == "Curved")
if (chart.type %in% c("BarMultiColor", "ColumnMultiColor", "Pyramid", "Bar Pictograph") ||
(isScatter(chart.type) && !isTRUE(args$scatter.colors.as.categorical)))
res$ShowLegend <- FALSE
if (chart.type == "Scatter" && tmp.data.label.show && isTRUE(args$grid.show))
{
# LabeledScatter does not have options to control color and width of grid
res$PrimaryAxis$MajorGridLine <- list(Style = "Solid", Width = 1,
Color = "#E1E1E1")
res$ValueAxis$MajorGridLine <- list(Style = "Solid", Width = 1,
Color = "#E1E1E1")
}
# There are some issues with Scatterplot exporting
# See RS-7154 - try master.displayr.com
if (isScatter(chart.type))
{
res$ValueAxis$Crosses <- "Minimum"
res$BubbleSizeType = if (isTRUE(args$scatter.sizes.as.diameter)) "Width" else "Area"
res$BubbleScale = args$marker.size * 10
}
return(res)
}
# Fix minimum axes bounds if they are not already set
# This is performed only because PPT will set the minimum to 0 if not specified
# The default values for the maximum is usually quite reasonable
setScatterAxesBounds <- function(settings, data)
{
# Skip setting if data is multiple tables
if (is.list(data) && !is.data.frame(data))
return(settings)
.isValidIndex <- function(i) {return (!is.null(i) && !is.na(i) && i > 0 &&
i <= NCOL(data))}
v.ind <- attr(data, "scatter.variable.indices")
ind.x <- v.ind["x"]
ind.y <- v.ind["y"]
if (is.numeric(data) && !is.null(attr(data, "statistic")) &&
grepl(attr(data, "statistic"), pattern = "%"))
data <- data/100
if (is.null(settings$ValueAxis$Minimum) && .isValidIndex(ind.y))
{
rg <- range(AsNumeric(data[,ind.y], binary = FALSE), na.rm = TRUE)
if (all(is.finite(rg)) && rg[1] != rg[2])
{
offset <- (rg[2] - rg[1]) * 0.1
sc <- 10^(floor(log10(rg[2] - rg[1])))
settings$ValueAxis$Minimum <- floor((rg[1] - offset)/sc) * sc
}
}
if (is.null(settings$PrimaryAxis$Minimum) && .isValidIndex(ind.x))
{
rg <- range(AsNumeric(data[,ind.x], binary = FALSE), na.rm = TRUE)
if (all(is.finite(rg)) && rg[1] != rg[2])
{
offset <- (rg[2] - rg[1]) * 0.1
sc <- 10^(floor(log10(rg[2] - rg[1])))
settings$PrimaryAxis$Minimum <- floor((rg[1] - offset)/sc) * sc
}
}
return(settings)
}
# converts sizes from pixels (which is used by plotly)
# into points (which is used in Displayr and PPT exporting)
px2pt <- function(x)
{
return(x/1.3333)
}
pt2px <- function(x)
{
return(x * 1.3333)
}
getHexCode <- function(color, opacity)
{
if (!(opacity >= 0 && opacity <= 1))
return(color)
if (startsWith(color, "#") && nchar(color) == 7)
return(sprintf("%s%02X", color, round(opacity * 255)))
# Returns grey if color is not recognized - matches checkColors
col.as.rgb <- try(t(col2rgb(color, alpha = TRUE)), silent = TRUE)
if (inherits(col.as.rgb, "try-error"))
return("#CCCCCC")
# If color is 8-digit hex than multiply opacity - matches plotly::toRGB
return(rgb(col.as.rgb, alpha = round(opacity * col.as.rgb[, "alpha"]), maxColorValue = 255))
}
# This function determines whether the font should be shown in black or white
# on the brightness of background. The coefficients are the same as in
# flipStandardCharts and rhtmlHeatmap. The values are originally from
# http://stackoverflow.com/questions/11867545/change-text-color-based-on-brightness-of-the-covered-background-area
#' @importFrom grDevices col2rgb rgb2hsv
autoFontColor <- function (colors)
{
tmp.rgb <- col2rgb(colors)
tmp.lum <- apply(tmp.rgb, 2, function(x) return(0.299*x[1] + 0.587*x[2] + 0.114*x[3]))
return(ifelse(tmp.lum > 126, "#2C2C2C", "#FFFFFF"))
}
#' @importFrom grDevices colorRamp rgb
getColorsAsNumericScale <- function(data, colors, opacity, size)
{
color.index <- attr(data, "scatter.variable.indices")["colors"]
if (is.na(color.index) || is.null(color.index) || NCOL(data) < color.index)
return(NULL)
if (length(colors) < 2)
return(NULL)
color.data <- data[,color.index]
if (is.ordered(color.data))
class(color.data) <- "factor"
color.data <- suppressWarnings(AsNumeric(color.data, binary = FALSE))
not.na <- which(!is.na(color.data))
color.func <- colorRamp(unique(colors))
dat.scaled <- (color.data - min(color.data, na.rm = TRUE))/
diff(range(color.data, na.rm = TRUE))
dat.scaled[is.na(color.data)] <- 0
color.vec <- rgb(color.func(dat.scaled), alpha = 255 * opacity,
maxColorValue = 255)
data.points <- lapply(not.na, function(i) {list(Index = i - 1,
Marker = list(BackgroundColor = color.vec[i],
Style = "Circle", Size = size))})
return(data.points)
}
#' substituteAxisNames
#'
#' Substitutes 'categories' or 'values' for 'x' and 'y'.
#' @details Unlike \code{substituteArgumentNames}, the behaviour is specific to
#' \code{chart.function}. It is also less agggressive in that it searches for
#' replacements only at the beginning of the name.
#' @param chart.function Name of charting function
#' @param arguments List of arguments supplied by user
substituteAxisNames <- function(chart.function, arguments)
{
a.names <- names(arguments)
# constrain to only the first position to prevent excessive matching
if (chart.function %in% c("Bar", "Pyramid", "BarMultiColor"))
{
a.names <- gsub("^categories", "y", a.names)
a.names <- gsub("^values", "x", a.names)
} else if (chart.function %in% c("Area", "Column", "ColumnMultiColor",
"Line", "Radar", "Scatter", "LabeledScatter"))
{
a.names <- gsub("^categories", "x", a.names)
a.names <- gsub("^values", "y", a.names)
}
names(arguments) <- a.names
return(arguments)
}
#' scaleFontSizes
#'
#' Convert font size from point to pixel.
#' @details All of the charts in flipStandardChart
#' take font sizes to be in units of pixels, however, textboxes in Displayr
#' assumes font sizes are in units of points. This function iterates through
#' \code{user.args} and scales all font sizes so they are in the equivalent points.
#' Note that arguments not specified by the user are not affected.
#' @param arguments List of arguments supplied by user
scaleFontSizes <- function(arguments)
{
ind <- which(grepl("font.size$", names(arguments)))
f.scale <- 1.3333
for (i in ind)
{
arguments[[i]] <- round(f.scale * arguments[[i]], 0)
}
return(arguments)
}
#' getFunctionNameAndParameters
#'
#' Gets the function, loading parameters if necessary, and the parameters of the function.
#' @param chart.function.name The name of the function used for creating the chart (aka plot).
#' @param small.multiples Logical; whether a panel of charts should be created
#' @return A list with the following elements:
#' \item{\code{chart.function}}{The function}.
#' \item{\code{parameter.1}}{The first parameter in \code{chart.function}}.
#' \item{\code{parameter.o}}{The other parameters in \code{chart.function}}.
#'
getFunctionAndParameters <- function(chart.function.name, small.multiples)
{
if (!is.character(chart.function.name))
stop("'chart.function.name' must be of type 'character'.")
chart.function <- gsub('"', "", chart.function.name, fixed = TRUE) # fixing mess created when 'type' is already a character
# Getting the function
loadPackage(chart.function)
chart.function <- get0(chart.function, mode = "function")
if (!is.function(chart.function))
stop(paste0("Cannot find ", chart.function,"."))
parameters <- formalArgs(chart.function)
p.1 <- parameters[1]
p.o <- parameters[-1]
if (small.multiples && chart.function.name != "CombinedScatter")
{
chart.function <- get0("SmallMultiples", mode = "function")
tmp.param <- formalArgs(chart.function)
p.o <- unique(c(p.o, "chart.type", tmp.param[-1]))
}
list(chart.function = chart.function, parameter.1 = p.1, parameters.o = p.o)
}
# #' RGUIControls
# #'
# #' Writes the JavaScript to create the RGUI Controls for Displayr and Q. Note that the first parameter of the function
# #' is not automatically written (as it is assumed to be data, to be addressed by the remaining arguments).
# #' @param chart.function.name The name of the function used for creating the chart (aka plot).
# #' @param vector \code{TRUE} if the function accepts a vector as the sole data input.
# #' @param matrix \code{TRUE} if the function accepts a \code{matrix} as the sole data input.
# #' @param raw.data.1 \code{TRUE} if the function accepts a single variable of 'raw' (non-aggregated) data as an input.
# #' @param raw.data.2 \code{TRUE} if the function accepts a pair of variables of 'raw' data.
# #' @param raw.data.multi \code{TRUE} if the function accepts multiple variables of 'raw' data as an input.
# #' @return A \code{character} object of JavaScript code.
# #' @export
#
# RGUIControls <- function(chart.function.name,
# vector = FALSE,
# matrix = FALSE,
# raw.data.1 = FALSE,
# raw.data.2 = FALSE,
# raw.data.multi = FALSE,
# r.object = FALSE,
# scalar = FALSE)
# {
# parameters <- getFunctionAndParameters(chart.function.name)$parameters.o
# parameters
# }
#
#' substituteArgumentNames
#'
#' @param parameter.names The names of parameters.
#' @param arguments The arguments to match to the parameters.
#' @param warn.if.no.match If TRUE, a warning is shown if any arugments are not matched.
#'
substituteArgumentNames <- function(parameter.names, arguments, warn.if.no.match = TRUE)
{
a.names <- names(arguments)
p.names <- parameter.names
a.unmatched <- !a.names %in% p.names
p.unmatched <- !p.names %in% a.names
if (any(a.unmatched)) # Some argument names do not match parameter names
{
# Perform matches and update a.names
.replaceMatches <- function(aa, pp)
{
for (a in aa[a.unmatched])
for(p in pp[p.unmatched]){
if (parametersEqual(p, a))
a.names[match(a, aa)] <- p.names[match(p, pp)]
}
a.names <<- a.names
}
## Permuting order and ignoring case
a.regularized <- tolower(a.names)
p.regularized <- tolower(p.names)
.replaceMatches(a.regularized, p.regularized)
# Substituting synonyms
a.unmatched <- !a.names %in% p.names
p.unmatched <- !p.names %in% a.names
if (any(a.unmatched))
{
.replaceSynonyms <- function(names)
{
for (i in seq_along(synonyms))
{
syns <- synonyms[[i]]
init.syn <- syns[1]
for (s in syns[-1])
names <- gsub(s, init.syn, names, fixed = TRUE)
}
names
}
a.regularized[a.unmatched] <- .replaceSynonyms(tolower(a.names[a.unmatched]))
p.regularized[p.unmatched] <- .replaceSynonyms(tolower(p.names[p.unmatched]))
.replaceMatches(a.regularized, p.regularized)
}
}
a.unmatched <- !a.names %in% p.names
if (any(a.unmatched) && warn.if.no.match)
warning("The following arguments have been ignored: ", paste(a.names[a.unmatched], collapse = ", "))
names(arguments) <- a.names
arguments[!a.unmatched]
}
#' Make sure that the first synonym is the briefiest, and from there they are ordered from
#' most more verbose to any roots.
#' These are swapped out in order. E.g., after the first line, all "colours" will be changed to 'col'.
synonyms <- list(c("col", "colours", "colour", "colors", "color"),
c("size", "sizes"),
c("label", "labels"),
c("categories", "x", "categories", "x.axis", "xaxis"),
c("values", "y", "values", "y.axis", "yaxis"),
c("paneltitle", "panel.title"),
c("xtitle", "x.title", "xlab"),
c("ytitle", "y.title", "ylab"),
c("title", "main"),
c("label.show", "data.label.show"),
c("fontsize", "font.size")
)
#' parametersEqual
#'
#' Checks if parameters are equal
#' @param recipient The name of the parameter in the function.
#' @param donor The provided name of the parameter.
parametersEqual <- function(recipient, donor)
{
# Exact match
if (recipient == donor)
return(TRUE)
# Matching after re-ordering full stops
recipient.split <- sort(strsplit(recipient, ".", fixed = TRUE)[[1]])
donor.split <- sort(strsplit(donor, ".", fixed = TRUE)[[1]])
if(length(recipient.split) == length(donor.split))
return((all(recipient.split == donor.split)))
return(FALSE)
}
#' loadPackage
#'
#' Loads the package in which a chart is located. Only does this if the chart's function is registered
#' in this function as having a package requiring loading.
#'
#' @param chart.type The name of the function of the chart type to be loaded.
#' @importFrom flipU LookupName
loadPackage <- function(chart.type)
{
# Make sure that the package is specified in suggest in the descriptions file
#
# package chart function
package.type.lookup <- c("rhtmlMoonPlot" = "moonplot")
package <- LookupName(chart.type, package.type.lookup)
if (is.null(package))
package <- "flipStandardCharts"
if (!is.null(package))
require(package, character.only = TRUE)
}
# Convert ChartData attribute so that it is readable by Powerpoint
# For X, Y and Size variables, this needs to numeric
# which means that any label information will be lost
# For Color variables, factor levels can be retained
#' @importFrom flipTransformations AsNumeric
#' @importFrom flipU CopyAttributes
convertChartDataToNumeric <- function(data)
{
.isValidIndex <- function(i) {return (!is.null(i) && !is.na(i) && i > 0 &&
i <= NCOL(data))}
v.ind <- attr(data, "scatter.variable.indices")
new.data <- suppressWarnings(AsNumeric(data, binary = FALSE))
# Color variable can be returned as a factor to retain
# label names
ind.color <- v.ind["colors"]
if (.isValidIndex(ind.color) && is.factor(data[,ind.color]))
new.data[,ind.color] <- data[,ind.color]
else if (.isValidIndex(ind.color) && is.character(data[,ind.color]))
new.data[,ind.color] <- factor(data[,ind.color], levels = unique(data[,ind.color]))
return(CopyAttributes(new.data, data))
}
# Currently only the data labels formats are converted
# So we only look for floating point or percentage formats
# Note that d3 and PPT handle formats differently when
# the number of decimals is unspecified but make a best guess
# (In particular we don't handle dates, especially not weekly ranges)
# If format is unknown we return NULL to allow PPT to use default settings
convertToPPTNumFormat <- function(d3format)
{
num.decimals <- NULL
if (length(d3format) == 0)
return("General")
mm <- regexpr("\\.(\\d+)", d3format, perl = TRUE)
if (length(mm) == 1 && mm > 0)
{
m.start <- attr(mm, "capture.start")[1]
m.len <- attr(mm, "capture.length")[1]
num.decimals <- as.numeric(substr(d3format, m.start, m.start + m.len - 1))
}
if (grepl("%", d3format, fixed = TRUE))
{
if (is.null(num.decimals))
return("0.#%")
else if (num.decimals <= 0)
return("0%")
else
return(paste0("0.", paste(rep(0, num.decimals), collapse = ""), "%"))
} else if (!is.null(num.decimals)) # assume other formats are floating points
{
if (num.decimals <= 0)
return("0")
else
return(paste0("0.", paste(rep(0, num.decimals), collapse = "")))
} else
return("General")
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.