[Guide] How to create function backed chart with dynamic axis in workshop

Goal: Build and publish a Foundry Function that powers a chart where viewers can choose the X‑axis (category), optional series (segment by), and metric (count or sum) at runtime.


What we will build

  • A TypeScript Function that:

    • Accepts an ObjectSet of a dummy ontology type (e.g., DemoOrder).

    • Lets users pick Category, Segment By, and Aggregate On at render time.

    • Returns a ThreeDimensionalAggregation suitable for bar/column charts (Category × Segment → Value).

  • A chart wired to this Function with dropdown controls for the three parameters.


Prerequisites

  1. An ontology object type with fields you want to bucket by (strings or dates) and fields you want to aggregate (numbers).

Replace imports that reference your org‑specific ontology path with the correct path in your deployment.


Implementation

1) Dynamic Code Example

import {
  Function,
  ThreeDimensionalAggregation,
  Double,
  IAggregatableProperty,
  AggregatablePropertiesForResult,
  IRange,
  Timestamp,
} from "@foundry/functions-api";
import {
  DemoOrder,
  ObjectSet,
} from "@foundry/ontology-api";
import { BucketableProperties } from "@foundry/ontology-api/yourontology/ontology-api/DemoOrder";
import * as FunctionsApi from "@foundry/functions-api";
import {
  IStringPropertyBucketing,
  IDatePropertyBucketing,
} from "@foundry/functions-typescript-object-set-base";


export class Chart {

  // toCamelCase to convert the for e.g. Order Category from dropdown to orderCategory (apiName in onotology manager properties)
  private toCamelCase(input: string) {
    return input
      .replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, function (match, index) {
        if (+match === 0) return ""; // skip spaces
        return index === 0 ? match.toLowerCase() : match.toUpperCase();
      })
      .replace(/\s+/g, "");
  }

  @Function()
  public async variableChart(
    objects: ObjectSet<DemoOrder>,
    categoryValue: string,
    aggregateOn: string,
    segmentByValue: string
  ): Promise<ThreeDimensionalAggregation<string, string, Double>> {
    let categoryProperty = this.toCamelCase(
      categoryValue
    ) as keyof BucketableProperties;
    let segmentByProperty = segmentByValue
      ? (this.toCamelCase(segmentByValue) as keyof BucketableProperties)
      : undefined;
    let aggregateOnProperty = this.toCamelCase(
      aggregateOn
    ) as unknown as keyof AggregatablePropertiesForResult<
      DemoOrder,
      number
    >;

    // Start with the base query
    const baseQuery = objects.groupBy((o: BucketableProperties) =>
      (o[categoryProperty] as IStringPropertyBucketing).topValues()
    );

    // perform segment by operation
    const aggregatedQuery = baseQuery.segmentBy((s) =>
      (s[segmentByProperty!] as IStringPropertyBucketing).topValues()
    );

    // Perform the either count or sum aggregation based on parameter
    const chartData =
      aggregateOn === "Count"
        ? await aggregatedQuery.count()
        : await aggregatedQuery.sum(
            (s) => s[aggregateOnProperty!] as IAggregatableProperty<number>
          );

    return chartData;
  }

  
}


2) Test the Function: Test the function in Code Repo and then commit and tag!

3) Add UI Controls: Add String Selector widget for each parameter in the workshop, then pass the selected variable from the string selector to the function-backed chart widget.

Result


Room for Enhancements

  • Whitelist properties: Instead of free‑form strings + toCamelCase, map user‑facing labels to known property keys.

  • Cardinality control, Null handling, and Performance


Troubleshooting

  • Be mindful of type mismatches (Integer vs. Double).
  • Verify that dropdowns correspond to the actual apiName of the property (see the details section in the properties tab of the object in the ontology manager).
  • For instance, if the apiName of the property is countryName, use “Country Name” in the string selector, as we employ the toCamelCase function to convert “Country Name” back to countryName.

I hope I was clear with the steps :slight_smile:

6 Likes

Definitely fills a gap in documentation! Nice example :clap:

1 Like

Mighty fist-bump :right_facing_fist::left_facing_fist: @arjunsoni for this great post!

I totally feel you - the default widgets like xy-charts and pivot table lack the customizability to allow us a first-class building experience. Falling back to custom solutions like you are providing are unfortunately a necessity :-/

But speaking of custom - have you explored encapsulating this in a custom widget to make it accessible within your org?

To PD: I believe the community would really appreciate you shedding a bit of light on the workshop roadmap. Just to prevent people now starting to rebuild a lot of default widgets. Just to add their required functionality, but maybe learning there work is obsolete by you having the same enhancement on your roadmap!

2 Likes

Thank you for the writeup! We do not have any near term plans to offer additional variable backed configuration options for the XY Chart widget.

For now, function backed chart layers are a catch all for dynamic chart configuration.

We are more likely to invest in the Vega chart widget in the future due to the flexibility it offers. The XY Chart will remain available so if you wish to use it investing in function backed series is worthwhile over waiting for more variable backed config options.

I answered a similar ask for more variable backed config here highlighting that adding variable backed config is not always trivial since it requires us to handle async loading states for values that are currently always statically available.

1 Like

If I google “workshop function backed chart” this post comes up first result, even above our docs!

We do have this section on function backed chart layers, but this guide expands on passing in Workshop variables controlled via other widgets to dynamically adjust the chart layer.

1 Like

Thank you @evanj for sharing some insights into your roadmap :victory_hand:t2: would you share what you have planned for Vega? We are indeed using it very frequently and we are curious what the future might bring :slight_smile:

Nothing to note for now - just that Vega is where our future efforts likely lie!

Since the Vega spec supports many more charting options than what previous Workshop chart widgets were able to offer and we have function backed Vega chart data and spec (via variable) I think we expect the current offering to be quite flexible.

Feel free to open separate threads for feature requests as appropriate!

1 Like