Common

SomeGraphs.Common.Graph Type

The type of a figure we can display. This is a combination of some AbstractGraphData and AbstractGraphConfiguration . Accessing the .figure property of the graph will return it as a PlotlyFigure , which can be displayed in interactive environments (Julia REPL and/or Jupyter notebooks).

Note

You should call save_graph to save the graph to a file, instead of calling savefig on the .figure , because the latter does't obey the graph size, because Plotly.

SomeGraphs.Common.AbstractGraphConfiguration Type

A configuration of a Graph specifies how to display the data while being (as much as possible) independent of the data itself. That is, it should be possible to apply the same configuration to multiple sets of data to generate multiple similar graphs. In some cases (e.g., colors) you can specify a default in the configuration and override it for specific entities in the data.

SomeGraphs.Common.AbstractGraphData Type

Some data of a Graph specifies what to display the data while being (as much as possible) independent of how to display it. That is, it should be possible to apply multiple sets of data to the same configuration to to generate multiple similar graphs. In some cases (e.g., colors) you can specify a default in the configuration and override it for specific entities in the data.

SomeGraphs.Common.PlotlyFigure Type

The type of a rendered graph which Julia knows how to display.

A plotly figure contains everything needed to display an interactive graph (or generate a static one on disk). It can also be converted to a JSON string for handing it over to a different programming language (e.g., to be used to display the interactive graph in a Python Jupyter notebook, given an appropriate wrapper code).

SomeGraphs.Common.save_graph Function
save_graph(graph::Graph, output_file::AbstractString)::Nothing

Save the graph to a file. Unlike the Plotly savefig function, this function will actually obey the width and height parameters specified in the graph's configuration. The format is deduced from the suffix of the file name.

SomeGraphs.Common.FigureConfiguration Type
@kwdef mutable struct FigureConfiguration <: Validated
    margins::MarginsConfiguration = MarginsConfiguration()
    width::Maybe{Int} = nothing
    height::Maybe{Int} = nothing
    grid_color::AbstractString = "lightgrey"
    background_color::AbstractString = "white"
    paper_color::AbstractString = "white"
    colors_scale_offsets::AbstractVector{<:Real} = [1.2, 1.4, 1.6, 1.8, 2.0]
end

Generic configuration that applies to the whole figure. Each complete AbstractGraphConfiguration contains a figure field of this type.

The optional width and height are in pixels, that is, 1/96 of an inch. The margins are specified in the same units.

You can also manually change the background_color (inside the graph's area) and paper_color (outside the graph's area, that is, the margins). the axes.

If a graph has both a legend and a color scale, or multiple color scales, then by default, Plotly in its infinite wisdom will happily place them all on top of each other. We therefore need to tell it how to position each and every color scale (except the 1st one if there's no legend) by specifying an explicit offset. Great fun!

These colors_scale_offsets are specified in what Plotly calls "paper coordinates" which are singularly unsuitable for this purpose, as they are in a scale where 0 to 1 is the plot area and therefore dependent not only on the width of the preceding legend and/or color scales (which is bad by itself), but also on the overall size of the graph (so scaling an interactive graph will definitely misbehave). We provide a vector of hopefully reasonable default offsets here. For optimal results you will need to manually tweak these to match your specific graph. In 21st century, when "AI" is a thing. Sigh.

We provide "too many" (5) offsets here. Normally a graph displays one color scale, but in theory it can have any number (e.g. when using annotations in bars or heatmap graphs). You will need to extend this array if you have a graph that requires more offsets.

SomeGraphs.Common.Stacking Type

If stacking elements, how to do so:

StackValues just adds the raw values on top of each other.

StackFractions normalizes the values so their sum is 1. This can be combined with setting the percent field of the relevant AxisConfiguration to display percents.

Examples:

Default (serves as a baseline to compare with when modifying options):

using SomeGraphs
graph = distribution_graph(; distribution_values = [0, 0, 1, 1, 1, 3])
using PlotlyDocumenter
to_documenter(graph.figure)

Reduced size:

using SomeGraphs
graph = distribution_graph(; distribution_values = [0, 0, 1, 1, 1, 3])
graph.configuration.figure.width = 300
graph.configuration.figure.height = 300
using PlotlyDocumenter
to_documenter(graph.figure)

Change colors:

using SomeGraphs
graph = distribution_graph(; distribution_values = [0, 0, 1, 1, 1, 3])
graph.configuration.figure.background_color = "lightyellow"
graph.configuration.figure.paper_color = "lightgrey"
using PlotlyDocumenter
to_documenter(graph.figure)

SomeGraphs.Common.MarginsConfiguration Type
@kwdef mutable struct MarginsConfiguration <: Validated
    left::Int = 50
    bottom::Int = 50
    right::Int = 50
    top::Int = 50
end

Configure the margins of the graph. Sizes are in pixels (1/96th of an inch). Plotly is uncapable of automatically adjusting the margins to adapt to tick labels, so you may have to tweak this manually to avoid clipping or reduce wasted white space. In the 21st century. Sigh.

Examples:

Increased margins:

using SomeGraphs
graph = distribution_graph(; distribution_values = [0, 0, 1, 1, 1, 3])
graph.configuration.figure.margins.left = 100
graph.configuration.figure.margins.right = 200
graph.configuration.figure.margins.top = 150
graph.configuration.figure.margins.bottom = 250
using PlotlyDocumenter
to_documenter(graph.figure)

SomeGraphs.Common.AxisConfiguration Type
@kwdef mutable struct AxisConfiguration <: Validated
    minimum::Maybe{Real} = nothing
    maximum::Maybe{Real} = nothing
    log_scale::Maybe{LogScale} = nothing
    log_regularization::Real = 0
    percent::Bool = false
    show_ticks::Bool = true
    show_grid::Bool = true
    grid_color::AbstractString = "lightgrey"
    title::Maybe{AbstractString} = nothing
end

Generic configuration for a graph axis. Everything is optional; by default, the minimum and maximum are computed automatically from the data.

If log_scale is specified, then the log_regularization is added to the coordinate to avoid zero values, and the axis is shown according to the LogScale . Otherwise, log_regularization must be 0.

If percent is set, then the values are multiplied by 100 and a % suffix is added to the tick labels.

The show_ticks and/or show_grid can be disabled for a cleaner (though less informative) graph appearance. By default the grid lines are shown in lightgrey .

The minimum/maximum, data values, color palette values etc. are all in the original scale. That is, you should be able to control log scale and/or percent scaling without changing anything else.

If title is specified, it will be shown next to the axis. However, in some cases the correct title depends on the data set, so you can override this in the data.

Examples:

Disable ticks:

using SomeGraphs
graph = distribution_graph(; distribution_values = [0, 0, 1, 1, 1, 3])
graph.configuration.value_axis.show_ticks = false
using PlotlyDocumenter
to_documenter(graph.figure)

Disable grid:

using SomeGraphs
graph = distribution_graph(; distribution_values = [0, 0, 1, 1, 1, 3])
graph.configuration.value_axis.show_grid = false
using PlotlyDocumenter
to_documenter(graph.figure)

Override grid color:

using SomeGraphs
graph = distribution_graph(; distribution_values = [0, 0, 1, 1, 1, 3])
graph.configuration.value_axis.grid_color = "red"
using PlotlyDocumenter
to_documenter(graph.figure)

Override range:

using SomeGraphs
graph = distribution_graph(; distribution_values = [0, 0, 1, 1, 1, 3])
graph.configuration.value_axis.minimum = 1
graph.configuration.value_axis.maximum = 4
using PlotlyDocumenter
to_documenter(graph.figure)

Percent:

using SomeGraphs
graph = distribution_graph(; distribution_values = [0, 0, 1, 1, 1, 3])
graph.configuration.value_axis.percent = 1
using PlotlyDocumenter
to_documenter(graph.figure)

SomeGraphs.Common.LogScale Type

Supported log scales (when log scaling is enabled):

  • Log10Scale converts values to their log (base 10).

  • Log2Scale converts values to their log (base 2).

Examples:

Log 2:

using SomeGraphs
graph = distribution_graph(; distribution_values = [0, 0, 1, 1, 1, 3])
graph.configuration.value_axis.log_scale = Log2Scale
graph.configuration.value_axis.log_regularization = 1
using PlotlyDocumenter
to_documenter(graph.figure)

Log 10:

using SomeGraphs
graph = distribution_graph(; distribution_values = [0, 0, 1, 1, 1, 3])
graph.configuration.value_axis.log_scale = Log10Scale
graph.configuration.value_axis.log_regularization = 1e-5
using PlotlyDocumenter
to_documenter(graph.figure)

SomeGraphs.Common.SizesConfiguration Type
@kwdef mutable struct SizesConfiguration <: Validated
    fixed::Maybe{Real} = nothing
    minimum::Maybe{Real} = nothing
    maximum::Maybe{Real} = nothing
    log_scale::Bool = false
    log_regularization::Real = 0
    smallest::Real = 6
    span::Real = 12
end

Configure how to map sizes data to a size in pixels (1/96th of an inch). If fixed is specified, it is the size to be used, and none of the other fields should be set (and no sizes data may be specified). Otherwise, sizes data must be specified. The minimal size data value (or any values at most the specified minimum ) is mapped to the smallest size in pixels, and the maximum size data value (or any values at least the specified maximum ) is mapped to a size with an additional span in pixels. If log_scale , then we use the log of the data values (and of the specified minimum and/or maximum , if any), adding the log_regularization to avoid a log of zero or negative values.

SomeGraphs.Common.LineConfiguration Type
@kwdef mutable struct LineConfiguration <: Validated
    width::Maybe{Real} = nothing
    style::Maybe{LineStyle} = SolidLine
    is_filled::Bool = false
    color::Maybe{AbstractString} = nothing
end

Configure a line in a graph.

If style is nothing no line is drawn. If is_filled then the region defined by the line (below it in a lines graph) is filled. A line with no style and no filled is not drawn but can still be used to define a region (e.g., for a BandsConfiguration ).

By default, the color is chosen automatically.

SomeGraphs.Common.LineStyle Type

Styles of drawing a line

  • SolidLine draws a solid line (the default).

  • DashLine draws a dashed line.

  • DotLine draws a dotted line.

  • DashDotLine draws a dash-dotted line.

SomeGraphs.Common.ValuesOrientation Type

The orientation of the values axis in a distribution(s) or bars graph:

HorizontalValues - The values are the X axis

VerticalValues - The values are the Y axis (the default).

SomeGraphs.Common.BandsConfiguration Type
@kwdef mutable struct BandsConfiguration <: Validated
    low::BandConfiguration = BandConfiguration(is_dashed = true)
    middle::BandConfiguration = BandConfiguration()
    high::BandConfiguration = BandConfiguration(is_dashed = true)
end

Configure the partition of the graph up to three band regions. The low and high bands are for the "outer" regions (so their lines are at their border, dashed by default) and the middle band is for the "inner" region between them (so its line is inside it, solid by default).

If show_legend , then a legend showing the bands will be shown.

SomeGraphs.Common.BandConfiguration Type
@kwdef mutable struct BandConfiguration <: Validated
    offset::Maybe{Real} = nothing
    line::LineConfiguration = LineConfiguration()
end

Configure a region of the graph defined by some band of values.

The offset specifies the band's defining line position. We allow up to three bands in a complete BandsConfiguration . A band exists only if its offset is specified, in which case the line specifies how to render its defining line. The low and high bands are defined the region below and above their defining line's offset . If both are defined, the middle band offset defines the center line of the band; the band can still be filled even if this offset is not specified.

SomeGraphs.Common.BandsData Type
@kwdef mutable struct BandsData
    low_offset::Maybe{Real} = nothing
    middle_offset::Maybe{Real} = nothing
    high_offset::Maybe{Real} = nothing
end

Specify data for bands.

SomeGraphs.Common.ColorsConfiguration Type
@kwdef mutable struct ColorsConfiguration <: Validated
    fixed::Maybe{AbstractString} = nothing
    palette::Maybe{Union{AbstractString, ContinuousColors, CategoricalColors}} = nothing
    axis::AxisConfiguration = AxisConfiguration()
    show_legend::Bool = false
    title::Maybe{AbstractString} = nothing
end

Configure how to color some data. Supported combinations of configuration and data are:

fixed palette colors data axis show_legend Behavior
color name nothing nothing Restricted(A) false Named fixed (1)
nothing nothing nothing Restricted(A) false Auto fixed (2)
nothing nothing str[] Restricted(A) false Named data (3)
nothing nothing num[] Any Any Auto scale (4)
nothing palette name num[] Any Any Named scale (5)
nothing (value::num, color::str)[] num[] Any Any Manual scale (6)
nothing Dict{value::str => color::str} str[] Restricted(A) Any Categorical (7)

Any other combination of configuration is not allowed.

Restricted Axis: The axis can't specify log_scale , percent , minimum , maximum as they make no sense in this case.

Named fixed (1): All the data entities will be given the same fixed color.

Auto fixed (2): All the data entities will be given the same color, chosen automatically by Plotly.

Named data (3): The colors data contains explicit color names. An empty color name will prevent the matching data from being plotted. If the fixed color is specified, it is ignored.

Auto scale (4): The colors data (transformed by the axis ) will be shown in a color scale chosen by Plotly.

Named scale (5): The colors data (transformed by the axis ) will be shown using the named standard Plotly color scale (see NAMED_COLOR_SCALES ).

Manual scale (6): The colors data (transformed by the axis ) will be shown using the specified palette (whose values will also be transformed by the axis ). The values must be in non-decreasing order, and the overall range of values must not be empty.

Categorical (7): The colors data contains valid value keys of the categorical colors dictionary. An empty color name in the data or the dictionary will prevent the matching data from being plotted.

If show_legend is specified, categorical colors (case 7 above) will be shown in the legend; numerical colors will be shown in a color scale. Plotly is dumb when it comes to positioning color scales next to a legend (or next to each other); see the colors_scale_offsets vector of FigureConfiguration for details.

If title is specified, it will be used when showing the legend (whatever it is). However, in some cases the correct title depends on the data set, so you can override this in the data.

SomeGraphs.Common.ContinuousColors Type

A continuous colors palette, mapping numeric values to colors. We also allow specifying tuples instead of pairs to make it easy to invoke the API from other languages such as Python which do not have the concept of a Pair . The legend_title is only used if show_legend is set in the configuration.

SomeGraphs.Common.CategoricalColors Type

A categorical colors palette, mapping string values to colors. An empty string color means the entity will not be shown (as if it was masked or never included in the data). As a convenience, a named vector of strings can be specified instead of a dictionary.

SomeGraphs.Common.categorical_palette Function
categorical_palette(
    values::Union{AbstractSet{<:AbstractString}, AbstractVector{<:AbstractString}},
    palette::Union{AbstractString, ContinuousColors} = "HSV",
)::CategoricalColors

Given some string values , generate a categorical palette for them based on some continuous palette . This is done by giving each unique value an index from 1 to N and then mapping the range 0 to N into the continuous palette (adding 0 allows for safely using cyclical palettes). This palette should have as many distinct colors in it as possible, to maximize legibility of the result; HSV is a good default choice.

Note

This is not the best way to color categorical data. It is merely an adequate one for quick and dirty results. A manually created palette would be better for a low number of values, and for a high number of values, HSV uses only a 1D subset of the 3D color space. It would be nice to offer a robust "generate N colors most different from each other" function... PRs are welcome :-)

SomeGraphs.Common.NAMED_COLOR_SCALES Constant

Builtin color scales from Plotly , both linear: Blackbody , Bluered , Blues , Cividis , Earth , Electric , Greens , Greys , Hot , Jet , Picnic , Portland , Rainbow , RdBu , Reds , Viridis , YlGnBu , YlOrRd and cyclical: Twilight , IceFire , Edge , Phase , HSV , mrybm , mygbm . In addition, BuWtRd which is useful for showing +/- diff values and WtOrRdDm is useful for showing absolute values.

Note

You would think we could just give the builtin color scale names to plotly, but it turns out that "builtin" in Python plotly doesn't mean "builtin" in JavaScript plotly, because "reasons". We therefore have to copy their definition here. An upside of having this dictionary is that you are free to insert additional named scale into and gain the convenience of refering to them by name (e.g., for coloring heatmap annotations).

A _r suffix specifies reversing the order of the scale.

You can also append a final _z:<value_fraction>:<color_fraction> suffix to the name. This will map values in the bottom 0.. value_fraction of the range to white, and map the rest of the values to the top color_fraction ..1 range of the scale. For example, Blues_z:0.3:0.2 will color the bottom 30% of the values in white, and color the top 70% of the values to the top 80% of the Blues scale.

A _c:<value_fraction>:<color_fraction> works similarly to the _z suffix, except that the fractions are centered on 0.5 (the middle) of the values and color ranges. This is meant to be applied when the values range is +/-Diff, and the scale has white in the middle. For example RdBu_c:0.3:0.2 will color the 30% of the values near the 0 middle in white, and color the rest of the values using the top and bottom 40% of the RdBu scale.

An _o:<value_fraction>:<color> suffix maps the scale to the range 0.. 1-value_fraction , and all the values above this to the color , to denote overflow (too high) values. For example, Blues_o:0.01:magenta will color all the values in the bottom 99% of the values range to Blues , and the top 1% of the range to magenta. A _u suffix works similarly but applies to the bottom range of the values.

You can combine multiple suffixes together, for example Reds_z:0.2:0.2_o:0.99:magenta or RdBu_r_c:0.2:0.2_o:0.01:magenta_u:0.01:darkgreen .

Palettes with suffixes (including _r ) are computed on the fly and cached for future use.

Note

The implementation of the suffixes uses 1e-6 as a color difference "too small to matter". Don't use fractions this small in the prefixes or you will have a bad day.

Note

Always hold the COLOR_SCALES_LOCK when manually accessing the NAMED_COLOR_SCALES , otherwise you will regret it at some point.

SomeGraphs.Common.AnnotationData Type
@kwdef mutable struct AnnotationData <: Validated
    title::Maybe{AbstractString} = nothing
    values::Union{AbstractVector{<:Real}, AbstractVector{<:AbstractString}} = Float32[]
    hovers::Maybe{AbstractVector{<:AbstractString}} = nothing
    colors::ColorsConfiguration = ColorsConfiguration()
end

An annotation to attach to an axis. This applies to discrete axes (bars axis for a BarsGraph or the rows and/or columns of a HeatmapGraph ). The number of the values and the optional hovers must be the same as the number of entries in the axis.

The colors configuration is part of the data, because the color of annotations (in particular, categorical ones) is tightly coupled with the data.

Note

If you want to show the color scale of some annotation(s) using show_legend , keep in mind Plotly places a limit of at most two color scales in a graph, because "reasons".

SomeGraphs.Common.AnnotationSize Type
@kwdef mutable struct AnnotationSize
    size::AbstractFloat = 0.05
    gap::AbstractFloat = 0.005
end

Specify the sizes of annotations shown to the side of an axis, with a gap between each other and the heatmap itself. The sizes are in the usual inconvenient units (fraction of the overall graph size), because Plotly.

Index