diff --git a/.github/scripts/add-console-tabs.py b/.github/scripts/add-console-tabs.py new file mode 100644 index 0000000000..f638350481 --- /dev/null +++ b/.github/scripts/add-console-tabs.py @@ -0,0 +1,242 @@ +#!/usr/bin/env python3 +""" +Transform API dropdown blocks to include Console + curl tabs. + +Each block of the form: + :::...{dropdown} Create this chart using the API + :applies_to: ... + + + + ```bash + curl ... -d '{ + + }' + ``` + + 1. callout 1 + 2. callout 2 + + For more information, refer to the [Visualizations API](...). + :::... + +Becomes: + :::::::{dropdown} Create this chart using the API + :applies_to: ... + + + + :::::{tab-set} + + :::{tab-item} Console + :sync: api-console + ```console + POST kbn://api/visualizations + { + + } + ``` + + 1. callout 1 + 2. callout 2 + ::: + + :::{tab-item} curl + :sync: api-curl + ```bash + curl ... -d '{ + + }' + ``` + + 1. callout 1 + 2. callout 2 + ::: + + ::::: + + For more information, refer to the [Visualizations API](...). + ::::::: +""" + +import re +import sys +from pathlib import Path + + +DROPDOWN_TITLE = "Create this chart using the API" + +# Matches the opening fence: one or more colons followed by {dropdown} +OPEN_RE = re.compile(r"^(:{3,})\{dropdown\} " + re.escape(DROPDOWN_TITLE) + r"\s*$") + + +def transform_file(path: Path) -> str: + text = path.read_text() + lines = text.splitlines(keepends=True) + + out = [] + i = 0 + while i < len(lines): + line = lines[i] + m = OPEN_RE.match(line) + if not m: + out.append(line) + i += 1 + continue + + # Found a dropdown opening — collect the entire block + open_colons = m.group(1) # e.g. ":::" or "::::" or ":::::" + close_fence = open_colons + "\n" + + block_lines = [line] + i += 1 + while i < len(lines): + block_lines.append(lines[i]) + if lines[i] == close_fence: + i += 1 + break + i += 1 + + transformed = transform_block(block_lines) + out.append(transformed) + + return "".join(out) + + +def transform_block(block_lines: list[str]) -> str: + """Transform a single dropdown block.""" + raw = "".join(block_lines) + + # ---- Extract the :applies_to: option line (may be absent) ---- + applies_re = re.compile(r"^(:applies_to:.*)\n", re.MULTILINE) + applies_m = applies_re.search(raw) + applies_line = (applies_m.group(0) if applies_m else "") + + # ---- Split the block body into: intro | bash block | callout list | api link ---- + # Pattern: optional intro text, ```bash ... ```, numbered list, "For more..." + bash_block_re = re.compile( + r"(```bash\n.*?```\n)", + re.DOTALL, + ) + bash_m = bash_block_re.search(raw) + if not bash_m: + # No bash block found — return unchanged + print(f" WARNING: no bash block found, skipping block", file=sys.stderr) + return raw + + bash_block = bash_m.group(1) + + # ---- Extract JSON from curl -d '{ ... }' ---- + # The payload sits between -d '{\n and \n}' (end of block) + json_body = extract_json_from_curl(bash_block) + if json_body is None: + print(f" WARNING: could not extract JSON from curl block, skipping", file=sys.stderr) + return raw + + # ---- Extract numbered callout list ---- + # Everything between end of bash block and "For more information" + after_bash = raw[bash_m.end():] + for_more_re = re.compile(r"(For more information.*?\n)", re.DOTALL) + for_more_m = for_more_re.search(after_bash) + + if for_more_m: + callout_section = after_bash[: for_more_m.start()] + for_more_line = for_more_m.group(1) + else: + # No "For more" line — treat remaining content (before closing fence) as callouts + callout_section = after_bash + for_more_line = "" + + # ---- Extract the intro text (between :applies_to: / opening line and ```bash) ---- + # intro = everything after the opening line (and optional :applies_to:) up to ```bash + header_end = applies_m.end() if applies_m else raw.index("\n") + 1 + intro = raw[header_end : bash_m.start()] + + # ---- Build the Console JSON block ---- + console_block = f"```console\nPOST kbn://api/visualizations\n{{\n{json_body}\n}}\n```\n" + + # ---- Assemble the new block ---- + new_open = ":::::::" + "{dropdown} " + DROPDOWN_TITLE + "\n" + new_close = ":::::::\n" + tab_set_open = "\n:::::{tab-set}\n" + tab_set_close = "\n:::::\n" + # 4-colon tab-items so that any :::{note} (3 colons) inside content stays nested + console_tab_open = "\n::::{tab-item} Console\n:sync: api-console\n" + console_tab_close = "::::\n" + curl_tab_open = "\n::::{tab-item} curl\n:sync: api-curl\n" + curl_tab_close = "::::\n" + + result = ( + new_open + + applies_line + + intro + + tab_set_open + + console_tab_open + + console_block + + callout_section + + console_tab_close + + curl_tab_open + + bash_block + + callout_section + + curl_tab_close + + tab_set_close + + "\n" + + for_more_line + + new_close + ) + return result + + +def extract_json_from_curl(bash_block: str) -> str | None: + """ + Extract the JSON body from a curl -d '{ ... }' block. + Returns the lines between -d '{ and }' (exclusive), preserving indentation. + """ + # Find -d '{\n + start_re = re.compile(r" -d '\{\n") + start_m = start_re.search(bash_block) + if not start_m: + return None + + rest = bash_block[start_m.end():] + + # The JSON ends at a line that is exactly "}'\n" or "}'\n```" + end_re = re.compile(r"\n\}'(\n|$)") + end_m = end_re.search(rest) + if not end_m: + return None + + json_body = rest[: end_m.start()] + # Convert shell single-quote escapes to literal characters so the Console + # tab contains valid JSON (e.g. shift='\''1w'\'' → shift='1w'). + json_body = json_body.replace("'\\''", "'") + json_body = json_body.replace("'''", "") + return json_body + + +def main(): + import argparse + + parser = argparse.ArgumentParser(description="Add Console tabs to API dropdowns") + parser.add_argument("files", nargs="+", help="Markdown files to transform") + parser.add_argument("--dry-run", action="store_true", help="Print output, don't write") + args = parser.parse_args() + + for filepath in args.files: + path = Path(filepath) + if not path.exists(): + print(f"File not found: {filepath}", file=sys.stderr) + continue + + print(f"Processing {path.name}...", file=sys.stderr) + result = transform_file(path) + + if args.dry_run: + print(result) + else: + path.write_text(result) + print(f" Written: {filepath}", file=sys.stderr) + + +if __name__ == "__main__": + main() diff --git a/.github/scripts/verify-lens-api-examples.py b/.github/scripts/verify-lens-api-examples.py new file mode 100755 index 0000000000..f2583ef948 --- /dev/null +++ b/.github/scripts/verify-lens-api-examples.py @@ -0,0 +1,244 @@ +#!/usr/bin/env python3 +"""Verify that every Lens visualization API example in the chart docs still +creates a working visualization. + +Background +---------- +The chart pages under ``explore-analyze/visualize/charts/`` each embed one or +more API examples that POST to ``/api/visualizations``. The Visualizations +API is in technical preview, so its schema can change between minor versions. +This script extracts every JSON payload from the Console tab of each dropdown, +strips docs-builder ```` callout markers, posts each one to a live Kibana, +asserts HTTP 201, then cleans up by deleting the created visualization. + +Usage +----- +Set ``KIBANA_URL`` and ``API_KEY`` (a Kibana API key with privileges to create +and delete visualizations) in your environment, then run:: + + python3 .github/scripts/verify-lens-api-examples.py + +Optional flags:: + + --keep Do not delete test visualizations after verification. + --file F Only verify a single markdown file (path or basename). + +Failure modes +------------- +The script exits ``0`` on success and non-zero when any payload fails, with +the reason printed to stderr. Failure paths: + +1. **Environment not set** — ``KIBANA_URL`` or ``API_KEY`` missing. +2. **JSON parse error** — a docs edit broke the JSON inside a curl block + (stray comma, unbalanced brace, smart quote). The error includes the + character offset to look at. +3. **API rejected the request (HTTP 4xx/5xx)** — Kibana's response body is + printed verbatim and includes the field path and reason (for example, + ``layers.0.breakdown_by: field 'fields' is required``). +4. **Non-201 success status** — the request didn't error but didn't return + 201. Rare; usually a redirect or proxy quirk. +""" + +from __future__ import annotations + +import argparse +import json +import os +import re +import ssl +import sys +import urllib.error +import urllib.request +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[2] +CHARTS_DIR = REPO_ROOT / "explore-analyze" / "visualize" / "charts" +CHART_FILES = [ + "area-charts.md", + "bar-charts.md", + "gauge-charts.md", + "heat-map-charts.md", + "line-charts.md", + "metric-charts.md", + "mosaic-charts.md", + "pie-charts.md", + "region-map-charts.md", + "tables.md", + "tag-cloud-charts.md", + "treemap-charts.md", + "waffle-charts.md", +] + + +def extract_payloads(markdown_path: Path) -> list[tuple[str, dict]]: + """Return (title, parsed_payload) for every Console tab block in the file. + + Extraction strategy + ------------------- + Each API dropdown now contains a ``console`` code fence of the form:: + + ```console + POST kbn://api/visualizations + { + ...JSON... + } + ``` + + We locate every such block, strip ```` callout markers, then + JSON-parse the body. No shell-unescaping is needed because the Console + tab carries plain JSON (unlike the curl tab which uses single-quote + shell escaping). + """ + text = markdown_path.read_text(encoding="utf-8") + results = [] + + pattern = re.compile( + r"```console\nPOST kbn://api/visualizations\n(\{.*?\n\})\n```", + re.DOTALL, + ) + + for match in pattern.finditer(text): + raw = match.group(1) + # Strip callout markers e.g. <1> <2> + cleaned = re.sub(r"\s*<\d+>", "", raw) + + try: + payload = json.loads(cleaned) + except json.JSONDecodeError as exc: + context_start = max(0, exc.pos - 120) + context_end = min(len(cleaned), exc.pos + 120) + snippet = cleaned[context_start:context_end].replace("\n", "↵") + print( + f" ✗ JSON parse error in {markdown_path.name}: {exc}\n" + f" ...{snippet}...", + file=sys.stderr, + ) + continue + + title = payload.get("title", "(untitled)") + results.append((title, payload)) + + return results + + +def post_visualization(kibana_url: str, api_key: str, payload: dict) -> str: + """POST payload to /api/visualizations, return the new visualization ID.""" + body = json.dumps(payload).encode("utf-8") + req = urllib.request.Request( + f"{kibana_url.rstrip('/')}/api/visualizations", + data=body, + method="POST", + headers={ + "Authorization": f"ApiKey {api_key}", + "kbn-xsrf": "true", + "Content-Type": "application/json", + }, + ) + ctx = ssl.create_default_context() + try: + with urllib.request.urlopen(req, context=ctx) as resp: + status = resp.status + response_body = resp.read().decode("utf-8") + except urllib.error.HTTPError as exc: + # Failure mode 3: API rejected the payload. + detail = exc.read().decode("utf-8", errors="replace") + raise RuntimeError( + f"POST /api/visualizations HTTP {exc.code}: {detail}" + ) from exc + + if status != 201: + # Failure mode 4: unexpected success status. + raise RuntimeError(f"Unexpected status {status}: {response_body}") + + data = json.loads(response_body) + return data.get("id") or data.get("data", {}).get("id", "") + + +def delete_visualization(kibana_url: str, api_key: str, viz_id: str) -> None: + req = urllib.request.Request( + f"{kibana_url.rstrip('/')}/api/visualizations/{viz_id}", + method="DELETE", + headers={ + "Authorization": f"ApiKey {api_key}", + "kbn-xsrf": "true", + }, + ) + ctx = ssl.create_default_context() + try: + urllib.request.urlopen(req, context=ssl.create_default_context()) + except urllib.error.HTTPError as exc: + print( + f" Warning: cleanup DELETE failed with HTTP {exc.code}", + file=sys.stderr, + ) + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--keep", + action="store_true", + help="Do not delete test visualizations after verification.", + ) + parser.add_argument( + "--file", + help="Only verify a specific markdown file (path or basename).", + ) + args = parser.parse_args() + + kibana_url = os.environ.get("KIBANA_URL") + api_key = os.environ.get("API_KEY") + if not kibana_url or not api_key: + # Failure mode 1. + raise SystemExit( + "KIBANA_URL and API_KEY must be set in the environment.\n" + "Example:\n" + " export KIBANA_URL=https://my-kibana.example.com\n" + " export API_KEY=" + ) + + if args.file: + p = Path(args.file) + files = [p if p.is_absolute() else CHARTS_DIR / p.name] + else: + files = [CHARTS_DIR / f for f in CHART_FILES] + + total = passed = failed = 0 + + for md_file in files: + if not md_file.exists(): + print(f"⚠ File not found: {md_file}", file=sys.stderr) + continue + + payloads = extract_payloads(md_file) + if not payloads: + print(f"{md_file.name}: no payloads found — skipping") + continue + + print(f"\n{md_file.name} ({len(payloads)} payload{'s' if len(payloads) != 1 else ''})") + + for title, payload in payloads: + total += 1 + try: + viz_id = post_visualization(kibana_url, api_key, payload) + if not args.keep and viz_id: + delete_visualization(kibana_url, api_key, viz_id) + status_str = "(kept)" if args.keep else "(deleted)" + print(f" ✓ {title} {status_str}") + passed += 1 + except RuntimeError as exc: + print(f" ✗ {title}\n {exc}", file=sys.stderr) + failed += 1 + + print(f"\n{'─' * 50}") + print(f"Results: {passed}/{total} passed", end="") + if failed: + print(f", {failed} FAILED") + else: + print(" — all OK") + + return 0 if failed == 0 else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/explore-analyze/visualize/charts/area-charts.md b/explore-analyze/visualize/charts/area-charts.md index 3c146e107e..e3cd8774ff 100644 --- a/explore-analyze/visualize/charts/area-charts.md +++ b/explore-analyze/visualize/charts/area-charts.md @@ -112,9 +112,9 @@ In Area charts, you can enable time shift to compare different periods and ident 4. Optionally, customize the appearance of the layer to adjust how it looks on the chart. When you duplicate a layer, {{kib}} automatically assigns a different **Series color** to the new layer. You can for example change this color, or adjust the layer's name and axis position. This name is used for the chart's legend. ::::{tip} -You can also compute the relative change using a formula, for example: +You can also compute the relative change using a formula, for example: `(average(bytes) - average(bytes, shift='1w')) / average(bytes, shift='1w')` -:::: +:::: ## Area chart settings [area-chart-settings] @@ -203,28 +203,337 @@ When creating or editing a visualization, you can adjust the following settings. ## Area chart examples + + **Traffic by geographic region** : Visualizing which geographic regions generate the most traffic: - **Horizontal axis**: `@timestamp` (Date histogram) - **Vertical axis**: `records` - **Breakdown**: `geo.dest` - ![Example Lens area chart geographical regions](../../images/kibana-area-geo-regions.png " =70%") +![Example Lens area chart geographical regions](../../images/kibana-area-geo-regions.png " =70%") + +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +Send the following request to create a stacked area chart that shows record counts broken down by geographic destination. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "xy", + "title": "Traffic by geographic region", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "auto", "placement": "outside", "position": "right" }, + "axis": {}, + "layers": [ + { + "type": "area_stacked", <1> + "x": { "operation": "date_histogram", "field": "timestamp", "include_empty_rows": true }, <2> + "y": [ + { + "operation": "count", + "empty_as_null": false, + "label": "Records", + "format": { "type": "number", "decimals": 0 }, + "filter": { "expression": "" } + } + ], + "breakdown_by": { + "operation": "terms", + "fields": ["geo.dest"], + "limit": 3, <3> + "other_bucket": { "include_documents_without_field": false }, <4> + "rank_by": { "type": "metric", "metric_index": 0, "direction": "desc" }, + "aggregate_first": true <5> + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + } + } + ], + "styling": { + "fitting": { "type": "none" }, + "areas": { "fill_opacity": 0.3 }, + "points": { "visibility": "visible" } + } +} +``` + +1. Uses `area_stacked` to show each region's contribution to the total traffic. +2. `include_empty_rows: true` fills time buckets with no data as zero rather than leaving gaps. +3. Shows only the top 3 geographic destinations. +4. `other_bucket` groups all remaining destinations into an **Other** segment. +5. `aggregate_first: true` ranks destinations globally across the full time range, ensuring the same top 3 appear consistently in every bucket. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "xy", + "title": "Traffic by geographic region", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "auto", "placement": "outside", "position": "right" }, + "axis": {}, + "layers": [ + { + "type": "area_stacked", <1> + "x": { "operation": "date_histogram", "field": "timestamp", "include_empty_rows": true }, <2> + "y": [ + { + "operation": "count", + "empty_as_null": false, + "label": "Records", + "format": { "type": "number", "decimals": 0 }, + "filter": { "expression": "" } + } + ], + "breakdown_by": { + "operation": "terms", + "fields": ["geo.dest"], + "limit": 3, <3> + "other_bucket": { "include_documents_without_field": false }, <4> + "rank_by": { "type": "metric", "metric_index": 0, "direction": "desc" }, + "aggregate_first": true <5> + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + } + } + ], + "styling": { + "fitting": { "type": "none" }, + "areas": { "fill_opacity": 0.3 }, + "points": { "visibility": "visible" } + } +}' +``` + +1. Uses `area_stacked` to show each region's contribution to the total traffic. +2. `include_empty_rows: true` fills time buckets with no data as zero rather than leaving gaps. +3. Shows only the top 3 geographic destinations. +4. `other_bucket` groups all remaining destinations into an **Other** segment. +5. `aggregate_first: true` ranks destinations globally across the full time range, ensuring the same top 3 appear consistently in every bucket. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: **Response code over time with annotations** -: Visualizing HTTP response codes over time, highlighting the proportion of success, client error, and server error responses, with annotations for key events: +Visualizing HTTP response codes over time, highlighting the proportion of success, client error, and server error responses, with annotations for key events: * **Horizontal axis**: `@timestamp` (Date histogram) * **Vertical axis**: `Count of records` - * **Breakdown**: - * **Success/Redirection**`response.keyword >= 200 and response.keyword < 400` - * **Client Error**`response.keyword >= 400 and response.keyword < 500` - * **Server Error**`response.keyword >= 500` - * **Stacking**: `Percentage` to show the distribution relative to the total count at each point in time. - * **Annotation Query**: `tags:error AND tags:security` - - ![Example Lens area chart response code annotations](../../images/kibana-response-code-annotations.png " =70%") +* **Breakdown**: + * **Success/Redirection**: `response.keyword >= 200 and response.keyword < 400` + * **Client Error**: `response.keyword >= 400 and response.keyword < 500` + * **Server Error**: `response.keyword >= 500` +* **Stacking**: `Percentage` to show the distribution relative to the total count at each point in time. +* **Annotation query**: `tags:error AND tags:security` + +![Example Lens area chart response code annotations](../../images/kibana-response-code-annotations.png " =70%") + +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +Send the following request to create a percentage area chart that shows the distribution of HTTP response codes over time. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "xy", + "title": "Response code over time", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "auto", "placement": "outside", "position": "bottom", "layout": { "type": "list" } }, + "axis": {}, + "layers": [ + { + "type": "area_percentage", <1> + "x": { + "operation": "date_histogram", + "field": "timestamp" + }, + "y": [ + { + "operation": "count", + "empty_as_null": false, + "label": "Count of records", + "format": { "type": "number", "decimals": 2 }, + "filter": { "expression": "" } + } + ], + "breakdown_by": { + "operation": "filters", <2> + "filters": [ + { + "filter": { "expression": "response.keyword >= 200 and response.keyword < 400" }, + "label": "Success/Redirection" + }, + { + "filter": { "expression": "response.keyword >= 400 and response.keyword < 500" }, + "label": "Client Error" + }, + { "filter": { "expression": "response.keyword >= 500" }, "label": "Server Error" } + ] + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + } + }, + { + "type": "annotations", <3> + "ignore_global_filters": true, + "events": [ + { + "type": "query", + "label": "Event", + "query": { "language": "kql", "expression": "tags:error AND tags:security" }, + "time_field": "timestamp", + "color": { "type": "static", "color": "#ee72a6" }, + "visible": true, + "icon": "asterisk" + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + } + } + ], + "styling": { + "fitting": { "type": "none" }, + "areas": { "fill_opacity": 0.3 } + } +} +``` + +1. `area_percentage` normalizes each time bucket to 100% so you see the proportion of each response class, not absolute counts. +2. `filters` breakdown creates one area per KQL filter instead of splitting by field values. +3. The `annotations` layer marks events matching `tags:error AND tags:security` on the time axis with a pink asterisk. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "xy", + "title": "Response code over time", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "auto", "placement": "outside", "position": "bottom", "layout": { "type": "list" } }, + "axis": {}, + "layers": [ + { + "type": "area_percentage", <1> + "x": { + "operation": "date_histogram", + "field": "timestamp" + }, + "y": [ + { + "operation": "count", + "empty_as_null": false, + "label": "Count of records", + "format": { "type": "number", "decimals": 2 }, + "filter": { "expression": "" } + } + ], + "breakdown_by": { + "operation": "filters", <2> + "filters": [ + { + "filter": { "expression": "response.keyword >= 200 and response.keyword < 400" }, + "label": "Success/Redirection" + }, + { + "filter": { "expression": "response.keyword >= 400 and response.keyword < 500" }, + "label": "Client Error" + }, + { "filter": { "expression": "response.keyword >= 500" }, "label": "Server Error" } + ] + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + } + }, + { + "type": "annotations", <3> + "ignore_global_filters": true, + "events": [ + { + "type": "query", + "label": "Event", + "query": { "language": "kql", "expression": "tags:error AND tags:security" }, + "time_field": "timestamp", + "color": { "type": "static", "color": "#ee72a6" }, + "visible": true, + "icon": "asterisk" + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + } + } + ], + "styling": { + "fitting": { "type": "none" }, + "areas": { "fill_opacity": 0.3 } + } +}' +``` + +1. `area_percentage` normalizes each time bucket to 100% so you see the proportion of each response class, not absolute counts. +2. `filters` breakdown creates one area per KQL filter instead of splitting by field values. +3. The `annotations` layer marks events matching `tags:error AND tags:security` on the time axis with a pink asterisk. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: diff --git a/explore-analyze/visualize/charts/bar-charts.md b/explore-analyze/visualize/charts/bar-charts.md index cde7d5b838..9708ece3b1 100644 --- a/explore-analyze/visualize/charts/bar-charts.md +++ b/explore-analyze/visualize/charts/bar-charts.md @@ -110,6 +110,118 @@ To create a stacked bar chart: ![Bar chart with stacking](../../images/stacked-bar-chart.png "=70%") +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example creates a stacked bar chart that counts log entries over time and breaks them down by HTTP response code. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "xy", + "title": "Stacked bar chart", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "visible", "placement": "outside", "position": "right" }, + "axis": {}, + "layers": [ + { + "type": "bar_stacked", <1> + "x": { "operation": "date_histogram", "field": "timestamp" }, + "y": [ + { + "operation": "count", + "empty_as_null": true, + "format": { "type": "number" } + } + ], + "breakdown_by": { <2> + "operation": "terms", + "fields": ["response.keyword"], + "limit": 3, + "other_bucket": { "include_documents_without_field": false }, <3> + "rank_by": { "type": "metric", "metric_index": 0, "direction": "desc" } + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + } + } + ], + "styling": { + "fitting": { "type": "none" } + } +} +``` + +1. `bar_stacked` renders bars with colored segments stacked on top of each other, showing both the total and the contribution of each category. +2. `breakdown_by` splits each bar into segments by the top 3 HTTP response codes, ranked by document count. +3. `other_bucket` groups any remaining response codes beyond the top 3 into an **Other** segment. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "xy", + "title": "Stacked bar chart", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "visible", "placement": "outside", "position": "right" }, + "axis": {}, + "layers": [ + { + "type": "bar_stacked", <1> + "x": { "operation": "date_histogram", "field": "timestamp" }, + "y": [ + { + "operation": "count", + "empty_as_null": true, + "format": { "type": "number" } + } + ], + "breakdown_by": { <2> + "operation": "terms", + "fields": ["response.keyword"], + "limit": 3, + "other_bucket": { "include_documents_without_field": false }, <3> + "rank_by": { "type": "metric", "metric_index": 0, "direction": "desc" } + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + } + } + ], + "styling": { + "fitting": { "type": "none" } + } +}' +``` + +1. `bar_stacked` renders bars with colored segments stacked on top of each other, showing both the total and the contribution of each category. +2. `breakdown_by` splits each bar into segments by the top 3 HTTP response codes, ranked by document count. +3. `other_bucket` groups any remaining response codes beyond the top 3 into an **Other** segment. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: + ### Create unstacked (side-by-side) bar charts [grouped-bars] Unstacked bar charts display multiple bars side by side for each category, allowing you to compare different metrics or time periods. @@ -128,6 +240,116 @@ To create an unstacked bar chart: ![Bar chart without stacking showing breakdown](../../images/unstacked-bar-chart.png "=70%") +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example creates an unstacked bar chart where each breakdown category renders as a separate bar placed side by side, making individual values straightforward to compare. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "xy", + "title": "Unstacked bar chart", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "visible", "placement": "outside", "position": "right" }, + "axis": {}, + "layers": [ + { + "type": "bar", <1> + "x": { "operation": "date_histogram", "field": "timestamp" }, + "y": [ + { + "operation": "count", + "empty_as_null": true, + "format": { "type": "number" } + } + ], + "breakdown_by": { <2> + "operation": "terms", + "fields": ["response.keyword"], + "limit": 3, + "other_bucket": { "include_documents_without_field": false }, + "rank_by": { "type": "metric", "metric_index": 0, "direction": "desc" } + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + } + } + ], + "styling": { + "fitting": { "type": "none" } + } +} +``` + +1. `bar` (instead of `bar_stacked`) places each category's bar side by side for direct comparison. +2. `breakdown_by` creates a separate bar for each of the top 3 HTTP response codes within every time bucket. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "xy", + "title": "Unstacked bar chart", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "visible", "placement": "outside", "position": "right" }, + "axis": {}, + "layers": [ + { + "type": "bar", <1> + "x": { "operation": "date_histogram", "field": "timestamp" }, + "y": [ + { + "operation": "count", + "empty_as_null": true, + "format": { "type": "number" } + } + ], + "breakdown_by": { <2> + "operation": "terms", + "fields": ["response.keyword"], + "limit": 3, + "other_bucket": { "include_documents_without_field": false }, + "rank_by": { "type": "metric", "metric_index": 0, "direction": "desc" } + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + } + } + ], + "styling": { + "fitting": { "type": "none" } + } +}' +``` + +1. `bar` (instead of `bar_stacked`) places each category's bar side by side for direct comparison. +2. `breakdown_by` creates a separate bar for each of the top 3 HTTP response codes within every time bucket. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: + ## Bar chart settings [settings] Customize your bar chart to display exactly the information you need, formatted the way you want. @@ -265,6 +487,11 @@ Configure elements of your bar chart's legend. Configurable options include: ## Bar chart examples + + The following examples show various configuration options that you can use for building impactful bar charts. @@ -284,12 +511,124 @@ The following examples show various configuration options that you can use for b ![Stacked bar chart showing traffic per week broken down per region](/explore-analyze/images/weekly-website-traffic-per-region.png "=70%") +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example creates a stacked bar chart that tracks page views over time with a custom metric label and breaks them down by the top 9 destination regions. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "xy", + "title": "Weekly website traffic per region", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "auto" }, + "axis": {}, + "layers": [ + { + "type": "bar_stacked", + "x": { "operation": "date_histogram", "field": "timestamp", "suggested_interval": "1w" }, <1> + "y": [ + { + "operation": "count", + "label": "Page Views", <2> + "format": { "type": "number" }, + "filter": { "expression": "" } + } + ], + "breakdown_by": { + "operation": "terms", + "fields": ["geo.dest"], + "limit": 9, <3> + "other_bucket": { "include_documents_without_field": false }, <4> + "rank_by": { "type": "metric", "metric_index": 0, "direction": "desc" } + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + } + } + ], + "styling": { "fitting": { "type": "none" } } +} +``` + +1. `suggested_interval: "1w"` sets each bar to represent one week of data. +2. `label` overrides the default axis label so the vertical axis reads "Page Views" instead of "Count." +3. `limit: 9` shows the top 9 regions, giving a broader geographic breakdown than the default 5. +4. `other_bucket` groups remaining regions into an **Other** segment so the total is always accounted for. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "xy", + "title": "Weekly website traffic per region", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "auto" }, + "axis": {}, + "layers": [ + { + "type": "bar_stacked", + "x": { "operation": "date_histogram", "field": "timestamp", "suggested_interval": "1w" }, <1> + "y": [ + { + "operation": "count", + "label": "Page Views", <2> + "format": { "type": "number" }, + "filter": { "expression": "" } + } + ], + "breakdown_by": { + "operation": "terms", + "fields": ["geo.dest"], + "limit": 9, <3> + "other_bucket": { "include_documents_without_field": false }, <4> + "rank_by": { "type": "metric", "metric_index": 0, "direction": "desc" } + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + } + } + ], + "styling": { "fitting": { "type": "none" } } +}' +``` + +1. `suggested_interval: "1w"` sets each bar to represent one week of data. +2. `label` overrides the default axis label so the vertical axis reads "Page Views" instead of "Count." +3. `limit: 9` shows the top 9 regions, giving a broader geographic breakdown than the default 5. +4. `other_bucket` groups remaining regions into an **Other** segment so the total is always accounted for. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: + **Request error rate per host (with threshold)** : Monitor error rates across hosts with a target threshold line: * **Title**: "Request error rate per host" - * **Vertical axis**: `count(kql='response > "300"') / count()` - * **Name**: "Error Rate %" + * **Vertical axis**: `count(kql='response.keyword > "300"') / count()` + * **Name**: "Error rate" * **Value format**: `Percent` * **Horizontal axis**: `terms(service.name)` * **Name**: "Hosts" @@ -300,4 +639,154 @@ The following examples show various configuration options that you can use for b * **Color**: Red, dashed line * **Layout**: Horizontal orientation (for better service name readability) -![Bar chart with reference line showing traffic per week broken down per region](/explore-analyze/images/request-error-rate-per-host.png "=70%") \ No newline at end of file +![Bar chart with reference line showing traffic per week broken down per region](/explore-analyze/images/request-error-rate-per-host.png "=70%") + +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example creates a horizontal bar chart with a formula-based metric and a reference line layer that marks the acceptable error threshold. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "xy", + "title": "Request error rate per host", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "auto" }, + "axis": {}, + "layers": [ + { + "type": "bar_horizontal", <1> + "x": { + "operation": "terms", + "fields": ["host.keyword"], + "limit": 4, + "label": "Hosts", + "rank_by": { "type": "alphabetical", "direction": "asc" } <2> + }, + "y": [ + { + "operation": "formula", <3> + "formula": "count(kql='response.keyword > \"300\"') / count()", + "label": "Error rate", + "format": { "type": "percent" }, + "filter": { "expression": "" } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + } + }, + { + "type": "reference_lines", <4> + "thresholds": [ + { + "operation": "static_value", + "value": 0.1, + "format": { "type": "percent" }, + "label": "Maximum acceptable error rate", + "color": { "type": "static", "color": "#BD271E" }, + "stroke_dash": "dashed", + "text": { "visible": true } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + } + } + ], + "styling": { "fitting": { "type": "none" } } +} +``` + +1. `bar_horizontal` renders bars horizontally, giving more room for long host names. +2. `rank_by: "alphabetical"` sorts the hosts alphabetically so the order is consistent regardless of error rate. +3. `formula` computes the error rate as the ratio of responses above 300 to total requests. `response.keyword` is stored as a string in the sample data. +4. A `reference_lines` layer draws a threshold at 10% so hosts exceeding it are immediately visible. `stroke_dash: "dashed"` and `color` mark it red and dashed. `text.visible: true` displays the label on the line. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "xy", + "title": "Request error rate per host", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "auto" }, + "axis": {}, + "layers": [ + { + "type": "bar_horizontal", <1> + "x": { + "operation": "terms", + "fields": ["host.keyword"], + "limit": 4, + "label": "Hosts", + "rank_by": { "type": "alphabetical", "direction": "asc" } <2> + }, + "y": [ + { + "operation": "formula", <3> + "formula": "count(kql='response.keyword > \"300\"') / count()", + "label": "Error rate", + "format": { "type": "percent" }, + "filter": { "expression": "" } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + } + }, + { + "type": "reference_lines", <4> + "thresholds": [ + { + "operation": "static_value", + "value": 0.1, + "format": { "type": "percent" }, + "label": "Maximum acceptable error rate", + "color": { "type": "static", "color": "#BD271E" }, + "stroke_dash": "dashed", + "text": { "visible": true } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + } + } + ], + "styling": { "fitting": { "type": "none" } } +}' +``` + +1. `bar_horizontal` renders bars horizontally, giving more room for long host names. +2. `rank_by: "alphabetical"` sorts the hosts alphabetically so the order is consistent regardless of error rate. +3. `formula` computes the error rate as the ratio of responses above 300 to total requests. `response.keyword` is stored as a string in the sample data. +4. A `reference_lines` layer draws a threshold at 10% so hosts exceeding it are immediately visible. `stroke_dash: "dashed"` and `color` mark it red and dashed. `text.visible: true` displays the label on the line. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: diff --git a/explore-analyze/visualize/charts/gauge-charts.md b/explore-analyze/visualize/charts/gauge-charts.md index f3ad024ac7..ebc6f69fad 100644 --- a/explore-analyze/visualize/charts/gauge-charts.md +++ b/explore-analyze/visualize/charts/gauge-charts.md @@ -91,6 +91,112 @@ Use a gauge to track progress toward a specific target, such as monthly sales go ![Example Lens gauge chart showing yearly sales goal](/explore-analyze/images/gauge-chart-scenario-goal.png "=75%") +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example creates a horizontal bullet gauge that sums order revenue from the eCommerce sample data, providing a quick view of progress toward a sales goal. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "gauge", + "title": "Yearly sales goal", + "filters": [], + "query": { "expression": "" }, + "styling": { + "shape": { "type": "bullet", "orientation": "horizontal" } <1> + }, + "metric": { + "operation": "sum", <2> + "field": "products.taxful_price", + "empty_as_null": true, + "label": "Yearly sales", + "max": { "operation": "static_value", "value": 1500000 }, <3> + "goal": { "operation": "static_value", "value": 1000000 }, + "title": { "visible": true }, + "ticks": { "visible": true, "mode": "bands" }, + "color": { + "type": "dynamic", + "range": "absolute", + "steps": [ + { "gte": 0, "lt": 750000, "color": "#f6726a" }, + { "gte": 750000, "lt": 1000000, "color": "#aee8d2" }, + { "gte": 1000000, "color": "#24c292" } + ] + } + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_ecommerce", + "time_field": "order_date" + } +} +``` + +1. `bullet` with `orientation: "horizontal"` renders a compact linear bar gauge, ideal for placing multiple KPIs in a row. +2. `sum` of `products.taxful_price` tracks cumulative revenue as the gauge metric. +3. `max` sets the upper bound of the scale (1.5M) and `goal` places a target marker at 1M, so progress toward the goal is immediately visible. The three `steps` color bands turn the bar red below 750K, light green approaching the goal, and green once it's reached. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "gauge", + "title": "Yearly sales goal", + "filters": [], + "query": { "expression": "" }, + "styling": { + "shape": { "type": "bullet", "orientation": "horizontal" } <1> + }, + "metric": { + "operation": "sum", <2> + "field": "products.taxful_price", + "empty_as_null": true, + "label": "Yearly sales", + "max": { "operation": "static_value", "value": 1500000 }, <3> + "goal": { "operation": "static_value", "value": 1000000 }, + "title": { "visible": true }, + "ticks": { "visible": true, "mode": "bands" }, + "color": { + "type": "dynamic", + "range": "absolute", + "steps": [ + { "gte": 0, "lt": 750000, "color": "#f6726a" }, + { "gte": 750000, "lt": 1000000, "color": "#aee8d2" }, + { "gte": 1000000, "color": "#24c292" } + ] + } + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_ecommerce", + "time_field": "order_date" + } +}' +``` + +1. `bullet` with `orientation: "horizontal"` renders a compact linear bar gauge, ideal for placing multiple KPIs in a row. +2. `sum` of `products.taxful_price` tracks cumulative revenue as the gauge metric. +3. `max` sets the upper bound of the scale (1.5M) and `goal` places a target marker at 1M, so progress toward the goal is immediately visible. The three `steps` color bands turn the bar red below 750K, light green approaching the goal, and green once it's reached. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: + ### Configure color bands for thresholds [color-bands] Color bands help users quickly understand whether a value is within acceptable ranges. @@ -113,6 +219,106 @@ This example shows a gauge with server response time and color-coded health indi ![Example Lens gauge chart showing average response time in milliseconds](/explore-analyze/images/gauge-chart-scenario-thresholds.png "=50%") +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example creates a gauge with three color-coded threshold bands so the arc turns green, yellow, or red depending on the average byte count. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "gauge", + "title": "Server response time", + "filters": [], + "query": { "expression": "" }, + "styling": { "shape": { "type": "bullet", "orientation": "horizontal" } }, + "metric": { + "operation": "median", + "field": "memory", + "label": "Average response time - last hour", + "format": { "type": "duration", "from": "microseconds", "to": "asMilliseconds" }, + "min": { "operation": "static_value", "value": 0 }, + "max": { "operation": "formula", "formula": "1000000" }, + "title": { "visible": true }, + "ticks": { "visible": true, "mode": "bands" }, + "color": { <1> + "type": "dynamic", + "range": "absolute", + "steps": [ <2> + { "color": "#24c292", "gte": 0, "lt": 200000 }, + { "color": "#aee8d2", "gte": 200000, "lt": 500000 }, + { "color": "#f6726a", "gte": 500000 } + ] + } + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + } +} +``` + +1. `color.type: "dynamic"` applies color bands based on the metric value, giving instant visual feedback on health status. +2. Each `steps` entry defines a range and color: green for healthy (0–200ms), light green for warning (200–500ms), and red for critical (500ms+). The `duration` format converts raw microsecond values to human-readable milliseconds. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "gauge", + "title": "Server response time", + "filters": [], + "query": { "expression": "" }, + "styling": { "shape": { "type": "bullet", "orientation": "horizontal" } }, + "metric": { + "operation": "median", + "field": "memory", + "label": "Average response time - last hour", + "format": { "type": "duration", "from": "microseconds", "to": "asMilliseconds" }, + "min": { "operation": "static_value", "value": 0 }, + "max": { "operation": "formula", "formula": "1000000" }, + "title": { "visible": true }, + "ticks": { "visible": true, "mode": "bands" }, + "color": { <1> + "type": "dynamic", + "range": "absolute", + "steps": [ <2> + { "color": "#24c292", "gte": 0, "lt": 200000 }, + { "color": "#aee8d2", "gte": 200000, "lt": 500000 }, + { "color": "#f6726a", "gte": 500000 } + ] + } + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + } +}' +``` + +1. `color.type: "dynamic"` applies color bands based on the metric value, giving instant visual feedback on health status. +2. Each `steps` entry defines a range and color: green for healthy (0–200ms), light green for warning (200–500ms), and red for critical (500ms+). The `duration` format converts raw microsecond values to human-readable milliseconds. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: + ### Use dynamic bounds and goals [dynamic-bounds] Instead of entering fixed static values, you can use fields from your data to set the minimum, maximum, or goal dynamically using aggregations. @@ -215,26 +421,233 @@ When creating or editing a visualization, you can customize several appearance o ## Gauge chart examples + + The following examples show various configuration options for building impactful gauge charts. **CPU usage monitoring** : Monitor system CPU usage with threshold-based coloring: - * Example based on: System metrics data - * **Metric**: `Average(system.cpu.total.pct)` formatted as percent - * **Shape**: Minor arc - * **Minimum**: 0, **Maximum**: 100 - * **Color bands**: 0-50% (green), 50-75% (yellow), 75-100% (red) + * Example based on: {{kib}} Sample Data Logs + * **Metric**: Formula `average(machine.ram)/20000000000` formatted as percent + * **Maximum**: Formula `max(machine.ram)/32211000000` + * **Shape**: Semi-circle + * **Color bands**: 0–50% (green), 50–75% (yellow), 75%+ (red) ![Example Lens gauge chart showing average CPU usage in percent](/explore-analyze/images/gauge-chart-example-cpu.png "=50%") +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example creates a CPU-monitoring gauge with green/yellow/red threshold bands, using `machine.ram` from the logs sample data as a proxy for a CPU-like metric. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "gauge", + "title": "CPU usage", + "filters": [], + "query": { "expression": "" }, + "styling": { "shape": { "type": "semi_circle" } }, + "metric": { + "operation": "formula", <1> + "formula": "average(machine.ram)/20000000000", + "label": "CPU usage", + "format": { "type": "percent", "decimals": 2, "compact": false }, + "max": { "operation": "formula", "formula": "max(machine.ram)/32211000000" }, + "title": { "visible": true }, + "ticks": { "visible": true, "mode": "bands" }, + "color": { + "type": "dynamic", + "range": "percentage", + "steps": [ <2> + { "gte": 0, "lt": 50, "color": "#24c292" }, + { "gte": 50, "lt": 75, "color": "#EAAE01" }, + { "gte": 75, "color": "#f6726a" } + ] + } + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + } +} +``` + +1. `formula` computes the ratio of average RAM to a reference value, producing a 0–1 ratio formatted as a percentage. Replace with your actual CPU metric field (for example, `average(system.cpu.total.pct)`). +2. `range: "percentage"` maps the color thresholds proportionally across the gauge's 0–100% scale: green below 50%, yellow between 50–75%, and red above 75%. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "gauge", + "title": "CPU usage", + "filters": [], + "query": { "expression": "" }, + "styling": { "shape": { "type": "semi_circle" } }, + "metric": { + "operation": "formula", <1> + "formula": "average(machine.ram)/20000000000", + "label": "CPU usage", + "format": { "type": "percent", "decimals": 2, "compact": false }, + "max": { "operation": "formula", "formula": "max(machine.ram)/32211000000" }, + "title": { "visible": true }, + "ticks": { "visible": true, "mode": "bands" }, + "color": { + "type": "dynamic", + "range": "percentage", + "steps": [ <2> + { "gte": 0, "lt": 50, "color": "#24c292" }, + { "gte": 50, "lt": 75, "color": "#EAAE01" }, + { "gte": 75, "color": "#f6726a" } + ] + } + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + } +}' +``` + +1. `formula` computes the ratio of average RAM to a reference value, producing a 0–1 ratio formatted as a percentage. Replace with your actual CPU metric field (for example, `average(system.cpu.total.pct)`). +2. `range: "percentage"` maps the color thresholds proportionally across the gauge's 0–100% scale: green below 50%, yellow between 50–75%, and red above 75%. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: + **Disk space utilization** : Display disk space usage as a percentage of capacity: - * Example based on: System metrics data - * **Metric**: Formula `sum(system.filesystem.used.bytes) / sum(system.filesystem.total.bytes) * 100` + * Example based on: {{kib}} Sample Data Logs + * **Metric**: Formula `average(machine.ram)/29000000000` formatted as percent + * **Maximum**: Formula `max(machine.ram)/32211000000` * **Shape**: Circle - * **Minimum**: 0, **Maximum**: 100 - * **Color bands**: 0-60% (green), 60-80% (yellow), 80-100% (red) + * **Color bands**: 0–60% (green), 60–80% (yellow), 80%+ (red) ![Example Lens gauge chart showing disk space utilization in percent](/explore-analyze/images/gauge-chart-example-disk-space.png "=50%") + +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example creates a full-circle disk utilization gauge with color bands that shift from green to red as usage increases, using `machine.ram` from the logs sample data as a proxy. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "gauge", + "title": "Disk space utilization", + "filters": [], + "query": { "expression": "" }, + "styling": { + "shape": { "type": "circle" } <1> + }, + "metric": { + "operation": "formula", + "formula": "average(machine.ram)/29000000000", <2> + "label": "Disk space utilization", + "format": { "type": "percent", "decimals": 2, "compact": false }, + "max": { "operation": "formula", "formula": "max(machine.ram)/32211000000" }, + "title": { "visible": true }, + "ticks": { "visible": true, "mode": "bands" }, + "color": { + "type": "dynamic", + "range": "percentage", + "steps": [ <3> + { "gte": 0, "lt": 60, "color": "#24c292" }, + { "gte": 60, "lt": 80, "color": "#EAAE01" }, + { "gte": 80, "color": "#f6726a" } + ] + } + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + } +} +``` + +1. `circle` draws a full 360-degree gauge, which works well for utilization percentages where the full range is always visible. +2. The `formula` divides average RAM by a reference capacity value to produce a 0–1 ratio formatted as percent. Replace with your actual disk metric (for example, `sum(system.filesystem.used.bytes) / sum(system.filesystem.total.bytes)`). +3. The `percentage` range maps thresholds proportionally: green below 60%, yellow between 60–80%, and red above 80%, so the gauge turns red only when disk usage becomes critical. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "gauge", + "title": "Disk space utilization", + "filters": [], + "query": { "expression": "" }, + "styling": { + "shape": { "type": "circle" } <1> + }, + "metric": { + "operation": "formula", + "formula": "average(machine.ram)/29000000000", <2> + "label": "Disk space utilization", + "format": { "type": "percent", "decimals": 2, "compact": false }, + "max": { "operation": "formula", "formula": "max(machine.ram)/32211000000" }, + "title": { "visible": true }, + "ticks": { "visible": true, "mode": "bands" }, + "color": { + "type": "dynamic", + "range": "percentage", + "steps": [ <3> + { "gte": 0, "lt": 60, "color": "#24c292" }, + { "gte": 60, "lt": 80, "color": "#EAAE01" }, + { "gte": 80, "color": "#f6726a" } + ] + } + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + } +}' +``` + +1. `circle` draws a full 360-degree gauge, which works well for utilization percentages where the full range is always visible. +2. The `formula` divides average RAM by a reference capacity value to produce a 0–1 ratio formatted as percent. Replace with your actual disk metric (for example, `sum(system.filesystem.used.bytes) / sum(system.filesystem.total.bytes)`). +3. The `percentage` range maps thresholds proportionally: green below 60%, yellow between 60–80%, and red above 80%, so the gauge turns red only when disk usage becomes critical. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: diff --git a/explore-analyze/visualize/charts/heat-map-charts.md b/explore-analyze/visualize/charts/heat-map-charts.md index 4347cbc3af..5fdb4c3683 100644 --- a/explore-analyze/visualize/charts/heat-map-charts.md +++ b/explore-analyze/visualize/charts/heat-map-charts.md @@ -87,6 +87,134 @@ You can configure custom color ranges on the **Cell value** dimension to emphasi ![Example Lens heat map chart showing error rates per day for various errors](/explore-analyze/images/heat-map-chart-example-server-errors.png) +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example tracks 404 and 503 error activity over time. Named filter rows isolate each error type, and absolute count thresholds drive the color — gray for normal, yellow for elevated, red for high — so anomalous periods stand out immediately. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "heatmap", + "title": "Error rates per day", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "visible", "position": "right" }, + "axis": { "x": { "scale": "ordinal" }, "y": {} }, + "styling": { "cells": { "labels": { "visible": true } } }, + "x": { + "operation": "date_histogram", + "field": "@timestamp", + "suggested_interval": "auto", + "use_original_time_range": false, + "include_empty_rows": true, + "drop_partial_intervals": false + }, + "y": { + "operation": "filters", <1> + "label": "Errors", + "filters": [ + { "filter": { "expression": "\"response.keyword\" : \"404\"" }, "label": "Client errors" }, + { "filter": { "expression": "\"response.keyword\" : \"503\"" }, "label": "Server errors" } + ] + }, + "metric": { + "operation": "formula", <2> + "formula": "count()", + "format": { "type": "number", "decimals": 0, "suffix": "%", "compact": false }, + "color": { + "type": "dynamic", <3> + "range": "absolute", + "steps": [ + { "color": "#c2cbdb", "gte": 0, "lt": 5 }, + { "color": "#EAAE01", "gte": 5, "lt": 10 }, + { "color": "#F6726A", "gte": 10, "lte": null } + ] + } + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "@timestamp" + } +} +``` + +1. The `filters` grouping on the vertical axis creates two named rows — "Client errors" (404s) and "Server errors" (503s) — isolating each error type so every cell represents one error type in one time bucket. +2. The `formula` metric counts matching documents with `count()` and appends a `%` suffix to the display format, presenting counts in a percentage-like style without computing an actual ratio. +3. The `dynamic` color uses absolute count thresholds: gray for 0–4 errors, yellow for 5–9, and red for 10 or more, flagging time buckets with elevated error activity at a glance. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "heatmap", + "title": "Error rates per day", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "visible", "position": "right" }, + "axis": { "x": { "scale": "ordinal" }, "y": {} }, + "styling": { "cells": { "labels": { "visible": true } } }, + "x": { + "operation": "date_histogram", + "field": "@timestamp", + "suggested_interval": "auto", + "use_original_time_range": false, + "include_empty_rows": true, + "drop_partial_intervals": false + }, + "y": { + "operation": "filters", <1> + "label": "Errors", + "filters": [ + { "filter": { "expression": "\"response.keyword\" : \"404\"" }, "label": "Client errors" }, + { "filter": { "expression": "\"response.keyword\" : \"503\"" }, "label": "Server errors" } + ] + }, + "metric": { + "operation": "formula", <2> + "formula": "count()", + "format": { "type": "number", "decimals": 0, "suffix": "%", "compact": false }, + "color": { + "type": "dynamic", <3> + "range": "absolute", + "steps": [ + { "color": "#c2cbdb", "gte": 0, "lt": 5 }, + { "color": "#EAAE01", "gte": 5, "lt": 10 }, + { "color": "#F6726A", "gte": 10, "lte": null } + ] + } + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "@timestamp" + } +}' +``` + +1. The `filters` grouping on the vertical axis creates two named rows — "Client errors" (404s) and "Server errors" (503s) — isolating each error type so every cell represents one error type in one time bucket. +2. The `formula` metric counts matching documents with `count()` and appends a `%` suffix to the display format, presenting counts in a percentage-like style without computing an actual ratio. +3. The `dynamic` color uses absolute count thresholds: gray for 0–4 errors, yellow for 5–9, and red for 10 or more, flagging time buckets with elevated error activity at a glance. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: + ## Heat map chart settings [heat-map-chart-settings] Customize your heat map chart to display exactly the information you need, formatted the way you want. @@ -222,6 +350,11 @@ When creating or editing a visualization, you can customize several appearance o ## Heat map chart examples + + The following examples show various configuration options for building impactful heat map charts. **Request volume by day and hour** @@ -235,6 +368,122 @@ The following examples show various configuration options for building impactful ![Heat map showing request volume by hour and day](/explore-analyze/images/heat-map-example-request-volume.png "=70%") +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example builds a day-by-hour traffic grid using a runtime field (`hour_of_day`) on the vertical axis to reveal peak activity patterns across the week. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "heatmap", + "title": "Request volume by day and hour", + "filters": [], + "query": { "expression": "" }, + "legend": { "size": "auto" }, + "axis": { "x": { "scale": "temporal" }, "y": {} }, + "x": { "operation": "date_histogram", "field": "timestamp" }, + "y": { + "operation": "terms", + "fields": ["hour_of_day"], <1> + "limit": 24, <2> + "other_bucket": { "include_documents_without_field": false }, + "rank_by": { "type": "alphabetical", "direction": "desc" } + }, + "metric": { + "operation": "count", + "empty_as_null": true, + "color": { + "type": "legacy_dynamic", <3> + "palette": "cool", + "range": "percentage", + "shift": true, + "steps": [ + { "color": "#cee1ff", "gte": 0, "lt": 20 }, + { "color": "#b5d2ff", "gte": 20, "lt": 40 }, + { "color": "#9bc2ff", "gte": 40, "lt": 60 }, + { "color": "#80b2ff", "gte": 60, "lt": 80 }, + { "color": "#61a2ff", "gte": 80, "lte": null } + ] + } + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + } +} +``` + +1. `hour_of_day` is a runtime field that extracts the hour (0–23) from `@timestamp`, creating one row per hour. +2. `limit: 24` ensures all 24 hours appear on the vertical axis. Combined with alphabetical descending sort, hours are ordered 23 → 0 for a top-to-bottom timeline feel. +3. The `legacy_dynamic` coloring with the `cool` palette and `range: "percentage"` distributes the blue gradient proportionally across the actual value range, so the lightest blue always marks the quietest hours and the darkest blue the busiest. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "heatmap", + "title": "Request volume by day and hour", + "filters": [], + "query": { "expression": "" }, + "legend": { "size": "auto" }, + "axis": { "x": { "scale": "temporal" }, "y": {} }, + "x": { "operation": "date_histogram", "field": "timestamp" }, + "y": { + "operation": "terms", + "fields": ["hour_of_day"], <1> + "limit": 24, <2> + "other_bucket": { "include_documents_without_field": false }, + "rank_by": { "type": "alphabetical", "direction": "desc" } + }, + "metric": { + "operation": "count", + "empty_as_null": true, + "color": { + "type": "legacy_dynamic", <3> + "palette": "cool", + "range": "percentage", + "shift": true, + "steps": [ + { "color": "#cee1ff", "gte": 0, "lt": 20 }, + { "color": "#b5d2ff", "gte": 20, "lt": 40 }, + { "color": "#9bc2ff", "gte": 40, "lt": 60 }, + { "color": "#80b2ff", "gte": 60, "lt": 80 }, + { "color": "#61a2ff", "gte": 80, "lte": null } + ] + } + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + } +}' +``` + +1. `hour_of_day` is a runtime field that extracts the hour (0–23) from `@timestamp`, creating one row per hour. +2. `limit: 24` ensures all 24 hours appear on the vertical axis. Combined with alphabetical descending sort, hours are ordered 23 → 0 for a top-to-bottom timeline feel. +3. The `legacy_dynamic` coloring with the `cool` palette and `range: "percentage"` distributes the blue gradient proportionally across the actual value range, so the lightest blue always marks the quietest hours and the darkest blue the busiest. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: + **Sales performance by product and region** : Compare product sales across geographic regions: @@ -245,3 +494,127 @@ The following examples show various configuration options for building impactful * **Color palette**: Positive (sequential) ![Heat map showing sales performance by product and region](/explore-analyze/images/heat-map-example-sales-performance.png "=70%") + +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example uses two `terms` dimensions (city and product category) to create a category-versus-region grid, with cell color representing total revenue. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "heatmap", + "title": "Sales performance by product and region", + "filters": [], + "query": { "expression": "" }, + "legend": { "size": "auto" }, + "axis": { "x": { "scale": "ordinal" }, "y": {} }, + "x": { + "operation": "terms", + "fields": ["geoip.city_name"], <1> + "limit": 10 + }, + "y": { + "operation": "terms", + "fields": ["category.keyword"], <2> + "limit": 5 + }, + "metric": { + "operation": "sum", <3> + "field": "taxful_total_price", + "label": "Revenue", + "empty_as_null": true, + "color": { + "type": "legacy_dynamic", + "palette": "positive", + "range": "percentage", + "shift": true, + "steps": [ + { "color": "#d4efe6", "gte": 0, "lt": 20 }, + { "color": "#b1e4d1", "gte": 20, "lt": 40 }, + { "color": "#8cd9bb", "gte": 40, "lt": 60 }, + { "color": "#62cea6", "gte": 60, "lt": 80 }, + { "color": "#24c292", "gte": 80, "lte": null } + ] + } + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_ecommerce", + "time_field": "order_date" + } +} +``` + +1. Cities on the horizontal axis create one column per location, making geographic performance quick to scan. +2. Product categories on the vertical axis form the rows, so each cell shows revenue for one category in one city. +3. `sum` of `taxful_total_price` colors cells by total revenue rather than document count. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "heatmap", + "title": "Sales performance by product and region", + "filters": [], + "query": { "expression": "" }, + "legend": { "size": "auto" }, + "axis": { "x": { "scale": "ordinal" }, "y": {} }, + "x": { + "operation": "terms", + "fields": ["geoip.city_name"], <1> + "limit": 10 + }, + "y": { + "operation": "terms", + "fields": ["category.keyword"], <2> + "limit": 5 + }, + "metric": { + "operation": "sum", <3> + "field": "taxful_total_price", + "label": "Revenue", + "empty_as_null": true, + "color": { + "type": "legacy_dynamic", + "palette": "positive", + "range": "percentage", + "shift": true, + "steps": [ + { "color": "#d4efe6", "gte": 0, "lt": 20 }, + { "color": "#b1e4d1", "gte": 20, "lt": 40 }, + { "color": "#8cd9bb", "gte": 40, "lt": 60 }, + { "color": "#62cea6", "gte": 60, "lt": 80 }, + { "color": "#24c292", "gte": 80, "lte": null } + ] + } + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_ecommerce", + "time_field": "order_date" + } +}' +``` + +1. Cities on the horizontal axis create one column per location, making geographic performance quick to scan. +2. Product categories on the vertical axis form the rows, so each cell shows revenue for one category in one city. +3. `sum` of `taxful_total_price` colors cells by total revenue rather than document count. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: diff --git a/explore-analyze/visualize/charts/line-charts.md b/explore-analyze/visualize/charts/line-charts.md index 3c64380f4b..15f244ac86 100644 --- a/explore-analyze/visualize/charts/line-charts.md +++ b/explore-analyze/visualize/charts/line-charts.md @@ -92,9 +92,9 @@ In line charts, you can enable time shift to compare the current value with a pr 4. Optionally, customize the appearance of the layer to adjust how it looks on the chart. When you duplicate a layer, {{kib}} automatically assigns a different **Series color** to the new layer. You can for example change this color, or adjust the layer's name and axis position. This name is used for the chart's legend. ::::{tip} -You can also compute the relative change by defining the axis data with a [formula](/explore-analyze/visualize/lens.md#lens-formulas), for example: +You can also compute the relative change by defining the axis data with a [formula](/explore-analyze/visualize/lens.md#lens-formulas), for example: `(average(bytes) - average(bytes, shift='1w')) / average(bytes, shift='1w')` -:::: +:::: ### Highlight thresholds with reference lines [line-reference-lines] @@ -191,6 +191,11 @@ When creating or editing a visualization, you can adjust the following settings. ## Line chart examples + + **Average RAM per host** : Monitoring the average of RAM over time for the first four hosts: @@ -206,21 +211,280 @@ When creating or editing a visualization, you can adjust the following settings. * **Number of values**: `4` 4. Save your chart. - ![Average RAM per host](../../images/kibana-lens-average-ram-host.png "=70%") +![Average RAM per host](../../images/kibana-lens-average-ram-host.png "=70%") + +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +Send the following request to create a line chart that plots the moving average of RAM, broken down by the top 4 hosts. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "xy", + "title": "Average RAM per host", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "visible", "placement": "outside", "position": "bottom" }, + "axis": {}, + "layers": [ + { + "type": "line", + "x": { + "operation": "date_histogram", + "field": "timestamp", + "suggested_interval": "1h" + }, + "y": [ + { + "operation": "moving_average", <1> + "of": { + "operation": "average", + "field": "machine.ram", + "format": { "type": "bytes", "decimals": 0 }, + "filter": { "expression": "" } + }, + "label": "Moving average of RAM", + "format": { "type": "bytes", "decimals": 0 }, + "filter": { "expression": "" }, + "color": { + "type": "static", + "color": "#6092c0" + } + } + ], + "breakdown_by": { <2> + "operation": "terms", + "fields": ["host.keyword"], + "limit": 4, + "rank_by": { "type": "alphabetical", "direction": "asc" } <3> + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + } + } + ], + "styling": { + "fitting": { "type": "linear" } + } +} +``` + +1. `moving_average` over the `average` of `machine.ram` smooths out spikes and produces a trend line. `decimals: 0` keeps the byte labels clean. +2. `breakdown_by` splits the chart into one line per host, limited to the top 4 values of `host.keyword`. +3. `rank_by: "alphabetical"` is required here because `moving_average` is a pipeline aggregation and cannot be used to sort breakdown buckets. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "xy", + "title": "Average RAM per host", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "visible", "placement": "outside", "position": "bottom" }, + "axis": {}, + "layers": [ + { + "type": "line", + "x": { + "operation": "date_histogram", + "field": "timestamp", + "suggested_interval": "1h" + }, + "y": [ + { + "operation": "moving_average", <1> + "of": { + "operation": "average", + "field": "machine.ram", + "format": { "type": "bytes", "decimals": 0 }, + "filter": { "expression": "" } + }, + "label": "Moving average of RAM", + "format": { "type": "bytes", "decimals": 0 }, + "filter": { "expression": "" }, + "color": { + "type": "static", + "color": "#6092c0" + } + } + ], + "breakdown_by": { <2> + "operation": "terms", + "fields": ["host.keyword"], + "limit": 4, + "rank_by": { "type": "alphabetical", "direction": "asc" } <3> + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + } + } + ], + "styling": { + "fitting": { "type": "linear" } + } +}' +``` + +1. `moving_average` over the `average` of `machine.ram` smooths out spikes and produces a trend line. `decimals: 0` keeps the byte labels clean. +2. `breakdown_by` splits the chart into one line per host, limited to the top 4 values of `host.keyword`. +3. `rank_by: "alphabetical"` is required here because `moving_average` is a pipeline aggregation and cannot be used to sort breakdown buckets. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: **Unique IPs over time** -: Visualizing unique IP sessions throughout the day: +: Visualize the number of unique client IPs throughout the day to identify traffic patterns and peak usage periods: -1. Drag `@timestamp` to the **Horizontal axis** and set the following settings: - * **Functions**: `Date histogram` - * **Minimum interval**: `Hour` -2. Drag `host.keyword` to the **Vertical axis** and set the following settings: - * **Functions**: : `Unique count` - * **Value format**: `Bytes (1024)` +1. Drag `timestamp` to the **Horizontal axis** and set **Functions** to `Date histogram` with **Minimum interval** set to `Hour`. +2. Drag `clientip` to the **Vertical axis** and set the following settings: + * **Functions**: `Unique count` + * **Value format**: `Number` * **Decimals**: `0` +3. In the **Visual options**, set **Missing values** to `Linear` to connect gaps in the line. 4. Save your chart. - ![Unique IPs throughout the day](../../images/kibana-lens-unique-ip-throughout-day.png "=70%") +![Unique IPs throughout the day](../../images/kibana-lens-unique-ip-throughout-day.png "=70%") + +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +Send the following request to create a line chart that plots the unique count of client IPs over time, with linear interpolation for missing values. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "xy", + "title": "Unique IPs over time", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "auto" }, + "axis": {}, + "layers": [ + { + "type": "line", + "x": { + "operation": "date_histogram", + "field": "timestamp", + "suggested_interval": "1h" <1> + }, + "y": [ + { + "operation": "unique_count", <2> + "field": "clientip", + "label": "Unique IPs", + "format": { + "type": "number", + "decimals": 0 + }, + "filter": { "expression": "" } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + } + } + ], + "styling": { + "fitting": { + "type": "linear" <3> + } + } +} +``` + +1. `suggested_interval: "1h"` sets the date histogram to bucket by hour. +2. `unique_count` on `clientip` counts distinct IP addresses per time bucket, tracking unique visitors rather than total requests. +3. `fitting.type: "linear"` connects data points across empty buckets with a straight line instead of leaving gaps. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "xy", + "title": "Unique IPs over time", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "auto" }, + "axis": {}, + "layers": [ + { + "type": "line", + "x": { + "operation": "date_histogram", + "field": "timestamp", + "suggested_interval": "1h" <1> + }, + "y": [ + { + "operation": "unique_count", <2> + "field": "clientip", + "label": "Unique IPs", + "format": { + "type": "number", + "decimals": 0 + }, + "filter": { "expression": "" } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + } + } + ], + "styling": { + "fitting": { + "type": "linear" <3> + } + } +}' +``` + +1. `suggested_interval: "1h"` sets the date histogram to bucket by hour. +2. `unique_count` on `clientip` counts distinct IP addresses per time bucket, tracking unique visitors rather than total requests. +3. `fitting.type: "linear"` connects data points across empty buckets with a straight line instead of leaving gaps. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: --- diff --git a/explore-analyze/visualize/charts/metric-charts.md b/explore-analyze/visualize/charts/metric-charts.md index dfe7acdcc5..32a8e76cac 100644 --- a/explore-analyze/visualize/charts/metric-charts.md +++ b/explore-analyze/visualize/charts/metric-charts.md @@ -56,7 +56,7 @@ Using the dropdown indicating **Bar**, select **Metric**. The chart preview updates to show a large numeric value. If you added a secondary metric, it appears below the primary value. If you added a breakdown dimension, separate tiles appear for each category. -Refer to [](#settings) to find all data configuration options for your metric chart. +See [](#settings) for all data configuration options for your metric chart. :::: ::::{step} Customize the chart to follow best practices @@ -133,6 +133,132 @@ To add trend indicators to your metric visualization: The metric visualization now shows the secondary metric as a comparison with a trend indicator. +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +Send the following request to create a metric that counts unique orders for the current period and compares the result to the previous week using a time-shifted formula. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "metric", + "title": "Total orders with trend", + "metrics": [ + { + "type": "primary", + "operation": "unique_count", + "field": "order_id", + "empty_as_null": true, + "label": "Total orders", + "subtitle": "Weekly", + "color": { "type": "auto" } + }, + { + "type": "secondary", + "operation": "formula", + "formula": "count(order_id,shift='1w')", <1> + "label": "Compared to previous week", + "compare": { <2> + "to": "primary", + "palette": "compare_to", + "icon": true, + "value": true + }, + "color": { "type": "none" } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_ecommerce", + "time_field": "order_date" + }, + "styling": { + "primary": { "position": "bottom", "labels": { "alignment": "left" }, "value": { "sizing": "auto", "alignment": "right" } }, + "secondary": { + "label": { "visible": true, "placement": "before" }, + "value": { "alignment": "right" } + } + } +} +``` + +1. `shift='1w'` runs the same count offset by one week, providing the comparison baseline. +2. `compare.to: "primary"` displays the *difference* between primary and secondary instead of the raw secondary value. `palette: "compare_to"` colors the badge green for increases and red for decreases. `icon: true` adds a directional arrow. + +:::{note} +When `compare.to: "primary"` is first applied, Kibana sets the secondary metric **Label** to **Auto**, which displays **Difference**. To use a custom label, select the secondary metric in the Lens editor, set **Label** to **Custom**, and type your text. Once saved, the custom label is stored in the `label` field of the payload and preserved in exports. +::: + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "metric", + "title": "Total orders with trend", + "metrics": [ + { + "type": "primary", + "operation": "unique_count", + "field": "order_id", + "empty_as_null": true, + "label": "Total orders", + "subtitle": "Weekly", + "color": { "type": "auto" } + }, + { + "type": "secondary", + "operation": "formula", + "formula": "count(order_id,shift='\''1w'\'')", <1> + "label": "Compared to previous week", + "compare": { <2> + "to": "primary", + "palette": "compare_to", + "icon": true, + "value": true + }, + "color": { "type": "none" } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_ecommerce", + "time_field": "order_date" + }, + "styling": { + "primary": { "position": "bottom", "labels": { "alignment": "left" }, "value": { "sizing": "auto", "alignment": "right" } }, + "secondary": { + "label": { "visible": true, "placement": "before" }, + "value": { "alignment": "right" } + } + } +}' +``` + +1. `shift='1w'` runs the same count offset by one week, providing the comparison baseline. +2. `compare.to: "primary"` displays the *difference* between primary and secondary instead of the raw secondary value. `palette: "compare_to"` colors the badge green for increases and red for decreases. `icon: true` adds a directional arrow. + +:::{note} +When `compare.to: "primary"` is first applied, Kibana sets the secondary metric **Label** to **Auto**, which displays **Difference**. To use a custom label, select the secondary metric in the Lens editor, set **Label** to **Custom**, and type your text. Once saved, the custom label is stored in the `label` field of the payload and preserved in exports. +::: + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: + ### Show progress by setting a maximum value [metric-progress] When creating **Metric** visualizations with numeric data, you can specify a maximum value to show progress toward a goal or capacity limit. When combined with the **Bar** background chart option, this displays a progress bar that visually represents how close your current metric is to reaching the maximum value. @@ -163,6 +289,136 @@ The metric visualization now shows a progress bar indicating how close the curre You can combine progress bars with secondary metrics to show both progress toward a goal and trends over time. To do this, add both a maximum value and a secondary metric to your visualization. :::: +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example creates a metric that shows the current count of users against a target of 2,500, with a progress bar and a weekly comparison badge. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "metric", + "title": "Quarterly net new users", + "filters": [], + "query": { "expression": "" }, + "metrics": [ + { + "type": "primary", + "operation": "formula", + "formula": "count()", + "label": "Quarterly net new users", + "subtitle": "Target: 2500", + "background_chart": { <1> + "type": "bar", + "max_value": { "operation": "formula", "formula": "2500" } <2> + }, + "color": { <3> + "type": "dynamic", + "range": "absolute", + "steps": [ + { "lt": 1000, "color": "#f6726a" }, + { "gte": 1000, "lt": 2000, "color": "#fcd883" }, + { "gte": 2000, "color": "#24c292" } + ] + } + }, + { + "type": "secondary", + "operation": "formula", + "formula": "count(shift='1w')", + "label": "Since last week", + "compare": { "to": "primary", "palette": "compare_to", "icon": true, "value": true }, + "color": { "type": "none" } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + }, + "styling": { + "primary": { "position": "top", "labels": { "alignment": "left" }, "value": { "sizing": "auto", "alignment": "left" } }, + "secondary": { "label": { "visible": true, "placement": "before" }, "value": { "alignment": "left" } } + } +} +``` + +1. `background_chart.type: "bar"` renders a horizontal progress bar behind the metric value. +2. `max_value` with `formula: "2500"` sets the target. The bar fills proportionally as the metric approaches this value. You can also use `operation: "static_value", value: 2500` for a plain number. +3. `dynamic` color with `absolute` thresholds changes the tile color as the metric progresses — red below 1,000, yellow between 1,000 and 2,000, and green at or above 2,000. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "metric", + "title": "Quarterly net new users", + "filters": [], + "query": { "expression": "" }, + "metrics": [ + { + "type": "primary", + "operation": "formula", + "formula": "count()", + "label": "Quarterly net new users", + "subtitle": "Target: 2500", + "background_chart": { <1> + "type": "bar", + "max_value": { "operation": "formula", "formula": "2500" } <2> + }, + "color": { <3> + "type": "dynamic", + "range": "absolute", + "steps": [ + { "lt": 1000, "color": "#f6726a" }, + { "gte": 1000, "lt": 2000, "color": "#fcd883" }, + { "gte": 2000, "color": "#24c292" } + ] + } + }, + { + "type": "secondary", + "operation": "formula", + "formula": "count(shift='\''1w'\'')", + "label": "Since last week", + "compare": { "to": "primary", "palette": "compare_to", "icon": true, "value": true }, + "color": { "type": "none" } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + }, + "styling": { + "primary": { "position": "top", "labels": { "alignment": "left" }, "value": { "sizing": "auto", "alignment": "left" } }, + "secondary": { "label": { "visible": true, "placement": "before" }, "value": { "alignment": "left" } } + } +}' +``` + +1. `background_chart.type: "bar"` renders a horizontal progress bar behind the metric value. +2. `max_value` with `formula: "2500"` sets the target. The bar fills proportionally as the metric approaches this value. You can also use `operation: "static_value", value: 2500` for a plain number. +3. `dynamic` color with `absolute` thresholds changes the tile color as the metric progresses — red below 1,000, yellow between 1,000 and 2,000, and green at or above 2,000. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: + ## Metric chart settings [settings] Customize your metric chart to display exactly the information you need, formatted the way you want. @@ -278,13 +534,18 @@ When creating or editing a visualization, you can customize several appearance o ## Metric chart examples + + The following examples show various configuration options that you can use for building impactful metrics. **Ratio of successful requests** : Display the percentage of successful requests on a monitoring dashboard: * **Title**: "Successful requests (2xx)" - * **Primary metric**: `count(kql='response.code >= "200" and response.code < "300"') / count(response.code)` + * **Primary metric**: `count(kql='response.keyword >= "200" and response.keyword < "300"') / count(response.keyword)` * **Value format**: `Percent` * **Color mode** or **Color by value**: `Dynamic` (green when above 95%, yellow between 75% and 95%, red when below) * **Background chart** or **Supporting visualization**: "Line" to show evolution over time @@ -292,24 +553,296 @@ The following examples show various configuration options that you can use for b * **Value format**: `Percent` * **Label**: Custom, set to `Target:` - ![Metric with below target successful request percentage](../../images/metric-example-successful-requests-rate.png "=70%") +![Metric with below target successful request percentage](../../images/metric-example-successful-requests-rate.png "=70%") + +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example creates a metric that calculates the ratio of successful HTTP requests using a formula with a KQL filter, and adds a fixed target value as a secondary metric. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "metric", + "title": "Successful requests (2xx)", + "filters": [], + "query": { "expression": "" }, + "metrics": [ + { + "type": "primary", + "operation": "formula", + "formula": "count(kql=response.keyword >= \"200\" and response.keyword < \"300\") / count(response.keyword)", <1> + "label": "Successful requests (2xx)", + "format": { "type": "percent", "decimals": 1, "compact": true }, + "background_chart": { "type": "trend" }, <2> + "color": { <3> + "type": "dynamic", + "range": "absolute", + "steps": [ + { "lt": 0.75, "color": "#f6726a" }, + { "gte": 0.75, "lt": 0.95, "color": "#fcd883" }, + { "gte": 0.95, "color": "#24c292" } + ] + }, + "apply_color_to": "background" <4> + }, + { + "type": "secondary", + "operation": "formula", + "formula": "0.95", <5> + "format": { "type": "percent", "decimals": 2 }, + "label": "Target:", + "color": { "type": "none" } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + }, + "styling": { + "primary": { "position": "top", "labels": { "alignment": "left" }, "value": { "sizing": "auto", "alignment": "left" } }, + "secondary": { "label": { "visible": true, "placement": "before" }, "value": { "alignment": "right" } } + } +} +``` + +1. `response.keyword` is stored as a string in the sample data, so the comparison values must be quoted. +2. `background_chart.type: "trend"` renders a sparkline in the background showing how the metric evolves over time. +3. `dynamic` color with `absolute` thresholds applies different colors based on the metric value — red below 75%, yellow between 75% and 95%, green at or above 95%. +4. `apply_color_to: "background"` colors the tile background rather than the number itself. +5. The secondary metric displays a fixed 95% target for comparison. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "metric", + "title": "Successful requests (2xx)", + "filters": [], + "query": { "expression": "" }, + "metrics": [ + { + "type": "primary", + "operation": "formula", + "formula": "count(kql='''response.keyword >= \"200\" and response.keyword < \"300\"''') / count(response.keyword)", <1> + "label": "Successful requests (2xx)", + "format": { "type": "percent", "decimals": 1, "compact": true }, + "background_chart": { "type": "trend" }, <2> + "color": { <3> + "type": "dynamic", + "range": "absolute", + "steps": [ + { "lt": 0.75, "color": "#f6726a" }, + { "gte": 0.75, "lt": 0.95, "color": "#fcd883" }, + { "gte": 0.95, "color": "#24c292" } + ] + }, + "apply_color_to": "background" <4> + }, + { + "type": "secondary", + "operation": "formula", + "formula": "0.95", <5> + "format": { "type": "percent", "decimals": 2 }, + "label": "Target:", + "color": { "type": "none" } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + }, + "styling": { + "primary": { "position": "top", "labels": { "alignment": "left" }, "value": { "sizing": "auto", "alignment": "left" } }, + "secondary": { "label": { "visible": true, "placement": "before" }, "value": { "alignment": "right" } } + } +}' +``` + +1. `response.keyword` is stored as a string in the sample data, so the comparison values must be quoted. +2. `background_chart.type: "trend"` renders a sparkline in the background showing how the metric evolves over time. +3. `dynamic` color with `absolute` thresholds applies different colors based on the metric value — red below 75%, yellow between 75% and 95%, green at or above 95%. +4. `apply_color_to: "background"` colors the tile background rather than the number itself. +5. The secondary metric displays a fixed 95% target for comparison. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: **Ratio of successful requests per origin** : This example builds on the previous one to display the percentage of successful requests for the 10 countries with the most incoming requests on a monitoring dashboard: - * **Title**: "Successful requests (2xx)" - * **Primary metric**: `count(kql='response.code >= 200 and response.code < 300') / count(response.code)` + * **Title**: "Successful requests per origin" + * **Primary metric**: `count(kql='response.keyword >= "200" and response.keyword < "300"') / count(response.keyword)` * **Value format**: `Percent` * **Color mode** or **Color by value**: `Dynamic`. Green when above 95%, yellow between 75% and 95%, red when below * **Background chart** or **Supporting visualization**: "Line" to show evolution over time * **Secondary metric**: `0.95` formula - * **Name**: `Target:` + * **Label**: Custom, set to `Target:` * **Value format**: `Percent` * **Break down by**: `geo.dest` * **Number of values**: `10` - * **Rank by**: `Custom` > `Count` > `Records` to get countries generating most requests + * **Rank by**: `Custom` > `Count` > `Records` to get countries generating most requests + +![Metric with below target successful request percentage](../../images/metric-example-successful-requests-rate-top-countries.png "=70%") + +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example builds on the previous one by adding a breakdown that splits the success rate into one tile per destination country. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "metric", + "title": "Successful requests per origin", + "filters": [], + "query": { "expression": "" }, + "metrics": [ + { + "type": "primary", + "operation": "formula", + "formula": "count(kql=response.keyword >= \"200\" and response.keyword < \"300\") / count(response.keyword)", + "label": "Successful requests (2xx)", + "format": { "type": "percent", "decimals": 1, "compact": true }, + "background_chart": { "type": "trend" }, + "color": { + "type": "dynamic", + "range": "absolute", + "steps": [ + { "lt": 0.75, "color": "#f6726a" }, + { "gte": 0.75, "lt": 0.95, "color": "#fcd883" }, + { "gte": 0.95, "color": "#24c292" } + ] + }, + "apply_color_to": "background" + }, + { + "type": "secondary", + "operation": "formula", + "formula": "0.95", + "format": { "type": "percent", "decimals": 2 }, + "label": "Target:", + "color": { "type": "none" } + } + ], + "breakdown_by": { <1> + "operation": "terms", + "fields": ["geo.dest"], + "limit": 10, <2> + "rank_by": { "type": "custom", "operation": "count", "field": "___records___", "direction": "desc" }, <3> + "columns": 5 <4> + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + }, + "styling": { + "primary": { "position": "top", "labels": { "alignment": "left" }, "value": { "sizing": "auto", "alignment": "left" } }, + "secondary": { "label": { "visible": true, "placement": "before" }, "value": { "alignment": "right" } } + } +} +``` + +1. `breakdown_by` splits the metric into separate tiles, one per destination country. +2. Shows the top 10 countries by document count. +3. `rank_by` with `field: "___records___"` ranks tiles by document count, ensuring the countries with the most traffic appear first. +4. `columns: 5` arranges the tiles in a 5-column grid. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "metric", + "title": "Successful requests per origin", + "filters": [], + "query": { "expression": "" }, + "metrics": [ + { + "type": "primary", + "operation": "formula", + "formula": "count(kql='''response.keyword >= \"200\" and response.keyword < \"300\"''') / count(response.keyword)", + "label": "Successful requests (2xx)", + "format": { "type": "percent", "decimals": 1, "compact": true }, + "background_chart": { "type": "trend" }, + "color": { + "type": "dynamic", + "range": "absolute", + "steps": [ + { "lt": 0.75, "color": "#f6726a" }, + { "gte": 0.75, "lt": 0.95, "color": "#fcd883" }, + { "gte": 0.95, "color": "#24c292" } + ] + }, + "apply_color_to": "background" + }, + { + "type": "secondary", + "operation": "formula", + "formula": "0.95", + "format": { "type": "percent", "decimals": 2 }, + "label": "Target:", + "color": { "type": "none" } + } + ], + "breakdown_by": { <1> + "operation": "terms", + "fields": ["geo.dest"], + "limit": 10, <2> + "rank_by": { "type": "custom", "operation": "count", "field": "___records___", "direction": "desc" }, <3> + "columns": 5 <4> + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + }, + "styling": { + "primary": { "position": "top", "labels": { "alignment": "left" }, "value": { "sizing": "auto", "alignment": "left" } }, + "secondary": { "label": { "visible": true, "placement": "before" }, "value": { "alignment": "right" } } + } +}' +``` + +1. `breakdown_by` splits the metric into separate tiles, one per destination country. +2. Shows the top 10 countries by document count. +3. `rank_by` with `field: "___records___"` ranks tiles by document count, ensuring the countries with the most traffic appear first. +4. `columns: 5` arranges the tiles in a 5-column grid. + +:::: - ![Metric with below target successful request percentage](../../images/metric-example-successful-requests-rate-top-countries.png "=70%") +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: **Website traffic with trend** : Monitor current traffic and show whether it's increasing or decreasing compared to the previous period: @@ -323,4 +856,142 @@ The following examples show various configuration options that you can use for b * **Label**: "Compared to previous week" * **Color palette**: Green for increases (more traffic is positive) - ![Metric showing weekly visits with weekly comparison trend](../../images/metric-website-views-weekly-trend-example.png "=70%") +![Metric showing weekly visits with weekly comparison trend](../../images/metric-website-views-weekly-trend-example.png "=70%") + +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example creates a metric that counts page views and compares the current value to the previous week using a time-shifted formula. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "metric", + "title": "Weekly page views", + "filters": [], + "query": { "expression": "" }, + "metrics": [ + { + "type": "primary", + "operation": "count", + "empty_as_null": true, + "label": "Page views", + "format": { <1> + "type": "number", + "decimals": 0, + "compact": true + } + }, + { + "type": "secondary", <2> + "operation": "formula", + "formula": "count(shift='1w')", <3> + "label": "Compared to previous week", + "compare": { <4> + "to": "primary", + "palette": "compare_to", + "icon": true, + "value": true + }, + "color": { "type": "none" } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + }, + "styling": { + "primary": { "position": "top", "labels": { "alignment": "left" }, "value": { "sizing": "auto", "alignment": "left" } }, + "secondary": { + "label": { "visible": true, "placement": "before" }, + "value": { "alignment": "right" } + } + } +} +``` + +1. `compact: true` displays large numbers in abbreviated form (for example, 1.2K). +2. The secondary metric appears below the primary value as a comparison badge. +3. `shift='1w'` runs the same count query offset by one week. +4. `compare.to: "primary"` displays the difference from the primary rather than the raw secondary value. `palette: "compare_to"` colors the badge green for increases and red for decreases. `icon: true` adds a directional arrow (↑ or ↓). + +:::{note} +When `compare.to: "primary"` is first applied, Kibana sets the secondary metric **Label** to **Auto**, which displays **Difference**. To use a custom label, select the secondary metric in the Lens editor, set **Label** to **Custom**, and type your text. Once saved, the custom label is stored in the `label` field of the payload and preserved in exports. +::: + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "metric", + "title": "Weekly page views", + "filters": [], + "query": { "expression": "" }, + "metrics": [ + { + "type": "primary", + "operation": "count", + "empty_as_null": true, + "label": "Page views", + "format": { <1> + "type": "number", + "decimals": 0, + "compact": true + } + }, + { + "type": "secondary", <2> + "operation": "formula", + "formula": "count(shift='\''1w'\'')", <3> + "label": "Compared to previous week", + "compare": { <4> + "to": "primary", + "palette": "compare_to", + "icon": true, + "value": true + }, + "color": { "type": "none" } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + }, + "styling": { + "primary": { "position": "top", "labels": { "alignment": "left" }, "value": { "sizing": "auto", "alignment": "left" } }, + "secondary": { + "label": { "visible": true, "placement": "before" }, + "value": { "alignment": "right" } + } + } +}' +``` + +1. `compact: true` displays large numbers in abbreviated form (for example, 1.2K). +2. The secondary metric appears below the primary value as a comparison badge. +3. `shift='1w'` runs the same count query offset by one week. +4. `compare.to: "primary"` displays the difference from the primary rather than the raw secondary value. `palette: "compare_to"` colors the badge green for increases and red for decreases. `icon: true` adds a directional arrow (↑ or ↓). + +:::{note} +When `compare.to: "primary"` is first applied, Kibana sets the secondary metric **Label** to **Auto**, which displays **Difference**. To use a custom label, select the secondary metric in the Lens editor, set **Label** to **Custom**, and type your text. Once saved, the custom label is stored in the `label` field of the payload and preserved in exports. +::: + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: diff --git a/explore-analyze/visualize/charts/mosaic-charts.md b/explore-analyze/visualize/charts/mosaic-charts.md index a7af68e360..92b7c1e056 100644 --- a/explore-analyze/visualize/charts/mosaic-charts.md +++ b/explore-analyze/visualize/charts/mosaic-charts.md @@ -77,7 +77,7 @@ Customize your mosaic chart to display exactly the information you need, formatt ### Horizontal axis settings [horizontal-axis-settings] -The **Horizontal axis** dimension defines the columns of the mosaic. Column widths represent the proportion of each category. +The **Horizontal axis** dimension defines the columns of the mosaic. Column widths represent the proportion of each category. In the API, this corresponds to `group_breakdown_by`. **Data** : The **Horizontal axis** dimension supports the following functions: @@ -110,7 +110,7 @@ The **Horizontal axis** dimension defines the columns of the mosaic. Column widt ### Vertical axis settings [vertical-axis-settings] -The **Vertical axis** dimension defines the rows within each column. Rectangle heights represent the proportion of each category within the column. +The **Vertical axis** dimension defines the rows within each column. Rectangle heights represent the proportion of each category within the column. In the API, this corresponds to `group_by`, and color mapping is configured here. **Data** : The **Vertical axis** dimension supports the following functions: @@ -192,28 +192,279 @@ When creating or editing a visualization, you can customize several appearance o ## Mosaic chart examples + + The following examples show various configuration options for building impactful mosaic charts. **Response status by operating system** : Visualize how response status categories vary across operating systems: * Example based on: {{kib}} Sample Data Logs - * **Horizontal axis**: `machine.os.keyword` (Top 5 values) - * **Vertical axis**: **Filters** + * **Horizontal axis** (columns): `machine.os.keyword` (Top 5 values) + * **Vertical axis** (rows): **Filters** - "Success (2xx/3xx)": `response.keyword >= "200" AND response.keyword < "400"` - "Client errors (4xx)": `response.keyword >= "400" AND response.keyword < "500"` - "Server errors (5xx)": `response.keyword >= "500"` * **Metric**: Count - * **Color mapping**: Green for success, yellow for client errors, red for server errors + * **Color mapping**: Teal for success, pink for client errors, red for server errors ![Mosaic chart showing response status by operating system](/explore-analyze/images/mosaic-example-response-by-os.png "=70%") +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example creates a mosaic chart where column widths represent OS popularity and the colored rows within each column show the proportion of success, client error, and server error responses. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "mosaic", <1> + "title": "Response status by operating system", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "auto", "nested": false }, + "metric": { "operation": "count", "empty_as_null": true }, + "group_by": [ <2> + { + "operation": "filters", + "filters": [ + { + "filter": { "expression": "response.keyword >= \"200\" AND response.keyword < \"400\"" }, + "label": "Success (2xx/3xx)" + }, + { + "filter": { "expression": "response.keyword >= \"400\" AND response.keyword < \"500\"" }, + "label": "Client errors (4xx)" + }, + { + "filter": { "expression": "response.keyword >= \"500\"" }, + "label": "Server errors (5xx)" + } + ], + "color": { + "mode": "categorical", + "palette": "default", + "mapping": [ + { "values": ["Success (2xx/3xx)"], "color": { "type": "from_palette", "palette": "default", "index": 0 } }, + { "values": ["Client errors (4xx)"], "color": { "type": "from_palette", "palette": "default", "index": 8 } }, + { "values": ["Server errors (5xx)"], "color": { "type": "from_palette", "palette": "default", "index": 6 } } + ] + } + } + ], + "group_breakdown_by": [ <3> + { + "operation": "terms", + "fields": ["machine.os.keyword"], + "limit": 5, + "other_bucket": { "include_documents_without_field": false }, + "rank_by": { "type": "metric", "metric_index": 0, "direction": "desc" } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + }, + "styling": { "values": { "visible": true, "mode": "percentage" } } +} +``` + +1. `mosaic` renders a grid where both column width and row height encode proportions, making distribution comparisons clear across two dimensions. +2. `group_by` defines the vertical axis (rows). Using `filters` here creates three named rows — one per response status category — each colored from the `mapping` so success, client errors, and server errors are immediately distinguishable. +3. `group_breakdown_by` defines the horizontal axis (columns). Each OS becomes a column whose width reflects its share of total traffic. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "mosaic", <1> + "title": "Response status by operating system", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "auto", "nested": false }, + "metric": { "operation": "count", "empty_as_null": true }, + "group_by": [ <2> + { + "operation": "filters", + "filters": [ + { + "filter": { "expression": "response.keyword >= \"200\" AND response.keyword < \"400\"" }, + "label": "Success (2xx/3xx)" + }, + { + "filter": { "expression": "response.keyword >= \"400\" AND response.keyword < \"500\"" }, + "label": "Client errors (4xx)" + }, + { + "filter": { "expression": "response.keyword >= \"500\"" }, + "label": "Server errors (5xx)" + } + ], + "color": { + "mode": "categorical", + "palette": "default", + "mapping": [ + { "values": ["Success (2xx/3xx)"], "color": { "type": "from_palette", "palette": "default", "index": 0 } }, + { "values": ["Client errors (4xx)"], "color": { "type": "from_palette", "palette": "default", "index": 8 } }, + { "values": ["Server errors (5xx)"], "color": { "type": "from_palette", "palette": "default", "index": 6 } } + ] + } + } + ], + "group_breakdown_by": [ <3> + { + "operation": "terms", + "fields": ["machine.os.keyword"], + "limit": 5, + "other_bucket": { "include_documents_without_field": false }, + "rank_by": { "type": "metric", "metric_index": 0, "direction": "desc" } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + }, + "styling": { "values": { "visible": true, "mode": "percentage" } } +}' +``` + +1. `mosaic` renders a grid where both column width and row height encode proportions, making distribution comparisons clear across two dimensions. +2. `group_by` defines the vertical axis (rows). Using `filters` here creates three named rows — one per response status category — each colored from the `mapping` so success, client errors, and server errors are immediately distinguishable. +3. `group_breakdown_by` defines the horizontal axis (columns). Each OS becomes a column whose width reflects its share of total traffic. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: + **Product categories by continent** : Show how product preferences vary across regions: * Example based on: {{kib}} Sample Data eCommerce - * **Horizontal axis**: `geoip.continent_name` (Top values) - * **Vertical axis**: `category.keyword` (Top 5 values) + * **Horizontal axis** (columns): `geoip.continent_name` (Top values) + * **Vertical axis** (rows): `category.keyword` (Top 9 values) * **Metric**: Count -![Mosaic chart showing product categories by continent](/explore-analyze/images/mosaic-example-category-by-continent.png "=70%") \ No newline at end of file +![Mosaic chart showing product categories by continent](/explore-analyze/images/mosaic-example-category-by-continent.png "=70%") + +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example uses `terms` on both axes to compare product category preferences across continents, revealing how shopping behavior varies by region. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "mosaic", + "title": "Product categories by continent", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "auto", "nested": false }, + "metric": { "operation": "count", "empty_as_null": true }, + "group_by": [ + { + "operation": "terms", + "fields": ["category.keyword"], <1> + "limit": 9, + "other_bucket": { "include_documents_without_field": false }, + "rank_by": { "type": "metric", "metric_index": 0, "direction": "desc" }, + "color": { "mode": "categorical", "palette": "default", "mapping": [] } + } + ], + "group_breakdown_by": [ + { + "operation": "terms", + "fields": ["geoip.continent_name"], <2> + "limit": 9, + "other_bucket": { "include_documents_without_field": false }, + "rank_by": { "type": "metric", "metric_index": 0, "direction": "desc" } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_ecommerce", + "time_field": "order_date" + }, + "styling": { "values": { "visible": true, "mode": "percentage" } } +} +``` + +1. `category.keyword` on the vertical axis (`group_by`) creates one row per product category. The `color` mapping assigns a distinct palette color to each category, making each category trackable across columns. +2. `geoip.continent_name` on the horizontal axis (`group_breakdown_by`) creates one column per continent, with width proportional to order volume from that region. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "mosaic", + "title": "Product categories by continent", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "auto", "nested": false }, + "metric": { "operation": "count", "empty_as_null": true }, + "group_by": [ + { + "operation": "terms", + "fields": ["category.keyword"], <1> + "limit": 9, + "other_bucket": { "include_documents_without_field": false }, + "rank_by": { "type": "metric", "metric_index": 0, "direction": "desc" }, + "color": { "mode": "categorical", "palette": "default", "mapping": [] } + } + ], + "group_breakdown_by": [ + { + "operation": "terms", + "fields": ["geoip.continent_name"], <2> + "limit": 9, + "other_bucket": { "include_documents_without_field": false }, + "rank_by": { "type": "metric", "metric_index": 0, "direction": "desc" } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_ecommerce", + "time_field": "order_date" + }, + "styling": { "values": { "visible": true, "mode": "percentage" } } +}' +``` + +1. `category.keyword` on the vertical axis (`group_by`) creates one row per product category. The `color` mapping assigns a distinct palette color to each category, making each category trackable across columns. +2. `geoip.continent_name` on the horizontal axis (`group_breakdown_by`) creates one column per continent, with width proportional to order volume from that region. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: diff --git a/explore-analyze/visualize/charts/pie-charts.md b/explore-analyze/visualize/charts/pie-charts.md index 182d9454e5..4b696f22ed 100644 --- a/explore-analyze/visualize/charts/pie-charts.md +++ b/explore-analyze/visualize/charts/pie-charts.md @@ -88,6 +88,122 @@ Donut charts are pie charts with a hollow center. The empty space can provide a ![Setting the donut hole size in Pie chart Style settings](/explore-analyze/images/pie-chart-donut.png "50%") +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example creates a donut chart sliced by the top 5 log tag values, with raw counts shown inside each slice. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "pie", <1> + "title": "Logs per type", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "visible", "nested": false }, + "metrics": [{ "operation": "count", "empty_as_null": true }], + "group_by": [ + { + "operation": "terms", + "fields": ["tags.keyword"], + "limit": 5, + "other_bucket": { "include_documents_without_field": false }, + "rank_by": { "type": "metric", "metric_index": 0, "direction": "desc" }, + "color": { + "mode": "categorical", + "palette": "default", + "mapping": [ <2> + { "values": ["success"], "color": { "type": "from_palette", "palette": "default", "index": 0 } }, + { "values": ["error"], "color": { "type": "from_palette", "palette": "default", "index": 6 } }, + { "values": ["info"], "color": { "type": "from_palette", "palette": "default", "index": 3 } }, + { "values": ["warning"], "color": { "type": "from_palette", "palette": "default", "index": 9 } }, + { "values": ["security"],"color": { "type": "from_palette", "palette": "default", "index": 4 } } + ] + } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + }, + "styling": { + "donut_hole": "m", <3> + "labels": { "visible": true, "position": "inside" }, + "values": { "visible": true, "mode": "absolute" } + } +} +``` + +1. `pie` creates a pie or donut chart. The `donut_hole` style setting controls the size of the center hole. +2. `color.mapping` pins each tag value to a specific palette index so colors stay consistent even when data changes. +3. `donut_hole: "m"` hollows out the center of the pie. Valid values are `"s"` (small), `"m"` (medium), and `"l"` (large). + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "pie", <1> + "title": "Logs per type", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "visible", "nested": false }, + "metrics": [{ "operation": "count", "empty_as_null": true }], + "group_by": [ + { + "operation": "terms", + "fields": ["tags.keyword"], + "limit": 5, + "other_bucket": { "include_documents_without_field": false }, + "rank_by": { "type": "metric", "metric_index": 0, "direction": "desc" }, + "color": { + "mode": "categorical", + "palette": "default", + "mapping": [ <2> + { "values": ["success"], "color": { "type": "from_palette", "palette": "default", "index": 0 } }, + { "values": ["error"], "color": { "type": "from_palette", "palette": "default", "index": 6 } }, + { "values": ["info"], "color": { "type": "from_palette", "palette": "default", "index": 3 } }, + { "values": ["warning"], "color": { "type": "from_palette", "palette": "default", "index": 9 } }, + { "values": ["security"],"color": { "type": "from_palette", "palette": "default", "index": 4 } } + ] + } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + }, + "styling": { + "donut_hole": "m", <3> + "labels": { "visible": true, "position": "inside" }, + "values": { "visible": true, "mode": "absolute" } + } +}' +``` + +1. `pie` creates a pie or donut chart. The `donut_hole` style setting controls the size of the center hole. +2. `color.mapping` pins each tag value to a specific palette index so colors stay consistent even when data changes. +3. `donut_hole: "m"` hollows out the center of the pie. Valid values are `"s"` (small), `"m"` (medium), and `"l"` (large). + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: + ### Compare multiple metrics in a pie chart [multiple-metrics] By default, pie charts use a single metric with **Slice by** to split that metric by values in a categorical field. The **Multiple metrics** option lets you take a different approach: each slice represents a distinct metric you define, rather than values from your data. @@ -100,7 +216,7 @@ This is useful when: #### Example: Server resource consumption -Imagine your web logs data includes multiple numeric fields representing different resource types, such as `response_bytes`, `processing_time_ms`, and `memory_used_kb`. You want to visualize how each resource contributes to overall server load. +This example uses the **Kibana Sample Data Logs** data set and groups requests by file extension to approximate resource usage: `.zip` files map to processing time, `.gz` files map to bandwidth, and `.rpm` files map to memory usage. 1. Create a **Pie** chart and remove any existing **Slice by** dimension. 2. Open **Layer settings**: @@ -109,23 +225,135 @@ Imagine your web logs data includes multiple numeric fields representing differe 3. Select **Multiple metrics**, then close the **Layer settings** menu. 4. Add metrics for each resource type: - | Slice | Metric configuration | - |-------|---------------------| - | Bandwidth | `Sum(response_bytes)` | - | Processing time | `Sum(processing_time_ms)` | - | Memory usage | `Sum(memory_used_kb)` | + | Slice | Metric configuration | Filter | + |-------|---------------------|--------| + | Processing time | `Count of records` | `extension: zip` | + | Bandwidth | `Count of records` | `extension: gz` | + | Memory usage | `Count of records` | `extension: rpm` | - For each metric, select the **Metric** dimension and configure the field. Customize the **Name** to display meaningful labels in the legend. + For each metric, select the **Metric** dimension, set the function to **Count**, add a KQL filter in the **Advanced** section, and customize the **Name** to display meaningful labels. -5. Optionally, assign custom colors to each metric by selecting the metric and choosing a **Series color**. +5. Assign a custom color to each metric by selecting the metric and choosing a **Series color**. ![Pie chart with three slices showing different server resource metrics](/explore-analyze/images/pie-chart-multiple-metrics-example.png) -This example demonstrates the core value of multiple metrics: comparing different numeric fields that represent distinct concepts. Unlike **Slice by**, which splits a single metric by category values, multiple metrics lets you place fundamentally different measurements side by side. +This example demonstrates the core value of multiple metrics: using KQL filters on each metric to compare distinct categories. Unlike **Slice by**, which splits a single metric by category values, multiple metrics lets you define completely custom groupings — even those that don't exist as a field in your data. + +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example uses multiple metrics mode (no `group_by`) where each metric counts documents matching a specific file extension, with each metric assigned a static color. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "pie", + "title": "Server resource consumption", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "auto", "nested": false }, + "metrics": [ <1> + { + "operation": "count", + "empty_as_null": true, + "label": "Processing time", <2> + "filter": { "expression": "extension: zip", "language": "kql" }, + "color": { "type": "static", "color": "#eaae01" } + }, + { + "operation": "count", + "empty_as_null": true, + "label": "Bandwidth", + "filter": { "expression": "extension: gz", "language": "kql" }, + "color": { "type": "static", "color": "#61a2ff" } + }, + { + "operation": "count", + "empty_as_null": true, + "label": "Memory usage", + "filter": { "expression": "extension: rpm", "language": "kql" }, + "color": { "type": "static", "color": "#16c5c0" } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + }, + "styling": { + "labels": { "visible": true, "position": "outside" }, + "values": { "visible": true, "mode": "percentage" } + } +} +``` + +1. Three entries in `metrics` without a `group_by` activates multiple-metrics mode, where each metric becomes its own slice. +2. Each metric uses a `filter` to scope its count to a specific file extension, and a `color` with `type: "static"` to pin it to a specific hex color regardless of palette. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "pie", + "title": "Server resource consumption", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "auto", "nested": false }, + "metrics": [ <1> + { + "operation": "count", + "empty_as_null": true, + "label": "Processing time", <2> + "filter": { "expression": "extension: zip", "language": "kql" }, + "color": { "type": "static", "color": "#eaae01" } + }, + { + "operation": "count", + "empty_as_null": true, + "label": "Bandwidth", + "filter": { "expression": "extension: gz", "language": "kql" }, + "color": { "type": "static", "color": "#61a2ff" } + }, + { + "operation": "count", + "empty_as_null": true, + "label": "Memory usage", + "filter": { "expression": "extension: rpm", "language": "kql" }, + "color": { "type": "static", "color": "#16c5c0" } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + }, + "styling": { + "labels": { "visible": true, "position": "outside" }, + "values": { "visible": true, "mode": "percentage" } + } +}' +``` + +1. Three entries in `metrics` without a `group_by` activates multiple-metrics mode, where each metric becomes its own slice. +2. Each metric uses a `filter` to scope its count to a specific file extension, and a `color` with `type: "static"` to pin it to a specific hex color regardless of palette. + +:::: -:::{note} -The {{kib}} sample data sets don't include multiple comparable numeric fields. To try this scenario, adapt the field names to match your own data. -::: +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: ### Group smaller values into a single slice [other-category] @@ -137,7 +365,7 @@ When you have many categories with small values, you can group them into an "Oth 4. Enable **Group other values as "Other"** to combine remaining values into a single slice. :::{tip} -Be careful when using "Other", as it could end up being the largest category, which might obscure the meaning of your chart. Consider whether a bar chart might be more appropriate for data with many categories. +Be careful when using "Other," as it could end up being the largest category, which might obscure the meaning of your chart. Consider whether a bar chart might be more appropriate for data with many categories. ::: #### Example: Top hosts with remaining grouped as "Other" @@ -153,6 +381,94 @@ The resulting chart shows the 3 most common hosts, with all remaining hosts comb ![Pie chart showing top 3 hosts with remaining hosts grouped as Other](/explore-analyze/images/pie-chart-group-as-other.png) +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example shows the top 3 hosts as individual slices and groups all remaining hosts into an "Other" slice using the `other_bucket` option. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "pie", + "title": "Top hosts with Other", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "auto", "nested": false }, + "metrics": [{ "operation": "count", "empty_as_null": true }], + "group_by": [ + { + "operation": "terms", + "fields": ["host.keyword"], + "limit": 3, <1> + "other_bucket": { "include_documents_without_field": true } <2> + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + }, + "styling": { + "labels": { "visible": true, "position": "inside" }, + "values": { "visible": true, "mode": "percentage" } + } +} +``` + +1. `limit: 3` limits the chart to the top 3 hosts by count. +2. `other_bucket` groups every remaining host into a single "Other" slice, including documents that lack the field entirely. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "pie", + "title": "Top hosts with Other", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "auto", "nested": false }, + "metrics": [{ "operation": "count", "empty_as_null": true }], + "group_by": [ + { + "operation": "terms", + "fields": ["host.keyword"], + "limit": 3, <1> + "other_bucket": { "include_documents_without_field": true } <2> + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + }, + "styling": { + "labels": { "visible": true, "position": "inside" }, + "values": { "visible": true, "mode": "percentage" } + } +}' +``` + +1. `limit: 3` limits the chart to the top 3 hosts by count. +2. `other_bucket` groups every remaining host into a single "Other" slice, including documents that lack the field entirely. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: + ## Pie chart settings [pie-chart-settings] Customize your pie chart to display exactly the information you need, formatted the way you want. @@ -267,6 +583,11 @@ Configure elements of your pie chart's legend: ## Pie chart examples + + The following examples show various configuration options for building impactful pie charts. **Website traffic by source** @@ -284,6 +605,130 @@ The following examples show various configuration options for building impactful ![Donut chart with traffic by sources](/explore-analyze/images/pie-chart-example-traffic-by-source.png "=60%") +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example uses formula-based metrics to count visits from specific referrer domains, producing one slice per traffic source without a `group_by` dimension. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "pie", + "title": "Website traffic by source", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "visible", "nested": false }, + "metrics": [ + { + "operation": "formula", <1> + "formula": "count(kql='referer : *elastic*')", <2> + "label": "Elastic website", + "color": { "type": "auto" } + }, + { + "operation": "formula", + "formula": "count(kql='referer : *facebook*')", + "label": "Facebook", + "color": { "type": "auto" } + }, + { + "operation": "formula", + "formula": "count(kql='referer:*twitter*')", + "label": "Twitter/X", + "color": { "type": "auto" } + }, + { + "operation": "formula", + "formula": "count(kql='referer : *nytimes*')", + "label": "NY Times", + "color": { "type": "auto" } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + }, + "styling": { + "donut_hole": "m", + "labels": { "visible": true, "position": "outside" }, + "values": { "visible": true, "mode": "percentage" } + } +} +``` + +1. `formula` lets each metric apply its own KQL filter, creating slices that represent custom-defined categories. +2. The `kql` parameter inside `count()` filters documents to only those whose `referer` field matches the given pattern. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "pie", + "title": "Website traffic by source", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "visible", "nested": false }, + "metrics": [ + { + "operation": "formula", <1> + "formula": "count(kql='referer : *elastic*')", <2> + "label": "Elastic website", + "color": { "type": "auto" } + }, + { + "operation": "formula", + "formula": "count(kql='referer : *facebook*')", + "label": "Facebook", + "color": { "type": "auto" } + }, + { + "operation": "formula", + "formula": "count(kql='referer:*twitter*')", + "label": "Twitter/X", + "color": { "type": "auto" } + }, + { + "operation": "formula", + "formula": "count(kql='referer : *nytimes*')", + "label": "NY Times", + "color": { "type": "auto" } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + }, + "styling": { + "donut_hole": "m", + "labels": { "visible": true, "position": "outside" }, + "values": { "visible": true, "mode": "percentage" } + } +}' +``` + +1. `formula` lets each metric apply its own KQL filter, creating slices that represent custom-defined categories. +2. The `kql` parameter inside `count()` filters documents to only those whose `referer` field matches the given pattern. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: + **Revenue distribution by product category** : Show how revenue is distributed across product categories: @@ -294,6 +739,110 @@ The following examples show various configuration options for building impactful ![Pie chart with revenue by product category](/explore-analyze/images/pie-chart-example-revenue-by-product-category.png "=60%") +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example creates a pie chart that sums revenue per product category from the eCommerce sample data, showing each category's share of total revenue. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "pie", + "title": "Revenue distribution by product category", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "auto", "nested": false }, + "metrics": [ + { + "operation": "sum", <1> + "field": "taxful_total_price", + "empty_as_null": true + } + ], + "group_by": [ + { + "operation": "terms", + "fields": ["category.keyword"], <2> + "limit": 6, + "other_bucket": { "include_documents_without_field": false }, + "rank_by": { "type": "metric", "metric_index": 0, "direction": "desc" }, + "color": { "mode": "categorical", "palette": "default", "mapping": [] } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_ecommerce", + "time_field": "order_date" + }, + "styling": { + "labels": { "visible": true, "position": "outside" }, + "values": { "visible": true, "mode": "percentage" } + } +} +``` + +1. `sum` on `taxful_total_price` sizes each slice by total revenue rather than document count. +2. `category.keyword` splits the pie by product category, with each of the top 6 categories becoming its own slice. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "pie", + "title": "Revenue distribution by product category", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "auto", "nested": false }, + "metrics": [ + { + "operation": "sum", <1> + "field": "taxful_total_price", + "empty_as_null": true + } + ], + "group_by": [ + { + "operation": "terms", + "fields": ["category.keyword"], <2> + "limit": 6, + "other_bucket": { "include_documents_without_field": false }, + "rank_by": { "type": "metric", "metric_index": 0, "direction": "desc" }, + "color": { "mode": "categorical", "palette": "default", "mapping": [] } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_ecommerce", + "time_field": "order_date" + }, + "styling": { + "labels": { "visible": true, "position": "outside" }, + "values": { "visible": true, "mode": "percentage" } + } +}' +``` + +1. `sum` on `taxful_total_price` sizes each slice by total revenue rather than document count. +2. `category.keyword` splits the pie by product category, with each of the top 6 categories becoming its own slice. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: + **Error distribution by type** : Display the proportion of different error types in your application: @@ -307,3 +856,135 @@ The following examples show various configuration options for building impactful * **Color mapping**: Red for client errors, yellow for server errors, green for success ![Donut chart with distribution of errors by type](/explore-analyze/images/pie-chart-example-response-distribution.png "=60%") + +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example uses the `filters` grouping operation to define custom slices based on HTTP response code ranges, rather than relying on field values directly. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "pie", + "title": "Error distribution by type", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "auto", "nested": false }, + "metrics": [ + { + "operation": "count", + "empty_as_null": true + } + ], + "group_by": [ + { + "operation": "filters", <1> + "filters": [ + { + "filter": { "expression": "response.keyword >= \"400\" AND response.keyword < \"500\"" }, + "label": "Client Error" + }, + { "filter": { "expression": "response.keyword >= \"500\"" }, "label": "Server Error" }, + { + "filter": { "expression": "response.keyword >= \"200\" AND response.keyword < \"400\"" }, + "label": "Success" + } + ], + "color": { + "mode": "categorical", + "palette": "default", + "mapping": [ + { "values": ["Client Error"], "color": { "type": "color_code", "value": "#CC5642" } }, + { "values": ["Server Error"], "color": { "type": "color_code", "value": "#D6BF57" } }, + { "values": ["Success"], "color": { "type": "color_code", "value": "#209280" } } + ] + } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + }, + "styling": { + "donut_hole": "l", + "labels": { "visible": true, "position": "inside" }, + "values": { "visible": true, "mode": "percentage" } + } +} +``` + +1. `filters` creates one slice per KQL query, letting you define arbitrary categories such as "Client Error" (4xx), "Server Error" (5xx), and "Success" (2xx/3xx). The `color` mapping assigns explicit hex colors to each category — red for client errors, yellow for server errors, green for success. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "pie", + "title": "Error distribution by type", + "filters": [], + "query": { "expression": "" }, + "legend": { "visibility": "auto", "nested": false }, + "metrics": [ + { + "operation": "count", + "empty_as_null": true + } + ], + "group_by": [ + { + "operation": "filters", <1> + "filters": [ + { + "filter": { "expression": "response.keyword >= \"400\" AND response.keyword < \"500\"" }, + "label": "Client Error" + }, + { "filter": { "expression": "response.keyword >= \"500\"" }, "label": "Server Error" }, + { + "filter": { "expression": "response.keyword >= \"200\" AND response.keyword < \"400\"" }, + "label": "Success" + } + ], + "color": { + "mode": "categorical", + "palette": "default", + "mapping": [ + { "values": ["Client Error"], "color": { "type": "color_code", "value": "#CC5642" } }, + { "values": ["Server Error"], "color": { "type": "color_code", "value": "#D6BF57" } }, + { "values": ["Success"], "color": { "type": "color_code", "value": "#209280" } } + ] + } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + }, + "styling": { + "donut_hole": "l", + "labels": { "visible": true, "position": "inside" }, + "values": { "visible": true, "mode": "percentage" } + } +}' +``` + +1. `filters` creates one slice per KQL query, letting you define arbitrary categories such as "Client Error" (4xx), "Server Error" (5xx), and "Success" (2xx/3xx). The `color` mapping assigns explicit hex colors to each category — red for client errors, yellow for server errors, green for success. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: diff --git a/explore-analyze/visualize/charts/region-map-charts.md b/explore-analyze/visualize/charts/region-map-charts.md index de3188d74e..016cd92156 100644 --- a/explore-analyze/visualize/charts/region-map-charts.md +++ b/explore-analyze/visualize/charts/region-map-charts.md @@ -119,6 +119,11 @@ The **Metric** dimension defines the value that determines each region's color. ## Region map chart examples + + The following examples show various configuration options for building impactful region map charts. **Website traffic by destination country** @@ -131,6 +136,102 @@ The following examples show various configuration options for building impactful ![Region map showing website traffic by destination country](/explore-analyze/images/region-map-example-traffic.png "=70%") +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example creates a choropleth map that colors countries by request count, using the `geo.dest` field which contains two-letter ISO country codes. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "region_map", <1> + "title": "Website traffic by destination country", + "filters": [], + "query": { "expression": "" }, + "metric": { + "operation": "count", + "format": { + "type": "number" + }, + "filter": { "expression": "" } + }, + "region": { + "operation": "terms", + "fields": ["geo.dest"], <2> + "limit": 50, <3> + "ems": { <4> + "boundaries": "world_countries", + "join": "iso2" + } + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + } +} +``` + +1. `region_map` renders a geographic choropleth where region color intensity reflects the metric value. +2. `geo.dest` contains ISO country codes that are matched to EMS world country boundaries. +3. `limit: 50` includes up to 50 countries, providing broad geographic coverage. +4. `ems` sets the EMS boundary layer (`world_countries`) and the join field (`iso2`) used to match `geo.dest` values to map regions. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "region_map", <1> + "title": "Website traffic by destination country", + "filters": [], + "query": { "expression": "" }, + "metric": { + "operation": "count", + "format": { + "type": "number" + }, + "filter": { "expression": "" } + }, + "region": { + "operation": "terms", + "fields": ["geo.dest"], <2> + "limit": 50, <3> + "ems": { <4> + "boundaries": "world_countries", + "join": "iso2" + } + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + } +}' +``` + +1. `region_map` renders a geographic choropleth where region color intensity reflects the metric value. +2. `geo.dest` contains ISO country codes that are matched to EMS world country boundaries. +3. `limit: 50` includes up to 50 countries, providing broad geographic coverage. +4. `ems` sets the EMS boundary layer (`world_countries`) and the join field (`iso2`) used to match `geo.dest` values to map regions. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: + **Customer distribution by country** : Show where your customers are located around the world: @@ -141,6 +242,98 @@ The following examples show various configuration options for building impactful ![Region map showing customer distribution by country](/explore-analyze/images/region-map-example-customers.png "=70%") +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example colors countries by the number of unique customers rather than total orders, giving a clearer picture of market reach per region. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "region_map", + "title": "Customer distribution by country", + "filters": [], + "query": { "expression": "" }, + "metric": { + "operation": "unique_count", <1> + "field": "customer_id", + "label": "Unique customers", + "format": { "type": "number" }, + "filter": { "expression": "" } + }, + "region": { + "operation": "terms", + "fields": ["geoip.country_iso_code"], <2> + "limit": 50, + "ems": { + "boundaries": "world_countries", + "join": "iso2" + } + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_ecommerce", + "time_field": "order_date" + } +} +``` + +1. `unique_count` on `customer_id` counts distinct customers per country, avoiding inflation from repeat buyers. +2. `geoip.country_iso_code` provides ISO codes that map directly to EMS world country boundaries via the `iso2` join field. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "region_map", + "title": "Customer distribution by country", + "filters": [], + "query": { "expression": "" }, + "metric": { + "operation": "unique_count", <1> + "field": "customer_id", + "label": "Unique customers", + "format": { "type": "number" }, + "filter": { "expression": "" } + }, + "region": { + "operation": "terms", + "fields": ["geoip.country_iso_code"], <2> + "limit": 50, + "ems": { + "boundaries": "world_countries", + "join": "iso2" + } + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_ecommerce", + "time_field": "order_date" + } +}' +``` + +1. `unique_count` on `customer_id` counts distinct customers per country, avoiding inflation from repeat buyers. +2. `geoip.country_iso_code` provides ISO codes that map directly to EMS world country boundaries via the `iso2` join field. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: + **Average ticket price by destination country** : Compare average flight ticket prices across destination countries: @@ -150,3 +343,95 @@ The following examples show various configuration options for building impactful * **Metric**: Average of `AvgTicketPrice` ![Region map showing average ticket price by destination country](/explore-analyze/images/region-map-example-ticket-price.png "=70%") + +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example maps average flight ticket prices by destination country, highlighting which regions have the most expensive flights. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "region_map", + "title": "Average ticket price by destination country", + "filters": [], + "query": { "expression": "" }, + "metric": { + "operation": "average", <1> + "field": "AvgTicketPrice", + "label": "Average ticket price", + "format": { "type": "number" }, + "filter": { "expression": "" } + }, + "region": { + "operation": "terms", + "fields": ["DestCountry"], <2> + "limit": 50, + "ems": { + "boundaries": "world_countries", + "join": "iso2" + } + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_flights", + "time_field": "timestamp" + } +} +``` + +1. `average` on `AvgTicketPrice` computes the mean ticket price per country, making color intensity reflect cost rather than volume. +2. `DestCountry` contains ISO country codes from the flights sample data, matched to EMS world boundaries. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "region_map", + "title": "Average ticket price by destination country", + "filters": [], + "query": { "expression": "" }, + "metric": { + "operation": "average", <1> + "field": "AvgTicketPrice", + "label": "Average ticket price", + "format": { "type": "number" }, + "filter": { "expression": "" } + }, + "region": { + "operation": "terms", + "fields": ["DestCountry"], <2> + "limit": 50, + "ems": { + "boundaries": "world_countries", + "join": "iso2" + } + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_flights", + "time_field": "timestamp" + } +}' +``` + +1. `average` on `AvgTicketPrice` computes the mean ticket price per country, making color intensity reflect cost rather than volume. +2. `DestCountry` contains ISO country codes from the flights sample data, matched to EMS world boundaries. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: diff --git a/explore-analyze/visualize/charts/tables.md b/explore-analyze/visualize/charts/tables.md index 2de5cdf35e..5757507a79 100644 --- a/explore-analyze/visualize/charts/tables.md +++ b/explore-analyze/visualize/charts/tables.md @@ -55,7 +55,7 @@ Using the dropdown indicating **Bar**, select **Table**. The table preview updates to show your metrics as columns. If you added row dimensions, each unique value creates a separate row. If you added a **Split metrics by** dimension, metrics are broken into multiple columns by category. -Refer to [](#settings) to find all configuration options for your table. +See [](#settings) for all configuration options for your table. :::: ::::{step} Customize the table to follow best practices @@ -99,7 +99,7 @@ To create a pivot table: 3. Add one or more metrics. 4. Drag a categorical field to **Split metrics by** to create separate columns for each unique value. -For example, you could show visits per date in rows, split by the top 3 hours of the day with most traffic, and add various metrics such as the number of visits or the percentage of successful requests. This creates a pivot table showing the various metrics for each hour of the day with the most traffic. +For example, you could show visits per date in rows, split by the top 3 HTTP response codes, and add various metrics such as the number of unique visitors, total bytes, or the percentage of successful requests. This creates a pivot table showing the various metrics for each response code. ![Example of a table in Lens using the Split metrics by functionality](../../images/lens-table-breakdown-by-example.png) @@ -258,6 +258,11 @@ When creating or editing a table visualization, you can customize several appear ## Table examples + + The following examples show various configuration options you can use for building effective tables. **Top pages by unique visitors** @@ -265,11 +270,108 @@ The following examples show various configuration options you can use for buildi * **Rows**: `request.keyword` field using **Top values** function * **Number of values**: `5` + * **Advanced**: Group remaining values as "Other" * **Metrics**: `clientip` field using **Unique count** function * **Value format**: `Number` * **Text alignment**: `Right` - ![Table showing top pages by unique visitors](../../images/kibana-table-with-request-keyword-and-client-ip-8.16.0.png "=70%") +![Table showing top pages by unique visitors](../../images/kibana-table-with-request-keyword-and-client-ip-8.16.0.png "=70%") + +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +Send the following request to create a table that displays the top 5 request pages ranked by unique visitor count. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "data_table", + "title": "Top pages by unique visitors", + "filters": [], + "query": { "expression": "" }, + "rows": [ + { + "operation": "terms", <1> + "fields": ["request.keyword"], + "limit": 5, + "other_bucket": { "include_documents_without_field": false } + } + ], + "metrics": [ + { + "operation": "unique_count", <2> + "field": "clientip", + "label": "Unique visitors", + "format": { "type": "number" }, + "filter": { "expression": "" } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + }, + "styling": { "density": { "mode": "default" } } +} +``` + +1. Uses `terms` on `request.keyword` to create one row per top page, limited to 5. +2. Counts unique values of `clientip` to measure distinct visitors per page. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "data_table", + "title": "Top pages by unique visitors", + "filters": [], + "query": { "expression": "" }, + "rows": [ + { + "operation": "terms", <1> + "fields": ["request.keyword"], + "limit": 5, + "other_bucket": { "include_documents_without_field": false } + } + ], + "metrics": [ + { + "operation": "unique_count", <2> + "field": "clientip", + "label": "Unique visitors", + "format": { "type": "number" }, + "filter": { "expression": "" } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + }, + "styling": { "density": { "mode": "default" } } +}' +``` + +1. Uses `terms` on `request.keyword` to create one row per top page, limited to 5. +2. Counts unique values of `clientip` to measure distinct visitors per page. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: **Sales by date and continent (pivot table)** : Create a pivot table showing customer counts across different continents over time: @@ -279,8 +381,123 @@ The following examples show various configuration options you can use for buildi * **Name**: `Sales per day` * **Metrics**: `customer_id` field using **Unique count** function * **Split metrics by**: `geoip.continent_name` field using **Top values** set to `3` + * **Advanced**: Group remaining values as "Other" + +![Table showing customers over time by continent](../../images/kibana-lens_table_over_time.png "=70%") + +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +Send the following request to create a pivot table that shows unique customer counts per day, split into columns by the top 3 continents. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "data_table", + "title": "Sales by date and continent", + "filters": [], + "query": { "expression": "" }, + "rows": [ + { + "operation": "date_histogram", <1> + "field": "order_date", + "suggested_interval": "1d", + "label": "Sales per day" + } + ], + "metrics": [ + { + "operation": "unique_count", <2> + "field": "customer_id", + "label": "Unique customers", + "format": { "type": "number", "decimals": 0 }, + "filter": { "expression": "" } + } + ], + "split_metrics_by": [ <3> + { + "operation": "terms", + "fields": ["geoip.continent_name"], + "limit": 3, + "other_bucket": { "include_documents_without_field": false } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_ecommerce", + "time_field": "order_date" + }, + "styling": { "density": { "mode": "default" } } +} +``` + +1. Groups rows by `order_date` using a date histogram. +2. Counts unique `customer_id` values as the table metric. +3. Splits the metric into separate columns for the top 3 continents. - ![Table showing customers over time by continent](../../images/kibana-lens_table_over_time.png "=70%") +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "data_table", + "title": "Sales by date and continent", + "filters": [], + "query": { "expression": "" }, + "rows": [ + { + "operation": "date_histogram", <1> + "field": "order_date", + "suggested_interval": "1d", + "label": "Sales per day" + } + ], + "metrics": [ + { + "operation": "unique_count", <2> + "field": "customer_id", + "label": "Unique customers", + "format": { "type": "number", "decimals": 0 }, + "filter": { "expression": "" } + } + ], + "split_metrics_by": [ <3> + { + "operation": "terms", + "fields": ["geoip.continent_name"], + "limit": 3, + "other_bucket": { "include_documents_without_field": false } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_ecommerce", + "time_field": "order_date" + }, + "styling": { "density": { "mode": "default" } } +}' +``` + +1. Groups rows by `order_date` using a date histogram. +2. Counts unique `customer_id` values as the table metric. +3. Splits the metric into separate columns for the top 3 continents. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: **Document comparison with custom ranges** : Compare metrics across custom-defined ranges: @@ -294,9 +511,119 @@ The following examples show various configuration options you can use for buildi * **Name**: `Total bytes transferred` * **Value format**: `Bytes` * **Text alignment**: `Right` - * **Additional styling**: + * **Additional styling**: * **Color by value**: Dynamic coloring to highlight ranges with higher byte transfers +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +Send the following request to create a table that compares total bytes transferred across custom-defined file size ranges. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "data_table", + "title": "Document comparison with custom ranges", + "filters": [], + "query": { "expression": "" }, + "rows": [ + { + "operation": "range", <1> + "field": "bytes", + "ranges": [ + { "lte": 10240, "label": "Below 10KB" }, + { "gt": 10240, "label": "Above 10KB" } + ], <2> + "label": "File size" + } + ], + "metrics": [ + { + "operation": "sum", <3> + "field": "bytes", + "label": "Total bytes transferred", + "format": { "type": "bytes" }, + "alignment": "right", + "color": { "type": "auto" }, + "apply_color_to": "value", + "filter": { "expression": "" } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + }, + "styling": { "density": { "mode": "default" } } +} +``` + +1. Uses a `range` operation to create custom row buckets from the `bytes` field. +2. Defines two ranges, each with a label: documents up to 10 KB (`Below 10KB`) and documents above 10 KB (`Above 10KB`). +3. The `sum` metric sums the `bytes` field within each range, right-aligns the column, and applies dynamic `auto` coloring to highlight ranges with higher byte transfers. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "data_table", + "title": "Document comparison with custom ranges", + "filters": [], + "query": { "expression": "" }, + "rows": [ + { + "operation": "range", <1> + "field": "bytes", + "ranges": [ + { "lte": 10240, "label": "Below 10KB" }, + { "gt": 10240, "label": "Above 10KB" } + ], <2> + "label": "File size" + } + ], + "metrics": [ + { + "operation": "sum", <3> + "field": "bytes", + "label": "Total bytes transferred", + "format": { "type": "bytes" }, + "alignment": "right", + "color": { "type": "auto" }, + "apply_color_to": "value", + "filter": { "expression": "" } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + }, + "styling": { "density": { "mode": "default" } } +}' +``` + +1. Uses a `range` operation to create custom row buckets from the `bytes` field. +2. Defines two ranges, each with a label: documents up to 10 KB (`Below 10KB`) and documents above 10 KB (`Above 10KB`). +3. The `sum` metric sums the `bytes` field within each range, right-aligns the column, and applies dynamic `auto` coloring to highlight ranges with higher byte transfers. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: + **Weekly sales with percentage change** : Show week-over-week sales trends with calculated percentage changes: @@ -306,8 +633,118 @@ The following examples show various configuration options you can use for buildi * **Metrics** (two columns): 1. `Records` using **Count** function * **Name**: `Orders this week` + * **Value format**: `Number`, 0 decimals 2. **Formula**: `count() / count(shift='1w') - 1` * **Name**: `Change from last week` * **Value format**: `Percent`, 2 decimals - * **Color by value**: Dynamic (green for positive growth, red for negative) - * **Text alignment**: `Right` \ No newline at end of file + * **Text alignment**: `Right` + +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +Send the following request to create a table that shows weekly order counts alongside a formula-based percentage change column. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "data_table", + "title": "Weekly sales with percentage change", + "filters": [], + "query": { "expression": "" }, + "rows": [ + { + "operation": "date_histogram", <1> + "field": "order_date", + "suggested_interval": "1w", + "label": "Week" + } + ], + "metrics": [ + { + "operation": "count", + "label": "Orders this week", + "format": { "type": "number", "decimals": 0 }, + "filter": { "expression": "" } + }, + { + "operation": "formula", <2> + "formula": "count() / count(shift='1w') - 1", <3> + "label": "Change from last week", + "format": { "type": "percent", "decimals": 2 }, + "alignment": "right" + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_ecommerce", + "time_field": "order_date" + }, + "styling": { "density": { "mode": "default" } } +} +``` + +1. Groups rows by `order_date` with a weekly date histogram. +2. Uses a `formula` metric to compute a calculated column. Formula metrics must not include a `filter` field — omitting it is required for time shift to work correctly. +3. Divides the current week's count by the previous week's count and subtracts 1 to get the percentage change. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "data_table", + "title": "Weekly sales with percentage change", + "filters": [], + "query": { "expression": "" }, + "rows": [ + { + "operation": "date_histogram", <1> + "field": "order_date", + "suggested_interval": "1w", + "label": "Week" + } + ], + "metrics": [ + { + "operation": "count", + "label": "Orders this week", + "format": { "type": "number", "decimals": 0 }, + "filter": { "expression": "" } + }, + { + "operation": "formula", <2> + "formula": "count() / count(shift='1w') - 1", <3> + "label": "Change from last week", + "format": { "type": "percent", "decimals": 2 }, + "alignment": "right" + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_ecommerce", + "time_field": "order_date" + }, + "styling": { "density": { "mode": "default" } } +}' +``` + +1. Groups rows by `order_date` with a weekly date histogram. +2. Uses a `formula` metric to compute a calculated column. Formula metrics must not include a `filter` field — omitting it is required for time shift to work correctly. +3. Divides the current week's count by the previous week's count and subtracts 1 to get the percentage change. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: diff --git a/explore-analyze/visualize/charts/tag-cloud-charts.md b/explore-analyze/visualize/charts/tag-cloud-charts.md index 5757f65d23..465b250c8e 100644 --- a/explore-analyze/visualize/charts/tag-cloud-charts.md +++ b/explore-analyze/visualize/charts/tag-cloud-charts.md @@ -140,6 +140,11 @@ When creating or editing a visualization, you can customize several appearance o ## Tag cloud chart examples + + The following examples show various configuration options for building impactful tag cloud charts. **Popular request URLs** @@ -152,13 +157,199 @@ The following examples show various configuration options for building impactful ![Tag cloud showing popular request URLs](/explore-analyze/images/tag-cloud-example-urls.png "=70%") +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example creates a tag cloud of the 30 most frequently requested URLs, where tag size reflects how often each URL was accessed. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "tag_cloud", + "title": "Popular request URLs", + "filters": [], + "query": { "expression": "" }, + "metric": { "operation": "count", "empty_as_null": true }, + "tag_by": { + "operation": "terms", + "fields": ["request.keyword"], <1> + "limit": 30, <2> + "rank_by": { "type": "metric", "metric_index": 0, "direction": "desc" }, + "color": { "mode": "categorical", "palette": "default", "mapping": [] } + }, + "styling": { + "orientation": "horizontal", + "font_size": { "min": 18, "max": 72 }, + "caption": { "visible": true } + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + } +} +``` + +1. `request.keyword` provides the text for each tag, showing the full URL path. Tags are ranked by count so the most-visited URLs appear largest. +2. `limit: 30` displays the top 30 URLs, which is within the recommended range for readable tag clouds. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "tag_cloud", + "title": "Popular request URLs", + "filters": [], + "query": { "expression": "" }, + "metric": { "operation": "count", "empty_as_null": true }, + "tag_by": { + "operation": "terms", + "fields": ["request.keyword"], <1> + "limit": 30, <2> + "rank_by": { "type": "metric", "metric_index": 0, "direction": "desc" }, + "color": { "mode": "categorical", "palette": "default", "mapping": [] } + }, + "styling": { + "orientation": "horizontal", + "font_size": { "min": 18, "max": 72 }, + "caption": { "visible": true } + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + } +}' +``` + +1. `request.keyword` provides the text for each tag, showing the full URL path. Tags are ranked by count so the most-visited URLs appear largest. +2. `limit: 30` displays the top 30 URLs, which is within the recommended range for readable tag clouds. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: + **Most popular flight destinations** : Show which cities receive the most flights, with larger tags indicating higher traffic: * Example based on: {{kib}} Sample Data Flights * **Tags**: `DestCityName` (Top 30 values) * **Metric**: Count - * **Orientation**: Multiple + * **Orientation**: Angled * **Color**: Gradient ![Tag cloud showing most popular flight destinations](/explore-analyze/images/tag-cloud-example-destinations.png "=70%") + +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example creates a tag cloud of destination city names from the flights sample data, with the most popular destinations appearing in larger text. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "tag_cloud", + "title": "Most popular flight destinations", + "filters": [], + "query": { "expression": "" }, + "metric": { "operation": "count", "empty_as_null": true }, + "tag_by": { + "operation": "terms", + "fields": ["DestCityName"], <1> + "limit": 30, + "rank_by": { "type": "metric", "metric_index": 0, "direction": "desc" }, + "color": { + "mode": "gradient", <2> + "palette": "default", + "mapping": [], + "sort": "desc", + "gradient": [{ "type": "from_palette", "palette": "default", "index": 0 }] + } + }, + "styling": { + "orientation": "angled", <3> + "font_size": { "min": 18, "max": 112 }, + "caption": { "visible": true } + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_flights", + "time_field": "timestamp" + } +} +``` + +1. `DestCityName` provides human-readable city names as tag labels, sized by flight count so the busiest destinations appear largest. +2. The `gradient` color mode with `sort: "desc"` applies the palette spectrum from the most to the least popular city, reinforcing the frequency ranking with color intensity. +3. `orientation: "angled"` allows tags to appear at multiple angles, making better use of space when many cities compete for room. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "tag_cloud", + "title": "Most popular flight destinations", + "filters": [], + "query": { "expression": "" }, + "metric": { "operation": "count", "empty_as_null": true }, + "tag_by": { + "operation": "terms", + "fields": ["DestCityName"], <1> + "limit": 30, + "rank_by": { "type": "metric", "metric_index": 0, "direction": "desc" }, + "color": { + "mode": "gradient", <2> + "palette": "default", + "mapping": [], + "sort": "desc", + "gradient": [{ "type": "from_palette", "palette": "default", "index": 0 }] + } + }, + "styling": { + "orientation": "angled", <3> + "font_size": { "min": 18, "max": 112 }, + "caption": { "visible": true } + }, + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_flights", + "time_field": "timestamp" + } +}' +``` + +1. `DestCityName` provides human-readable city names as tag labels, sized by flight count so the busiest destinations appear largest. +2. The `gradient` color mode with `sort: "desc"` applies the palette spectrum from the most to the least popular city, reinforcing the frequency ranking with color intensity. +3. `orientation: "angled"` allows tags to appear at multiple angles, making better use of space when many cities compete for room. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: diff --git a/explore-analyze/visualize/charts/treemap-charts.md b/explore-analyze/visualize/charts/treemap-charts.md index 8aa7bda6f2..9a08c8560f 100644 --- a/explore-analyze/visualize/charts/treemap-charts.md +++ b/explore-analyze/visualize/charts/treemap-charts.md @@ -159,6 +159,11 @@ When creating or editing a visualization, you can customize several appearance o ## Treemap chart examples + + The following examples show various configuration options for building impactful treemap charts. **Bytes per file extension** @@ -169,9 +174,120 @@ The following examples show various configuration options for building impactful * Top 6 values * Advanced: include values matching the `.+` regular expression to exclude blank values * **Metric**: Sum of `bytes` + * **Value display**: Percentage ![Treemap showing bytes per file extension](/explore-analyze/images/treemap-example-bytes-per-extension.png "=70%") +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example creates a single-level treemap where each rectangle represents a file extension and its size reflects total bandwidth consumed. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "treemap", <1> + "title": "Bytes per file extension", + "filters": [], + "query": { "expression": "" }, + "legend": { "size": "auto" }, + "metrics": [ + { + "operation": "sum", + "field": "bytes", + "label": "Total bytes", + "format": { + "type": "number" + }, + "filter": { "expression": "" } + } + ], + "group_by": [ + { + "operation": "terms", <2> + "fields": ["extension.keyword"], + "limit": 6, + "includes": { "values": [".+"], "as_regex": true }, + "other_bucket": { "include_documents_without_field": false }, + "rank_by": { "type": "metric", "direction": "desc", "metric_index": 0 }, + "increase_accuracy": true + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + }, + "styling": { "values": { "mode": "percentage" } } <3> +} +``` + +1. `treemap` renders nested rectangles whose area is proportional to the metric value. +2. The `terms` grouping limits to the top 6 extensions ranked by `sum of bytes` (descending), uses `includes` with `as_regex: true` and pattern `.+` to exclude blank values, groups any remaining extensions into an **Other** bucket, and enables accuracy mode. +3. `percentage` displays each extension's share of total bytes instead of the raw count. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "treemap", <1> + "title": "Bytes per file extension", + "filters": [], + "query": { "expression": "" }, + "legend": { "size": "auto" }, + "metrics": [ + { + "operation": "sum", + "field": "bytes", + "label": "Total bytes", + "format": { + "type": "number" + }, + "filter": { "expression": "" } + } + ], + "group_by": [ + { + "operation": "terms", <2> + "fields": ["extension.keyword"], + "limit": 6, + "includes": { "values": [".+"], "as_regex": true }, + "other_bucket": { "include_documents_without_field": false }, + "rank_by": { "type": "metric", "direction": "desc", "metric_index": 0 }, + "increase_accuracy": true + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + }, + "styling": { "values": { "mode": "percentage" } } <3> +}' +``` + +1. `treemap` renders nested rectangles whose area is proportional to the metric value. +2. The `terms` grouping limits to the top 6 extensions ranked by `sum of bytes` (descending), uses `includes` with `as_regex: true` and pattern `.+` to exclude blank values, groups any remaining extensions into an **Other** bucket, and enables accuracy mode. +3. `percentage` displays each extension's share of total bytes instead of the raw count. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: + **Flights by carrier and destination country** : Show how flight volume is distributed across airlines and their destination countries, using two hierarchy levels: @@ -179,19 +295,267 @@ The following examples show various configuration options for building impactful * **Group by** (Level 1): `Carrier` (Top 5 values) * **Group by** (Level 2): `DestCountry` (Top 5 values) * **Metric**: Count + * **Value display**: Percentage ![Treemap showing flights by carrier and destination country](/explore-analyze/images/treemap-example-flights-carrier.png "=70%") +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example creates a two-level treemap: the outer rectangles represent airlines and the inner rectangles show destination countries, revealing how each carrier's flight volume is distributed geographically. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "treemap", + "title": "Flights by carrier and destination country", + "filters": [], + "query": { "expression": "" }, + "legend": { "size": "auto" }, + "metrics": [ + { + "operation": "count", + "label": "Flights", + "format": { "type": "number" }, + "filter": { "expression": "" } + } + ], + "group_by": [ + { + "operation": "terms", <1> + "fields": ["Carrier"], + "limit": 5 + }, + { + "operation": "terms", <2> + "fields": ["DestCountry"], + "limit": 5 + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_flights", + "time_field": "timestamp" + }, + "styling": { "values": { "mode": "percentage" } } +} +``` + +1. The first `group_by` entry creates the outer (parent) rectangles, one per airline carrier. +2. The second `group_by` entry nests destination countries inside each carrier, forming the two-level hierarchy. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "treemap", + "title": "Flights by carrier and destination country", + "filters": [], + "query": { "expression": "" }, + "legend": { "size": "auto" }, + "metrics": [ + { + "operation": "count", + "label": "Flights", + "format": { "type": "number" }, + "filter": { "expression": "" } + } + ], + "group_by": [ + { + "operation": "terms", <1> + "fields": ["Carrier"], + "limit": 5 + }, + { + "operation": "terms", <2> + "fields": ["DestCountry"], + "limit": 5 + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_flights", + "time_field": "timestamp" + }, + "styling": { "values": { "mode": "percentage" } } +}' +``` + +1. The first `group_by` entry creates the outer (parent) rectangles, one per airline carrier. +2. The second `group_by` entry nests destination countries inside each carrier, forming the two-level hierarchy. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: + **Response status per host** : See which hosts handle the most traffic and how their response status breaks down: * Example based on: {{kib}} Sample Data Logs - * **Group by** (Level 1): `host.keyword` (Top 3 values) + * **Group by** (Level 1): `host.keyword` (Top 3 values, with Other) * **Group by** (Level 2): Filters - "Success (2xx/3xx)": `response.keyword >= "200" AND response.keyword < "400"` - "Client errors (4xx)": `response.keyword >= "400" AND response.keyword < "500"` - "Server errors (5xx)": `response.keyword >= "500"` * **Metric**: Count - * **Color**: Gradient (`#ffc7db`) + * **Value display**: Percentage + * **Color**: Gradient (`#ffc7db`), reversed ![Treemap showing response status per host](/explore-analyze/images/treemap-example-response-per-host.png "=70%") + +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example nests KQL-based response status categories inside host rectangles, combining `terms` and `filters` groupings to show both traffic volume and health per host. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "treemap", + "title": "Response status per host", + "filters": [], + "query": { "expression": "" }, + "legend": { "size": "auto" }, + "metrics": [ + { + "operation": "count", + "label": "Count", + "format": { "type": "number" }, + "filter": { "expression": "" } + } + ], + "group_by": [ + { + "operation": "terms", <1> + "fields": ["host.keyword"], + "limit": 3, + "other_bucket": { "include_documents_without_field": false }, + "color": { + "mode": "gradient", + "palette": "default", + "gradient": [{ "type": "color_code", "value": "#ffc7db" }], + "sort": "desc" + } + }, + { + "operation": "filters", <2> + "filters": [ + { + "filter": { "expression": "response.keyword >= \"200\" AND response.keyword < \"400\"" }, + "label": "Success (2xx/3xx)" + }, + { + "filter": { "expression": "response.keyword >= \"400\" AND response.keyword < \"500\"" }, + "label": "Client errors (4xx)" + }, + { + "filter": { "expression": "response.keyword >= \"500\"" }, + "label": "Server errors (5xx)" + } + ] + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + }, + "styling": { "values": { "mode": "percentage" } } +} +``` + +1. The top-level `terms` grouping creates one outer rectangle per host, sized by total request count. `other_bucket` adds an "Other" segment for hosts outside the top 3. The `color` gradient applies a pink hue (`#ffc7db`) across the host values, reversed with `sort: "desc"` to match the UI example. +2. The nested `filters` grouping splits each host rectangle into success, client error, and server error segments using KQL queries. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "treemap", + "title": "Response status per host", + "filters": [], + "query": { "expression": "" }, + "legend": { "size": "auto" }, + "metrics": [ + { + "operation": "count", + "label": "Count", + "format": { "type": "number" }, + "filter": { "expression": "" } + } + ], + "group_by": [ + { + "operation": "terms", <1> + "fields": ["host.keyword"], + "limit": 3, + "other_bucket": { "include_documents_without_field": false }, + "color": { + "mode": "gradient", + "palette": "default", + "gradient": [{ "type": "color_code", "value": "#ffc7db" }], + "sort": "desc" + } + }, + { + "operation": "filters", <2> + "filters": [ + { + "filter": { "expression": "response.keyword >= \"200\" AND response.keyword < \"400\"" }, + "label": "Success (2xx/3xx)" + }, + { + "filter": { "expression": "response.keyword >= \"400\" AND response.keyword < \"500\"" }, + "label": "Client errors (4xx)" + }, + { + "filter": { "expression": "response.keyword >= \"500\"" }, + "label": "Server errors (5xx)" + } + ] + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + }, + "styling": { "values": { "mode": "percentage" } } +}' +``` + +1. The top-level `terms` grouping creates one outer rectangle per host, sized by total request count. `other_bucket` adds an "Other" segment for hosts outside the top 3. The `color` gradient applies a pink hue (`#ffc7db`) across the host values, reversed with `sort: "desc"` to match the UI example. +2. The nested `filters` grouping splits each host rectangle into success, client error, and server error segments using KQL queries. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: diff --git a/explore-analyze/visualize/charts/waffle-charts.md b/explore-analyze/visualize/charts/waffle-charts.md index 53cba74609..c3e3ea8454 100644 --- a/explore-analyze/visualize/charts/waffle-charts.md +++ b/explore-analyze/visualize/charts/waffle-charts.md @@ -94,6 +94,112 @@ This example uses the **Kibana Sample Data eCommerce** data set. If you haven't ![Waffle chart showing revenue progress toward a sales target](/explore-analyze/images/waffle-scenario-completion.png "=70%") +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example uses two metrics without a `group_by` to create a goal-tracking waffle: one metric shows earned revenue and the other calculates the gap to a $500K target. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "waffle", <1> + "title": "Revenue progress toward sales target", + "filters": [], + "query": { "expression": "" }, + "legend": { "size": "auto" }, + "metrics": [ + { + "operation": "sum", <2> + "field": "taxful_total_price", + "label": "Revenue earned", + "format": { + "type": "number" + }, + "filter": { "expression": "" } + }, + { + "operation": "formula", <3> + "formula": "500000 - sum(taxful_total_price)", + "label": "Remaining to goal", + "format": { + "type": "number" + }, + "filter": { "expression": "" } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_ecommerce", + "time_field": "order_date" + }, + "styling": { "values": { "mode": "percentage" } } +} +``` + +1. `waffle` renders a 10x10 grid of squares where each square represents 1% of the whole. +2. The first metric fills squares proportionally to earned revenue, showing progress at a glance. +3. The `formula` metric computes the gap between a $500K target and actual revenue, filling the remaining squares. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "waffle", <1> + "title": "Revenue progress toward sales target", + "filters": [], + "query": { "expression": "" }, + "legend": { "size": "auto" }, + "metrics": [ + { + "operation": "sum", <2> + "field": "taxful_total_price", + "label": "Revenue earned", + "format": { + "type": "number" + }, + "filter": { "expression": "" } + }, + { + "operation": "formula", <3> + "formula": "500000 - sum(taxful_total_price)", + "label": "Remaining to goal", + "format": { + "type": "number" + }, + "filter": { "expression": "" } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_ecommerce", + "time_field": "order_date" + }, + "styling": { "values": { "mode": "percentage" } } +}' +``` + +1. `waffle` renders a 10x10 grid of squares where each square represents 1% of the whole. +2. The first metric fills squares proportionally to earned revenue, showing progress at a glance. +3. The `formula` metric computes the gap between a $500K target and actual revenue, filling the remaining squares. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: + ## Waffle chart settings [waffle-chart-settings] Customize your waffle chart to display exactly the information you need, formatted the way you want. @@ -173,6 +279,11 @@ Waffle charts do not have configurable style settings. The chart automatically d ## Waffle chart examples + + The following examples show various configuration options for building impactful waffle charts. **Response status breakdown** @@ -188,6 +299,140 @@ The following examples show various configuration options for building impactful ![Waffle chart showing response status breakdown](/explore-analyze/images/waffle-example-response-status.png "=70%") +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example uses `filters` grouping to define three custom categories based on HTTP response code ranges, each filling a proportional section of the waffle grid. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "waffle", + "title": "Response status breakdown", + "filters": [], + "query": { "expression": "" }, + "legend": { "size": "auto" }, + "metrics": [ + { + "operation": "count", + "format": { "type": "number" }, + "filter": { "expression": "" } + } + ], + "group_by": [ + { + "operation": "filters", <1> + "filters": [ + { + "filter": { "expression": "response.keyword >= \"200\" AND response.keyword < \"400\"" }, + "label": "Success (2xx/3xx)" + }, + { + "filter": { "expression": "response.keyword >= \"400\" AND response.keyword < \"500\"" }, + "label": "Client errors (4xx)" + }, + { + "filter": { "expression": "response.keyword >= \"500\"" }, + "label": "Server errors (5xx)" + } + ], + "color": { <2> + "mode": "categorical", + "palette": "default", + "mapping": [ + { "values": ["Success (2xx/3xx)"], "color": { "type": "color_code", "value": "#209280" } }, + { "values": ["Client errors (4xx)"], "color": { "type": "color_code", "value": "#D6BF57" } }, + { "values": ["Server errors (5xx)"], "color": { "type": "color_code", "value": "#CC5642" } } + ] + } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + }, + "styling": { "values": { "mode": "percentage" } } +} +``` + +1. `filters` creates one waffle section per KQL query, letting you define arbitrary status categories rather than grouping by raw field values. +2. `color` assigns explicit hex colors to each category by label — green for success, yellow for client errors, red for server errors. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "waffle", + "title": "Response status breakdown", + "filters": [], + "query": { "expression": "" }, + "legend": { "size": "auto" }, + "metrics": [ + { + "operation": "count", + "format": { "type": "number" }, + "filter": { "expression": "" } + } + ], + "group_by": [ + { + "operation": "filters", <1> + "filters": [ + { + "filter": { "expression": "response.keyword >= \"200\" AND response.keyword < \"400\"" }, + "label": "Success (2xx/3xx)" + }, + { + "filter": { "expression": "response.keyword >= \"400\" AND response.keyword < \"500\"" }, + "label": "Client errors (4xx)" + }, + { + "filter": { "expression": "response.keyword >= \"500\"" }, + "label": "Server errors (5xx)" + } + ], + "color": { <2> + "mode": "categorical", + "palette": "default", + "mapping": [ + { "values": ["Success (2xx/3xx)"], "color": { "type": "color_code", "value": "#209280" } }, + { "values": ["Client errors (4xx)"], "color": { "type": "color_code", "value": "#D6BF57" } }, + { "values": ["Server errors (5xx)"], "color": { "type": "color_code", "value": "#CC5642" } } + ] + } + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + }, + "styling": { "values": { "mode": "percentage" } } +}' +``` + +1. `filters` creates one waffle section per KQL query, letting you define arbitrary status categories rather than grouping by raw field values. +2. `color` assigns explicit hex colors to each category by label — green for success, yellow for client errors, red for server errors. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: + **OS distribution** : Show the distribution of operating systems used by your website visitors: @@ -195,4 +440,84 @@ The following examples show various configuration options for building impactful * **Group by**: `machine.os.keyword` (Top 5 values) * **Metric**: Count -![Waffle chart showing OS distribution](/explore-analyze/images/waffle-example-os.png "=70%") \ No newline at end of file +![Waffle chart showing OS distribution](/explore-analyze/images/waffle-example-os.png "=70%") + +:::::::{dropdown} Create this chart using the API +:applies_to: { stack: preview 9.4, serverless: preview } + +This example creates a waffle chart grouped by operating system, where each colored section represents a different OS and its size shows the proportion of visitors using it. + + +:::::{tab-set} + +::::{tab-item} Console +:sync: api-console +```console +POST kbn://api/visualizations +{ + "type": "waffle", + "title": "OS distribution", + "filters": [], + "query": { "expression": "" }, + "legend": { "size": "auto" }, + "metrics": [{ "operation": "count", "format": { "type": "number" }, "filter": { "expression": "" } }], + "group_by": [ <1> + { + "operation": "terms", + "fields": ["machine.os.keyword"], + "limit": 5 + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + }, + "styling": { "values": { "mode": "percentage" } } <2> +} +``` + +1. `group_by` splits the waffle into sections by `machine.os.keyword`, with the top 5 OSes each getting a proportionally sized colored section. +2. `percentage` mode labels each section with its share of total traffic, which maps naturally to the waffle's 100-square grid. + +:::: + +::::{tab-item} curl +:sync: api-curl +```bash +curl -X POST "${KIBANA_URL}/api/visualizations" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "kbn-xsrf: true" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "waffle", + "title": "OS distribution", + "filters": [], + "query": { "expression": "" }, + "legend": { "size": "auto" }, + "metrics": [{ "operation": "count", "format": { "type": "number" }, "filter": { "expression": "" } }], + "group_by": [ <1> + { + "operation": "terms", + "fields": ["machine.os.keyword"], + "limit": 5 + } + ], + "data_source": { + "type": "data_view_spec", + "index_pattern": "kibana_sample_data_logs", + "time_field": "timestamp" + }, + "styling": { "values": { "mode": "percentage" } } <2> +}' +``` + +1. `group_by` splits the waffle into sections by `machine.os.keyword`, with the top 5 OSes each getting a proportionally sized colored section. +2. `percentage` mode labels each section with its share of total traffic, which maps naturally to the waffle's 100-square grid. + +:::: + +::::: + +For more information, refer to the [Visualizations API](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations). +::::::: diff --git a/explore-analyze/visualize/lens.md b/explore-analyze/visualize/lens.md index 67cad87e45..4b7a364ff9 100644 --- a/explore-analyze/visualize/lens.md +++ b/explore-analyze/visualize/lens.md @@ -45,6 +45,21 @@ With Lens, you can create the following visualization types: | [Tag cloud](/explore-analyze/visualize/charts/tag-cloud-charts.md) | Highlight the most frequent or important terms in a dataset. | | [Region map](/explore-analyze/visualize/charts/region-map-charts.md) | Show how values vary across geographic regions (choropleth). | +## Create visualizations with the API [lens-api] + +```{applies_to} +stack: preview 9.4 +serverless: preview +``` + +You can create and manage Lens visualizations programmatically using the Visualizations API ([stateful](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-visualizations), [serverless](https://www.elastic.co/docs/api/doc/serverless/group/endpoint-visualizations)). This is useful for managing visualizations as code, automating their lifecycle, or building tooling around Lens charts. + +Visualizations created through this API can be added to dashboards using the Kibana UI or the Dashboards API. + +:::{note} +The Visualizations API is in technical preview and may change in future releases. +::: + ## Create visualizations [create-the-visualization-panel] If you’re unsure about the visualization type you want to use, or how you want to display the data, drag the fields you want to visualize onto the workspace, then let **Lens** choose for you.