phiz

Examples

Gallery

Each chart below is drawn from the code under it. The code uses the bundled datasets, loaded as $mpg = Datasets::mpg(), $mtcars = Datasets::mtcars(), $economics = Datasets::economics(), and $faithful = Datasets::faithful(). The statistics examples use a linear model fit and posterior draws defined at the top of examples/catalog.php, which holds every example.

Basics

Scatterplot

Points with a color mapping.

Plot::of($mpg)
    ->aes(x: 'displ', y: 'hwy', color: 'class')
    ->point()
    ->labs(x: 'Engine displacement (L)', y: 'Highway mpg', color: 'Class');

Linear fit with confidence band

lm fits are computed in PHP, with a 95% band, per group.

Plot::of($mpg)
    ->aes(x: 'displ', y: 'hwy', color: 'drv')
    ->point(alpha: 0.5)
    ->smooth(method: 'lm')
    ->labs(title: 'Bigger engines, fewer miles', x: 'Displacement (L)', y: 'Highway mpg', color: 'Drive');

With ggplot2-style functions

ggplot2-style functions compose with apply(), or with the pipe operator on PHP 8.5.

ggplot($mpg, aes(x: 'displ', y: 'hwy'))->apply(
        geom_point(alpha: 0.4),
        geom_smooth(span: 0.5),
        labs(title: 'Loess smooth', x: 'Displacement (L)', y: 'Highway mpg'),
        theme_minimal(),
    );

Time series

ISO dates are read as temporal fields.

Plot::of($economics)
    ->aes(x: 'date', y: 'unemploy')
    ->line(color: '#1F77B4')
    ->labs(title: 'US unemployment', x: null, y: 'Unemployed (thousands)');

Stacked area

Areas stack by fill.

Plot::of(array_merge(...array_map(fn ($r) => [
        ['date' => $r['date'], 'series' => 'Unemployed', 'value' => $r['unemploy']],
        ['date' => $r['date'], 'series' => 'Median weeks unemployed (x1000)', 'value' => $r['uempmed'] * 1000],
    ], array_slice($economics, -120))))
        ->aes(x: 'date', y: 'value', fill: 'series')
        ->area(alpha: 0.8)
        ->theme(legendPosition: 'bottom');

Labels

Text labels with nudges.

Plot::of(array_slice($mtcars, 0, 12))
    ->aes(x: 'wt', y: 'mpg', label: 'model')
    ->point()
    ->text(nudgeY: 8, size: 9)
    ->labs(x: 'Weight (1000 lbs)', y: 'mpg');

Distributions

Histogram

Binned counts; bins sets the maximum number of bins.

Plot::of($faithful)
    ->aes(x: 'waiting')
    ->histogram(bins: 30, fill: '#4C78A8')
    ->labs(x: 'Minutes between eruptions', y: 'Count');

Histogram with density

density: true scales bars to unit area, so a KDE overlays.

Plot::of($faithful)
    ->aes(x: 'eruptions')
    ->histogram(binwidth: 0.25, density: true, fill: '#DDDDDD', color: '#999999')
    ->density(color: '#D62728', linewidth: 2)
    ->labs(x: 'Eruption length (minutes)');

Density by group

Kernel densities per group with Vega-Lite’s density transform.

Plot::of($mpg)
    ->aes(x: 'hwy', fill: 'drv')
    ->density(alpha: 0.4)
    ->labs(x: 'Highway mpg', fill: 'Drive');

Boxplots

Tukey boxplots, flipped for readable labels.

Plot::of($mpg)
    ->aes(x: 'class', y: 'hwy', fill: 'class')
    ->boxplot()
    ->coordFlip()
    ->scaleFill(guide: false)
    ->labs(x: null, y: 'Highway mpg');

Violins with boxplots

Densities per level, layered with boxplots, in ggplot2’s gray theme and hue palette.

Plot::of($mpg)
    ->aes(x: 'class', y: 'hwy', fill: 'class')
    ->violin(alpha: 0.6)
    ->boxplot(width: 8, outliers: false)
    ->scaleFill(guide: false)
    ->theme('gray')
    ->labs(x: null, y: 'Highway mpg');

Jittered strip plot

Points spread within each band.

Plot::of($mpg)
    ->aes(x: factor('cyl'), y: 'hwy', color: factor('cyl'))
    ->jitter(width: 0.3, alpha: 0.6)
    ->labs(x: 'Cylinders', y: 'Highway mpg')
    ->scaleColor(guide: false);

Empirical CDF

Cumulative distributions per group.

Plot::of($mpg)
    ->aes(x: 'cty', color: 'drv')
    ->ecdf()
    ->labs(x: 'City mpg', y: 'Proportion ≤ x', color: 'Drive');

QQ plot

Sample quantiles against normal quantiles, with the quartile line.

Plot::of($mpg)
    ->aes(sample: 'hwy')
    ->qq(alpha: 0.6)
    ->qqLine(color: '#D62728');

2D density

Filled density bands from MASS::kde2d, computed in PHP, with the points on top.

Plot::of($faithful)
    ->aes(x: 'eruptions', y: 'waiting')
    ->density2dFilled()
    ->point(size: 6, color: 'white', alpha: 0.5)
    ->labs(x: 'Eruption (minutes)', y: 'Waiting (minutes)', fill: 'density');

Confidence ellipses

stat_ellipse: robust t ellipses per group.

Plot::of($mpg)
    ->aes(x: 'displ', y: 'hwy', color: 'drv')
    ->point(alpha: 0.5)
    ->ellipse()
    ->labs(x: 'Displacement (L)', y: 'Highway mpg', color: 'Drive');

Counts

Bar chart of counts

bar() counts rows when y is unmapped.

Plot::of($mpg)
    ->aes(x: 'class')
    ->bar(fill: '#4C78A8')
    ->labs(x: null, y: 'Cars');

Dodged bars

Groups side by side.

Plot::of($mpg)
    ->aes(x: 'class', fill: 'drv')
    ->bar(position: 'dodge')
    ->labs(x: null, y: 'Cars', fill: 'Drive');

Proportions

position: fill stacks to 100%.

Plot::of($mpg)
    ->aes(x: 'class', fill: 'drv')
    ->bar(position: 'fill')
    ->scaleY(format: '%')
    ->labs(x: null, y: 'Share of cars', fill: 'Drive');

Bars of values

col() draws the y values, here an aggregate.

Plot::of($mpg)
    ->aes(x: 'manufacturer', y: 'mean(hwy)')
    ->col(fill: '#72B7B2')
    ->coordFlip()
    ->labs(x: null, y: 'Mean highway mpg');

Heatmap

Tiles filled by an aggregate.

Plot::of($mpg)
    ->aes(x: 'class', y: factor('cyl'), fill: 'mean(hwy)')
    ->tile(color: 'white')
    ->scaleFill(scheme: 'viridis')
    ->labs(x: null, y: 'Cylinders', fill: 'Mean hwy');

2D histogram

Counts in x-by-y bins.

Plot::of($faithful)
    ->aes(x: 'eruptions', y: 'waiting')
    ->bin2d(bins: 20)
    ->scaleFill(scheme: 'blues');

Donut

Arcs sized by counts.

Plot::of($mpg)
    ->aes(fill: 'drv')
    ->pie(innerRadius: 50)
    ->labs(fill: 'Drive');

Polar bars

coordPolar() turns bars into a rose.

Plot::of($mpg)
    ->aes(x: 'class', fill: 'class')
    ->bar()
    ->coordPolar()
    ->labs(fill: null);

Annotations

annotate() adds shaded ranges, segments, and text.

Plot::of($mpg)
    ->aes(x: 'cty', y: 'hwy')
    ->annotate('rect', xmin: 15, xmax: 20, ymin: -INF, ymax: INF, fill: 'orange', alpha: 0.2)
    ->count(alpha: 0.6)
    ->annotate('text', x: 30, y: 23, label: 'Most efficient', color: '#D62728')
    ->labs(x: 'City mpg', y: 'Highway mpg');

Facets

Small multiples

facetWrap with a fitted line in every panel.

Plot::of($mpg)
    ->aes(x: 'displ', y: 'hwy')
    ->point(alpha: 0.5)
    ->smooth(method: 'lm')
    ->facetWrap('class', ncol: 4)
    ->size(150, 120);

Grid of panels

Rows by drive, columns by year.

Plot::of($mpg)
    ->aes(x: 'cty', y: 'hwy')
    ->point(alpha: 0.5)
    ->abline(0, 1, linetype: 'dashed', color: '#888888')
    ->facetGrid(rows: 'drv', cols: 'year')
    ->size(200, 120);

Side by side

Compose::beside lays plots out like patchwork.

Compose::beside(
        Plot::of($faithful)->aes(x: 'eruptions', y: 'waiting')->point(alpha: 0.5)->size(260, 220),
        Plot::of($faithful)->aes(x: 'waiting')->histogram(bins: 20)->coordFlip()->size(120, 220),
    )->title('Old Faithful');

Scales and themes

Log scale

scaleX(type: log) with chosen breaks.

Plot::of($economics)
    ->aes(x: 'pce', y: 'psavert')
    ->point(alpha: 0.4)
    ->scaleX(type: 'log', breaks: [500, 1000, 2000, 5000, 10000], name: 'Personal consumption ($bn, log)')
    ->labs(y: 'Savings rate (%)');

Manual colors

A level => color map.

Plot::of($mpg)
    ->aes(x: 'displ', y: 'hwy', color: 'drv')
    ->point()
    ->scaleColor(values: ['4' => '#D95F02', 'f' => '#1B9E77', 'r' => '#7570B3'], name: 'Drive');

Continuous color

A viridis gradient for a numeric field.

Plot::of($mpg)
    ->aes(x: 'displ', y: 'hwy', color: 'cty')
    ->point(size: 50)
    ->scaleColor(scheme: 'viridis');

theme(“gray”)

ggplot2’s default look.

Plot::of($mpg)
    ->aes(x: 'displ', y: 'hwy', color: 'drv')
    ->point()
    ->theme('gray');

theme(“classic”, baseSize: 13)

Axis lines, no grid, larger text.

Plot::of($mpg)
    ->aes(x: 'displ', y: 'hwy')
    ->point()
    ->theme('classic', baseSize: 13);

theme(“dark”)

A dark preset.

Plot::of($economics)
    ->aes(x: 'date', y: 'psavert')
    ->line(color: '#F58518')
    ->theme('dark');

Interaction

Tooltips, pan and zoom

Hover for values; drag and scroll to explore.

Plot::of($mpg)
    ->aes(x: 'displ', y: 'hwy', color: 'class')
    ->point()
    ->tooltip(['manufacturer', 'model', 'year', 'displ', 'hwy'])
    ->interactive();

Linked brushing

Brush the scatterplot to filter the bars.

Compose::stack(
        Plot::of($mpg)->aes(x: 'displ', y: 'hwy', color: 'drv')->point()->brush(encodings: ['x', 'y'])->size(420, 220),
        Plot::of($mpg)->aes(x: 'class')->bar(filter: ['param' => 'brush'])->size(420, 140),
    );

Legend highlight

Click a legend entry.

Plot::of($mpg)
    ->aes(x: 'displ', y: 'hwy', color: 'drv')
    ->point(size: 40)
    ->highlight();

Slider-driven reference line

A parameter bound to a slider moves the rule and filters points.

Plot::of($mpg)
    ->aes(x: 'displ', y: 'hwy')
    ->slider('cutoff', 15, 40, 1, 30, 'Highway mpg cutoff')
    ->point(filter: 'datum.hwy >= cutoff', color: '#D62728')
    ->point(filter: 'datum.hwy < cutoff', color: '#BBBBBB')
    ->hline(expr('cutoff'), linetype: 'dashed');

Statistics

Coefficient plot

Straight from a phit fit (fit_lm, fit_glm, …).

coefplot($fit)
    ->labs(title: 'mpg ~ wt + hp + am');

Comparing models

Several fits, dodged.

coefplot(['Full' => $fit, 'Without am' => $reduced]);

Regression diagnostics

Residuals, QQ, scale-location, observed vs fitted.

diagnostic_plots($fit, width: 230, height: 170);

Predicted values

Predictions with 95% bands as weight varies, by transmission.

effect_plot($fit, $mtcars, 'wt', by: 'am', design: fn ($r) => [1, $r['wt'], $r['hp'], $r['am']])
    ->labs(x: 'Weight (1000 lbs)', y: 'mpg', color: 'Manual', fill: 'Manual');

Posterior intervals

From CmdStanPHP draws_columns(), as bayesplot’s mcmc_intervals.

interval_plot($draws);

Means with error bars

Precomputed summaries with errorbar and point.

Plot::of(array_map(function ($class) use ($mpg) {
        $values = array_column(array_filter($mpg, fn ($r) => $r['class'] === $class), 'hwy');
        $mean = array_sum($values) / count($values);
        $sd = sqrt(array_sum(array_map(fn ($v) => ($v - $mean) ** 2, $values)) / (count($values) - 1));
        $se = $sd / sqrt(count($values));

        return ['class' => $class, 'mean' => $mean, 'lower' => $mean - 1.96 * $se, 'upper' => $mean + 1.96 * $se];
    }, array_values(array_unique(array_column($mpg, 'class')))))
        ->aes(x: 'class', y: 'mean', ymin: 'lower', ymax: 'upper')
        ->errorbar(width: 8)
        ->point(size: 50)
        ->labs(x: null, y: 'Mean highway mpg (95% CI)');

Summaries

stat_summary: means with t intervals over the raw data.

Plot::of($mpg)
    ->aes(x: 'class', y: 'hwy')
    ->point(alpha: 0.2)
    ->summary(funData: 'mean_cl_normal', color: '#D62728')
    ->labs(x: null, y: 'Highway mpg');

Functions

curve() draws a PHP function, here the normal density over a histogram.

Plot::of(array_map(fn ($i) => ['z' => Fit::normalQuantile(($i + 0.5) / 500)], range(0, 499)))
    ->aes(x: 'z')
    ->histogram(binwidth: 0.25, density: true, fill: '#DDDDDD')
    ->curve(fn (float $x) => exp(-$x * $x / 2) / sqrt(2 * M_PI), -3.5, 3.5, aes: aes(x: 'z', y: 'density'), color: '#D62728', linewidth: 2);

From phpstats/phiz@bc7ed3e.