mirror of
https://github.com/grafana/grafana.git
synced 2025-09-24 19:34:09 +08:00
MarketTrend: rename to candlestick panel (#41582)
This commit is contained in:
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@ -102,6 +102,7 @@ go.sum @grafana/backend-platform
|
|||||||
/public/app/plugins/panel/timeseries @grafana/grafana-bi-squad
|
/public/app/plugins/panel/timeseries @grafana/grafana-bi-squad
|
||||||
/public/app/plugins/panel/geomap @grafana/grafana-edge-squad
|
/public/app/plugins/panel/geomap @grafana/grafana-edge-squad
|
||||||
/public/app/plugins/panel/canvas @grafana/grafana-edge-squad
|
/public/app/plugins/panel/canvas @grafana/grafana-edge-squad
|
||||||
|
/public/app/plugins/panel/candlestick @grafana/grafana-edge-squad
|
||||||
/public/app/plugins/panel/icon @grafana/grafana-edge-squad
|
/public/app/plugins/panel/icon @grafana/grafana-edge-squad
|
||||||
/scripts/build/release-packages.sh @grafana/plugins-platform-frontend
|
/scripts/build/release-packages.sh @grafana/plugins-platform-frontend
|
||||||
/scripts/circle-release-next-packages.sh @grafana/plugins-platform-frontend
|
/scripts/circle-release-next-packages.sh @grafana/plugins-platform-frontend
|
||||||
|
@ -144,7 +144,7 @@
|
|||||||
},
|
},
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"options": {
|
"options": {
|
||||||
"colorStrategy": "intra",
|
"colorStrategy": "open-close",
|
||||||
"colors": {
|
"colors": {
|
||||||
"down": "red",
|
"down": "red",
|
||||||
"up": "green"
|
"up": "green"
|
||||||
@ -155,8 +155,8 @@
|
|||||||
"displayMode": "list",
|
"displayMode": "list",
|
||||||
"placement": "bottom"
|
"placement": "bottom"
|
||||||
},
|
},
|
||||||
"mode": "pricevolume",
|
"mode": "candles+volume",
|
||||||
"priceStyle": "candles"
|
"candleStyle": "candles"
|
||||||
},
|
},
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
@ -166,7 +166,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"title": "Price & Volume",
|
"title": "Price & Volume",
|
||||||
"type": "market-trend"
|
"type": "candlestick"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldConfig": {
|
"fieldConfig": {
|
||||||
@ -227,7 +227,7 @@
|
|||||||
},
|
},
|
||||||
"id": 7,
|
"id": 7,
|
||||||
"options": {
|
"options": {
|
||||||
"colorStrategy": "inter",
|
"colorStrategy": "close-close",
|
||||||
"colors": {
|
"colors": {
|
||||||
"down": "red",
|
"down": "red",
|
||||||
"up": "green"
|
"up": "green"
|
||||||
@ -238,8 +238,8 @@
|
|||||||
"displayMode": "list",
|
"displayMode": "list",
|
||||||
"placement": "bottom"
|
"placement": "bottom"
|
||||||
},
|
},
|
||||||
"mode": "price",
|
"mode": "candles",
|
||||||
"priceStyle": "candles"
|
"candleStyle": "candles"
|
||||||
},
|
},
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
@ -265,7 +265,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"type": "market-trend"
|
"type": "candlestick"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldConfig": {
|
"fieldConfig": {
|
||||||
@ -326,7 +326,7 @@
|
|||||||
},
|
},
|
||||||
"id": 6,
|
"id": 6,
|
||||||
"options": {
|
"options": {
|
||||||
"colorStrategy": "intra",
|
"colorStrategy": "open-close",
|
||||||
"colors": {
|
"colors": {
|
||||||
"down": "red",
|
"down": "red",
|
||||||
"up": "blue"
|
"up": "blue"
|
||||||
@ -337,8 +337,8 @@
|
|||||||
"displayMode": "list",
|
"displayMode": "list",
|
||||||
"placement": "bottom"
|
"placement": "bottom"
|
||||||
},
|
},
|
||||||
"mode": "price",
|
"mode": "candles",
|
||||||
"priceStyle": "ohlcbars"
|
"candleStyle": "ohlcbars"
|
||||||
},
|
},
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
@ -364,7 +364,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"type": "market-trend"
|
"type": "candlestick"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldConfig": {
|
"fieldConfig": {
|
||||||
@ -458,7 +458,7 @@
|
|||||||
},
|
},
|
||||||
"id": 4,
|
"id": 4,
|
||||||
"options": {
|
"options": {
|
||||||
"colorStrategy": "intra",
|
"colorStrategy": "open-close",
|
||||||
"colors": {
|
"colors": {
|
||||||
"down": "red",
|
"down": "red",
|
||||||
"up": "yellow"
|
"up": "yellow"
|
||||||
@ -470,7 +470,7 @@
|
|||||||
"placement": "bottom"
|
"placement": "bottom"
|
||||||
},
|
},
|
||||||
"mode": "volume",
|
"mode": "volume",
|
||||||
"priceStyle": "candles"
|
"candleStyle": "candles"
|
||||||
},
|
},
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
@ -495,7 +495,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"type": "market-trend"
|
"type": "candlestick"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"refresh": false,
|
"refresh": false,
|
||||||
@ -515,7 +515,7 @@
|
|||||||
},
|
},
|
||||||
"timepicker": {},
|
"timepicker": {},
|
||||||
"timezone": "",
|
"timezone": "",
|
||||||
"title": "Market Trend",
|
"title": "Candlestick",
|
||||||
"uid": "MP-Di9F7k",
|
"uid": "MP-Di9F7k",
|
||||||
"version": 7,
|
"version": 7,
|
||||||
"weekStart": ""
|
"weekStart": ""
|
@ -8,7 +8,7 @@ import (
|
|||||||
pdashlist "github.com/grafana/grafana/public/app/plugins/panel/dashlist:grafanaschema"
|
pdashlist "github.com/grafana/grafana/public/app/plugins/panel/dashlist:grafanaschema"
|
||||||
pgauge "github.com/grafana/grafana/public/app/plugins/panel/gauge:grafanaschema"
|
pgauge "github.com/grafana/grafana/public/app/plugins/panel/gauge:grafanaschema"
|
||||||
phistogram "github.com/grafana/grafana/public/app/plugins/panel/histogram:grafanaschema"
|
phistogram "github.com/grafana/grafana/public/app/plugins/panel/histogram:grafanaschema"
|
||||||
pmt "github.com/grafana/grafana/public/app/plugins/panel/market-trend:grafanaschema"
|
pcandlestick "github.com/grafana/grafana/public/app/plugins/panel/candlestick:grafanaschema"
|
||||||
pnews "github.com/grafana/grafana/public/app/plugins/panel/news:grafanaschema"
|
pnews "github.com/grafana/grafana/public/app/plugins/panel/news:grafanaschema"
|
||||||
pstat "github.com/grafana/grafana/public/app/plugins/panel/stat:grafanaschema"
|
pstat "github.com/grafana/grafana/public/app/plugins/panel/stat:grafanaschema"
|
||||||
st "github.com/grafana/grafana/public/app/plugins/panel/state-timeline:grafanaschema"
|
st "github.com/grafana/grafana/public/app/plugins/panel/state-timeline:grafanaschema"
|
||||||
@ -32,7 +32,7 @@ Family: dashboard.Family & {
|
|||||||
dashlist: pdashlist.Panel
|
dashlist: pdashlist.Panel
|
||||||
gauge: pgauge.Panel
|
gauge: pgauge.Panel
|
||||||
histogram: phistogram.Panel
|
histogram: phistogram.Panel
|
||||||
"market-trend": pmt.Panel
|
candlestick: pcandlestick.Panel
|
||||||
news: pnews.Panel
|
news: pnews.Panel
|
||||||
stat: pstat.Panel
|
stat: pstat.Panel
|
||||||
"state-timeline": st.Panel
|
"state-timeline": st.Panel
|
||||||
|
@ -44,7 +44,7 @@ var skipPaths = []string{
|
|||||||
"public/app/plugins/panel/gauge/models.cue",
|
"public/app/plugins/panel/gauge/models.cue",
|
||||||
"public/app/plugins/panel/histogram/models.cue",
|
"public/app/plugins/panel/histogram/models.cue",
|
||||||
"public/app/plugins/panel/stat/models.cue",
|
"public/app/plugins/panel/stat/models.cue",
|
||||||
"public/app/plugins/panel/market-trend/models.cue",
|
"public/app/plugins/panel/candlestick/models.cue",
|
||||||
"public/app/plugins/panel/state-timeline/models.cue",
|
"public/app/plugins/panel/state-timeline/models.cue",
|
||||||
"public/app/plugins/panel/status-history/models.cue",
|
"public/app/plugins/panel/status-history/models.cue",
|
||||||
"public/app/plugins/panel/table/models.cue",
|
"public/app/plugins/panel/table/models.cue",
|
||||||
|
@ -72,7 +72,7 @@ func verifyCorePluginCatalogue(t *testing.T, pm *PluginManager) {
|
|||||||
"icon": {},
|
"icon": {},
|
||||||
"live": {},
|
"live": {},
|
||||||
"logs": {},
|
"logs": {},
|
||||||
"market-trend": {},
|
"candlestick": {},
|
||||||
"news": {},
|
"news": {},
|
||||||
"nodeGraph": {},
|
"nodeGraph": {},
|
||||||
"piechart": {},
|
"piechart": {},
|
||||||
|
@ -20,6 +20,7 @@ export const panelsToCheckFirst = [
|
|||||||
'text',
|
'text',
|
||||||
'dashlist',
|
'dashlist',
|
||||||
'logs',
|
'logs',
|
||||||
|
// 'candlestick', // uncomment when beta
|
||||||
];
|
];
|
||||||
|
|
||||||
export async function getAllSuggestions(data?: PanelData, panel?: PanelModel): Promise<VisualizationSuggestion[]> {
|
export async function getAllSuggestions(data?: PanelData, panel?: PanelModel): Promise<VisualizationSuggestion[]> {
|
||||||
|
@ -44,7 +44,7 @@ import * as textPanel from 'app/plugins/panel/text/module';
|
|||||||
import * as timeseriesPanel from 'app/plugins/panel/timeseries/module';
|
import * as timeseriesPanel from 'app/plugins/panel/timeseries/module';
|
||||||
import * as stateTimelinePanel from 'app/plugins/panel/state-timeline/module';
|
import * as stateTimelinePanel from 'app/plugins/panel/state-timeline/module';
|
||||||
import * as statusHistoryPanel from 'app/plugins/panel/status-history/module';
|
import * as statusHistoryPanel from 'app/plugins/panel/status-history/module';
|
||||||
import * as marketTrendPanel from 'app/plugins/panel/market-trend/module';
|
import * as candlestickPanel from 'app/plugins/panel/candlestick/module';
|
||||||
import * as graphPanel from 'app/plugins/panel/graph/module';
|
import * as graphPanel from 'app/plugins/panel/graph/module';
|
||||||
import * as xyChartPanel from 'app/plugins/panel/xychart/module';
|
import * as xyChartPanel from 'app/plugins/panel/xychart/module';
|
||||||
import * as dashListPanel from 'app/plugins/panel/dashlist/module';
|
import * as dashListPanel from 'app/plugins/panel/dashlist/module';
|
||||||
@ -100,7 +100,7 @@ const builtInPlugins: any = {
|
|||||||
'app/plugins/panel/timeseries/module': timeseriesPanel,
|
'app/plugins/panel/timeseries/module': timeseriesPanel,
|
||||||
'app/plugins/panel/state-timeline/module': stateTimelinePanel,
|
'app/plugins/panel/state-timeline/module': stateTimelinePanel,
|
||||||
'app/plugins/panel/status-history/module': statusHistoryPanel,
|
'app/plugins/panel/status-history/module': statusHistoryPanel,
|
||||||
'app/plugins/panel/market-trend/module': marketTrendPanel,
|
'app/plugins/panel/candlestick/module': candlestickPanel,
|
||||||
'app/plugins/panel/graph/module': graphPanel,
|
'app/plugins/panel/graph/module': graphPanel,
|
||||||
'app/plugins/panel/xychart/module': xyChartPanel,
|
'app/plugins/panel/xychart/module': xyChartPanel,
|
||||||
'app/plugins/panel/geomap/module': geomapPanel,
|
'app/plugins/panel/geomap/module': geomapPanel,
|
||||||
|
@ -13,15 +13,15 @@ import { AnnotationEditorPlugin } from '../timeseries/plugins/AnnotationEditorPl
|
|||||||
import { ThresholdControlsPlugin } from '../timeseries/plugins/ThresholdControlsPlugin';
|
import { ThresholdControlsPlugin } from '../timeseries/plugins/ThresholdControlsPlugin';
|
||||||
import { config } from 'app/core/config';
|
import { config } from 'app/core/config';
|
||||||
import { drawMarkers, FieldIndices } from './utils';
|
import { drawMarkers, FieldIndices } from './utils';
|
||||||
import { defaultColors, MarketOptions, MarketTrendMode } from './models.gen';
|
import { defaultColors, CandlestickOptions, VizDisplayMode } from './models.gen';
|
||||||
import { ScaleProps } from '@grafana/ui/src/components/uPlot/config/UPlotScaleBuilder';
|
import { ScaleProps } from '@grafana/ui/src/components/uPlot/config/UPlotScaleBuilder';
|
||||||
import { AxisProps } from '@grafana/ui/src/components/uPlot/config/UPlotAxisBuilder';
|
import { AxisProps } from '@grafana/ui/src/components/uPlot/config/UPlotAxisBuilder';
|
||||||
import { prepareCandlestickFields } from './fields';
|
import { prepareCandlestickFields } from './fields';
|
||||||
import uPlot from 'uplot';
|
import uPlot from 'uplot';
|
||||||
|
|
||||||
interface MarketPanelProps extends PanelProps<MarketOptions> {}
|
interface CandlestickPanelProps extends PanelProps<CandlestickOptions> {}
|
||||||
|
|
||||||
export const MarketTrendPanel: React.FC<MarketPanelProps> = ({
|
export const MarketTrendPanel: React.FC<CandlestickPanelProps> = ({
|
||||||
data,
|
data,
|
||||||
timeRange,
|
timeRange,
|
||||||
timeZone,
|
timeZone,
|
||||||
@ -61,7 +61,7 @@ export const MarketTrendPanel: React.FC<MarketPanelProps> = ({
|
|||||||
return doNothing;
|
return doNothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { mode, priceStyle, colorStrategy } = options;
|
const { mode, candleStyle, colorStrategy } = options;
|
||||||
const colors = { ...defaultColors, ...options.colors };
|
const colors = { ...defaultColors, ...options.colors };
|
||||||
let { open, high, low, close, volume } = fieldMap; // names from matched fields
|
let { open, high, low, close, volume } = fieldMap; // names from matched fields
|
||||||
|
|
||||||
@ -76,7 +76,7 @@ export const MarketTrendPanel: React.FC<MarketPanelProps> = ({
|
|||||||
let shouldRenderVolume = false;
|
let shouldRenderVolume = false;
|
||||||
|
|
||||||
// find volume field and set overrides
|
// find volume field and set overrides
|
||||||
if (volume != null && mode !== MarketTrendMode.Price) {
|
if (volume != null && mode !== VizDisplayMode.Candles) {
|
||||||
let volumeField = info.volume!;
|
let volumeField = info.volume!;
|
||||||
|
|
||||||
if (volumeField != null) {
|
if (volumeField != null) {
|
||||||
@ -89,7 +89,7 @@ export const MarketTrendPanel: React.FC<MarketPanelProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// we only want to put volume on own shorter axis when rendered with price
|
// we only want to put volume on own shorter axis when rendered with price
|
||||||
if (mode !== MarketTrendMode.Volume) {
|
if (mode !== VizDisplayMode.Volume) {
|
||||||
volumeField.config = { ...volumeField.config };
|
volumeField.config = { ...volumeField.config };
|
||||||
volumeField.config.unit = 'short';
|
volumeField.config.unit = 'short';
|
||||||
volumeField.display = getDisplayProcessor({
|
volumeField.display = getDisplayProcessor({
|
||||||
@ -133,7 +133,7 @@ export const MarketTrendPanel: React.FC<MarketPanelProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let shouldRenderPrice = mode !== MarketTrendMode.Volume && high != null && low != null;
|
let shouldRenderPrice = mode !== VizDisplayMode.Volume && high != null && low != null;
|
||||||
|
|
||||||
if (!shouldRenderPrice && !shouldRenderVolume) {
|
if (!shouldRenderPrice && !shouldRenderVolume) {
|
||||||
return doNothing;
|
return doNothing;
|
||||||
@ -187,7 +187,7 @@ export const MarketTrendPanel: React.FC<MarketPanelProps> = ({
|
|||||||
flatColor: config.theme2.visualization.getColorByName(colors.flat),
|
flatColor: config.theme2.visualization.getColorByName(colors.flat),
|
||||||
volumeAlpha,
|
volumeAlpha,
|
||||||
colorStrategy,
|
colorStrategy,
|
||||||
priceStyle,
|
candleStyle,
|
||||||
flatAsUp: true,
|
flatAsUp: true,
|
||||||
})
|
})
|
||||||
);
|
);
|
9
public/app/plugins/panel/candlestick/README.md
Normal file
9
public/app/plugins/panel/candlestick/README.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Candlestick
|
||||||
|
|
||||||
|
The candlestick panel shows a chart that is typically used to describe price movements of a security, derivative, or currency.
|
||||||
|
|
||||||
|
This chart is **included** with Grafana.
|
||||||
|
|
||||||
|
Read more about it here:
|
||||||
|
|
||||||
|
[https://grafana.com/docs/grafana/latest/visualizations/candlestick/](https://grafana.com/docs/grafana/latest/visualizations/candlestick/)
|
@ -1,11 +1,11 @@
|
|||||||
import { createTheme, toDataFrame } from '@grafana/data';
|
import { createTheme, toDataFrame } from '@grafana/data';
|
||||||
import { prepareCandlestickFields } from './fields';
|
import { prepareCandlestickFields } from './fields';
|
||||||
import { MarketOptions } from './models.gen';
|
import { CandlestickOptions } from './models.gen';
|
||||||
|
|
||||||
const theme = createTheme();
|
const theme = createTheme();
|
||||||
|
|
||||||
describe('Candlestick data', () => {
|
describe('Candlestick data', () => {
|
||||||
const options: MarketOptions = {} as MarketOptions;
|
const options: CandlestickOptions = {} as CandlestickOptions;
|
||||||
|
|
||||||
it('require a time field', () => {
|
it('require a time field', () => {
|
||||||
const info = prepareCandlestickFields(
|
const info = prepareCandlestickFields(
|
@ -9,7 +9,7 @@ import {
|
|||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { findField } from 'app/features/dimensions';
|
import { findField } from 'app/features/dimensions';
|
||||||
import { prepareGraphableFields } from '../timeseries/utils';
|
import { prepareGraphableFields } from '../timeseries/utils';
|
||||||
import { MarketOptions, CandlestickFieldMap } from './models.gen';
|
import { CandlestickOptions, CandlestickFieldMap } from './models.gen';
|
||||||
|
|
||||||
export interface FieldPickerInfo {
|
export interface FieldPickerInfo {
|
||||||
/** property name */
|
/** property name */
|
||||||
@ -30,31 +30,31 @@ export const candlestickFieldsInfo: Record<keyof CandlestickFieldMap, FieldPicke
|
|||||||
key: 'open',
|
key: 'open',
|
||||||
name: 'Open',
|
name: 'Open',
|
||||||
defaults: ['open', 'o'],
|
defaults: ['open', 'o'],
|
||||||
description: 'The value at the beginning of the period',
|
description: 'Value at the start of the period',
|
||||||
},
|
},
|
||||||
high: {
|
high: {
|
||||||
key: 'high',
|
key: 'high',
|
||||||
name: 'High',
|
name: 'High',
|
||||||
defaults: ['high', 'h', 'max'],
|
defaults: ['high', 'h', 'max'],
|
||||||
description: 'The maximum value within the period',
|
description: 'Maximum value within the period',
|
||||||
},
|
},
|
||||||
low: {
|
low: {
|
||||||
key: 'low',
|
key: 'low',
|
||||||
name: 'Low',
|
name: 'Low',
|
||||||
defaults: ['low', 'l', 'min'],
|
defaults: ['low', 'l', 'min'],
|
||||||
description: 'The minimum value within the period',
|
description: 'Minimum value within the period',
|
||||||
},
|
},
|
||||||
close: {
|
close: {
|
||||||
key: 'close',
|
key: 'close',
|
||||||
name: 'Close',
|
name: 'Close',
|
||||||
defaults: ['close', 'c'],
|
defaults: ['close', 'c'],
|
||||||
description: 'The value at the end of the measured period',
|
description: 'Value at the end of the period',
|
||||||
},
|
},
|
||||||
volume: {
|
volume: {
|
||||||
key: 'volume',
|
key: 'volume',
|
||||||
name: 'Volume',
|
name: 'Volume',
|
||||||
defaults: ['volume', 'v'],
|
defaults: ['volume', 'v'],
|
||||||
description: 'Activity within the measured period',
|
description: 'Sample count within the period',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -94,7 +94,7 @@ function findFieldOrAuto(frame: DataFrame, info: FieldPickerInfo, options: Candl
|
|||||||
|
|
||||||
export function prepareCandlestickFields(
|
export function prepareCandlestickFields(
|
||||||
series: DataFrame[] | undefined,
|
series: DataFrame[] | undefined,
|
||||||
options: MarketOptions,
|
options: CandlestickOptions,
|
||||||
theme: GrafanaTheme2
|
theme: GrafanaTheme2
|
||||||
): CandlestickData {
|
): CandlestickData {
|
||||||
if (!series?.length) {
|
if (!series?.length) {
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
@ -7,13 +7,13 @@ import { LegendDisplayMode, OptionsWithLegend } from '@grafana/schema';
|
|||||||
|
|
||||||
export const modelVersion = Object.freeze([1, 0]);
|
export const modelVersion = Object.freeze([1, 0]);
|
||||||
|
|
||||||
export enum MarketTrendMode {
|
export enum VizDisplayMode {
|
||||||
Price = 'price',
|
CandlesVolume = 'candles+volume',
|
||||||
|
Candles = 'candles',
|
||||||
Volume = 'volume',
|
Volume = 'volume',
|
||||||
PriceVolume = 'pricevolume',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum PriceStyle {
|
export enum CandleStyle {
|
||||||
Candles = 'candles',
|
Candles = 'candles',
|
||||||
OHLCBars = 'ohlcbars',
|
OHLCBars = 'ohlcbars',
|
||||||
}
|
}
|
||||||
@ -21,10 +21,10 @@ export enum PriceStyle {
|
|||||||
export enum ColorStrategy {
|
export enum ColorStrategy {
|
||||||
// up/down color depends on current close vs current open
|
// up/down color depends on current close vs current open
|
||||||
// filled always
|
// filled always
|
||||||
Intra = 'intra',
|
OpenClose = 'open-close',
|
||||||
// up/down color depends on current close vs prior close
|
// up/down color depends on current close vs prior close
|
||||||
// filled/hollow depends on current close vs current open
|
// filled/hollow depends on current close vs current open
|
||||||
Inter = 'inter',
|
CloseClose = 'close-close',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CandlestickFieldMap {
|
export interface CandlestickFieldMap {
|
||||||
@ -35,30 +35,30 @@ export interface CandlestickFieldMap {
|
|||||||
volume?: string;
|
volume?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MarketTrendColors {
|
export interface CandlestickColors {
|
||||||
up: string;
|
up: string;
|
||||||
down: string;
|
down: string;
|
||||||
flat: string;
|
flat: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defaultColors: MarketTrendColors = {
|
export const defaultColors: CandlestickColors = {
|
||||||
up: 'green',
|
up: 'green',
|
||||||
down: 'red',
|
down: 'red',
|
||||||
flat: 'gray',
|
flat: 'gray',
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface MarketOptions extends OptionsWithLegend {
|
export interface CandlestickOptions extends OptionsWithLegend {
|
||||||
mode: MarketTrendMode;
|
mode: VizDisplayMode;
|
||||||
priceStyle: PriceStyle;
|
candleStyle: CandleStyle;
|
||||||
colorStrategy: ColorStrategy;
|
colorStrategy: ColorStrategy;
|
||||||
fields: CandlestickFieldMap;
|
fields: CandlestickFieldMap;
|
||||||
colors: MarketTrendColors;
|
colors: CandlestickColors;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defaultPanelOptions: MarketOptions = {
|
export const defaultPanelOptions: CandlestickOptions = {
|
||||||
mode: MarketTrendMode.PriceVolume,
|
mode: VizDisplayMode.CandlesVolume,
|
||||||
priceStyle: PriceStyle.Candles,
|
candleStyle: CandleStyle.Candles,
|
||||||
colorStrategy: ColorStrategy.Intra,
|
colorStrategy: ColorStrategy.OpenClose,
|
||||||
colors: defaultColors,
|
colors: defaultColors,
|
||||||
fields: {},
|
fields: {},
|
||||||
legend: {
|
legend: {
|
@ -1,7 +1,6 @@
|
|||||||
import { GraphFieldConfig } from '@grafana/schema';
|
import { GraphFieldConfig } from '@grafana/schema';
|
||||||
import {
|
import {
|
||||||
Field,
|
Field,
|
||||||
FieldConfigProperty,
|
|
||||||
FieldType,
|
FieldType,
|
||||||
getFieldDisplayName,
|
getFieldDisplayName,
|
||||||
PanelOptionsEditorBuilder,
|
PanelOptionsEditorBuilder,
|
||||||
@ -9,48 +8,40 @@ import {
|
|||||||
SelectableValue,
|
SelectableValue,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { commonOptionsBuilder } from '@grafana/ui';
|
import { commonOptionsBuilder } from '@grafana/ui';
|
||||||
import { MarketTrendPanel } from './MarketTrendPanel';
|
import { MarketTrendPanel } from './CandlestickPanel';
|
||||||
import {
|
import {
|
||||||
defaultColors,
|
defaultColors,
|
||||||
MarketOptions,
|
CandlestickOptions,
|
||||||
MarketTrendMode,
|
VizDisplayMode,
|
||||||
ColorStrategy,
|
ColorStrategy,
|
||||||
PriceStyle,
|
|
||||||
defaultPanelOptions,
|
defaultPanelOptions,
|
||||||
|
CandleStyle,
|
||||||
} from './models.gen';
|
} from './models.gen';
|
||||||
import { defaultGraphConfig, getGraphFieldConfig } from '../timeseries/config';
|
import { defaultGraphConfig, getGraphFieldConfig } from '../timeseries/config';
|
||||||
import { CandlestickData, candlestickFieldsInfo, FieldPickerInfo, prepareCandlestickFields } from './fields';
|
import { CandlestickData, candlestickFieldsInfo, FieldPickerInfo, prepareCandlestickFields } from './fields';
|
||||||
import { config } from '@grafana/runtime';
|
import { config } from '@grafana/runtime';
|
||||||
|
import { CandlestickSuggestionsSupplier } from './suggestions';
|
||||||
|
|
||||||
const modeOptions = [
|
const modeOptions = [
|
||||||
{ label: 'Price & Volume', value: MarketTrendMode.PriceVolume },
|
{ label: 'Candles', value: VizDisplayMode.Candles },
|
||||||
{ label: 'Price', value: MarketTrendMode.Price },
|
{ label: 'Volume', value: VizDisplayMode.Volume },
|
||||||
{ label: 'Volume', value: MarketTrendMode.Volume },
|
{ label: 'Both', value: VizDisplayMode.CandlesVolume },
|
||||||
] as Array<SelectableValue<MarketTrendMode>>;
|
] as Array<SelectableValue<VizDisplayMode>>;
|
||||||
|
|
||||||
const priceStyle = [
|
const candleStyles = [
|
||||||
{ label: 'Candles', value: PriceStyle.Candles },
|
{ label: 'Candles', value: CandleStyle.Candles },
|
||||||
{ label: 'OHLC Bars', value: PriceStyle.OHLCBars },
|
{ label: 'OHLC Bars', value: CandleStyle.OHLCBars },
|
||||||
] as Array<SelectableValue<PriceStyle>>;
|
] as Array<SelectableValue<CandleStyle>>;
|
||||||
|
|
||||||
const colorStrategy = [
|
const colorStrategies = [
|
||||||
{ label: 'Since Open', value: 'intra' },
|
{ label: 'Since Open', value: ColorStrategy.OpenClose },
|
||||||
{ label: 'Since Prior Close', value: 'inter' },
|
{ label: 'Since Prior Close', value: ColorStrategy.CloseClose },
|
||||||
] as Array<SelectableValue<ColorStrategy>>;
|
] as Array<SelectableValue<ColorStrategy>>;
|
||||||
|
|
||||||
function getMarketFieldConfig() {
|
|
||||||
const v = getGraphFieldConfig(defaultGraphConfig);
|
|
||||||
v.standardOptions![FieldConfigProperty.Unit] = {
|
|
||||||
settings: {},
|
|
||||||
defaultValue: 'currencyUSD',
|
|
||||||
};
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
const numericFieldFilter = (f: Field) => f.type === FieldType.number;
|
const numericFieldFilter = (f: Field) => f.type === FieldType.number;
|
||||||
|
|
||||||
function addFieldPicker(
|
function addFieldPicker(
|
||||||
builder: PanelOptionsEditorBuilder<MarketOptions>,
|
builder: PanelOptionsEditorBuilder<CandlestickOptions>,
|
||||||
info: FieldPickerInfo,
|
info: FieldPickerInfo,
|
||||||
data: CandlestickData
|
data: CandlestickData
|
||||||
) {
|
) {
|
||||||
@ -77,8 +68,8 @@ function addFieldPicker(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const plugin = new PanelPlugin<MarketOptions, GraphFieldConfig>(MarketTrendPanel)
|
export const plugin = new PanelPlugin<CandlestickOptions, GraphFieldConfig>(MarketTrendPanel)
|
||||||
.useFieldConfig(getMarketFieldConfig())
|
.useFieldConfig(getGraphFieldConfig(defaultGraphConfig))
|
||||||
.setPanelOptions((builder, context) => {
|
.setPanelOptions((builder, context) => {
|
||||||
const opts = context.options ?? defaultPanelOptions;
|
const opts = context.options ?? defaultPanelOptions;
|
||||||
const info = prepareCandlestickFields(context.data, opts, config.theme2);
|
const info = prepareCandlestickFields(context.data, opts, config.theme2);
|
||||||
@ -88,28 +79,28 @@ export const plugin = new PanelPlugin<MarketOptions, GraphFieldConfig>(MarketTre
|
|||||||
path: 'mode',
|
path: 'mode',
|
||||||
name: 'Mode',
|
name: 'Mode',
|
||||||
description: '',
|
description: '',
|
||||||
defaultValue: MarketTrendMode.PriceVolume,
|
defaultValue: defaultPanelOptions.mode,
|
||||||
settings: {
|
settings: {
|
||||||
options: modeOptions,
|
options: modeOptions,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.addRadio({
|
.addRadio({
|
||||||
path: 'priceStyle',
|
path: 'candleStyle',
|
||||||
name: 'Price style',
|
name: 'Candle style',
|
||||||
description: '',
|
description: '',
|
||||||
defaultValue: PriceStyle.Candles,
|
defaultValue: defaultPanelOptions.candleStyle,
|
||||||
settings: {
|
settings: {
|
||||||
options: priceStyle,
|
options: candleStyles,
|
||||||
},
|
},
|
||||||
showIf: (opts) => opts.mode !== MarketTrendMode.Volume,
|
showIf: (opts) => opts.mode !== VizDisplayMode.Volume,
|
||||||
})
|
})
|
||||||
.addRadio({
|
.addRadio({
|
||||||
path: 'colorStrategy',
|
path: 'colorStrategy',
|
||||||
name: 'Color strategy',
|
name: 'Color strategy',
|
||||||
description: '',
|
description: '',
|
||||||
defaultValue: ColorStrategy.Intra,
|
defaultValue: defaultPanelOptions.colorStrategy,
|
||||||
settings: {
|
settings: {
|
||||||
options: colorStrategy,
|
options: colorStrategies,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.addColorPicker({
|
.addColorPicker({
|
||||||
@ -124,17 +115,18 @@ export const plugin = new PanelPlugin<MarketOptions, GraphFieldConfig>(MarketTre
|
|||||||
});
|
});
|
||||||
|
|
||||||
addFieldPicker(builder, candlestickFieldsInfo.open, info);
|
addFieldPicker(builder, candlestickFieldsInfo.open, info);
|
||||||
if (opts.mode !== MarketTrendMode.Volume) {
|
if (opts.mode !== VizDisplayMode.Volume) {
|
||||||
addFieldPicker(builder, candlestickFieldsInfo.high, info);
|
addFieldPicker(builder, candlestickFieldsInfo.high, info);
|
||||||
addFieldPicker(builder, candlestickFieldsInfo.low, info);
|
addFieldPicker(builder, candlestickFieldsInfo.low, info);
|
||||||
}
|
}
|
||||||
addFieldPicker(builder, candlestickFieldsInfo.close, info);
|
addFieldPicker(builder, candlestickFieldsInfo.close, info);
|
||||||
|
|
||||||
if (opts.mode !== MarketTrendMode.Price) {
|
if (opts.mode !== VizDisplayMode.Candles) {
|
||||||
addFieldPicker(builder, candlestickFieldsInfo.volume, info);
|
addFieldPicker(builder, candlestickFieldsInfo.volume, info);
|
||||||
}
|
}
|
||||||
|
|
||||||
// commonOptionsBuilder.addTooltipOptions(builder);
|
// commonOptionsBuilder.addTooltipOptions(builder);
|
||||||
commonOptionsBuilder.addLegendOptions(builder);
|
commonOptionsBuilder.addLegendOptions(builder);
|
||||||
})
|
})
|
||||||
.setDataSupport({ annotations: true, alertStates: true });
|
.setDataSupport({ annotations: true, alertStates: true })
|
||||||
|
.setSuggestionsSupplier(new CandlestickSuggestionsSupplier());
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"type": "panel",
|
"type": "panel",
|
||||||
"name": "Market trend",
|
"name": "Candlestick",
|
||||||
"id": "market-trend",
|
"id": "candlestick",
|
||||||
"state": "alpha",
|
"state": "alpha",
|
||||||
|
|
||||||
"info": {
|
"info": {
|
53
public/app/plugins/panel/candlestick/suggestions.ts
Normal file
53
public/app/plugins/panel/candlestick/suggestions.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { VisualizationSuggestionsBuilder } from '@grafana/data';
|
||||||
|
import { config } from '@grafana/runtime';
|
||||||
|
import { SuggestionName } from 'app/types/suggestions';
|
||||||
|
import { prepareCandlestickFields } from './fields';
|
||||||
|
import { CandlestickOptions, defaultPanelOptions } from './models.gen';
|
||||||
|
|
||||||
|
export class CandlestickSuggestionsSupplier {
|
||||||
|
getSuggestionsForData(builder: VisualizationSuggestionsBuilder) {
|
||||||
|
const { dataSummary } = builder;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!builder.data?.series ||
|
||||||
|
!dataSummary.hasData ||
|
||||||
|
dataSummary.timeFieldCount < 1 ||
|
||||||
|
dataSummary.numberFieldCount < 2 ||
|
||||||
|
dataSummary.numberFieldCount > 10
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const info = prepareCandlestickFields(builder.data.series, defaultPanelOptions, config.theme2);
|
||||||
|
if (!info.open || info.warn || info.noTimeField) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regular timeseries
|
||||||
|
if (info.open === info.high && info.open === info.low) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = builder.getListAppender<CandlestickOptions, {}>({
|
||||||
|
name: '',
|
||||||
|
pluginId: 'candlestick',
|
||||||
|
options: {},
|
||||||
|
fieldConfig: {
|
||||||
|
defaults: {
|
||||||
|
custom: {},
|
||||||
|
},
|
||||||
|
overrides: [],
|
||||||
|
},
|
||||||
|
previewModifier: (s) => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
list.append({
|
||||||
|
name: SuggestionName.Candlestick,
|
||||||
|
options: defaultPanelOptions,
|
||||||
|
fieldConfig: {
|
||||||
|
defaults: {},
|
||||||
|
overrides: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import { MarketTrendMode, ColorStrategy, PriceStyle } from './models.gen';
|
import { VizDisplayMode, ColorStrategy, CandleStyle } from './models.gen';
|
||||||
import uPlot from 'uplot';
|
import uPlot from 'uplot';
|
||||||
import { colorManipulator } from '@grafana/data';
|
import { colorManipulator } from '@grafana/data';
|
||||||
|
|
||||||
@ -7,8 +7,8 @@ const { alpha } = colorManipulator;
|
|||||||
export type FieldIndices = Record<string, number>;
|
export type FieldIndices = Record<string, number>;
|
||||||
|
|
||||||
interface RendererOpts {
|
interface RendererOpts {
|
||||||
mode: MarketTrendMode;
|
mode: VizDisplayMode;
|
||||||
priceStyle: PriceStyle;
|
candleStyle: CandleStyle;
|
||||||
fields: FieldIndices;
|
fields: FieldIndices;
|
||||||
colorStrategy: ColorStrategy;
|
colorStrategy: ColorStrategy;
|
||||||
upColor: string;
|
upColor: string;
|
||||||
@ -19,11 +19,11 @@ interface RendererOpts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function drawMarkers(opts: RendererOpts) {
|
export function drawMarkers(opts: RendererOpts) {
|
||||||
let { mode, priceStyle, fields, colorStrategy, upColor, downColor, flatColor, volumeAlpha, flatAsUp = true } = opts;
|
let { mode, candleStyle, fields, colorStrategy, upColor, downColor, flatColor, volumeAlpha, flatAsUp = true } = opts;
|
||||||
|
|
||||||
let drawPrice = mode !== MarketTrendMode.Volume && fields.high != null && fields.low != null;
|
const drawPrice = mode !== VizDisplayMode.Volume && fields.high != null && fields.low != null;
|
||||||
let asCandles = drawPrice && priceStyle === PriceStyle.Candles;
|
const asCandles = drawPrice && candleStyle === CandleStyle.Candles;
|
||||||
let drawVolume = mode !== MarketTrendMode.Price && fields.volume != null;
|
const drawVolume = mode !== VizDisplayMode.Candles && fields.volume != null;
|
||||||
|
|
||||||
function selectPath(priceDir: number, flatPath: Path2D, upPath: Path2D, downPath: Path2D, flatAsUp: boolean) {
|
function selectPath(priceDir: number, flatPath: Path2D, upPath: Path2D, downPath: Path2D, flatAsUp: boolean) {
|
||||||
return priceDir > 0 ? upPath : priceDir < 0 ? downPath : flatAsUp ? upPath : flatPath;
|
return priceDir > 0 ? upPath : priceDir < 0 ? downPath : flatAsUp ? upPath : flatPath;
|
||||||
@ -120,11 +120,11 @@ export function drawMarkers(opts: RendererOpts) {
|
|||||||
// volume
|
// volume
|
||||||
if (drawVolume) {
|
if (drawVolume) {
|
||||||
let outerPath = selectPath(
|
let outerPath = selectPath(
|
||||||
colorStrategy === ColorStrategy.Inter ? interDir : intraDir,
|
colorStrategy === ColorStrategy.CloseClose ? interDir : intraDir,
|
||||||
flatPathVol as Path2D,
|
flatPathVol as Path2D,
|
||||||
upPathVol as Path2D,
|
upPathVol as Path2D,
|
||||||
downPathVol as Path2D,
|
downPathVol as Path2D,
|
||||||
i === idx0 && ColorStrategy.Inter ? false : flatAsUp
|
i === idx0 && ColorStrategy.CloseClose ? false : flatAsUp
|
||||||
);
|
);
|
||||||
|
|
||||||
let vPx = Math.round(u.valToPos(vData![i]!, u.series[vIdx!].scale!, true));
|
let vPx = Math.round(u.valToPos(vData![i]!, u.series[vIdx!].scale!, true));
|
||||||
@ -133,11 +133,11 @@ export function drawMarkers(opts: RendererOpts) {
|
|||||||
|
|
||||||
if (drawPrice) {
|
if (drawPrice) {
|
||||||
let outerPath = selectPath(
|
let outerPath = selectPath(
|
||||||
colorStrategy === ColorStrategy.Inter ? interDir : intraDir,
|
colorStrategy === ColorStrategy.CloseClose ? interDir : intraDir,
|
||||||
flatPath as Path2D,
|
flatPath as Path2D,
|
||||||
upPath as Path2D,
|
upPath as Path2D,
|
||||||
downPath as Path2D,
|
downPath as Path2D,
|
||||||
i === idx0 && ColorStrategy.Inter ? false : flatAsUp
|
i === idx0 && ColorStrategy.CloseClose ? false : flatAsUp
|
||||||
);
|
);
|
||||||
|
|
||||||
// stick
|
// stick
|
||||||
@ -155,7 +155,7 @@ export function drawMarkers(opts: RendererOpts) {
|
|||||||
let hgt = Math.max(1, btm - top);
|
let hgt = Math.max(1, btm - top);
|
||||||
outerPath.rect(tPx - halfWidth, top, barWidth, hgt);
|
outerPath.rect(tPx - halfWidth, top, barWidth, hgt);
|
||||||
|
|
||||||
if (colorStrategy === ColorStrategy.Inter) {
|
if (colorStrategy === ColorStrategy.CloseClose) {
|
||||||
if (intraDir >= 0 && hgt > outlineWidth * 2) {
|
if (intraDir >= 0 && hgt > outlineWidth * 2) {
|
||||||
hollowPath.rect(
|
hollowPath.rect(
|
||||||
tPx - halfWidth + outlineWidth,
|
tPx - halfWidth + outlineWidth,
|
@ -12,6 +12,7 @@ export enum SuggestionName {
|
|||||||
BarChartHorizontal = 'Bar chart horizontal',
|
BarChartHorizontal = 'Bar chart horizontal',
|
||||||
BarChartHorizontalStacked = 'Bar chart horizontal stacked',
|
BarChartHorizontalStacked = 'Bar chart horizontal stacked',
|
||||||
BarChartHorizontalStackedPercent = 'Bar chart horizontal 100% stacked',
|
BarChartHorizontalStackedPercent = 'Bar chart horizontal 100% stacked',
|
||||||
|
Candlestick = 'Candlestick',
|
||||||
PieChart = 'Pie chart',
|
PieChart = 'Pie chart',
|
||||||
PieChartDonut = 'Pie chart donut',
|
PieChartDonut = 'Pie chart donut',
|
||||||
Stat = 'Stat',
|
Stat = 'Stat',
|
||||||
|
Reference in New Issue
Block a user