diff --git a/docusaurus/docs/scenes-ml/advanced-callbacks.md b/docusaurus/docs/scenes-ml/advanced-callbacks.md
new file mode 100644
index 000000000..3c1dc67b6
--- /dev/null
+++ b/docusaurus/docs/scenes-ml/advanced-callbacks.md
@@ -0,0 +1,16 @@
+---
+id: advanced-callbacks
+title: Using callbacks
+---
+
+Many of the components in `scenes-ml` allow a callback to be passed in the constructor. For example, in `SceneOutlierDetector`:
+
+```ts
+const outlierDetector = new SceneOutlierDetector({
+ onOutlierDetected: (outlier: Outlier) => {},
+});
+```
+
+This callback can be used to create more customised experiences specific to your Scene.
+
+For example, you may have a custom scene showing all pods for a given Kubernetes deployment. By enabling the outlier detector, you could use the callback to store all pods and timestamps which appear to be behaving differently, and render a second panel to show the logs for those pods and timestamps.
diff --git a/docusaurus/docs/scenes-ml/baselines-and-forecasts.md b/docusaurus/docs/scenes-ml/baselines-and-forecasts.md
new file mode 100644
index 000000000..fd5a6b7d5
--- /dev/null
+++ b/docusaurus/docs/scenes-ml/baselines-and-forecasts.md
@@ -0,0 +1,55 @@
+---
+id: baselines-and-forecasts
+title: Baselines and forecasts
+---
+
+import DiscoverSeasonalitiesSvg from '/static/img/discover-seasonalities.svg';
+import PinnedSvg from '/static/img/pinned.svg';
+
+**Baselines** provide smoothed estimates of time series data along with lower and upper bounds for the data over time. They can also be used as **forecasts**, using historical data to predict the behaviour of future data.
+
+This functionality can also be used for **anomaly detection** by highlighting timestamps where the true value strayed outside of the lower and upper predicted bounds.
+
+Baselines can be added to panels by using the `SceneBaseliner` component of `scenes-ml`, which will add a control to enable/disable the calculation, adjust the prediction intervals, discover seasonalities, and pin the results.
+
+![Panel with baselines added](/img/baseliner.png)
+
+## Usage
+
+The code example below demonstrates how to add baselines to a time series panel.
+
+```ts
+import { SceneBaseliner } from '@grafana/scenes-ml';
+
+// Default values are shown here, all are optional.
+const baseliner = new SceneBaseliner({
+ interval: 0.95,
+ discoverSeasonalities: false,
+ pinned: false,
+});
+const panel = PanelBuilders.timeseries()
+ .setHeaderActions([baseliner])
+ .build();
+```
+
+:::note
+Make sure you only add baselines to **time series** panels, as they rarely make sense for other panel types.
+:::
+
+### Pinning results
+
+By default, baselines are recalculated on every state change, i.e. whenever the time range, query or interval changes. This isn't always desirable: for example, the user may want to zoom out and view the current forecasts in a future time range.
+
+Enabling the **pinned ** setting will freeze the current results, so they won't be recalculated as the time range or other settings are changed.
+
+## Technical details
+
+`scenes-ml` uses the [MSTL][mstl] algorithm to produce in-sample and out-of-sample forecasts. This algorithm decomposes the data into **trend**, **seasonality** and **residuals**, then uses an an [ETS][ets] algorithm to model the trend series.
+
+By default, the algorithm assumes **hourly**, **daily**, **weekly** and **yearly** seasonality (if the data spans at least two of the given season length, i.e. at least two hours for hourly or at least two days for daily).
+
+If the **discover seasonalities ** setting is enabled, the baseliner will first attempt to detect any non-standard seasonality in the data using a [periodogram] and account for these seasonalities when modeling the data.
+
+[mstl]: https://arxiv.org/abs/2107.13462
+[ets]: https://otexts.com/fpp3/ets-forecasting.html
+[periodogram]: https://www.sktime.net/en/latest/api_reference/auto_generated/sktime.param_est.seasonality.SeasonalityPeriodogram.html
diff --git a/docusaurus/docs/scenes-ml/changepoint-detection.md b/docusaurus/docs/scenes-ml/changepoint-detection.md
new file mode 100644
index 000000000..536307f69
--- /dev/null
+++ b/docusaurus/docs/scenes-ml/changepoint-detection.md
@@ -0,0 +1,50 @@
+---
+id: changepoint-detection
+title: Changepoint detection
+---
+
+import PinnedSvg from '/static/img/pinned.svg';
+
+**Changepoint detection** attempts to identify timestamps where a time series has changed behaviour. For example, it could be used to identify sudden changes in the **magnitude** or the **variance** of a time series.
+
+The `SceneChangepointDetector` component from `scenes-ml` can be used to add this functionality to all series in a panel. This component will add an annotation at every detected changepoint.
+
+![Panel with changepoints added](/img/changepoints.png)
+
+:::warning
+Changepoint detection is currently a beta feature. The underlying algorithm may perform slowly for certain panels, so be sure to test it thoroughly before using it.
+:::
+
+## Usage
+
+The code example below demonstrates how to add changepoint detection to a time series panel.
+
+```ts
+import { SceneChangepointDetector } from '@grafana/scenes-ml';
+
+// Default values are shown here, all are optional.
+const changepointDetector = new SceneChangepointDetector({
+ enabled: false,
+ pinned: false,
+ onChangepointDetected: (changepoint: Changepoint) => {},
+});
+const panel = PanelBuilders.timeseries()
+ .setHeaderActions([outlierDetector])
+ .build();
+```
+
+:::note
+Make sure you only add changepoint detection to **time series** panels, as it rarely makes sense for other panel types.
+:::
+
+### Pinning results
+
+By default, baselines are recalculated on every state change, i.e. whenever the time range, query or interval changes. This isn't always desirable: for example, the user may want to zoom out and view the current forecasts in a future time range.
+
+Enabling the **pinned ** setting will freeze the current results, so they won't be recalculated as the time range or other settings are changed.
+
+## Technical details
+
+`scenes-ml` currently uses the [AutoRegressive Gaussian Process Change Point detection][argpcp] (ARGPCP) algorithm, which can be slow in some cases. Alternative algorithms may be added in future.
+
+[argpcp]: https://redpoll.ai/blog/changepoint/#autoregressive-gaussian-process-change-point-detector-argpcp
diff --git a/docusaurus/docs/scenes-ml/getting-started.md b/docusaurus/docs/scenes-ml/getting-started.md
new file mode 100644
index 000000000..f8e529d2a
--- /dev/null
+++ b/docusaurus/docs/scenes-ml/getting-started.md
@@ -0,0 +1,85 @@
+---
+id: getting-started
+title: Getting started
+---
+
+`@grafana/scenes-ml` is a separate npm package which lets you add Machine Learning powered functionality to your scenes.
+
+This topic explains hows to install Scenes ML and use it within a Grafana App plugin.
+
+## Installation
+
+If you're adding Scenes ML to an existing Scenes app plugin, first make sure your plugin config is up-to-date by running:
+
+```bash
+npx @grafana/create-plugin@latest --update
+```
+
+Then add `@grafana/scenes-ml` to your plugin by running the following commands in your project:
+
+```bash
+yarn add @grafana/scenes-ml
+```
+
+## Add ML features to a scene
+
+### 1. Create a scene
+
+Create a scene using the snippet below. This will add a time series panel to the scene with built-in controls to add trend, lower and upper bounds to all series in the panel.
+
+```ts
+// helloMLScene.ts
+
+import { EmbeddedScene, SceneFlexLayout, SceneFlexItem, SceneQueryRunner, PanelBuilders, sceneUtils } from '@grafana/scenes';
+import { SceneBaseliner, MLDemoDS } from '@grafana/scenes-ml';
+
+// Register the demo datasource from `scenes-ml`.
+// This isn't required for normal usage, it just gives us some sensible demo data.
+sceneUtils.registerRuntimeDataSource({ dataSource: new MLDemoDS('ml-test', 'ml-test') })
+
+function getForecastQueryRunner() {
+ return new SceneQueryRunner({
+ queries: [
+ { refId: 'A', datasource: { uid: 'ml-test', type: 'ml-test', }, type: 'forecasts' },
+ ],
+ });
+}
+
+export function getScene() {
+ return new EmbeddedScene({
+ body: new SceneFlexLayout({
+ children: [
+ new SceneFlexItem({
+ width: '50%',
+ height: 300,
+ body: PanelBuilders.timeseries()
+ .setTitle('Forecast demo')
+ .setData(getForecastQueryRunner())
+ // Add the `SceneBaseliner` to the panel.
+ .setHeaderActions([new SceneBaseliner({ interval: 0.95 })])
+ .build()
+ }),
+ ],
+ }),
+ });
+}
+```
+
+### 2. Render the scene
+
+Use the following code in your Grafana app plugin page to render the "Hello ML" scene:
+
+```tsx
+import React from 'react';
+import { getScene } from './helloMLScene';
+
+export const HelloMLPluginPage = () => {
+ const scene = getScene();
+
+ return ;
+};
+```
+
+## Source code
+
+[View the example source code](https://github.com/grafana/scenes/tree/main/docusaurus/docs/scenes-ml/getting-started.tsx)
diff --git a/docusaurus/docs/scenes-ml/getting-started.tsx b/docusaurus/docs/scenes-ml/getting-started.tsx
new file mode 100644
index 000000000..c84d9fa62
--- /dev/null
+++ b/docusaurus/docs/scenes-ml/getting-started.tsx
@@ -0,0 +1,34 @@
+import { EmbeddedScene, SceneFlexLayout, SceneFlexItem, SceneQueryRunner, PanelBuilders, sceneUtils } from '@grafana/scenes';
+import { SceneBaseliner, MLDemoDS } from '@grafana/scenes-ml';
+
+// Register the demo datasource from `scenes-ml`.
+// This isn't required for normal usage, it just gives us some sensible demo data.
+sceneUtils.registerRuntimeDataSource({ dataSource: new MLDemoDS('ml-test', 'ml-test') })
+
+function getForecastQueryRunner() {
+ return new SceneQueryRunner({
+ queries: [
+ { refId: 'A', datasource: { uid: 'ml-test', type: 'ml-test', }, type: 'forecasts' },
+ ],
+ });
+}
+
+export function getScene() {
+ return new EmbeddedScene({
+ body: new SceneFlexLayout({
+ children: [
+ new SceneFlexItem({
+ width: '50%',
+ height: 300,
+ body: PanelBuilders.timeseries()
+ .setTitle('Forecast demo')
+ .setData(getForecastQueryRunner())
+ // Add the `SceneBaseliner` to the panel.
+ .setHeaderActions([new SceneBaseliner({ interval: 0.95 })])
+ .build()
+ }),
+ ],
+ }),
+ });
+}
+
diff --git a/docusaurus/docs/scenes-ml/outlier-detection.md b/docusaurus/docs/scenes-ml/outlier-detection.md
new file mode 100644
index 000000000..d062161c1
--- /dev/null
+++ b/docusaurus/docs/scenes-ml/outlier-detection.md
@@ -0,0 +1,47 @@
+---
+id: outlier-detection
+title: Outlier detection
+---
+
+import PinnedSvg from '/static/img/pinned.svg';
+
+**Outlier detection** is the problem of identifying when one or more series within a group is behaving differently to the rest.
+
+`scenes-ml` provides a `SceneOutlierDetector` component which will perform outlier detection and highlight any misbehaving series. It will also add a grey band indicating the 'cluster range' (the range of data that can be considered non-outlying), and (optionally) add annotations at time ranges where an outlier was detected.
+
+![Panel with outliers added](/img/outliers.png)
+
+## Usage
+
+The code example below demonstrates how to add outlier detection to a time series panel.
+
+```ts
+import { SceneOutlierDetector } from '@grafana/scenes-ml';
+
+// Default values are shown here, all are optional.
+const outlierDetector = new SceneOutlierDetector({
+ sensitivity: 0.5,
+ addAnnotations: false,
+ pinned: false,
+ onOutlierDetected: (outlier: Outlier) => {},
+});
+const panel = PanelBuilders.timeseries()
+ .setHeaderActions([outlierDetector])
+ .build();
+```
+
+:::note
+Make sure you only add outlier detection to **time series** panels, as it rarely makes sense for other panel types.
+:::
+
+### Pinning results
+
+By default, baselines are recalculated on every state change, i.e. whenever the time range, query or interval changes. This isn't always desirable: for example, the user may want to zoom out and view the current forecasts in a future time range.
+
+Enabling the **pinned ** setting will freeze the current results, so they won't be recalculated as the time range or other settings are changed.
+
+## Technical details
+
+`scenes-ml` currently uses a variant of the [DBSCAN][dbscan] algorithm to detect outliers. Additional algorithms may be added in future.
+
+[dbscan]: https://en.wikipedia.org/wiki/DBSCAN
diff --git a/docusaurus/website/sidebars.js b/docusaurus/website/sidebars.js
index e8bd22abb..23399f365 100644
--- a/docusaurus/website/sidebars.js
+++ b/docusaurus/website/sidebars.js
@@ -60,6 +60,18 @@ const sidebars = {
'advanced-time-range-comparison',
],
},
+ {
+ type: 'category',
+ label: '@grafana/scenes-ml',
+ collapsible: true,
+ collapsed: false,
+ items: [
+ 'getting-started',
+ 'baselines-and-forecasts',
+ 'outlier-detection',
+ 'changepoint-detection',
+ ].map(id => `scenes-ml/${id}`),
+ }
],
};
module.exports = sidebars;
diff --git a/docusaurus/website/src/css/custom.css b/docusaurus/website/src/css/custom.css
index e000825ba..ab345527f 100644
--- a/docusaurus/website/src/css/custom.css
+++ b/docusaurus/website/src/css/custom.css
@@ -219,9 +219,7 @@ html[data-theme='dark'] .algolia-autocomplete .ds-with-1.ds-dropdown-menu:before
color: #63676d;
}
-.algolia-autocomplete.algolia-autocomplete
- .algolia-docsearch-suggestion--text
- .algolia-docsearch-suggestion--highlight {
+.algolia-autocomplete.algolia-autocomplete .algolia-docsearch-suggestion--text .algolia-docsearch-suggestion--highlight {
box-shadow: inset 0 -2px 0 0 var(--primary-border);
}
@@ -237,3 +235,9 @@ html[data-theme='dark'] .algolia-autocomplete .ds-with-1.ds-dropdown-menu:before
.algolia-autocomplete.algolia-autocomplete .algolia-docsearch-suggestion--content:before {
background: var(--ifm-toc-border-color);
}
+
+/* icons */
+.ml-icon {
+ height: 16px;
+ fill: #FFFFFF;
+}
diff --git a/docusaurus/website/static/img/baseliner.png b/docusaurus/website/static/img/baseliner.png
new file mode 100644
index 000000000..3decb058b
Binary files /dev/null and b/docusaurus/website/static/img/baseliner.png differ
diff --git a/docusaurus/website/static/img/changepoints.png b/docusaurus/website/static/img/changepoints.png
new file mode 100644
index 000000000..e516854a0
Binary files /dev/null and b/docusaurus/website/static/img/changepoints.png differ
diff --git a/docusaurus/website/static/img/discover-seasonalities.svg b/docusaurus/website/static/img/discover-seasonalities.svg
new file mode 100644
index 000000000..6674465f9
--- /dev/null
+++ b/docusaurus/website/static/img/discover-seasonalities.svg
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/docusaurus/website/static/img/outliers.png b/docusaurus/website/static/img/outliers.png
new file mode 100644
index 000000000..73546c193
Binary files /dev/null and b/docusaurus/website/static/img/outliers.png differ
diff --git a/docusaurus/website/static/img/pinned.svg b/docusaurus/website/static/img/pinned.svg
new file mode 100644
index 000000000..70c45e48f
--- /dev/null
+++ b/docusaurus/website/static/img/pinned.svg
@@ -0,0 +1,4 @@
+
diff --git a/packages/scenes-app/package.json b/packages/scenes-app/package.json
index 3b805771f..3f591482e 100644
--- a/packages/scenes-app/package.json
+++ b/packages/scenes-app/package.json
@@ -6,8 +6,8 @@
"license": "AGPL-3.0-only",
"description": "A basic grafana app plugin",
"scripts": {
- "build": "TS_NODE_PROJECT=\"./.config/webpack/tsconfig.webpack.json\" webpack -c ./.config/webpack/webpack.config.ts --env production",
- "dev": "TS_NODE_PROJECT=\"./.config/webpack/tsconfig.webpack.json\" webpack -w -c ./.config/webpack/webpack.config.ts --env development",
+ "build": "TS_NODE_PROJECT=\"./.config/webpack/tsconfig.webpack.json\" webpack -c ./webpack.config.ts --env production",
+ "dev": "TS_NODE_PROJECT=\"./.config/webpack/tsconfig.webpack.json\" webpack -w -c ./webpack.config.ts --env development",
"e2e": "yarn cypress install && TZ=UTC yarn grafana-e2e run",
"test": "jest --passWithNoTests",
"test:ci": "jest --maxWorkers 4",
@@ -63,6 +63,7 @@
"@grafana/data": "^11.0.0",
"@grafana/runtime": "^11.0.0",
"@grafana/scenes": "workspace:*",
+ "@grafana/scenes-ml": "^0.2.0",
"@grafana/scenes-react": "workspace:*",
"@grafana/schema": "^11.0.0",
"@grafana/ui": "^11.0.0",
diff --git a/packages/scenes-app/src/demos/index.ts b/packages/scenes-app/src/demos/index.ts
index afb1e7178..e3ec6c7f0 100644
--- a/packages/scenes-app/src/demos/index.ts
+++ b/packages/scenes-app/src/demos/index.ts
@@ -37,6 +37,7 @@ import { getQueryControllerDemo } from './queryController';
import { getDynamicDataLayersDemo } from './dynamicDataLayers';
import { getInteropDemo } from './interopDemo';
import { getUrlSyncTest } from './urlSyncTest';
+import { getMlDemo } from './ml';
export interface DemoDescriptor {
title: string;
@@ -83,5 +84,6 @@ export function getDemos(): DemoDescriptor[] {
{ title: 'Query controller demo', getPage: getQueryControllerDemo },
{ title: 'Interop with hooks and context', getPage: getInteropDemo },
{ title: 'Url sync test', getPage: getUrlSyncTest },
+ { title: 'Machine Learning', getPage: getMlDemo },
].sort((a, b) => a.title.localeCompare(b.title));
}
diff --git a/packages/scenes-app/src/demos/ml.tsx b/packages/scenes-app/src/demos/ml.tsx
new file mode 100644
index 000000000..63a7b1dc9
--- /dev/null
+++ b/packages/scenes-app/src/demos/ml.tsx
@@ -0,0 +1,212 @@
+import {
+ EmbeddedScene,
+ PanelBuilders,
+ QueryRunnerState,
+ SceneAppPage,
+ SceneAppPageState,
+ SceneControlsSpacer,
+ SceneFlexItem,
+ SceneFlexLayout,
+ SceneQueryRunner,
+ SceneTimePicker,
+ SceneTimeRange,
+ SceneTimeRangeCompare,
+} from '@grafana/scenes';
+import { SceneBaseliner, SceneChangepointDetector, SceneOutlierDetector } from '@grafana/scenes-ml';
+import { DataQuery } from '@grafana/schema';
+import { DATASOURCE_REF } from '../constants';
+
+interface PredictableCSVWaveQuery extends DataQuery {
+ timeStep?: number;
+ valuesCSV?: string;
+ name?: string;
+}
+
+// Data from https://www.kaggle.com/datasets/rakannimer/air-passengers.
+const AIR_PASSENGERS = [
+ 112, 118, 132, 129, 121, 135, 148, 148, 136, 119, 104, 118, 115, 126, 141, 135, 125, 149, 170, 170, 158, 133, 114,
+ 140, 145, 150, 178, 163, 172, 178, 199, 199, 184, 162, 146, 166, 171, 180, 193, 181, 183, 218, 230, 242, 209, 191,
+ 172, 194, 196, 196, 236, 235, 229, 243, 264, 272, 237, 211, 180, 201, 204, 188, 235, 227, 234, 264, 302, 293, 259,
+ 229, 203, 229, 242, 233, 267, 269, 270, 315, 364, 347, 312, 274, 237, 278, 284, 277, 317, 313, 318, 374, 413, 405,
+ 355, 306, 271, 306, 315, 301, 356, 348, 355, 422, 465, 467, 404, 347, 305, 336, 340, 318, 362, 348, 363, 435, 491,
+ 505, 404, 359, 310, 337, 360, 342, 406, 396, 420, 472, 548, 559, 463, 407, 362, 405, 417, 391, 419, 461, 472, 535,
+ 622, 606, 508, 461, 390, 432,
+ // Add in a few changepoints to make things more interesting.
+ 10000, 10001, 10000.0, 10000.2,
+].join(',');
+
+// Data from https://www.abs.gov.au/statistics/people/population/national-state-and-territory-population/sep-2020.
+const AUSTRALIAN_RESIDENTS = [
+ 13067.3, 13130.5, 13198.4, 13254.2, 13303.7, 13353.9, 13409.3, 13459.2, 13504.5, 13552.6, 13614.3, 13669.5, 13722.6,
+ 13772.1, 13832.0, 13862.6, 13893.0, 13926.8, 13968.9, 14004.7, 14033.1, 14066.0, 14110.1, 14155.6, 14192.2, 14231.7,
+ 14281.5, 14330.3, 14359.3, 14396.6, 14430.8, 14478.4, 14515.7, 14554.9, 14602.5, 14646.4, 14695.4, 14746.6, 14807.4,
+ 14874.4, 14923.3, 14988.7, 15054.1, 15121.7, 15184.2, 15239.3, 15288.9, 15346.2, 15393.5, 15439.0, 15483.5, 15531.5,
+ 15579.4, 15628.5, 15677.3, 15736.7, 15788.3, 15839.7, 15900.6, 15961.5, 16018.3, 16076.9, 16139.0, 16203.0, 16263.3,
+ 16327.9, 16398.9, 16478.3, 16538.2, 16621.6, 16697.0, 16777.2, 16833.1, 16891.6, 16956.8, 17026.3, 17085.4, 17106.9,
+ 17169.4, 17239.4, 17292.0, 17354.2, 17414.2, 17447.3, 17482.6, 17526.0, 17568.7, 17627.1, 17661.5,
+].join(',');
+
+const INDIA_TEMPERATURES = [
+ 10.0, 7.4, 7.166666666666667, 8.666666666666666, 6.0, 7.0, 7.0, 8.857142857142858, 14.0, 11.0, 15.714285714285714,
+ 14.0, 15.833333333333334, 12.833333333333334, 14.714285714285714, 13.833333333333334, 16.5, 13.833333333333334, 12.5,
+ 11.285714285714286, 11.2, 9.5, 14.0, 13.833333333333334, 12.25, 12.666666666666666, 12.857142857142858,
+ 14.833333333333334, 14.125, 14.714285714285714, 16.2, 16.0, 16.285714285714285, 18.0, 17.428571428571427, 16.625,
+ 16.666666666666668, 15.6, 14.0, 15.428571428571429, 15.25, 15.875, 15.333333333333334, 16.285714285714285,
+ 17.333333333333332, 19.166666666666668, 14.428571428571429, 13.666666666666666, 15.6, 15.857142857142858,
+ 17.714285714285715, 20.0, 20.5, 17.428571428571427, 16.857142857142858, 16.875, 17.857142857142858, 20.8,
+ 19.428571428571427,
+].join(',');
+
+const OUTLIER_DATA = [
+ Array.from({ length: 100 }, () => Math.random() * 100),
+ Array.from({ length: 100 }, () => Math.random() * 100),
+ [
+ ...Array.from({ length: 49 }, () => Math.random() * 100),
+ ...Array.from({ length: 2 }, () => Math.random() * 1000),
+ ...Array.from({ length: 49 }, () => Math.random() * 100),
+ ],
+];
+
+function getOutlierQueryRunner() {
+ return new SceneQueryRunner({
+ queries: OUTLIER_DATA.map((values, i) => ({
+ refId: String.fromCharCode(65 + i),
+ datasource: DATASOURCE_REF,
+ scenarioId: 'predictable_csv_wave',
+ csvWave: [
+ {
+ timeStep: 600,
+ valuesCSV: values.join(','),
+ },
+ ],
+ })),
+ maxDataPointsFromWidth: true,
+ });
+}
+
+export function getQueryRunnerWithCSVWaveQuery(
+ overrides?: Partial,
+ queryRunnerOverrides?: Partial
+) {
+ return new SceneQueryRunner({
+ queries: [
+ {
+ refId: 'A',
+ datasource: DATASOURCE_REF,
+ scenarioId: 'predictable_csv_wave',
+ csvWave: [
+ {
+ timeStep: 600,
+ valuesCSV: '0,0,0.5,1,2,2,1,1,0.5,0.3',
+ ...overrides,
+ },
+ ],
+ },
+ ],
+ ...queryRunnerOverrides,
+ });
+}
+
+export function getMlDemo(defaults: SceneAppPageState) {
+ return new SceneAppPage({
+ ...defaults,
+ subTitle: 'Time series demos',
+ getScene: () => {
+ return new EmbeddedScene({
+ $data: getQueryRunnerWithCSVWaveQuery({}, { maxDataPointsFromWidth: false }),
+ $timeRange: new SceneTimeRange({ from: 'now-48h', to: 'now' }),
+
+ controls: [new SceneControlsSpacer(), new SceneTimePicker({}), new SceneTimeRangeCompare({ key: 'top' })],
+ key: 'Flex layout embedded scene',
+ body: new SceneFlexLayout({
+ direction: 'column',
+ children: [
+ new SceneFlexLayout({
+ direction: 'row',
+ wrap: 'wrap',
+ children: [
+ new SceneFlexItem({
+ minWidth: '100%',
+ minHeight: 300,
+ body: PanelBuilders.timeseries()
+ .setTitle('Outlier data')
+ .setData(getOutlierQueryRunner())
+ .setHeaderActions([
+ new SceneOutlierDetector({
+ sensitivity: 0.5,
+ // onOutlierDetected: console.log,
+ }),
+ ])
+ .build(),
+ }),
+ new SceneFlexItem({
+ minWidth: '100%',
+ minHeight: 300,
+ body: PanelBuilders.timeseries()
+ .setTitle('Simple wave')
+ .setData(getQueryRunnerWithCSVWaveQuery({}, { maxDataPointsFromWidth: true }))
+ .setHeaderActions([new SceneBaseliner({ interval: 0.95 })])
+ .build(),
+ }),
+
+ new SceneFlexItem({
+ minWidth: '100%',
+ minHeight: 300,
+ body: PanelBuilders.timeseries()
+ .setTitle('Spikey data with changepoints')
+ .setData(
+ getQueryRunnerWithCSVWaveQuery({ valuesCSV: AIR_PASSENGERS }, { maxDataPointsFromWidth: true })
+ )
+ .setHeaderActions([
+ new SceneBaseliner({}),
+ new SceneChangepointDetector({
+ enabled: true,
+ // onChangepointDetected: console.log,
+ }),
+ ])
+ .build(),
+ }),
+ new SceneFlexItem({
+ minWidth: '100%',
+ minHeight: 300,
+ body: PanelBuilders.timeseries()
+ .setTitle('Realistic repeated series')
+ .setData(
+ getQueryRunnerWithCSVWaveQuery(
+ { valuesCSV: INDIA_TEMPERATURES },
+ { maxDataPointsFromWidth: true }
+ )
+ )
+ .setHeaderActions([
+ new SceneBaseliner({
+ interval: 0.95,
+ }),
+ ])
+ .build(),
+ }),
+ new SceneFlexItem({
+ minWidth: '100%',
+ minHeight: 300,
+ body: PanelBuilders.timeseries()
+ .setTitle('Sawtooth data')
+ .setData(
+ getQueryRunnerWithCSVWaveQuery(
+ { valuesCSV: AUSTRALIAN_RESIDENTS },
+ { maxDataPointsFromWidth: true }
+ )
+ )
+ .setHeaderActions([
+ new SceneBaseliner({
+ interval: 0.95,
+ }),
+ ])
+ .build(),
+ }),
+ ],
+ }),
+ ],
+ }),
+ });
+ },
+ });
+}
diff --git a/packages/scenes-app/webpack.config.ts b/packages/scenes-app/webpack.config.ts
new file mode 100644
index 000000000..8f6ea6ef2
--- /dev/null
+++ b/packages/scenes-app/webpack.config.ts
@@ -0,0 +1,21 @@
+import type { Configuration } from 'webpack';
+import { merge } from 'webpack-merge';
+import grafanaConfig from './.config/webpack/webpack.config';
+import { getPluginId } from './.config/webpack/utils';
+
+const config = (env: any): Configuration => {
+ const pluginId = getPluginId();
+ const baseConfig = grafanaConfig(env);
+ return merge(baseConfig, {
+ experiments: {
+ asyncWebAssembly: true,
+ },
+ output: {
+ publicPath: `public/plugins/${pluginId}/`,
+ uniqueName: pluginId,
+ },
+ });
+};
+
+export default config;
+
diff --git a/yarn.lock b/yarn.lock
index c042be53d..11557490a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2210,6 +2210,13 @@ __metadata:
languageName: node
linkType: hard
+"@bsull/augurs@npm:0.2.0":
+ version: 0.2.0
+ resolution: "@bsull/augurs@npm:0.2.0"
+ checksum: 10/ed0dfd0d9feeeac4b252d0dc25b87471998b8fb6b657aca1d11f771872e26b33b0ba8b61e2f78c5a049a6f6c701f043ace050fcdea866e8299b011826396dc29
+ languageName: node
+ linkType: hard
+
"@colors/colors@npm:1.5.0":
version: 1.5.0
resolution: "@colors/colors@npm:1.5.0"
@@ -3462,6 +3469,24 @@ __metadata:
languageName: node
linkType: hard
+"@grafana/scenes-ml@npm:^0.2.0":
+ version: 0.2.0
+ resolution: "@grafana/scenes-ml@npm:0.2.0"
+ dependencies:
+ "@bsull/augurs": "npm:0.2.0"
+ date-fns: "npm:^3.6.0"
+ peerDependencies:
+ "@grafana/data": ^10.0.3
+ "@grafana/runtime": ^10.0.3
+ "@grafana/scenes": ">=4.26.1"
+ "@grafana/schema": ^10.0.3
+ "@grafana/ui": ^10.0.3
+ react: ^18.0.0
+ react-dom: ^18.0.0
+ checksum: 10/44c667f8eed7777af0851f6ea3ef1d34c0c1fb8e98311cdcebdc232e8aae17085c922988928a3bfb533e10f2df590411b6126afee97eee42cd906ab46d94c400
+ languageName: node
+ linkType: hard
+
"@grafana/scenes-react@workspace:*, @grafana/scenes-react@workspace:packages/scenes-react":
version: 0.0.0-use.local
resolution: "@grafana/scenes-react@workspace:packages/scenes-react"
@@ -11193,7 +11218,7 @@ __metadata:
languageName: node
linkType: hard
-"date-fns@npm:3.6.0":
+"date-fns@npm:3.6.0, date-fns@npm:^3.6.0":
version: 3.6.0
resolution: "date-fns@npm:3.6.0"
checksum: 10/cac35c58926a3b5d577082ff2b253612ec1c79eb6754fddef46b6a8e826501ea2cb346ecbd211205f1ba382ddd1f9d8c3f00bf433ad63cc3063454d294e3a6b8
@@ -23284,6 +23309,7 @@ __metadata:
"@grafana/eslint-config": "npm:5.0.0"
"@grafana/runtime": "npm:^11.0.0"
"@grafana/scenes": "workspace:*"
+ "@grafana/scenes-ml": "npm:^0.2.0"
"@grafana/scenes-react": "workspace:*"
"@grafana/schema": "npm:^11.0.0"
"@grafana/tsconfig": "npm:1.3.0-rc1"