Osy#betaRun it on Osyrin: osyrin.com

Reference / UI

A chart over every row

live var perDay = Entity.GroupBy(x => x.Day).Select(g => new Point { Label = g.Key, Value = g.Sum(x => x.Amount) }).ToList();

osy lint's ui-chart-over-unbounded-rows (SHOULD) fires when a chart or a map is drawn from a page query that reads every row of an entity with no Take. Past one answer (about 2 MB of rows) the chart silently plots the first answer's rows, and long before that it ships every row to draw a handful of marks. Aggregate in the query — one row per mark — or narrow it to the range the chart shows.

stable1 example compiled by CIuilintquerylimits

Summary#

ui-chart-over-unbounded-rows is a SHOULD finding: a Chart (or a map) is drawn from a read of every row of an entity. A page query is sent about 2 MB of rows an answer (How many rows a page is sent), so the chart of a growing table quietly leaves rows out — and before then it ships every row to draw a few bars.

Let the database do the summing, so the answer is one row per mark:

entity Sale {
  [Required, MaxLength(10)] string Day;
  decimal Amount;
  security { allow read, create when IsAnonymous || IsAuthenticated; }
}

class DayTotal { public string Day; public decimal Total; }

[Route("/sales")] [Render(CSR)] [AllowAnonymous]
component SalesPage() {
  live var perDay = Sale.GroupBy(s => s.Day)
                        .Select(g => new DayTotal { Day = g.Key, Total = g.Sum(s => s.Amount) }).ToList();

  render {
    Stack(gap: 2, p: 6) {
      foreach (var d in perDay) { Text($"{d.Day}: {d.Total}"); }
    }
  }
}

Signature#

Entity.GroupBy(x => key).Select(g => new Row { Key = g.Key, Value = g.Sum(x => …) }).ToList()   // one row per mark
Entity.Where(x => x.At >= from).OrderBy(x => x.At).Take(n).ToList()                              // or a narrowed range

Description#

When does it fire?#

On a component field read from an entity with no Take that a chart or map is drawn from — a Chart, a Pie, a Sparkline, or any control whose name holds Chart or Map — through its arguments or anything written inside it. It takes precedence over the list and dropdown rules for the same read.

When is it silent?#

When the read is an aggregate — a GroupBy (one row per key), a Count, a Sum — or bounded by a Take, a one-row read or a child collection pinned to one parent.

What can it not tell?#

How many distinct keys a GroupBy has. A group per customer can be large; a group per day of a month is not. A grouped read is trusted as bounded.

See also#

Related

How many rows a page is sent

A page's query is sent bounded. Its own .Take(n) is the ceiling: the browser may ask for fewer of those rows, never…

The chart kit — line, column, bar, scatter and candle, with no JavaScript

Line, area, column, bar, scatter and OHLC candle marks over one shared value scale, plus a Pie — an optional KIT you…

GroupBy (and HAVING)

Group rows by a key and reduce each group to one row — `g.Key`, `g.Count()`, `g.Count(o => p)`…