# Gemma Dashboard Patterns & Tips

Gemma-specific chart configuration patterns, workarounds, and operational tips. For the full Lightdash chart type reference (all 9 types: cartesian, pie, table, big_number, funnel, gauge, treemap, map, custom), see the native `developing-in-lightdash` skill.

---

## Axis Labels

Add descriptive axis labels to cartesian charts:

```yaml
eChartsConfig:
  series:
    - type: bar
      # ...
  yAxis:
    - name: "Portionen"
  xAxis:
    - name: "Monat"
```

Use German or English as appropriate for the client (e.g., "Ø Nettopreis (€)", "Netto-Umsatz (€)", "Jahr", "Anteil (%)").

---

## Stacking Modes

| Mode | Effect |
|---|---|
| `stack` | Normal stacked bars (absolute values) |
| `stack100` | 100% normalized stacking (each bar = 100%) |

```yaml
layout:
  stack: stack100
  xField: "explore_date_month"
  yField:
    - "explore_total_count"
```

No table calculations needed for percentage normalization — `stack100` handles it natively.

---

## Data Labels

### Cartesian charts

Add `label` to any series in `eChartsConfig.series[]`:

```yaml
eChartsConfig:
  series:
    - type: bar
      encode:
        xRef: { field: "explore_date_year" }
        yRef: { field: "explore_total_revenue" }
      yAxisIndex: 0
      label:
        show: true
        position: inside    # inside, top, bottom, left, right
```

### Pie/donut charts

```yaml
chartConfig:
  type: pie
  config:
    showDataLabels: true
```

### Controlling decimal places

Add `round: <n>` to the metric or `additionalMetrics` entry:

```yaml
additionalMetrics:
  - name: avg_price
    round: 1              # Controls decimal places in labels and table cells
```

---

## Total Labels on Stacked Bars

To show a total label above each stacked bar, add a table calculation and render it as an invisible line:

```yaml
metricQuery:
  tableCalculations:
    - name: total
      displayName: "Gesamt"
      sql: "${explore.metric_a} + ${explore.metric_b}"

chartConfig:
  config:
    layout:
      stack: stack
      xField: explore_date_year
      yField:
        - explore_metric_a
        - explore_metric_b
        - total                    # Include the table calculation
    eChartsConfig:
      series:
        - type: bar
          stack: my_stack
          encode:
            xRef: { field: explore_date_year }
            yRef: { field: explore_metric_a }
          yAxisIndex: 0
          label: { show: true, position: inside }
        - type: bar
          stack: my_stack
          encode:
            xRef: { field: explore_date_year }
            yRef: { field: explore_metric_b }
          yAxisIndex: 0
          label: { show: true, position: inside }
        - type: line                # Invisible line for total label
          encode:
            xRef: { field: explore_date_year }
            yRef: { field: total }
          yAxisIndex: 0
          showSymbol: false
          lineStyle:
            width: 0
          label:
            show: true
            position: top
```

---

## Additional Metrics (Chart-Level)

Define calculated metrics inline in a chart (not in the dbt semantic layer):

```yaml
metricQuery:
  additionalMetrics:
    - name: avg_net_price_subsidy
      label: "Ø Zuschuss (netto)"
      table: fct_invoice_lines_combined
      sql: "${TABLE}.net_amount_subsidy / NULLIF(${TABLE}.quantity, 0)"
      type: average
      format: '[$€]#,##0.00'
      round: 2
```

Reference in `metrics` as `{table}_{name}` → `fct_invoice_lines_combined_avg_net_price_subsidy`.

Use when a metric is chart-specific and doesn't belong in the dbt semantic layer.

---

## Dimension Colors

Ensure consistent colors across all charts by adding a `colors` mapping in the dbt schema:

```yaml
# In dbt schema.yml, on the dimension's config.meta.dimension
dimension:
  type: string
  colors:
    Schule: "#4E79A7"
    Kita: "#F28E2B"
```

When pivoted, Lightdash uses these colors in every chart — no per-chart configuration needed.

---

## Heading and Markdown Tiles

### Section headers (heading tile)

```yaml
- type: heading
  x: 0
  y: 0
  w: 36
  h: 1
  properties:
    title: "Revenue Overview"
  tabUuid: null
  tileSlug: "revenue-header"
```

### Explanatory notes (markdown tile)

```yaml
- type: markdown
  x: 0
  y: 9
  w: 36
  h: 2
  properties:
    title: "Notes"
    content: "Revenue figures exclude intercompany transactions."
  tabUuid: null
  tileSlug: "notes-section"
```

---

## Dashboard Tabs

Tabs group related content. Tab UUIDs must be valid UUIDs (not friendly names):

```yaml
tabs:
  - uuid: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
    name: "Revenue"
    order: 0
  - uuid: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
    name: "Operations"
    order: 1

tiles:
  - type: saved_chart
    tabUuid: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"   # Assign to tab
    # ...
```

Generate UUIDs with `uuidgen` or `python -c "import uuid; print(uuid.uuid4())"`.

---

## Cross-Explore Filter Targeting

When a dashboard has charts from multiple explores, use `tileTargets` to control which tiles a filter applies to:

```yaml
filters:
  dimensions:
    - id: "company-filter"
      target:
        fieldId: "fct_orders_company_name"
        tableName: "fct_orders"
      operator: "equals"
      values: ["Acme Corp"]
      tileTargets:
        "tile-slug-1":
          fieldId: "fct_orders_company_name"
          tableName: "fct_orders"
        "tile-slug-2":
          fieldId: "dim_companies_company_name"
          tableName: "dim_companies"
```

Without `tileTargets`, filters only apply to tiles from the same explore.

---

## REST API Fallback

When MCP and CLI are unavailable, use the REST API with a Personal Access Token:

```
Authorization: ApiKey <PAT>
```

| Operation | Method | Endpoint |
|---|---|---|
| Create chart | `POST` | `/api/v1/projects/{projectUuid}/saved` |
| Get chart | `GET` | `/api/v1/saved/{chartUuid}` |
| Update chart metadata | `PATCH` | `/api/v1/saved/{chartUuid}` |
| Update chart query/config | `POST` | `/api/v1/saved/{chartUuid}/version` |
| Delete chart | `DELETE` | `/api/v1/saved/{chartUuid}` |
| Get dashboard | `GET` | `/api/v1/dashboards/{dashboardUuid}` |
| Update dashboard | `PATCH` | `/api/v1/dashboards/{dashboardUuid}` |
| List spaces | `GET` | `/api/v1/projects/{projectUuid}/spaces` |
| Get explore fields | `GET` | `/api/v1/projects/{projectUuid}/explores/{exploreName}` |
| Run chart query | `POST` | `/api/v1/saved/{chartUuid}/results` |

Chart updates use a two-step pattern: `PATCH` for metadata, `POST /version` for query/config changes.

---

## Operational Tips

- **API vs YAML drift**: If CI runs `lightdash upload --force`, YAML is the source of truth. API changes are overwritten on next deploy. Always sync API changes to YAML before committing
- **CLI version mismatch**: Pin CLI to match server version: `npm install -g @lightdash/cli@<server-version>`. Check server version via the Lightdash UI footer or `get_version` MCP tool
- **Self-hosted instances**: Use `http://` (not `https://`) if the instance doesn't have TLS. Token-based login avoids interactive prompts: `lightdash login http://<host> --token <PAT>`
- **`round` on additionalMetrics**: Add `round: <n>` to control decimal places — `type: average` can produce many decimals even when SQL uses `ROUND()`
- **Normalize after first upload**: Run `lightdash download` after the first upload to get server-normalized YAML (sorted keys, computed fields). Commit that version to avoid churn
