flat/
lib.rs

1//! `flat` is textual data renderer for Rust.
2//!
3//! The goal of `flat` is to provide access to complex data in a low-resolution textual form.
4//! This can be useful in certain contexts where high-resolution graphics are not suitable for logistic reasons (ex: space constraints, accessibility, etc).
5//! Although textual rendering has its drawbacks, we believe these are acceptable in certain contexts.
6//! The output of `flat` is best observed using a monospaced font.
7//!
8//! This documentation begins with a few example renderings to showcase `flat`.
9//! Go to the next section for usage instructions.
10//!
11//! ### Zoo Dataset
12//! The code for this example lives [here](https://github.com/sawatzkylindsey/flat/tree/main/flat_examples_primitives/examples/zoo.rs).
13//!
14//! Imagine a zoo; it contains various species of animals held in enclosures.
15//! The enclosures of this imaginary zoo are organized into the quadrants NorthEast, NorthWest, SouthEast, and SouthWest.
16//!
17//! Let's use `flat` to visualize this dataset.
18//! We'll first look at the density of animals across the zoo:
19//! ```text
20//! Animal           Enclosure    Quadrant   |Sum(Count)
21//! Sea Otter      - Pen01      - NorthEast  |*****
22//! Falcon         - Pen02      ┘
23//! Duck           - Open       ┐
24//! Flamingo       ┘
25//! Tiger          - Pen03      - NorthWest  |************
26//! Crane          ┐
27//! Kingfisher     - Pen04      ┘
28//! Stork          ┘
29//! Black Bear     - Pen05      - SouthEast  |**
30//! Grizzly Bear   - Pen06      - SouthWest  |****
31//! Mountain Goat  - Pen07      ┘
32//! ```
33//!
34//! We can also drill into this dataset a number of ways.
35//! Let's look at the specific breakdown of animals across quadrants/enclosures:
36//! ```text
37//!                          Animal
38//!                          Sum(Count)
39//! Enclosure    Quadrant   |Black Bear     Crane       Duck       Falcon     Flamingo   Grizzly B.. Kingfisher  Mountain ..  Sea Otter     Stork       Tiger   |
40//! Pen01      - NorthEast  |                                        **                                                          ***                            |
41//! Pen02      ┘
42//! Open       ┐
43//! Pen03      - NorthWest  |                 *          ***                    ****                      *                                   *          **     |
44//! Pen04      ┘
45//! Pen05      - SouthEast  |    **                                                                                                                             |
46//! Pen06      - SouthWest  |                                                                 *                      ***                                        |
47//! Pen07      ┘
48//! ```
49//!
50//! Finally, let's take a look at an attribute directly - we'll compare animal lengths.
51//! Notice, the visualization on the right (commonly referred to as the rendering) show's relative values in `flat`.
52//! That's why we've also included the absolute value in this visualization (ex: Ducks are on average 29cm long).
53//! ```text
54//! Animal        Average  |Average(Length (cm))
55//! Black Bear    [   75]  |*
56//! Crane         [   60]  |*
57//! Duck          [   29]  |
58//! Falcon        [   55]  |*
59//! Flamingo      [   85]  |*
60//! Grizzly Bear  [  220]  |****
61//! Kingfisher    [   15]  |
62//! Mountain Goat [113.3]  |**
63//! Sea Otter     [133.3]  |**
64//! Stork         [   60]  |*
65//! Tiger         [  285]  |******
66//! ```
67//!
68//! We can also look at this data the other way around - how do the zoo animals spread across length.
69//! ```text
70//! Length (cm)   |Sum(Count)
71//! [15, 42.5)    |*****
72//! [42.5, 70)    |****
73//! [70, 97.5)    |****
74//! [97.5, 125)   |****
75//! [125, 152.5)  |***
76//! [152.5, 180)  |
77//! [180, 207.5)  |
78//! [207.5, 235)  |*
79//! [235, 262.5)  |
80//! [262.5, 290]  |**
81//! ```
82//!
83//! As before, we can further drill down into this by looking at how this spread looks specifically across the animal species.
84//! ```text
85//!                Animal
86//!                Sum(Count)
87//! Length (cm)   | Black Bear     Crane         Duck        Falcon      Flamingo   Grizzly Bear  Kingfisher  Mountain G..  Sea Otter      Stork        Tiger    |
88//! [15, 42.5)    |     *                        ***                                                  *                                                          |
89//! [42.5, 70)    |                  *                         **                                                                            *                   |
90//! [70, 97.5)    |                                                        ****                                                                                  |
91//! [97.5, 125)   |     *                                                                                          **           *                                |
92//! [125, 152.5)  |                                                                                                *            **                               |
93//! [152.5, 180)  |                                                                                                                                              |
94//! [180, 207.5)  |                                                                                                                                              |
95//! [207.5, 235)  |                                                                      *                                                                       |
96//! [235, 262.5)  |                                                                                                                                              |
97//! [262.5, 290]  |                                                                                                                                       **     |
98//! ```
99//!
100//! # Usage
101//! This rest of this page describes the general usage for `flat`.
102//! Detailed examples can be found in [the source](https://github.com/sawatzkylindsey/flat/tree/main).
103//!
104//! The general workflow for using `flat` is to 1) construct a dataset, 2) get a specific view of that dataset, and 3) render the view using a widget.
105//!
106//! ### Construct a Dataset
107//! To construct a [`Dataset`], you need both data and a [`Schemas`] \[sic\].
108//!
109//! The data must come in the form of dense vectors, typically expressed as a [rust tuple](https://doc.rust-lang.org/stable/book/ch03-02-data-types.html#the-tuple-type).
110//! For example, one data point in a dataset could be: `(49.238, -123.114, Direction::North, "Strawberry Bush")`.
111//! Then, all the data points for this example `Dataset` must come in this 4-dimensional form (and maintain the same type pattern).
112//! This is where `Schemas` come in - the schema defines the specific type pattern of your dataset, as well as the column names for that dataset.
113//! To continue our example, here's a schema to fit the dataset: `Schemas::four("Latitude", "Longitude", "Direction", "Object")`.
114//!
115//! Typically, the actual type definitions can be [inferred by the compiler](https://doc.rust-lang.org/stable/book/ch03-02-data-types.html).
116//! However, using explicit types is recommended, since this informs method discovery for the next section.
117//! ```
118//! # use flat::*;
119//! # enum Direction {
120//! #     North,
121//! #     South,
122//! # }
123//! let my_schema: Schema4<f64, f64, Direction, &str> = Schemas::four("Latitude", "Longitude", "Direction", "Object");
124//! ```
125//!
126//! Datasets are constructed using a builder.
127//! See the [`DatasetBuilder`] docs for more details.
128//!
129//! ### Get a View
130//! A view describes *what* to look at within the dataset (but not *how* to render it).
131//! It includes aspects like which columns form the frame vs. the rendering, as well as how to label these.
132//!
133//! The visualizations in `flat` always come in the following form.
134//! ```text
135//! Frame..  | Rendering..
136//! Frame..  | Rendering..
137//! Frame..
138//! Frame..  | Rendering..
139//! ```
140//!
141//! The "frame" is the organizational basis of the visualization, and it always shows at least 1 dimension from the dataset.
142//! The "rendering" is a visualized value which may come directly from the dataset, but also may be inferred (ex: in the case of a "count").
143//! Notice, not every line of output in the visualization need necessarily show a rendering.
144//!
145//! Typically, the rendering does not show a single value from the dataset, but rather some grouping of values.
146//! This is called the [`Aggregate`], and is defined at the time of rendering (next section).
147//!
148//! To get a view, simply invoke the appropriate method on your dataset.
149//! These methods are inferred based off the schema of the dataset, and multiple views may be drawn from the same dataset.
150//! For example: `dataset.reflect_1st()`, `dataset.view_2nd()`, `dataset.breakdown_3rd()`, or `dataset.count()`.
151//! See the [`Dataset`] docs for more details.
152//!
153//! **Note**: many views are made available through features, described later in this documentation.
154//!
155//! Additionally, custom view implementations may be defined by the user.
156//! Take a look at the docs for more details: [`View`].
157//!
158//! ### Render a Widget
159//! A widget describes *how* to render a view - the same view may be rendered differently by different widgets.
160//! This is where the specific appearance of the frame and rendering are defined.
161//!
162//! To render a widget, first instantiate it with a view, and then invoke render on it with a `Render` configuration.
163//! This will produce a [`Flat`] which provides a [`std::fmt::Display`] implementation for the visualization.
164//!
165//! The configuration includes a number of parameters, which come in two sections.
166//! 1. The general parameters which apply to all widgets.
167//! 2. The widget specific configuration (`widget_config`).
168//!
169//! We recommend using [the struct update syntax](https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-from-other-instances-with-struct-update-syntax) (with [`std::default::Default`]) to instantiate the config.
170//! ```
171//! # use flat::*;
172//! // Take the default widget specific configuration.
173//! let config: Render<HistogramConfig> = Render {
174//!     width_hint: 50,
175//!     ..Render::default()
176//! };
177//!
178//! // Override the widget specific configuration.
179//! let config: Render<DagChartConfig> = Render {
180//!     width_hint: 50,
181//!     widget_config: DagChartConfig {
182//!         show_aggregate: true,
183//!         ..DagChartConfig::default()
184//!     },
185//!     ..Render::default()
186//! };
187//! ```
188//!
189//! For specific details on the configurable values, take a look at the docs: [`Render`].
190//! A subsequent section describes one of the key parameters to a rendering - the width (via `width_hint`).
191//!
192//! # Features
193//! `flat` uses features to enable rendering for numeric types, specifically defined at the "Get a View" step.
194//! This is required due to how rust handles [specialization](https://rust-lang.github.io/rfcs/1210-impl-specialization.html).
195//!
196//! When deciding to use `flat`, you need to also decide the flavour of view generation.
197//! There are roughly three options:
198//! 1. `primitive_impls`: With this option, `flat` provides view implementations for all numeric primitive types.
199//! For example, a `SchemaN<*, u8>` knows to extract the `u8` value and render that within the view.
200//! Choosing this option is mutually exclusive with `pointer_impls`.
201//! This choice also limits the kinds of custom views you may implement (they must also follow the concrete numeric implementation pattern).
202//! 2. `pointer_impls`: With this option, `flat` provides view implementations for all `Deref` types.
203//! For example, a `SchemaN<*, Box<u8>>` knows to extract the `u8` value and render that within the view.
204//! Choosing this option is mutually exclusive with `primitive_impls`.
205//! This choice also limits the kinds of custom views you may implement (they must also follow the Deref type implementation pattern).
206//! 3. `default`: With this option, `flat` does not provide any numeric view generation.
207//! You still get non-numeric view generation (such as counts).
208//! You may also implement your own [`View`]s (see [the example here](https://github.com/sawatzkylindsey/flat/blob/main/examples/iris.rs)).
209//!
210//! If you don't know which to decide, a good starting point is `primitive_impls`.
211//!
212//! # Value Rendering Details
213//! `flat` follows a few simple rules when generating the "visual" rendering of data.
214//! The details follow, but in the general case the visual rendering should be assumed to be *relative*.
215//! That is, the literal count of characters (ex: `'*'`) does not necessarily represent the literal value of the data.
216//!
217//! The `flat` rendering process is subject to change, but can be summarized with the following procedure:
218//! 1. Calculate the visual rendering width by `width_hint - OTHER_COLUMNS`.
219//! 2. If this width is less than `2`, then set it to `2`.
220//! 3. Check if the greatest aggregate value to be rendered fits within the visual rendering.
221//! If it does, draw the values literally (in this case, a single character `'*'` does represent the absolute value).
222//! 4. Otherwise, find the linear scaling such that the greatest aggregate value fits within the visual rendering.
223//! Scale the aggregate values accordingly (ex: a single character `'*'` represents a relative value).
224//!
225//! Notice, by design this process will only *down-scale* the values to fit within the visual rendering.
226//! Values will never be *up-scaled* to fit the `width_hint`.
227//!
228//! The above process also applies for fractional and negative values.
229//! For fractions, `flat` always rounds the aggregate value before scaling.
230//! In the case of negatives, `flat` takes the absolute value to detect the appropriate bounds and to render the representation characters.
231//! Negative values are rendering using a different character marker (ex: `'⊖'`).
232mod abbreviate;
233mod aggregate;
234mod dagchart;
235mod dataset;
236mod histogram;
237mod pathchart;
238mod render;
239mod schema;
240mod view;
241
242pub use aggregate::{minimal_precision_string, Aggregate};
243pub use dagchart::*;
244pub use dataset::*;
245pub use histogram::*;
246pub use pathchart::*;
247pub use render::{Flat, Render};
248pub use schema::*;
249use std::fmt::{Display, Formatter};
250pub use view::*;
251
252#[cfg(all(feature = "primitive_impls", feature = "pointer_impls"))]
253compile_error!("Users may choose at most one of: [primitive_impls, pointer_impls]");
254
255/// Trait to extract the display values for rendering.
256///
257/// Typically, consumers need not implement this trait (as long as they use tuple data types, ex: `(T, U, V)`).
258pub trait Dimensions {
259    /// Get the string format values from this vector/data.
260    fn as_strings(&self) -> Vec<String>;
261
262    /// Get the length of this vector/data.
263    fn len(&self) -> usize;
264}
265
266/// No-op struct used to indicate an unused associated type in the widget's trait.
267#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
268pub struct Nothing;
269
270impl Display for Nothing {
271    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
272        write!(f, "")
273    }
274}
275
276impl<T> Dimensions for (T,)
277where
278    T: Display,
279{
280    fn as_strings(&self) -> Vec<String> {
281        vec![self.0.to_string()]
282    }
283
284    fn len(&self) -> usize {
285        1
286    }
287}
288
289impl<T, U> Dimensions for (T, U)
290where
291    T: Display,
292    U: Display,
293{
294    fn as_strings(&self) -> Vec<String> {
295        vec![self.0.to_string(), self.1.to_string()]
296    }
297
298    fn len(&self) -> usize {
299        2
300    }
301}
302
303impl<T, U, V> Dimensions for (T, U, V)
304where
305    T: Display,
306    U: Display,
307    V: Display,
308{
309    fn as_strings(&self) -> Vec<String> {
310        vec![self.0.to_string(), self.1.to_string(), self.2.to_string()]
311    }
312
313    fn len(&self) -> usize {
314        3
315    }
316}
317
318impl<T, U, V, W> Dimensions for (T, U, V, W)
319where
320    T: Display,
321    U: Display,
322    V: Display,
323    W: Display,
324{
325    fn as_strings(&self) -> Vec<String> {
326        vec![
327            self.0.to_string(),
328            self.1.to_string(),
329            self.2.to_string(),
330            self.3.to_string(),
331        ]
332    }
333
334    fn len(&self) -> usize {
335        4
336    }
337}
338
339#[cfg(test)]
340mod tests {
341    use super::*;
342
343    #[test]
344    fn dimensions_one() {
345        let one = ("abc".to_string(),);
346        assert_eq!(one.len(), 1);
347        assert_eq!(one.as_strings(), vec!["abc".to_string()]);
348    }
349
350    #[test]
351    fn dimensions_two() {
352        let two = ("abc".to_string(), 1);
353        assert_eq!(two.len(), 2);
354        assert_eq!(two.as_strings(), vec!["abc".to_string(), "1".to_string()]);
355    }
356
357    #[test]
358    fn dimensions_three() {
359        let three = ("abc".to_string(), 1, true);
360        assert_eq!(three.len(), 3);
361        assert_eq!(
362            three.as_strings(),
363            vec!["abc".to_string(), "1".to_string(), "true".to_string()]
364        );
365    }
366}