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).
Computedoes the math:+ - * /,ROUND,COALESCE,CASEand 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
- What DAR-C is
- Before you start
- Your first calculation: margin percent
- How a calculation is built
- Which rows count: contexts
- Changing what a fetch counts
- Doing the math: Compute
- Comparing over time
- Calculations on one visual
- Using the editor
- Aggregations
- Recipes
- When something goes wrong
- 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.
- Open Facts, then your Sales fact.
- Find the DAR-C Calculations group and add a calculation.
- Set Alias to
Margin %. The alias is the name your visuals will see. - Set Format to Percent with 1 decimal place, and tick ×100. A Percent format adds the % sign; ×100 turns 0.42 into 42%.
- Paste this into the first row:
Context.Value().AggregateFact(Sum, Revenue).SetQueryVariable(SalesTotal);
Context.Value().AggregateFact(Sum, Cost).SetQueryVariable(CostTotal);
Compute(([Query].[SalesTotal] - [Query].[CostTotal]) / [Query].[SalesTotal]).Return();- 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.
- Add a Card on the Sales fact and pick Margin % as its measure.
- 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 oneReturn(), 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(...)andFilterContext(...), which is kept exactly as you wrote it. - Capitals do not matter.
context.value()is the same asContext.Value(). - Every command has a short form: the first three letters of each capitalized word.
ContextisCon,AggregateFactisAggFac,SetQueryVariableisSetQueVar. You can mix full and short forms freely. Short forms are for commands only, never for aggregations, columns or names.
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:
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:
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:
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:
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 withLIKE, and join conditions withAND,ORandNOT. - 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.
RemoveDimensionFilternever removes it, and it applies even inContext.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:
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:
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]):
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
ComputeorFilterContextopens 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
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
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
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
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
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
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
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:
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
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 usWhat changed in this guide
- October 30, 2026: First edition, written for ConradBI 1.2.51.
