Development environment
ConradBI home

Home  /  How-to  /  Calculate with DAR-C

DAR-C: write your own measures

Turn the numbers in your facts into the measures your business talks about, like margin, share of total and year over year, in a few lines you can read out loud.

The short version

  • A DAR-C calculation is a short list of steps. Each step fetches one number or does math on numbers fetched earlier. The last step returns the answer.
  • Every fetch says which rows count (its context) and how to add them up (its aggregation).
  • Compute does the math: + - * /, ROUND, COALESCE, CASE and more.
  • Comparing over time takes one extra line first: Query.SetDate([Order Date]);.
  • Write a calculation once on the fact and every visual can use it, or keep it on one visual.
In this guide
  1. What DAR-C is
  2. Before you start
  3. Your first calculation: margin percent
  4. How a calculation is built
  5. Which rows count: contexts
  6. Changing what a fetch counts
  7. Doing the math: Compute
  8. Comparing over time
  9. Calculations on one visual
  10. Using the editor
  11. Aggregations
  12. Recipes
  13. When something goes wrong
  14. Command reference

What DAR-C is

Every fact already gives you simple measures: the sum of Revenue, the count of orders. DAR-C is for everything else. Margin percent, a product's share of its category, this month against the same month last year.

A DAR-C calculation reads like the question you would ask. It runs wherever a visual shows a value: on every bar, in every matrix cell, in every total. You write it once and it gives the right answer in each of those places.

You do not need SQL or a formula language to use it. If you can describe the number in a sentence, you can write it.

Before you start

You need three things in your project:

  • A fact with measures, for example a Sales fact with Revenue, Cost and Units.
  • Dimensions on that fact, for example Site, Product and Customer.
  • A Date Dimension, if you want to compare periods. In this guide it is called Order Date.

The examples in this guide all use the same small model:

Element What it holds
Sales (fact) Revenue, Cost, Units, and the column Channel ("Online" or "Store")
Site (dimension) Site Name, Region
Product (dimension) Category, Product Name
Customer (dimension) Customer Name, Segment
Order Date (Date Dimension) Date, Year, Month Name, Quarter and the other calendar columns

Swap in your own names as you go. The rest stays the same.

Your first calculation: margin percent

This takes about five minutes.

  1. Open Facts, then your Sales fact.
  2. Find the DAR-C Calculations group and add a calculation.
  3. Set Alias to Margin %. The alias is the name your visuals will see.
  4. Set Format to Percent with 1 decimal place, and tick ×100. A Percent format adds the % sign; ×100 turns 0.42 into 42%.
  5. Paste this into the first row:
DAR-C
Context.Value().AggregateFact(Sum, Revenue).SetQueryVariable(SalesTotal);
Context.Value().AggregateFact(Sum, Cost).SetQueryVariable(CostTotal);
Compute(([Query].[SalesTotal] - [Query].[CostTotal]) / [Query].[SalesTotal]).Return();
  1. Save the fact. If anything is wrong, the editor shows the problem under the row, and the calculation is not offered to visuals until it is fixed.
  2. Add a Card on the Sales fact and pick Margin % as its measure.
  3. Now add a Bar Chart by Site Name with the same measure. Each bar shows that site's own margin. You did not change anything: the calculation works out each bar on its own.

Read back, the calculation says: "For this value, add up Revenue and call it SalesTotal. Add up Cost and call it CostTotal. Return SalesTotal minus CostTotal, divided by SalesTotal."

Tip: Paste a whole calculation into one row. The editor splits it into one row per step for you.

How a calculation is built

A calculation is a list of steps. Each step ends with a semicolon ;. Inside a step, commands are chained with dots ..

There are three kinds of step:

Step Starts with What it does
Fetch Context Picks which rows count, then aggregates one column of the fact
Compute Compute Does math on the numbers fetched or computed above it
Setting Query Changes a setting for the steps after it, such as which date dimension to use

Each fetch or compute step ends in one of two ways:

  • SetQueryVariable(Name) keeps the result under a name, so a later step can use it.
  • Return() hands the result to the visual. A calculation has exactly one Return(), and it ends the last step.

Inside a Compute, you read a kept result as [Query].[Name]. A name starts with a letter, then letters and digits, and each name can be set only once.

A few rules that make typing easier:

  • Spaces and line breaks do not matter, so you can lay a step out over several lines. The exception is the text inside Compute(...) and FilterContext(...), which is kept exactly as you wrote it.
  • Capitals do not matter. context.value() is the same as Context.Value().
  • Every command has a short form: the first three letters of each capitalized word. Context is Con, AggregateFact is AggFac, SetQueryVariable is SetQueVar. You can mix full and short forms freely. Short forms are for commands only, never for aggregations, columns or names.
DAR-C
Con.Val().AggFac(Sum, Revenue).SetQueVar(SalesTotal);
Con.Val().AggFac(Sum, Units).SetQueVar(UnitsSold);
Com([Query].[SalesTotal] / [Query].[UnitsSold]).Ret();

That is the same as writing every command in full, and it returns the average selling price per unit.

Which rows count: contexts

Every value on a visual has filters on it. A bar for the North region only counts North's rows. A card filtered to 2025 only counts 2025. A click on another visual in the report narrows it further.

A context decides which of those filters a fetch keeps. You choose it at the start of every fetch: Context.Value(), Context.Visual() and so on.

Context Counts Use it for
Value() Only this bar, card or cell, with every filter The value itself
Row() This matrix row, across all its columns A cell's share of its row
Column() This matrix column, across all its rows A cell's share of its column
Visual() Everything this visual shows, before it is split into bars or cells Share of the visual's total
Report() Everything the report's filters and selections allow, ignoring this visual's own filters Share of what the whole report shows
Model() Every row of the fact, no filters at all Share of the all-time total

An example. A Bar Chart shows Revenue by Region, filtered to 2025: North 300, South 700.

Fetch North bar South bar
Context.Value().AggregateFact(Sum, Revenue) 300 700
Context.Visual().AggregateFact(Sum, Revenue) 1,000 1,000
Context.Model().AggregateFact(Sum, Revenue) every year, every region the same

Put the first two together and you have each region's share of the chart:

DAR-C
Context.Value().AggregateFact(Sum, Revenue).SetQueryVariable(Part);
Context.Visual().AggregateFact(Sum, Revenue).SetQueryVariable(Whole);
Compute([Query].[Part] / [Query].[Whole]).Return();

With a Percent format and ×100 ticked, North shows 30% and South 70%.

What each context keeps

Three layers of filters can narrow a value. Its own slot: the bar's category, or the cell's row and column. The visual's own filters: its Filters tab and its drill path. The report: the Interactive Report's filters, and selections made on other visuals.

Context Slot: row Slot: column Visual's filters and drill Report's filters and selections
Value() kept kept kept kept
Row() kept let go kept kept
Column() let go kept kept kept
Visual() let go let go kept kept
Report() let go let go let go kept
Model() let go let go let go let go

A chart's categories count as its rows and its legend as its columns. A visual with no columns reads Column() as Visual(), and one with no rows reads Row() the same way. Outside a report, Report() is the same as Model().

Totals are worked out, not added up

In a matrix, the row totals, column totals and grand total each run the whole calculation again in their own context. So the total of a margin percent is the margin of the total, not the sum of the margins. That is the right answer, and it is why ratios and averages stay correct in totals.

Changing what a fetch counts

Sometimes you want a fetch to ignore one filter, or keep only one, or count only some rows. Three commands do that. Write them after the context and before AggregateFact, as many as you need.

Naming a column

These commands name dimension columns as [Dimension].[Column], for example [Product].[Category].

  • You can use the name the fact gives the dimension or the dimension's own name.
  • [ANY] stands for every column of a dimension: [Product].[ANY].
  • The fact's own columns go under the fact's name: [Sales].[Channel].

RemoveDimensionFilter: ignore a filter

Lets go of every filter on the columns you name, wherever it came from: the bar's own category, the Filters tab, a drill, a condition.

A product's share of its category, on a chart drilled from Category into Product Name:

DAR-C
Context.Value().AggregateFact(Sum, Revenue).SetQueryVariable(ThisProduct);
Context.Value().RemoveDimensionFilter([Product].[Product Name]).AggregateFact(Sum, Revenue).SetQueryVariable(WholeCategory);
Compute([Query].[ThisProduct] / [Query].[WholeCategory]).Return();

The second fetch drops the Product Name filter but keeps the drill into the category, so it counts the whole category.

KeepDimensionFilter: keep only some filters

The mirror image: every filter goes except the ones on the columns you name. It also lets go of number filters.

However a visual is sliced, this compares each value with its whole year:

DAR-C
Context.Value().AggregateFact(Sum, Revenue).SetQueryVariable(Here);
Context.Value().KeepDimensionFilter([Order Date].[Year]).AggregateFact(Sum, Revenue).SetQueryVariable(YearTotal);
Compute([Query].[Here] / [Query].[YearTotal]).Return();

FilterContext: count only some rows

Adds a condition the rows must also meet. The condition is written in HU++, the same language as a visual's Filters tab condition.

The online share of revenue:

DAR-C
Context.Value().FilterContext([Channel] = "Online").AggregateFact(Sum, Revenue).SetQueryVariable(Online);
Context.Value().AggregateFact(Sum, Revenue).SetQueryVariable(AllChannels);
Compute([Query].[Online] / [Query].[AllChannels]).Return();

A few things to know about conditions:

  • Text goes in quotes. Compare with = != < <= > >=, match patterns with LIKE, and join conditions with AND, OR and NOT.
  • A column on its own, like [Channel], works when only one element has a column by that name. Otherwise write [Element].[Column].
  • Inside a condition, name a dimension by its own name, the name it has in the Dimensions list. The name a fact gives it only works in the other commands.
  • A condition always holds. RemoveDimensionFilter never removes it, and it applies even in Context.Model().

Doing the math: Compute

Compute(...) takes a value written in HU++ and works it out for every bar, cell and total. Inside it you use the names you kept above as [Query].[Name].

You can use Example
+ - * / and parentheses ([Query].[A] - [Query].[B]) / [Query].[B]
Numbers, with . for decimals [Query].[Units] * 0.5
ROUND(number, places) ROUND([Query].[A] / [Query].[B], 2)
ABS, FLOOR, CEILING ABS([Query].[Now] - [Query].[Before])
POWER(number, exponent) POWER([Query].[Growth], 2)
MOD(number, divisor) MOD([Query].[Units], 12)
GREATEST(...), LEAST(...) GREATEST([Query].[A], [Query].[B])
COALESCE(value, ...) COALESCE([Query].[LastYear], 0)
CASE WHEN ... THEN ... ELSE ... END CASE WHEN [Query].[A] = 0 THEN [NULL] ELSE [Query].[B] / [Query].[A] END

A Compute can be kept with SetQueryVariable and used by a later Compute, just like a fetch.

Blanks and zeros

  • A blank anywhere in the math gives a blank. Blank plus 1 is blank.
  • Dividing by zero, or by a blank, gives a blank, never an error.
  • To treat a blank as zero, wrap it: COALESCE([Query].[LastYear], 0).
  • A condition used as the value gives 1 when it is true and 0 when it is not.

Comparing over time

DAR-C can move a fetch in time: the month before, the same month last year, the year so far, a rolling three months.

Step 1: tell DAR-C which date to use

Start the calculation with this setting step:

DAR-C
Query.SetDate([Order Date]);

It names a Date Dimension, or a Role Playing Dimension over one, that the fact uses. Every time command after it works on that dimension.

Step 2: add a time command to a fetch

Write it after the context, before AggregateFact:

DAR-C
Query.SetDate([Order Date]);
Context.Value().SamePeriodPreviousYears().AggregateFact(Sum, Revenue).Return();

On a bar for March 2026 that returns the revenue of March 2025.

How DAR-C finds the period

DAR-C reads the period from the date filters on each value: a Year bar gives that year, a Year and Month Name gives the month. It also reads ticked dates, a date drill, a relative window like "last 3 months", or a selection on another visual in the report.

If a value has no date filter at all, there is no period to move, and a time command gives a blank. A card over the whole fact is the usual case: give it a date filter first.

The time commands

Command On a March 2026 value it counts
PreviousPeriods() February 2026
PreviousPeriods(3) December 2025 to February 2026, as one run
PreviousPeriods([INFINITY]) Everything before March 2026, back to your first date
SamePeriodPreviousYears() March 2025
SamePeriodPreviousYears(3) March 2025, 2024 and 2023, added together
Rolling(3) January to March 2026
Rolling([INFINITY]) Everything up to the end of March 2026, a running total
CalendarYearToDate() January to March 2026
QuarterToDate() January to March 2026, the quarter so far
MonthToDate() March 2026, the month so far
FiscalYearToDate() The start of your fiscal year to the end of March 2026
FiscalQuarterToDate() The start of your fiscal quarter to the end of March 2026

Good to know:

  • Commands chain. SamePeriodPreviousYears().CalendarYearToDate() is last year to date: January to March 2025.
  • Other filters still apply. On a chart with a Region legend, each region gets its own previous month.
  • The fiscal year starts in the month set on your Date Dimension. With a January start, the fiscal commands match the calendar ones.
  • Rolling is one count over the whole window, so a rolling average or a distinct count is right, not an average of averages.

Calculations on one visual

You do not have to put every calculation on the fact. Every visual that shows a measure has a Calculations tab, its last tab, where you can write calculations for that visual alone. It is on the Bar, Pie, Card, Scatter, Table, Matrix, Multi Axis Line Graph, Stacked Area and Dynamic Visual.

  • They use the same language and appear in that visual's measure lists.
  • The tab also shows what narrows this visual now: its rows and columns, its drill, its Filters tab and, when you open it from a report, the report's filters. Check it before you choose a context.
  • To build on a fact's calculation instead of copying it, start with FactCalculation([Alias]):
DAR-C
FactCalculation([Margin %]).SetQueryVariable(Margin);
Compute(ROUND([Query].[Margin] * 100, 1)).Return();

When a visual-only calculation turns out to be useful elsewhere, move it to the fact so every visual can use it.

Using the editor

The DAR-C editor has two views over the same text:

  • Designer shows one row per step. A Compute or FilterContext opens a small HU++ window between its parentheses, marked HU++ value or HU++ condition.
  • Text shows the whole calculation in one box, handy for pasting or a bigger edit.

While you type:

  • Press Ctrl+Space for suggestions. Each command shows its full name with its short form beside it. Left and Right pick the form, and Tab writes it.
  • After a finished command, suggestions wait until you type the . that starts the next one.
  • Undo and Redo are the editor's own, on the toolbar and in the Edit menu.
  • A mistake shows under its row or in its HU++ window. A calculation with any mistake is not offered to visuals.

Aggregations

AggregateFact takes one of fourteen methods, then a column. The column must be one of the fact's measures, such as Revenue. A dimension column like Region cannot be aggregated.

Method Gives
Sum The total
Average The total divided by the count
Min, Max The smallest or largest value
Count How many values are not blank
DistinctCount How many different values there are
Median The middle value
StdDev, Variance Sample standard deviation and variance
Percentile The value at a percentile, written AggregateFact(Percentile, Revenue, 90)
CountRows How many rows, blank or not. The column is optional: AggregateFact(CountRows)
CountBlank How many rows are blank
First, Last The first or last value in the fact's row order

Blanks are skipped and not counted by every method except CountRows and CountBlank. A group with only blanks is blank, not zero.

Recipes

Copy, paste, then rename the columns to yours. Set the Format noted under each one.

Average selling price

DAR-C
Context.Value().AggregateFact(Sum, Revenue).SetQueryVariable(SalesTotal);
Context.Value().AggregateFact(Sum, Units).SetQueryVariable(UnitsSold);
Compute([Query].[SalesTotal] / [Query].[UnitsSold]).Return();

Format: Currency, 2 decimals.

Share of a matrix row

DAR-C
Context.Value().AggregateFact(Sum, Revenue).SetQueryVariable(Cell);
Context.Row().AggregateFact(Sum, Revenue).SetQueryVariable(RowTotal);
Compute([Query].[Cell] / [Query].[RowTotal]).Return();

Format: Percent, ×100. Each row adds up to 100%. Use Context.Column() for the share of a column.

Year over year, percent

DAR-C
Query.SetDate([Order Date]);
Context.Value().AggregateFact(Sum, Revenue).SetQueryVariable(ThisYear);
Context.Value().SamePeriodPreviousYears().AggregateFact(Sum, Revenue).SetQueryVariable(LastYear);
Compute(([Query].[ThisYear] - [Query].[LastYear]) / [Query].[LastYear]).Return();

Format: Percent, ×100, 1 decimal. A fall shows as a negative number.

Change from the previous month

DAR-C
Query.SetDate([Order Date]);
Context.Value().AggregateFact(Sum, Revenue).SetQueryVariable(Now);
Context.Value().PreviousPeriods().AggregateFact(Sum, Revenue).SetQueryVariable(Before);
Compute([Query].[Now] - COALESCE([Query].[Before], 0)).Return();

Format: Currency. COALESCE counts a missing previous month as zero.

Running total

DAR-C
Query.SetDate([Order Date]);
Context.Value().Rolling([INFINITY]).AggregateFact(Sum, Revenue).Return();

Format: Currency. Use it on a chart by month or by year.

Rolling three-month average

DAR-C
Query.SetDate([Order Date]);
Context.Value().Rolling(3).AggregateFact(Sum, Revenue).SetQueryVariable(ThreeMonths);
Compute([Query].[ThreeMonths] / 3).Return();

Format: Currency. In the first two months of your data the window is shorter, so the average reads low there.

Year to date against last year to date

DAR-C
Query.SetDate([Order Date]);
Context.Value().CalendarYearToDate().AggregateFact(Sum, Revenue).SetQueryVariable(Ytd);
Context.Value().SamePeriodPreviousYears().CalendarYearToDate().AggregateFact(Sum, Revenue).SetQueryVariable(LastYtd);
Compute([Query].[Ytd] - [Query].[LastYtd]).Return();

Format: Currency.

Against a fixed year

Each year compared with 2019, whatever year the value is in:

DAR-C
Context.Value().AggregateFact(Sum, Revenue).SetQueryVariable(Now);
Context.Value().RemoveDimensionFilter([Order Date].[Year]).FilterContext([Order Date].[Year] = "2019").AggregateFact(Sum, Revenue).SetQueryVariable(Base);
Compute([Query].[Now] / [Query].[Base]).Return();

Format: Percent, ×100. The first fetch keeps the value's own year; the second lets it go and counts 2019 instead.

Number of order lines

DAR-C
Context.Value().AggregateFact(CountRows).Return();

Format: Number.

When something goes wrong

What you see What to do
"… isn't a dimension this fact uses (…)" Use one of the names listed in the message, in brackets: [Order Date].[Year]
A message telling you to write Query.SetDate Add Query.SetDate([Order Date]); as the first step
A message about a name without [Query] Inside Compute, write [Query].[Name], not [Name]
A condition does not recognize a dimension Inside FilterContext, use the dimension's own name from the Dimensions list
A time calculation is blank The value has no date filter. Give the card or chart a date filter, or put dates on its axis
A ratio is blank The denominator is zero or blank. Use CASE or COALESCE if you want another answer
A card shows a total far bigger than expected The card counts every row. Add a filter on its Filters tab, such as a year
A Percent shows 0.4% instead of 40% Tick ×100 on the format, or multiply by 100 in Compute
The calculation is not in a visual's measure list It has a mistake, it has no alias, or its alias is the same as a measure's name
A visual still shows an old result after you changed the calculation Open that visual and save it. A visual keeps its own copy of each calculation it uses

Calculations written before ConradBI 1.2.49

Version 1.2.49 replaced a few words. Rewrite older calculations like this:

Before 1.2.49 Now
Source.CurrentContext() Context.Value()
Source.SourceContext() Context.Report(), or Context.Visual() for a share of the visual
SetScript(Name) SetQueryVariable(Name)
Divide(A, B) Compute([Query].[A] / [Query].[B])

Then open each visual that uses the calculation, pick it again and save, so the visual takes the new version.

Command reference

Command Short form What it does
Context Con Starts a fetch. A context kind follows it
Value() Val This value only, every filter kept
Row() Row This row, its column let go
Column() Col This column, its row let go
Visual() Vis Everything the visual shows, its grouping let go
Report() Rep What the report allows, this visual's own filters let go
Model() Mod The whole fact, nothing filtered
RemoveDimensionFilter([Dim].[Col], …) RemDimFil Lets go of filters on those columns
KeepDimensionFilter([Dim].[Col], …) KeeDimFil Lets go of every filter except those
FilterContext(condition) FilCon Counts only rows that meet a HU++ condition
PreviousPeriods(n) PrePer The period before, or n periods before
SamePeriodPreviousYears(n) SamPerPreYea The same period last year, or n years back
Rolling(n) Rol The last n periods, this one included
CalendarYearToDate() CalYeaToDat From January 1 to the period's end
FiscalYearToDate() FisYeaToDat From the fiscal year's start to the period's end
QuarterToDate() QuaToDat From the quarter's start to the period's end
FiscalQuarterToDate() FisQuaToDat From the fiscal quarter's start to the period's end
MonthToDate() MonToDat From the month's start to the period's end
AggregateFact(method, column) AggFac Aggregates one column of the fact
Compute(value) Com Does math on kept results
FactCalculation([Alias]) FacCal On a visual: uses one of the fact's calculations
SetQueryVariable(Name) SetQueVar Keeps the step's result under a name
Return() Ret Hands the result to the visual. Once, at the end
Query Que Starts a setting step
SetDate([Dim]) SetDat Sets the date dimension for time commands

PreviousPeriods, SamePeriodPreviousYears and Rolling also take [INFINITY]: everything back to your first date. Rolling always needs a count.

Missing something?

Tell us what you were trying to do and what this guide didn't answer. We update it with every release.

Write to us

What changed in this guide

  • October 30, 2026: First edition, written for ConradBI 1.2.51.