mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
GraphNG - shared cursor (#33433)
* Initial work * WIP add cursor in debug panel * shared cursor.sync filter * explicit uplot events * explicit uplot events * uplot events * uplot events * depend on master uplot * sync sync sync * Fix merge * Get rid of PlotSyncContext and sync tooltip positions * make sync optional * Improve shared tooltip positioning * Plugins: add level and signature badges to plugin details page (#33553) * feat(grafana-ui): badge can accept react node for text, add shield-exclamation to icons * feat(plugins): add PluginSignatureType type * feat(pluginpage): introduce PluginSignatureDetailsBadge. Fix sidebar icon margin * feat(pluginlistpage): update filterinput placeholder, introduce filter by plugin type * Variables: Removes the never refresh option (#33533) * Variables: Removes the never refresh option * Tests: fixes DashboardModel repeat tests * Tests: fixs snapshots * Tests: fixes processVariable test * Tests: fixes DashboardModel tests * PageLayout: Fixes max-width breakpoint so that it triggers only when there is room for margin+ (#33558) * Alerting: Remove datasource (name) from migration (#33544) no longer needed as of https://github.com/grafana/grafana/pull/33416 for https://github.com/grafana/alerting-squad/issues/126 * Library panels: Adds description to library panels tab (#33428) * CodeOwners: Set owners of unified alerting migration (#33571) * ButtonSelect: updates component with the new theme model (#33565) * EmptySearchResult: updates component with the new theme model (#33573) * DashboardSettings: Slight design tweak to fix page toolbar padding and align design (#33575) * DashboardSettings: Slight design tweak to fix page toolbar padding and align design * Fixed font weight * Removed comment * Update * gitignore: Ignore files for accesscontrol provisioning (#33577) * Alerting/metrics (#33547) * moves alerting metrics to their own pkg * adds grafana_alerting_alerts (by state) metric * alerts_received_{total,invalid} * embed alertmanager alerting struct in ng metrics & remove duplicated notification metrics (already embed alertmanager notifier metrics) * use silence metrics from alertmanager lib * fix - manager has metrics * updates ngalert tests * comment lint Signed-off-by: Owen Diehl <ow.diehl@gmail.com> * cleaner prom registry code * removes ngalert global metrics * new registry use in all tests * ngalert metrics impl service, hack testinfra code to prevent duplicate metric registrations * nilmetrics unexported * Add note to Snapshot API doc to specify that user has to provide the entire dashboard model (#33572) * Added note as suggested by Macus E. * Update docs/sources/http_api/snapshot.md Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Alerting: backend "ng" code cleanup (#33578) * AlertMigration: remove alert_rule UID db check (#33568) do not believe this is needed due to uniqueness promised by shortid lib since there is no provisioning yet. https://github.com/teris-io/shortid * Live: persisting last message in cache for broadcast scope (#32938) * Alerting: Load annotations from rule into State cache (#33542) for https://github.com/grafana/alerting-squad/issues/127 * add template for dashboard url parameters (#33549) * Update dashboard-links.md parameters with plain text like `var-something=value` can make confusion. template it to clarify . * describe way for template link. * AlertingMigration: Create alert_rule_version entry (#33585) Create the alert rule version entry during the migration so it is consistent with rules created via api. for https://github.com/grafana/alerting-squad/issues/123 * Build: Fix with cleanup call maybe? (#33590) * add selector for code editor (#33554) * broadcast over eventBus * broadcasting to eventbus (but not useing it yet) * merge master * moved to context * fix yarn.lock * update snapshot * Fix direct state mutation * Persist location state on partial updates * GraphNG- use getStream rather than subscribe * Sync LegacyGraphHoverEvent with GraphNG * Chenge plotRef signature * use subscription * subscription * one fewer file * Update types * Remove unnecessary filtering * Disable cursor sync when in edit mode * GraphNG - bring back logging Co-authored-by: Ryan McKinley <ryantxu@gmail.com> Co-authored-by: Leon Sorokin <leeoniya@gmail.com> Co-authored-by: Jack Westbrook <jack.westbrook@gmail.com> Co-authored-by: Hugo Häggmark <hugo.haggmark@grafana.com> Co-authored-by: Torkel Ödegaard <torkel@grafana.org> Co-authored-by: Kyle Brandt <kyle@grafana.com> Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com> Co-authored-by: Uchechukwu Obasi <obasiuche62@gmail.com> Co-authored-by: gamab <gamab@users.noreply.github.com> Co-authored-by: Owen Diehl <ow.diehl@gmail.com> Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> Co-authored-by: Alexander Emelin <frvzmb@gmail.com> Co-authored-by: Nagle Zhang <nagle.zhang@sap.com> Co-authored-by: Erik Sundell <erik.sundell@grafana.com>
This commit is contained in:
@@ -0,0 +1,561 @@
|
||||
{
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": "-- Grafana --",
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"gnetId": null,
|
||||
"graphTooltip": 2,
|
||||
"id": 396,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 10,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"graph": false,
|
||||
"legend": false,
|
||||
"tooltip": false
|
||||
},
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "never",
|
||||
"spanNulls": true,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "degree"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 8,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 2,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom"
|
||||
},
|
||||
"tooltipOptions": {
|
||||
"mode": "single"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "7.5.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "-9.4, -8.2, -8, -8, -7, -7.9, -5.9, -6.1, -5.5, -5.8, -4.7, -4.7, -4.1, -4.5, -3.5, -4, -2.4, -3.4, -1, -1, -0.3, -0.8, 0.8, 0.5, 1.7, 1.2, 2, 1.9, 2.4, 2.3, 3.3, 3.2, 3.9, 3.8, 4.5, 4.5, 5.6, 5.1, 7.1, 6.7, 7.2, 7.1, 7.6, 7.5, 8.4, 7.9, 9, 8.5, 9.4, 9.2"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Origin",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 10,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"graph": false,
|
||||
"legend": false,
|
||||
"tooltip": false
|
||||
},
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "never",
|
||||
"spanNulls": true,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "degree"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "B-series"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "unit",
|
||||
"value": "tflops"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 8,
|
||||
"x": 8,
|
||||
"y": 0
|
||||
},
|
||||
"id": 9,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom"
|
||||
},
|
||||
"tooltipOptions": {
|
||||
"mode": "single"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "7.5.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"hide": false,
|
||||
"max": 10,
|
||||
"min": -10,
|
||||
"noise": 1,
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk",
|
||||
"spread": 2
|
||||
},
|
||||
{
|
||||
"hide": false,
|
||||
"max": 10,
|
||||
"min": -10,
|
||||
"noise": 1,
|
||||
"refId": "B",
|
||||
"scenarioId": "random_walk",
|
||||
"spread": 2
|
||||
},
|
||||
{
|
||||
"hide": false,
|
||||
"max": 10,
|
||||
"min": -10,
|
||||
"noise": 1,
|
||||
"refId": "C",
|
||||
"scenarioId": "random_walk",
|
||||
"spread": 2
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Sensor 1, ranging as Origin panel",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 10,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"graph": false,
|
||||
"legend": false,
|
||||
"tooltip": false
|
||||
},
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "never",
|
||||
"spanNulls": true,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "degree"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "B-series"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "unit",
|
||||
"value": "tflops"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "A-series"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "color",
|
||||
"value": {
|
||||
"fixedColor": "purple",
|
||||
"mode": "fixed"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 8,
|
||||
"x": 16,
|
||||
"y": 0
|
||||
},
|
||||
"id": 10,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom"
|
||||
},
|
||||
"tooltipOptions": {
|
||||
"mode": "single"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "7.5.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"hide": false,
|
||||
"max": 4,
|
||||
"min": -4,
|
||||
"noise": 0,
|
||||
"refId": "B",
|
||||
"scenarioId": "random_walk",
|
||||
"spread": 2
|
||||
},
|
||||
{
|
||||
"hide": false,
|
||||
"max": 4,
|
||||
"min": -4,
|
||||
"noise": 0,
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk",
|
||||
"spread": 2
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Sensor 2, ranging [-4, 4]",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 10,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"graph": false,
|
||||
"legend": false,
|
||||
"tooltip": false
|
||||
},
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "never",
|
||||
"spanNulls": true,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "degree"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "B-series"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "unit",
|
||||
"value": "tflops"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 8,
|
||||
"x": 8,
|
||||
"y": 9
|
||||
},
|
||||
"id": 7,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom"
|
||||
},
|
||||
"tooltipOptions": {
|
||||
"mode": "single"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "7.5.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "5, 4.6, 4.4, 3.5, 3.3, 3, 2.6, 2.6, 2.4, 2.1, 1.8, 1.8, 1.7, 1.6, 1.5, 1.5, 1.4, 0.5, 0.5, 0, -0.2, -0.2, -0.4, -0.6, -1.2, -1.6, -2.4, -2.4, -2.8, -2.9, -2.9, -3.2, -3.3, -3.4, -4.3, -4.3, -4.6, -6.1, -6.3, -6.6, -6.7, -7, -7.2, -7.3, -7.5, -7.8, -8.2, -8.3, -9.1, -9.3"
|
||||
},
|
||||
{
|
||||
"refId": "B",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "6,4,2"
|
||||
},
|
||||
{
|
||||
"hide": false,
|
||||
"refId": "C",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "-3,-4,-2"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Sensor 3",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 10,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"graph": false,
|
||||
"legend": false,
|
||||
"tooltip": false
|
||||
},
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "never",
|
||||
"spanNulls": true,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "degree"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "B-series"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "unit",
|
||||
"value": "tflops"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 8,
|
||||
"x": 16,
|
||||
"y": 9
|
||||
},
|
||||
"id": 8,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom"
|
||||
},
|
||||
"tooltipOptions": {
|
||||
"mode": "single"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "7.5.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "B",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "6,4,2"
|
||||
},
|
||||
{
|
||||
"hide": false,
|
||||
"refId": "C",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "-3,-4,-2"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Sensor 5",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"refresh": false,
|
||||
"schemaVersion": 28,
|
||||
"style": "dark",
|
||||
"tags": [
|
||||
"gdev",
|
||||
"panel-tests",
|
||||
"graph-ng"
|
||||
],
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "",
|
||||
"title": "Panel Tests - shared tooltips cursor positioning",
|
||||
"uid": "ICiqZ1rGk",
|
||||
"version": 51
|
||||
}
|
||||
780
devenv/dev-dashboards/panel-graph/graph-shared-tooltips.json
Normal file
780
devenv/dev-dashboards/panel-graph/graph-shared-tooltips.json
Normal file
@@ -0,0 +1,780 @@
|
||||
{
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": "-- Grafana --",
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"gnetId": null,
|
||||
"graphTooltip": 1,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 10,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"graph": false,
|
||||
"legend": false,
|
||||
"tooltip": false
|
||||
},
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "never",
|
||||
"spanNulls": true,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "celsius"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Speed"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "unit",
|
||||
"value": "velocitykmh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 9,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 4,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom"
|
||||
},
|
||||
"tooltipOptions": {
|
||||
"mode": "single"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "7.5.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"alias": "Temperature",
|
||||
"max": 25,
|
||||
"min": 1,
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk"
|
||||
},
|
||||
{
|
||||
"alias": "Speed",
|
||||
"hide": false,
|
||||
"refId": "B",
|
||||
"scenarioId": "random_walk"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "two units",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "points",
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"graph": false,
|
||||
"legend": false,
|
||||
"tooltip": false
|
||||
},
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "auto",
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "celsius"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Temperature"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "unit",
|
||||
"value": "celsius"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Speed"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "unit",
|
||||
"value": "velocitykmh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 8,
|
||||
"x": 9,
|
||||
"y": 0
|
||||
},
|
||||
"id": 13,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom"
|
||||
},
|
||||
"tooltipOptions": {
|
||||
"mode": "single"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "7.5.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"max": 25,
|
||||
"min": 1,
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk",
|
||||
"seriesCount": 2
|
||||
}
|
||||
],
|
||||
"title": "Speed vs Temperature (XY)",
|
||||
"transformations": [
|
||||
{
|
||||
"id": "seriesToColumns",
|
||||
"options": {
|
||||
"byField": "time"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "organize",
|
||||
"options": {
|
||||
"excludeByName": {
|
||||
"time": true
|
||||
},
|
||||
"indexByName": {},
|
||||
"renameByName": {
|
||||
"A-series": "Temperature",
|
||||
"A-series1": "Speed"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "xychart"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 7,
|
||||
"x": 17,
|
||||
"y": 0
|
||||
},
|
||||
"id": 2,
|
||||
"options": {
|
||||
"counters": {
|
||||
"dataChanged": true,
|
||||
"render": true,
|
||||
"schemaChanged": true
|
||||
},
|
||||
"mode": "cursor"
|
||||
},
|
||||
"pluginVersion": "7.5.0-pre",
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Cursor info",
|
||||
"type": "debug"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 10,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"graph": false,
|
||||
"legend": false,
|
||||
"tooltip": false
|
||||
},
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "never",
|
||||
"spanNulls": true,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "celsius"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 9,
|
||||
"x": 0,
|
||||
"y": 8
|
||||
},
|
||||
"id": 5,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom"
|
||||
},
|
||||
"tooltipOptions": {
|
||||
"mode": "single"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "7.5.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"alias": "Temperature",
|
||||
"max": 25,
|
||||
"min": 5,
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Only temperature",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 10,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"graph": false,
|
||||
"legend": false,
|
||||
"tooltip": false
|
||||
},
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "never",
|
||||
"spanNulls": true,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "velocitykmh"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Speed"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "color",
|
||||
"value": {
|
||||
"fixedColor": "yellow",
|
||||
"mode": "fixed"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 8,
|
||||
"x": 9,
|
||||
"y": 8
|
||||
},
|
||||
"id": 9,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom"
|
||||
},
|
||||
"tooltipOptions": {
|
||||
"mode": "single"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "7.5.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"alias": "Speed",
|
||||
"hide": false,
|
||||
"refId": "B",
|
||||
"scenarioId": "random_walk"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Only Speed",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 10,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"graph": false,
|
||||
"legend": false,
|
||||
"tooltip": false
|
||||
},
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "never",
|
||||
"spanNulls": true,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "accMS2"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Speed"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "color",
|
||||
"value": {
|
||||
"fixedColor": "yellow",
|
||||
"mode": "fixed"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Acceleration"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "color",
|
||||
"value": {
|
||||
"fixedColor": "purple",
|
||||
"mode": "fixed"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 7,
|
||||
"x": 17,
|
||||
"y": 8
|
||||
},
|
||||
"id": 11,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom"
|
||||
},
|
||||
"tooltipOptions": {
|
||||
"mode": "single"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "7.5.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"alias": "Acceleration",
|
||||
"hide": false,
|
||||
"refId": "B",
|
||||
"scenarioId": "random_walk"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Panel Title",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "celsius"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 14
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 8,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
"current": false,
|
||||
"max": false,
|
||||
"min": false,
|
||||
"show": true,
|
||||
"total": false,
|
||||
"values": false
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"nullPointMode": "null",
|
||||
"options": {
|
||||
"alertThreshold": true
|
||||
},
|
||||
"percentage": false,
|
||||
"pluginVersion": "7.5.0-pre",
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"spaceLength": 10,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"max": 25,
|
||||
"min": 1,
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk"
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeRegions": [],
|
||||
"timeShift": null,
|
||||
"title": "flot panel (temperature)",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"buckets": null,
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"format": "celsius",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"yaxis": {
|
||||
"align": false,
|
||||
"alignLevel": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": null,
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 14
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 10,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
"current": false,
|
||||
"max": false,
|
||||
"min": false,
|
||||
"show": true,
|
||||
"total": false,
|
||||
"values": false
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"nullPointMode": "null",
|
||||
"options": {
|
||||
"alertThreshold": true
|
||||
},
|
||||
"percentage": false,
|
||||
"pluginVersion": "7.5.0-pre",
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"spaceLength": 10,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeRegions": [],
|
||||
"timeShift": null,
|
||||
"title": "flot panel (no units)",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"buckets": null,
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"yaxis": {
|
||||
"align": false,
|
||||
"alignLevel": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"refresh": false,
|
||||
"schemaVersion": 28,
|
||||
"style": "dark",
|
||||
"tags": [
|
||||
"gdev",
|
||||
"panel-tests",
|
||||
"graph-ng"
|
||||
],
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
"time": {
|
||||
"from": "2020-09-14T16:13:20.000Z",
|
||||
"to": "2020-09-15T20:00:00.000Z"
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "",
|
||||
"title": "Panel Tests - shared tooltips",
|
||||
"uid": "TX2VU59MZ",
|
||||
"version": 2
|
||||
}
|
||||
@@ -1,20 +1,23 @@
|
||||
import { DataFrame } from '../types';
|
||||
import { BusEventWithPayload } from './types';
|
||||
|
||||
/** @alpha */
|
||||
/**
|
||||
* When hovering over an element this will identify
|
||||
*
|
||||
* For performance reasons, this object will usually be mutated between updates. This
|
||||
* will avoid creating new objects for events that fire frequently (ie each mouse pixel)
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export interface DataHoverPayload {
|
||||
raw: any; // Original mouse event (includes pageX etc)
|
||||
|
||||
x: Record<string, any>; // { time: 5678 },
|
||||
y: Record<string, any>; // { __fixed: 123, lengthft: 456 } // each axis|scale gets a value
|
||||
|
||||
data?: DataFrame; // source data
|
||||
rowIndex?: number; // the hover row
|
||||
columnIndex?: number; // the hover column
|
||||
dataId?: string; // identifying string to correlate data between publishers and subscribers
|
||||
|
||||
// When dragging, this will capture the original state
|
||||
down?: Omit<DataHoverPayload, 'down'>;
|
||||
// When dragging, this will capture the point when the mouse was down
|
||||
point: Record<string, any>; // { time: 5678, lengthft: 456 } // each axis|scale gets a value
|
||||
down?: Record<string, any>;
|
||||
}
|
||||
|
||||
/** @alpha */
|
||||
|
||||
@@ -17,6 +17,7 @@ export interface BusEvent {
|
||||
export abstract class BusEventBase implements BusEvent {
|
||||
readonly type: string;
|
||||
readonly payload?: any;
|
||||
readonly origin?: EventBus;
|
||||
|
||||
constructor() {
|
||||
//@ts-ignore
|
||||
|
||||
5
packages/grafana-data/src/types/dashboard.ts
Normal file
5
packages/grafana-data/src/types/dashboard.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export enum DashboardCursorSync {
|
||||
Off,
|
||||
Crosshair,
|
||||
Tooltip,
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
export * from './data';
|
||||
export * from './dataFrame';
|
||||
export * from './dataLink';
|
||||
export * from './dashboard';
|
||||
export * from './annotations';
|
||||
export * from './logs';
|
||||
export * from './navModel';
|
||||
|
||||
@@ -3,6 +3,7 @@ import { AngularPanelMenuItem } from './panel';
|
||||
import { DataFrame } from './dataFrame';
|
||||
import { eventFactory } from '../events/eventFactory';
|
||||
import { BusEventBase, BusEventWithPayload } from '../events/types';
|
||||
import { DataHoverPayload } from '../events';
|
||||
|
||||
export type AlertPayload = [string, string?];
|
||||
export type AlertErrorPayload = [string, (string | Error)?];
|
||||
@@ -28,7 +29,7 @@ export const PanelEvents = {
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export interface LegacyGraphHoverEventPayload {
|
||||
export interface LegacyGraphHoverEventPayload extends DataHoverPayload {
|
||||
pos: any;
|
||||
panel: {
|
||||
id: number;
|
||||
@@ -43,4 +44,5 @@ export class LegacyGraphHoverEvent extends BusEventWithPayload<LegacyGraphHoverE
|
||||
/** @alpha */
|
||||
export class LegacyGraphHoverClearEvent extends BusEventBase {
|
||||
static type = 'graph-hover-clear';
|
||||
payload: DataHoverPayload = { point: {} };
|
||||
}
|
||||
|
||||
@@ -1,14 +1,27 @@
|
||||
import React from 'react';
|
||||
import { AlignedData } from 'uplot';
|
||||
import { DataFrame, FieldMatcherID, fieldMatchers, FieldType, TimeRange, TimeZone } from '@grafana/data';
|
||||
import { Themeable2 } from '../../types';
|
||||
import { UPlotConfigBuilder } from '../uPlot/config/UPlotConfigBuilder';
|
||||
import { GraphNGLegendEvent, XYFieldMatchers } from './types';
|
||||
import { findMidPointYPosition, pluginLog, preparePlotData } from '../uPlot/utils';
|
||||
import {
|
||||
DataFrame,
|
||||
FieldMatcherID,
|
||||
fieldMatchers,
|
||||
FieldType,
|
||||
LegacyGraphHoverClearEvent,
|
||||
LegacyGraphHoverEvent,
|
||||
TimeRange,
|
||||
TimeZone,
|
||||
} from '@grafana/data';
|
||||
import { preparePlotFrame } from './utils';
|
||||
import { preparePlotData } from '../uPlot/utils';
|
||||
import { UPlotChart } from '../uPlot/Plot';
|
||||
|
||||
import { VizLegendOptions } from '../VizLegend/models.gen';
|
||||
import { PanelContext, PanelContextRoot } from '../PanelChrome/PanelContext';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { throttleTime } from 'rxjs/operators';
|
||||
import { GraphNGLegendEvent, XYFieldMatchers } from './types';
|
||||
import { UPlotConfigBuilder } from '../uPlot/config/UPlotConfigBuilder';
|
||||
import { VizLayout } from '../VizLayout/VizLayout';
|
||||
import { UPlotChart } from '../uPlot/Plot';
|
||||
|
||||
/**
|
||||
* @internal -- not a public API
|
||||
@@ -16,10 +29,10 @@ import { VizLayout } from '../VizLayout/VizLayout';
|
||||
export const FIXED_UNIT = '__fixed';
|
||||
|
||||
export interface GraphNGProps extends Themeable2 {
|
||||
width: number;
|
||||
height: number;
|
||||
frames: DataFrame[];
|
||||
structureRev?: number; // a number that will change when the frames[] structure changes
|
||||
width: number;
|
||||
height: number;
|
||||
timeRange: TimeRange;
|
||||
timeZone: TimeZone;
|
||||
legend: VizLegendOptions;
|
||||
@@ -55,9 +68,16 @@ export interface GraphNGState {
|
||||
* "Time as X" core component, expectes ascending x
|
||||
*/
|
||||
export class GraphNG extends React.Component<GraphNGProps, GraphNGState> {
|
||||
static contextType = PanelContextRoot;
|
||||
panelContext: PanelContext = {} as PanelContext;
|
||||
private plotInstance: React.RefObject<uPlot>;
|
||||
|
||||
private subscription = new Subscription();
|
||||
|
||||
constructor(props: GraphNGProps) {
|
||||
super(props);
|
||||
this.state = this.prepState(props);
|
||||
this.plotInstance = React.createRef();
|
||||
}
|
||||
|
||||
getTimeRange = () => this.props.timeRange;
|
||||
@@ -74,21 +94,76 @@ export class GraphNG extends React.Component<GraphNGProps, GraphNGState> {
|
||||
y: fieldMatchers.get(FieldMatcherID.numeric).get({}),
|
||||
}
|
||||
);
|
||||
pluginLog('GraphNG', false, 'data aligned', alignedFrame);
|
||||
|
||||
if (alignedFrame) {
|
||||
state = {
|
||||
alignedFrame,
|
||||
alignedData: preparePlotData(alignedFrame, [FieldType.number]),
|
||||
};
|
||||
pluginLog('GraphNG', false, 'data prepared', state.alignedData);
|
||||
|
||||
if (withConfig) {
|
||||
state.config = props.prepConfig(alignedFrame, this.getTimeRange);
|
||||
pluginLog('GraphNG', false, 'config prepared', state.config);
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.panelContext = this.context as PanelContext;
|
||||
const { eventBus } = this.panelContext;
|
||||
|
||||
this.subscription.add(
|
||||
eventBus
|
||||
.getStream(LegacyGraphHoverEvent)
|
||||
.pipe(throttleTime(50))
|
||||
.subscribe({
|
||||
next: (evt) => {
|
||||
const u = this.plotInstance?.current;
|
||||
if (u) {
|
||||
// Try finding left position on time axis
|
||||
const left = u.valToPos(evt.payload.point.time, 'time');
|
||||
let top;
|
||||
if (left) {
|
||||
// find midpoint between points at current idx
|
||||
top = findMidPointYPosition(u, u.posToIdx(left));
|
||||
}
|
||||
|
||||
if (!top || !left) {
|
||||
return;
|
||||
}
|
||||
|
||||
u.setCursor({
|
||||
left,
|
||||
top,
|
||||
});
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
this.subscription.add(
|
||||
eventBus
|
||||
.getStream(LegacyGraphHoverClearEvent)
|
||||
.pipe(throttleTime(50))
|
||||
.subscribe({
|
||||
next: () => {
|
||||
const u = this.plotInstance?.current;
|
||||
|
||||
if (u) {
|
||||
u.setCursor({
|
||||
left: -10,
|
||||
top: -10,
|
||||
});
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: GraphNGProps) {
|
||||
const { frames, structureRev, timeZone, propsToDiff } = this.props;
|
||||
|
||||
@@ -107,6 +182,7 @@ export class GraphNG extends React.Component<GraphNGProps, GraphNGState> {
|
||||
|
||||
if (shouldReconfig) {
|
||||
newState.config = this.props.prepConfig(newState.alignedFrame, this.getTimeRange);
|
||||
pluginLog('GraphNG', false, 'config recreated', newState.config);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,6 +190,10 @@ export class GraphNG extends React.Component<GraphNGProps, GraphNGState> {
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { width, height, children, timeRange, renderLegend } = this.props;
|
||||
const { config, alignedFrame } = this.state;
|
||||
@@ -131,6 +211,7 @@ export class GraphNG extends React.Component<GraphNGProps, GraphNGState> {
|
||||
width={vizWidth}
|
||||
height={vizHeight}
|
||||
timeRange={timeRange}
|
||||
plotRef={(u) => ((this.plotInstance as React.MutableRefObject<uPlot>).current = u)}
|
||||
>
|
||||
{children ? children(config, alignedFrame) : null}
|
||||
</UPlotChart>
|
||||
|
||||
@@ -12,7 +12,7 @@ Object {
|
||||
"width": 1,
|
||||
},
|
||||
"labelFont": "12px \\"Roboto\\", \\"Helvetica\\", \\"Arial\\", sans-serif",
|
||||
"scale": "x",
|
||||
"scale": "time",
|
||||
"show": true,
|
||||
"side": 2,
|
||||
"size": [Function],
|
||||
@@ -79,6 +79,20 @@ Object {
|
||||
"stroke": [Function],
|
||||
"width": [Function],
|
||||
},
|
||||
"sync": Object {
|
||||
"filters": Object {
|
||||
"pub": [Function],
|
||||
},
|
||||
"key": "__global_",
|
||||
"match": Array [
|
||||
[Function],
|
||||
[Function],
|
||||
],
|
||||
"scales": Array [
|
||||
"time",
|
||||
"__fixed",
|
||||
],
|
||||
},
|
||||
},
|
||||
"hooks": Object {},
|
||||
"scales": Object {
|
||||
@@ -91,7 +105,7 @@ Object {
|
||||
"range": [Function],
|
||||
"time": undefined,
|
||||
},
|
||||
"x": Object {
|
||||
"time": Object {
|
||||
"auto": false,
|
||||
"dir": 1,
|
||||
"ori": 0,
|
||||
|
||||
@@ -2,7 +2,9 @@ import { preparePlotFrame } from './utils';
|
||||
import { preparePlotConfigBuilder } from '../TimeSeries/utils';
|
||||
import {
|
||||
createTheme,
|
||||
DashboardCursorSync,
|
||||
DefaultTimeZone,
|
||||
EventBusSrv,
|
||||
FieldConfig,
|
||||
FieldMatcherID,
|
||||
fieldMatchers,
|
||||
@@ -194,6 +196,8 @@ describe('GraphNG utils', () => {
|
||||
theme: createTheme(),
|
||||
timeZone: DefaultTimeZone,
|
||||
getTimeRange: getDefaultTimeRange,
|
||||
eventBus: new EventBusSrv(),
|
||||
sync: DashboardCursorSync.Tooltip,
|
||||
}).getConfig();
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import { XYFieldMatchers } from './types';
|
||||
import {
|
||||
ArrayVector,
|
||||
DataFrame,
|
||||
EventBus,
|
||||
FieldType,
|
||||
GrafanaTheme2,
|
||||
outerJoinDataFrames,
|
||||
@@ -9,13 +10,14 @@ import {
|
||||
TimeZone,
|
||||
} from '@grafana/data';
|
||||
import { nullToUndefThreshold } from './nullToUndefThreshold';
|
||||
export interface PrepConfigOpts {
|
||||
|
||||
export type PrepConfigOpts<T extends Record<string, any> = {}> = {
|
||||
frame: DataFrame;
|
||||
theme: GrafanaTheme2;
|
||||
timeZone: TimeZone;
|
||||
getTimeRange: () => TimeRange;
|
||||
[prop: string]: any;
|
||||
}
|
||||
eventBus: EventBus;
|
||||
} & T;
|
||||
|
||||
function applySpanNullsThresholds(frames: DataFrame[]) {
|
||||
for (const frame of frames) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { EventBusSrv, EventBus } from '@grafana/data';
|
||||
import { EventBusSrv, EventBus, DashboardCursorSync } from '@grafana/data';
|
||||
import React from 'react';
|
||||
import { SeriesVisibilityChangeMode } from '.';
|
||||
|
||||
@@ -6,6 +6,9 @@ import { SeriesVisibilityChangeMode } from '.';
|
||||
export interface PanelContext {
|
||||
eventBus: EventBus;
|
||||
|
||||
/** Dashboard panels sync */
|
||||
sync?: DashboardCursorSync;
|
||||
|
||||
/**
|
||||
* Called when a component wants to change the color for a series
|
||||
*
|
||||
@@ -16,7 +19,7 @@ export interface PanelContext {
|
||||
onToggleSeriesVisibility?: (label: string, mode: SeriesVisibilityChangeMode) => void;
|
||||
}
|
||||
|
||||
const PanelContextRoot = React.createContext<PanelContext>({
|
||||
export const PanelContextRoot = React.createContext<PanelContext>({
|
||||
eventBus: new EventBusSrv(),
|
||||
});
|
||||
|
||||
|
||||
@@ -6,18 +6,20 @@ import { PlotLegend } from '../uPlot/PlotLegend';
|
||||
import { LegendDisplayMode } from '../VizLegend/models.gen';
|
||||
import { preparePlotConfigBuilder } from './utils';
|
||||
import { withTheme2 } from '../../themes/ThemeContext';
|
||||
import { PanelContext, PanelContextRoot } from '../PanelChrome/PanelContext';
|
||||
|
||||
const propsToDiff: string[] = [];
|
||||
|
||||
type TimeSeriesProps = Omit<GraphNGProps, 'prepConfig' | 'propsToDiff' | 'renderLegend'>;
|
||||
|
||||
export class UnthemedTimeSeries extends React.Component<TimeSeriesProps> {
|
||||
static contextType = PanelContextRoot;
|
||||
panelContext: PanelContext = {} as PanelContext;
|
||||
|
||||
prepConfig = (alignedFrame: DataFrame, getTimeRange: () => TimeRange) => {
|
||||
return preparePlotConfigBuilder({
|
||||
frame: alignedFrame,
|
||||
getTimeRange,
|
||||
...this.props,
|
||||
});
|
||||
const { eventBus, sync } = this.context;
|
||||
const { theme, timeZone } = this.props;
|
||||
return preparePlotConfigBuilder({ frame: alignedFrame, theme, timeZone, getTimeRange, eventBus, sync });
|
||||
};
|
||||
|
||||
renderLegend = (config: UPlotConfigBuilder) => {
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { isNumber } from 'lodash';
|
||||
import {
|
||||
DashboardCursorSync,
|
||||
DataFrame,
|
||||
DataHoverClearEvent,
|
||||
DataHoverEvent,
|
||||
DataHoverPayload,
|
||||
FieldConfig,
|
||||
FieldType,
|
||||
formattedValueToString,
|
||||
@@ -9,7 +13,6 @@ import {
|
||||
getFieldSeriesColor,
|
||||
} from '@grafana/data';
|
||||
|
||||
import { PrepConfigOpts } from '../GraphNG/utils';
|
||||
import { UPlotConfigBuilder } from '../uPlot/config/UPlotConfigBuilder';
|
||||
import { FIXED_UNIT } from '../GraphNG/GraphNG';
|
||||
import {
|
||||
@@ -22,6 +25,7 @@ import {
|
||||
ScaleOrientation,
|
||||
} from '../uPlot/config';
|
||||
import { collectStackingGroups } from '../uPlot/utils';
|
||||
import { PrepConfigOpts } from '../GraphNG/utils';
|
||||
|
||||
const defaultFormatter = (v: any) => (v == null ? '-' : v.toFixed(1));
|
||||
|
||||
@@ -31,9 +35,9 @@ const defaultConfig: GraphFieldConfig = {
|
||||
axisPlacement: AxisPlacement.Auto,
|
||||
};
|
||||
|
||||
type PrepConfig = (opts: PrepConfigOpts) => UPlotConfigBuilder;
|
||||
type PrepConfig = (opts: PrepConfigOpts<{ sync: DashboardCursorSync }>) => UPlotConfigBuilder;
|
||||
|
||||
export const preparePlotConfigBuilder: PrepConfig = ({ frame, theme, timeZone, getTimeRange }) => {
|
||||
export const preparePlotConfigBuilder: PrepConfig = ({ frame, theme, timeZone, getTimeRange, eventBus, sync }) => {
|
||||
const builder = new UPlotConfigBuilder(timeZone);
|
||||
|
||||
// X is the first field in the aligned frame
|
||||
@@ -44,20 +48,24 @@ export const preparePlotConfigBuilder: PrepConfig = ({ frame, theme, timeZone, g
|
||||
|
||||
let seriesIndex = 0;
|
||||
|
||||
let xScaleKey = '_x';
|
||||
let yScaleKey = '';
|
||||
|
||||
if (xField.type === FieldType.time) {
|
||||
xScaleKey = 'time';
|
||||
builder.addScale({
|
||||
scaleKey: 'x',
|
||||
scaleKey: xScaleKey,
|
||||
orientation: ScaleOrientation.Horizontal,
|
||||
direction: ScaleDirection.Right,
|
||||
isTime: true,
|
||||
range: () => {
|
||||
const timeRange = getTimeRange();
|
||||
return [timeRange.from.valueOf(), timeRange.to.valueOf()];
|
||||
const r = getTimeRange();
|
||||
return [r.from.valueOf(), r.to.valueOf()];
|
||||
},
|
||||
});
|
||||
|
||||
builder.addAxis({
|
||||
scaleKey: 'x',
|
||||
scaleKey: xScaleKey,
|
||||
isTime: true,
|
||||
placement: AxisPlacement.Bottom,
|
||||
timeZone,
|
||||
@@ -65,14 +73,18 @@ export const preparePlotConfigBuilder: PrepConfig = ({ frame, theme, timeZone, g
|
||||
});
|
||||
} else {
|
||||
// Not time!
|
||||
if (xField.config.unit) {
|
||||
xScaleKey = xField.config.unit;
|
||||
}
|
||||
|
||||
builder.addScale({
|
||||
scaleKey: 'x',
|
||||
scaleKey: xScaleKey,
|
||||
orientation: ScaleOrientation.Horizontal,
|
||||
direction: ScaleDirection.Right,
|
||||
});
|
||||
|
||||
builder.addAxis({
|
||||
scaleKey: 'x',
|
||||
scaleKey: xScaleKey,
|
||||
placement: AxisPlacement.Bottom,
|
||||
theme,
|
||||
});
|
||||
@@ -82,7 +94,7 @@ export const preparePlotConfigBuilder: PrepConfig = ({ frame, theme, timeZone, g
|
||||
|
||||
let indexByName: Map<string, number> | undefined = undefined;
|
||||
|
||||
for (let i = 0; i < frame.fields.length; i++) {
|
||||
for (let i = 1; i < frame.fields.length; i++) {
|
||||
const field = frame.fields[i];
|
||||
const config = field.config as FieldConfig<GraphFieldConfig>;
|
||||
const customConfig: GraphFieldConfig = {
|
||||
@@ -114,6 +126,10 @@ export const preparePlotConfigBuilder: PrepConfig = ({ frame, theme, timeZone, g
|
||||
softMax: customConfig.axisSoftMax,
|
||||
});
|
||||
|
||||
if (!yScaleKey) {
|
||||
yScaleKey = scaleKey;
|
||||
}
|
||||
|
||||
if (customConfig.axisPlacement !== AxisPlacement.Hidden) {
|
||||
builder.addAxis({
|
||||
scaleKey,
|
||||
@@ -183,7 +199,6 @@ export const preparePlotConfigBuilder: PrepConfig = ({ frame, theme, timeZone, g
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
collectStackingGroups(field, stackingGroups, seriesIndex);
|
||||
}
|
||||
|
||||
@@ -197,6 +212,45 @@ export const preparePlotConfigBuilder: PrepConfig = ({ frame, theme, timeZone, g
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
builder.scaleKeys = [xScaleKey, yScaleKey];
|
||||
|
||||
if (sync !== DashboardCursorSync.Off) {
|
||||
const payload: DataHoverPayload = {
|
||||
point: {
|
||||
[xScaleKey]: null,
|
||||
[yScaleKey]: null,
|
||||
},
|
||||
data: frame,
|
||||
};
|
||||
const hoverEvent = new DataHoverEvent(payload);
|
||||
builder.setCursor({
|
||||
sync: {
|
||||
key: '__global_',
|
||||
filters: {
|
||||
pub: (type: string, src: uPlot, x: number, y: number, w: number, h: number, dataIdx: number) => {
|
||||
payload.columnIndex = dataIdx;
|
||||
if (x < 0 && y < 0) {
|
||||
payload.point[xScaleKey] = null;
|
||||
payload.point[yScaleKey] = null;
|
||||
eventBus.publish(new DataHoverClearEvent(payload));
|
||||
} else {
|
||||
// convert the points
|
||||
payload.point[xScaleKey] = src.posToVal(x, xScaleKey);
|
||||
payload.point[yScaleKey] = src.posToVal(y, yScaleKey);
|
||||
eventBus.publish(hoverEvent);
|
||||
hoverEvent.payload.down = undefined;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
// ??? setSeries: syncMode === DashboardCursorSync.Tooltip,
|
||||
scales: builder.scaleKeys,
|
||||
match: [() => true, () => true],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return builder;
|
||||
};
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { GraphNG, GraphNGProps } from '../GraphNG/GraphNG';
|
||||
import { UPlotConfigBuilder } from '../uPlot/config/UPlotConfigBuilder';
|
||||
import { preparePlotConfigBuilder } from './utils';
|
||||
import { BarValueVisibility, TimelineMode } from './types';
|
||||
import { PanelContext, PanelContextRoot } from '../PanelChrome/PanelContext';
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
@@ -19,10 +20,17 @@ export interface TimelineProps extends Omit<GraphNGProps, 'prepConfig' | 'propsT
|
||||
const propsToDiff = ['mode', 'rowHeight', 'colWidth', 'showValue'];
|
||||
|
||||
export class UnthemedTimelineChart extends React.Component<TimelineProps> {
|
||||
static contextType = PanelContextRoot;
|
||||
panelContext: PanelContext = {} as PanelContext;
|
||||
|
||||
prepConfig = (alignedFrame: DataFrame, getTimeRange: () => TimeRange) => {
|
||||
this.panelContext = this.context as PanelContext;
|
||||
const { eventBus } = this.panelContext;
|
||||
|
||||
return preparePlotConfigBuilder({
|
||||
frame: alignedFrame,
|
||||
getTimeRange,
|
||||
eventBus,
|
||||
...this.props,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -42,14 +42,14 @@ export function preparePlotFrame(data: DataFrame[], dimFields: XYFieldMatchers)
|
||||
});
|
||||
}
|
||||
|
||||
interface PrepConfigOptsTimeline extends PrepConfigOpts {
|
||||
type PrepConfig = (
|
||||
opts: PrepConfigOpts<{
|
||||
mode: TimelineMode;
|
||||
rowHeight: number;
|
||||
colWidth?: number;
|
||||
showValue: BarValueVisibility;
|
||||
}
|
||||
|
||||
type PrepConfig = (opts: PrepConfigOptsTimeline) => UPlotConfigBuilder;
|
||||
}>
|
||||
) => UPlotConfigBuilder;
|
||||
|
||||
export const preparePlotConfigBuilder: PrepConfig = ({
|
||||
frame,
|
||||
|
||||
@@ -94,6 +94,7 @@ export const VizTooltipContainer: React.FC<VizTooltipContainerProps> = ({
|
||||
left: 0,
|
||||
top: 0,
|
||||
transform: `translate3d(${placement.x}px, ${placement.y}px, 0)`,
|
||||
transition: 'all ease-out 0.1s',
|
||||
}}
|
||||
{...otherProps}
|
||||
className={cx(styles.wrapper, className)}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { createRef } from 'react';
|
||||
import React, { createRef, MutableRefObject } from 'react';
|
||||
import uPlot, { Options } from 'uplot';
|
||||
import { PlotContext, PlotContextType } from './context';
|
||||
import { DEFAULT_PLOT_CONFIG } from './utils';
|
||||
@@ -28,6 +28,7 @@ type UPlotChartState = {
|
||||
*/
|
||||
export class UPlotChart extends React.Component<PlotProps, UPlotChartState> {
|
||||
plotContainer = createRef<HTMLDivElement>();
|
||||
plotCanvasBBox = createRef<DOMRect>();
|
||||
|
||||
constructor(props: PlotProps) {
|
||||
super(props);
|
||||
@@ -35,20 +36,27 @@ export class UPlotChart extends React.Component<PlotProps, UPlotChartState> {
|
||||
this.state = {
|
||||
ctx: {
|
||||
plot: null,
|
||||
getCanvasBoundingBox: () => this.plotCanvasBBox.current,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
reinitPlot() {
|
||||
let { ctx } = this.state;
|
||||
let { width, height, plotRef } = this.props;
|
||||
|
||||
ctx.plot?.destroy();
|
||||
|
||||
let { width, height } = this.props;
|
||||
|
||||
if (width === 0 && height === 0) {
|
||||
return;
|
||||
}
|
||||
this.props.config.addHook('setSize', (u) => {
|
||||
const canvas = u.root.querySelector<HTMLDivElement>('.u-over');
|
||||
if (!canvas) {
|
||||
return;
|
||||
}
|
||||
(this.plotCanvasBBox as MutableRefObject<any>).current = canvas.getBoundingClientRect();
|
||||
});
|
||||
|
||||
const config: Options = {
|
||||
...DEFAULT_PLOT_CONFIG,
|
||||
@@ -58,11 +66,19 @@ export class UPlotChart extends React.Component<PlotProps, UPlotChartState> {
|
||||
...this.props.config.getConfig(),
|
||||
};
|
||||
|
||||
this.setState({
|
||||
const plot = new uPlot(config, this.props.data, this.plotContainer!.current!);
|
||||
|
||||
if (plotRef) {
|
||||
plotRef(plot);
|
||||
}
|
||||
|
||||
this.setState((s) => ({
|
||||
...s,
|
||||
ctx: {
|
||||
plot: new uPlot(config, this.props.data, this.plotContainer!.current!),
|
||||
...s.ctx,
|
||||
plot,
|
||||
},
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -82,7 +98,7 @@ export class UPlotChart extends React.Component<PlotProps, UPlotChartState> {
|
||||
);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: PlotProps, prevState: object) {
|
||||
componentDidUpdate(prevProps: PlotProps) {
|
||||
let { ctx } = this.state;
|
||||
|
||||
if (!sameDims(prevProps, this.props)) {
|
||||
|
||||
@@ -28,6 +28,9 @@ export class UPlotConfigBuilder {
|
||||
this.tz = getTimeZoneInfo(timeZone, Date.now())?.ianaName;
|
||||
}
|
||||
|
||||
// Exposed to let the container know the primary scale keys
|
||||
scaleKeys: [string, string] = ['', ''];
|
||||
|
||||
addHook<T extends keyof Hooks.Defs>(type: T, hook: Hooks.Defs[T]) {
|
||||
pluginLog('UPlotConfigBuilder', false, 'addHook', type);
|
||||
|
||||
@@ -81,7 +84,7 @@ export class UPlotConfigBuilder {
|
||||
}
|
||||
|
||||
setCursor(cursor?: Cursor) {
|
||||
this.cursor = cursor;
|
||||
this.cursor = { ...this.cursor, ...cursor };
|
||||
}
|
||||
|
||||
setSelect(select: Select) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { useContext } from 'react';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
export interface PlotContextType {
|
||||
plot: uPlot | null;
|
||||
getCanvasBoundingBox: () => DOMRect | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
import React, { useEffect, useLayoutEffect, useState } from 'react';
|
||||
import { Portal } from '../../Portal/Portal';
|
||||
import { usePlotContext } from '../context';
|
||||
import {
|
||||
@@ -12,8 +12,9 @@ import {
|
||||
} from '@grafana/data';
|
||||
import { SeriesTable, SeriesTableRowProps, TooltipDisplayMode, VizTooltipContainer } from '../../VizTooltip';
|
||||
import { UPlotConfigBuilder } from '../config/UPlotConfigBuilder';
|
||||
import { pluginLog } from '../utils';
|
||||
import { findMidPointYPosition, pluginLog } from '../utils';
|
||||
import { useTheme2 } from '../../../themes/ThemeContext';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
interface TooltipPluginProps {
|
||||
mode?: TooltipDisplayMode;
|
||||
@@ -22,6 +23,8 @@ interface TooltipPluginProps {
|
||||
config: UPlotConfigBuilder;
|
||||
}
|
||||
|
||||
const TOOLTIP_OFFSET = 10;
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
@@ -33,46 +36,41 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
|
||||
}) => {
|
||||
const theme = useTheme2();
|
||||
const plotCtx = usePlotContext();
|
||||
const plotCanvas = useRef<HTMLDivElement>();
|
||||
const plotCanvasBBox = useRef<any>({ left: 0, top: 0, right: 0, bottom: 0, width: 0, height: 0 });
|
||||
const [focusedSeriesIdx, setFocusedSeriesIdx] = useState<number | null>(null);
|
||||
const [focusedPointIdx, setFocusedPointIdx] = useState<number | null>(null);
|
||||
const [coords, setCoords] = useState<{ viewport: CartesianCoords2D; plotCanvas: CartesianCoords2D } | null>(null);
|
||||
|
||||
const [coords, setCoords] = useState<CartesianCoords2D | null>(null);
|
||||
|
||||
const pluginId = `TooltipPlugin`;
|
||||
|
||||
// Debug logs
|
||||
useEffect(() => {
|
||||
pluginLog('TooltipPlugin', true, `Focused series: ${focusedSeriesIdx}, focused point: ${focusedPointIdx}`);
|
||||
pluginLog(pluginId, true, `Focused series: ${focusedSeriesIdx}, focused point: ${focusedPointIdx}`);
|
||||
}, [focusedPointIdx, focusedSeriesIdx]);
|
||||
|
||||
// Add uPlot hooks to the config, or re-add when the config changed
|
||||
useLayoutEffect(() => {
|
||||
const onMouseCapture = (e: MouseEvent) => {
|
||||
setCoords({
|
||||
plotCanvas: {
|
||||
x: e.clientX - plotCanvasBBox.current.left,
|
||||
y: e.clientY - plotCanvasBBox.current.top,
|
||||
},
|
||||
viewport: {
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
config.addHook('init', (u) => {
|
||||
const canvas = u.root.querySelector<HTMLDivElement>('.u-over');
|
||||
plotCanvas.current = canvas || undefined;
|
||||
plotCanvas.current?.addEventListener('mousemove', onMouseCapture);
|
||||
plotCanvas.current?.addEventListener('mouseleave', () => {});
|
||||
});
|
||||
|
||||
config.addHook('setCursor', (u) => {
|
||||
setFocusedPointIdx(u.cursor.idx === undefined ? null : u.cursor.idx);
|
||||
const bbox = plotCtx.getCanvasBoundingBox();
|
||||
if (!bbox) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { x, y } = positionTooltip(u, bbox);
|
||||
|
||||
if (x !== undefined && y !== undefined) {
|
||||
setCoords({ x, y });
|
||||
} else {
|
||||
setCoords(null);
|
||||
}
|
||||
|
||||
setFocusedPointIdx(u.cursor.idx === undefined ? u.posToIdx(u.cursor.left || 0) : u.cursor.idx);
|
||||
});
|
||||
|
||||
config.addHook('setSeries', (_, idx) => {
|
||||
setFocusedSeriesIdx(idx);
|
||||
});
|
||||
}, [config]);
|
||||
}, [plotCtx, config]);
|
||||
|
||||
const plotInstance = plotCtx.plot;
|
||||
if (!plotInstance || focusedPointIdx === null) {
|
||||
@@ -142,15 +140,52 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
|
||||
tooltip = <SeriesTable series={series} timestamp={xVal} />;
|
||||
}
|
||||
|
||||
if (!tooltip || !coords) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<VizTooltipContainer position={{ x: coords.viewport.x, y: coords.viewport.y }} offset={{ x: 10, y: 10 }}>
|
||||
{tooltip && coords && (
|
||||
<VizTooltipContainer position={{ x: coords.x, y: coords.y }} offset={{ x: TOOLTIP_OFFSET, y: TOOLTIP_OFFSET }}>
|
||||
{tooltip}
|
||||
</VizTooltipContainer>
|
||||
)}
|
||||
</Portal>
|
||||
);
|
||||
};
|
||||
|
||||
function isCursourOutsideCanvas({ left, top }: uPlot.Cursor, canvas: DOMRect) {
|
||||
if (left === undefined || top === undefined) {
|
||||
return false;
|
||||
}
|
||||
return left < 0 || left > canvas.width || top < 0 || top > canvas.height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given uPlot cursor position, figure out position of the tooltip withing the canvas bbox
|
||||
* Tooltip is positioned relatively to a viewport
|
||||
* @internal
|
||||
**/
|
||||
export function positionTooltip(u: uPlot, bbox: DOMRect) {
|
||||
let x, y;
|
||||
const cL = u.cursor.left || 0;
|
||||
const cT = u.cursor.top || 0;
|
||||
|
||||
if (isCursourOutsideCanvas(u.cursor, bbox)) {
|
||||
const idx = u.posToIdx(cL);
|
||||
// when cursor outside of uPlot's canvas
|
||||
if (cT < 0 || cT > bbox.height) {
|
||||
let pos = findMidPointYPosition(u, idx);
|
||||
|
||||
if (pos) {
|
||||
y = bbox.top + pos;
|
||||
if (cL >= 0 && cL <= bbox.width) {
|
||||
// find x-scale position for a current cursor left position
|
||||
x = bbox.left + u.valToPos(u.data[0][u.posToIdx(cL)], u.series[0].scale!);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
x = bbox.left + cL;
|
||||
y = bbox.top + cT;
|
||||
}
|
||||
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Options, AlignedData } from 'uplot';
|
||||
import uPlot, { Options, AlignedData } from 'uplot';
|
||||
import { TimeRange } from '@grafana/data';
|
||||
import { UPlotConfigBuilder } from './config/UPlotConfigBuilder';
|
||||
|
||||
@@ -19,6 +19,8 @@ export interface PlotProps {
|
||||
config: UPlotConfigBuilder;
|
||||
timeRange: TimeRange;
|
||||
children?: React.ReactNode;
|
||||
// Reference to uPlot instance
|
||||
plotRef?: (u: uPlot) => void;
|
||||
}
|
||||
|
||||
export abstract class PlotConfigBuilder<P, T> {
|
||||
|
||||
@@ -106,6 +106,57 @@ export function collectStackingGroups(f: Field, groups: Map<string, number[]>, s
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds y axis midpoind for point at given idx (css pixels relative to uPlot canvas)
|
||||
* @internal
|
||||
**/
|
||||
|
||||
export function findMidPointYPosition(u: uPlot, idx: number) {
|
||||
let y;
|
||||
let sMaxIdx = 1;
|
||||
let sMinIdx = 1;
|
||||
// assume min/max being values of 1st series
|
||||
let max = u.data[1][idx];
|
||||
let min = u.data[1][idx];
|
||||
|
||||
// find min max values AND ids of the corresponding series to get the scales
|
||||
for (let i = 1; i < u.data.length; i++) {
|
||||
const sData = u.data[i];
|
||||
const sVal = sData[idx];
|
||||
if (sVal !== null) {
|
||||
if (max === null) {
|
||||
max = sVal;
|
||||
} else {
|
||||
if (sVal > max) {
|
||||
max = u.data[i][idx];
|
||||
sMaxIdx = i;
|
||||
}
|
||||
}
|
||||
if (min === null) {
|
||||
min = sVal;
|
||||
} else {
|
||||
if (sVal < min) {
|
||||
min = u.data[i][idx];
|
||||
sMinIdx = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (min === null && max === null) {
|
||||
// no tooltip to show
|
||||
y = undefined;
|
||||
} else if (min !== null && max !== null) {
|
||||
// find median position
|
||||
y = (u.valToPos(min, u.series[sMinIdx].scale!) + u.valToPos(max, u.series[sMaxIdx].scale!)) / 2;
|
||||
} else {
|
||||
// snap tooltip to min OR max point, one of thos is not null :)
|
||||
y = u.valToPos((min || max)!, u.series[(sMaxIdx || sMinIdx)!].scale!);
|
||||
}
|
||||
|
||||
return y;
|
||||
}
|
||||
|
||||
// Dev helpers
|
||||
|
||||
/** @internal */
|
||||
|
||||
@@ -14,6 +14,7 @@ export interface Logger {
|
||||
logger: (...t: any[]) => void;
|
||||
enable: () => void;
|
||||
disable: () => void;
|
||||
isEnabled: () => boolean;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@@ -29,5 +30,6 @@ export const createLogger = (name: string): Logger => {
|
||||
},
|
||||
enable: () => (LOGGIN_ENABLED = true),
|
||||
disable: () => (LOGGIN_ENABLED = false),
|
||||
isEnabled: () => LOGGIN_ENABLED,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import { SelectableValue, TimeZone } from '@grafana/data';
|
||||
import { Select, TagsInput, Input, Field, CollapsableSection, RadioButtonGroup } from '@grafana/ui';
|
||||
import { TimeZone } from '@grafana/data';
|
||||
import { TagsInput, Input, Field, CollapsableSection, RadioButtonGroup } from '@grafana/ui';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { FolderPicker } from 'app/core/components/Select/FolderPicker';
|
||||
import { DashboardModel } from '../../state/DashboardModel';
|
||||
@@ -30,8 +30,8 @@ export const GeneralSettings: React.FC<Props> = ({ dashboard }) => {
|
||||
dashboard[event.currentTarget.name as 'title' | 'description'] = event.currentTarget.value;
|
||||
};
|
||||
|
||||
const onTooltipChange = (graphTooltip: SelectableValue<number>) => {
|
||||
dashboard.graphTooltip = graphTooltip.value;
|
||||
const onTooltipChange = (graphTooltip: number) => {
|
||||
dashboard.graphTooltip = graphTooltip;
|
||||
setRenderCounter(renderCounter + 1);
|
||||
};
|
||||
|
||||
@@ -117,12 +117,7 @@ export const GeneralSettings: React.FC<Props> = ({ dashboard }) => {
|
||||
label="Graph tooltip"
|
||||
description="Controls tooltip and hover highlight behavior across different panels"
|
||||
>
|
||||
<Select
|
||||
onChange={onTooltipChange}
|
||||
options={GRAPH_TOOLTIP_OPTIONS}
|
||||
width={40}
|
||||
value={dashboard.graphTooltip}
|
||||
/>
|
||||
<RadioButtonGroup onChange={onTooltipChange} options={GRAPH_TOOLTIP_OPTIONS} value={dashboard.graphTooltip} />
|
||||
</Field>
|
||||
</CollapsableSection>
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import { DashboardModel, PanelModel } from '../state';
|
||||
import { PANEL_BORDER } from 'app/core/constants';
|
||||
import {
|
||||
AbsoluteTimeRange,
|
||||
DashboardCursorSync,
|
||||
EventBusSrv,
|
||||
EventFilterOptions,
|
||||
FieldConfigSource,
|
||||
@@ -76,6 +77,7 @@ export class PanelChrome extends Component<Props, State> {
|
||||
renderCounter: 0,
|
||||
refreshWhenInView: false,
|
||||
context: {
|
||||
sync: props.isEditing ? DashboardCursorSync.Off : props.dashboard.graphTooltip,
|
||||
eventBus,
|
||||
onSeriesColorChange: this.onSeriesColorChange,
|
||||
onToggleSeriesVisibility: this.onSeriesVisibilityChange,
|
||||
@@ -139,7 +141,23 @@ export class PanelChrome extends Component<Props, State> {
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
const { isInView } = this.props;
|
||||
const { isInView, isEditing } = this.props;
|
||||
|
||||
if (prevProps.dashboard.graphTooltip !== this.props.dashboard.graphTooltip) {
|
||||
this.setState((s) => {
|
||||
return {
|
||||
context: { ...s.context, sync: isEditing ? DashboardCursorSync.Off : this.props.dashboard.graphTooltip },
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (isEditing !== prevProps.isEditing) {
|
||||
this.setState((s) => {
|
||||
return {
|
||||
context: { ...s.context, sync: isEditing ? DashboardCursorSync.Off : this.props.dashboard.graphTooltip },
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// View state has changed
|
||||
if (isInView !== prevProps.isInView) {
|
||||
|
||||
@@ -25,6 +25,7 @@ import { DashboardMigrator } from './DashboardMigrator';
|
||||
import {
|
||||
AnnotationQuery,
|
||||
AppEvent,
|
||||
DashboardCursorSync,
|
||||
dateTimeFormat,
|
||||
dateTimeFormatTimeAgo,
|
||||
DateTimeInput,
|
||||
@@ -74,7 +75,7 @@ export class DashboardModel {
|
||||
style: any;
|
||||
timezone: any;
|
||||
editable: any;
|
||||
graphTooltip: any;
|
||||
graphTooltip: DashboardCursorSync;
|
||||
time: any;
|
||||
private originalTime: any;
|
||||
timepicker: any;
|
||||
|
||||
69
public/app/plugins/panel/debug/CursorView.tsx
Normal file
69
public/app/plugins/panel/debug/CursorView.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import {
|
||||
EventBus,
|
||||
LegacyGraphHoverEvent,
|
||||
LegacyGraphHoverClearEvent,
|
||||
DataHoverEvent,
|
||||
DataHoverClearEvent,
|
||||
DataHoverPayload,
|
||||
BusEventWithPayload,
|
||||
} from '@grafana/data';
|
||||
import React, { Component } from 'react';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
interface Props {
|
||||
eventBus: EventBus;
|
||||
}
|
||||
|
||||
interface State {
|
||||
event?: BusEventWithPayload<DataHoverPayload>;
|
||||
}
|
||||
export class CursorView extends Component<Props, State> {
|
||||
subscription = new Subscription();
|
||||
state: State = {};
|
||||
|
||||
componentDidMount() {
|
||||
const { eventBus } = this.props;
|
||||
|
||||
this.subscription.add(
|
||||
eventBus.subscribe(DataHoverEvent, (event) => {
|
||||
this.setState({ event });
|
||||
})
|
||||
);
|
||||
|
||||
this.subscription.add(
|
||||
eventBus.subscribe(DataHoverClearEvent, (event) => {
|
||||
this.setState({ event });
|
||||
})
|
||||
);
|
||||
|
||||
this.subscription.add(
|
||||
eventBus.subscribe(LegacyGraphHoverEvent, (event) => {
|
||||
this.setState({ event });
|
||||
})
|
||||
);
|
||||
|
||||
this.subscription.add(
|
||||
eventBus.subscribe(LegacyGraphHoverClearEvent, (event) => {
|
||||
this.setState({ event });
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { event } = this.state;
|
||||
if (!event) {
|
||||
return <div>no events yet</div>;
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<h2>Origin: {(event.origin as any)?.path}</h2>
|
||||
<span>Type: {event.type}</span>
|
||||
<pre>{JSON.stringify(event.payload.point, null, ' ')}</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,50 +1,22 @@
|
||||
import React, { Component } from 'react';
|
||||
import { PanelProps } from '@grafana/data';
|
||||
|
||||
import { DebugPanelOptions, DebugMode, UpdateCounters } from './types';
|
||||
import { DebugPanelOptions, DebugMode } from './types';
|
||||
import { EventBusLoggerPanel } from './EventBusLogger';
|
||||
import { RenderInfoViewer } from './RenderInfoViewer';
|
||||
import { CursorView } from './CursorView';
|
||||
|
||||
type Props = PanelProps<DebugPanelOptions>;
|
||||
|
||||
export class DebugPanel extends Component<Props> {
|
||||
// Intentionally not state to avoid overhead -- yes, things will be 1 tick behind
|
||||
lastRender = Date.now();
|
||||
counters: UpdateCounters = {
|
||||
render: 0,
|
||||
dataChanged: 0,
|
||||
schemaChanged: 0,
|
||||
};
|
||||
|
||||
shouldComponentUpdate(prevProps: Props) {
|
||||
const { data, options } = this.props;
|
||||
|
||||
if (prevProps.data !== data) {
|
||||
this.counters.dataChanged++;
|
||||
|
||||
if (options.counters?.schemaChanged) {
|
||||
if (data.structureRev !== prevProps.data.structureRev) {
|
||||
this.counters.schemaChanged++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true; // always render?
|
||||
}
|
||||
|
||||
resetCounters = () => {
|
||||
this.counters = {
|
||||
render: 0,
|
||||
dataChanged: 0,
|
||||
schemaChanged: 0,
|
||||
};
|
||||
this.setState(this.state); // force update
|
||||
};
|
||||
|
||||
render() {
|
||||
const { options } = this.props;
|
||||
if (options.mode === DebugMode.Events) {
|
||||
return <EventBusLoggerPanel eventBus={this.props.eventBus} />;
|
||||
}
|
||||
if (options.mode === DebugMode.Cursor) {
|
||||
return <CursorView eventBus={this.props.eventBus} />;
|
||||
}
|
||||
|
||||
return <RenderInfoViewer {...this.props} />;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ export const plugin = new PanelPlugin<DebugPanelOptions>(DebugPanel).useFieldCon
|
||||
options: [
|
||||
{ label: 'Render', value: DebugMode.Render },
|
||||
{ label: 'Events', value: DebugMode.Events },
|
||||
{ label: 'Cursor', value: DebugMode.Cursor },
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
@@ -11,6 +11,7 @@ export type UpdateCounters = {
|
||||
export enum DebugMode {
|
||||
Render = 'render',
|
||||
Events = 'events',
|
||||
Cursor = 'cursor',
|
||||
}
|
||||
|
||||
export interface DebugPanelOptions {
|
||||
|
||||
@@ -7,6 +7,7 @@ export default function GraphTooltip(this: any, elem: any, dashboard: any, scope
|
||||
const self = this;
|
||||
const ctrl = scope.ctrl;
|
||||
const panel = ctrl.panel;
|
||||
const hoverEvent = new LegacyGraphHoverEvent({ pos: {}, point: {}, panel: this.panel });
|
||||
|
||||
const $tooltip = $('<div class="graph-tooltip">');
|
||||
|
||||
@@ -159,7 +160,10 @@ export default function GraphTooltip(this: any, elem: any, dashboard: any, scope
|
||||
|
||||
// broadcast to other graph panels that we are hovering!
|
||||
pos.panelRelY = (pos.pageY - elem.offset().top) / elem.height();
|
||||
dashboard.events.publish(new LegacyGraphHoverEvent({ pos: pos, panel: panel }));
|
||||
hoverEvent.payload.pos = pos;
|
||||
hoverEvent.payload.panel = panel;
|
||||
hoverEvent.payload.point['time'] = (pos as any).x;
|
||||
dashboard.events.publish(hoverEvent);
|
||||
});
|
||||
|
||||
elem.bind('plotclick', (event: any, pos: any, item: any) => {
|
||||
|
||||
@@ -62,6 +62,8 @@ export class HeatmapRenderer {
|
||||
margin: any;
|
||||
dataRangeWidingFactor: number;
|
||||
|
||||
hoverEvent: LegacyGraphHoverEvent;
|
||||
|
||||
constructor(private scope: any, private elem: any, attrs: any, private ctrl: any) {
|
||||
// $heatmap is JQuery object, but heatmap is D3
|
||||
this.$heatmap = this.elem.find('.heatmap-panel');
|
||||
@@ -91,6 +93,8 @@ export class HeatmapRenderer {
|
||||
this.$heatmap.on('mousedown', this.onMouseDown.bind(this));
|
||||
this.$heatmap.on('mousemove', this.onMouseMove.bind(this));
|
||||
this.$heatmap.on('mouseleave', this.onMouseLeave.bind(this));
|
||||
|
||||
this.hoverEvent = new LegacyGraphHoverEvent({ pos: {}, point: {}, panel: this.panel });
|
||||
}
|
||||
|
||||
onGraphHoverClear() {
|
||||
@@ -740,7 +744,10 @@ export class HeatmapRenderer {
|
||||
// Set minimum offset to prevent showing legend from another panel
|
||||
pos.panelRelY = Math.max(pos.offset.y / this.height, 0.001);
|
||||
// broadcast to other graph panels that we are hovering
|
||||
this.ctrl.dashboard.events.publish(new LegacyGraphHoverEvent({ pos: pos, panel: this.panel }));
|
||||
this.hoverEvent.payload.pos = pos;
|
||||
this.hoverEvent.payload.panel = this.panel;
|
||||
this.hoverEvent.payload.point['time'] = (pos as any).x;
|
||||
this.ctrl.dashboard.events.publish(this.hoverEvent);
|
||||
}
|
||||
|
||||
limitSelection(x2: number) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Field, PanelProps } from '@grafana/data';
|
||||
import { TimeSeries, TooltipPlugin, ZoomPlugin } from '@grafana/ui';
|
||||
import { DashboardCursorSync, Field, PanelProps } from '@grafana/data';
|
||||
import { TooltipDisplayMode, usePanelContext, TimeSeries, TooltipPlugin, ZoomPlugin } from '@grafana/ui';
|
||||
import { getFieldLinksForExplore } from 'app/features/explore/utils/links';
|
||||
import React from 'react';
|
||||
import { AnnotationsPlugin } from './plugins/AnnotationsPlugin';
|
||||
@@ -11,6 +11,7 @@ interface TimeSeriesPanelProps extends PanelProps<Options> {}
|
||||
|
||||
export const TimeSeriesPanel: React.FC<TimeSeriesPanelProps> = ({
|
||||
data,
|
||||
id,
|
||||
timeRange,
|
||||
timeZone,
|
||||
width,
|
||||
@@ -23,6 +24,8 @@ export const TimeSeriesPanel: React.FC<TimeSeriesPanelProps> = ({
|
||||
return getFieldLinksForExplore({ field, rowIndex, range: timeRange });
|
||||
};
|
||||
|
||||
const { sync } = usePanelContext();
|
||||
|
||||
if (!data || !data.series?.length) {
|
||||
return (
|
||||
<div className="panel-empty">
|
||||
@@ -48,7 +51,7 @@ export const TimeSeriesPanel: React.FC<TimeSeriesPanelProps> = ({
|
||||
<TooltipPlugin
|
||||
data={alignedDataFrame}
|
||||
config={config}
|
||||
mode={options.tooltipOptions.mode}
|
||||
mode={sync === DashboardCursorSync.Tooltip ? TooltipDisplayMode.Multi : options.tooltipOptions.mode}
|
||||
timeZone={timeZone}
|
||||
/>
|
||||
<ContextMenuPlugin
|
||||
|
||||
Reference in New Issue
Block a user