module.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. import React from 'react';
  2. import { FieldConfigProperty, FieldType, identityOverrideProcessor, PanelData, PanelPlugin } from '@grafana/data';
  3. import { config } from '@grafana/runtime';
  4. import { AxisPlacement, GraphFieldConfig, ScaleDistribution, ScaleDistributionConfig } from '@grafana/schema';
  5. import { addHideFrom, ScaleDistributionEditor } from '@grafana/ui/src/options/builder';
  6. import { ColorScale } from 'app/core/components/ColorScale/ColorScale';
  7. import { addHeatmapCalculationOptions } from 'app/features/transformers/calculateHeatmap/editor/helper';
  8. import { readHeatmapRowsCustomMeta } from 'app/features/transformers/calculateHeatmap/heatmap';
  9. import { HeatmapCellLayout } from 'app/features/transformers/calculateHeatmap/models.gen';
  10. import { HeatmapPanel } from './HeatmapPanel';
  11. import { prepareHeatmapData } from './fields';
  12. import { heatmapChangedHandler, heatmapMigrationHandler } from './migrations';
  13. import { PanelOptions, defaultPanelOptions, HeatmapColorMode, HeatmapColorScale } from './models.gen';
  14. import { colorSchemes, quantizeScheme } from './palettes';
  15. import { HeatmapSuggestionsSupplier } from './suggestions';
  16. export const plugin = new PanelPlugin<PanelOptions, GraphFieldConfig>(HeatmapPanel)
  17. .useFieldConfig({
  18. disableStandardOptions: Object.values(FieldConfigProperty).filter((v) => v !== FieldConfigProperty.Links),
  19. useCustomConfig: (builder) => {
  20. builder.addCustomEditor<void, ScaleDistributionConfig>({
  21. id: 'scaleDistribution',
  22. path: 'scaleDistribution',
  23. name: 'Y axis scale',
  24. category: ['Heatmap'],
  25. editor: ScaleDistributionEditor as any,
  26. override: ScaleDistributionEditor as any,
  27. defaultValue: { type: ScaleDistribution.Linear },
  28. shouldApply: (f) => f.type === FieldType.number,
  29. process: identityOverrideProcessor,
  30. hideFromDefaults: true,
  31. });
  32. addHideFrom(builder); // for tooltip etc
  33. },
  34. })
  35. .setPanelChangeHandler(heatmapChangedHandler)
  36. .setMigrationHandler(heatmapMigrationHandler)
  37. .setPanelOptions((builder, context) => {
  38. const opts = context.options ?? defaultPanelOptions;
  39. let isOrdinalY = false;
  40. try {
  41. const v = prepareHeatmapData({ series: context.data } as PanelData, opts, config.theme2);
  42. isOrdinalY = readHeatmapRowsCustomMeta(v.heatmap).yOrdinalDisplay != null;
  43. } catch {}
  44. let category = ['Heatmap'];
  45. builder.addRadio({
  46. path: 'calculate',
  47. name: 'Calculate from data',
  48. defaultValue: defaultPanelOptions.calculate,
  49. category,
  50. settings: {
  51. options: [
  52. { label: 'Yes', value: true },
  53. { label: 'No', value: false },
  54. ],
  55. },
  56. });
  57. if (opts.calculate) {
  58. addHeatmapCalculationOptions('calculation.', builder, opts.calculation, category);
  59. }
  60. category = ['Y Axis'];
  61. builder.addRadio({
  62. path: 'yAxis.axisPlacement',
  63. name: 'Placement',
  64. defaultValue: defaultPanelOptions.yAxis.axisPlacement ?? AxisPlacement.Left,
  65. category,
  66. settings: {
  67. options: [
  68. { label: 'Left', value: AxisPlacement.Left },
  69. { label: 'Right', value: AxisPlacement.Right },
  70. { label: 'Hidden', value: AxisPlacement.Hidden },
  71. ],
  72. },
  73. });
  74. builder
  75. .addUnitPicker({
  76. category,
  77. path: 'yAxis.unit',
  78. name: 'Unit',
  79. defaultValue: undefined,
  80. })
  81. .addNumberInput({
  82. category,
  83. path: 'yAxis.decimals',
  84. name: 'Decimals',
  85. settings: {
  86. placeholder: 'Auto',
  87. },
  88. });
  89. if (!isOrdinalY) {
  90. // if undefined, then show the min+max
  91. builder
  92. .addNumberInput({
  93. path: 'yAxis.min',
  94. name: 'Min value',
  95. settings: {
  96. placeholder: 'Auto',
  97. },
  98. category,
  99. })
  100. .addTextInput({
  101. path: 'yAxis.max',
  102. name: 'Max value',
  103. settings: {
  104. placeholder: 'Auto',
  105. },
  106. category,
  107. });
  108. }
  109. builder
  110. .addNumberInput({
  111. path: 'yAxis.axisWidth',
  112. name: 'Axis width',
  113. defaultValue: defaultPanelOptions.yAxis.axisWidth,
  114. settings: {
  115. placeholder: 'Auto',
  116. min: 5, // smaller should just be hidden
  117. },
  118. category,
  119. })
  120. .addTextInput({
  121. path: 'yAxis.axisLabel',
  122. name: 'Axis label',
  123. defaultValue: defaultPanelOptions.yAxis.axisLabel,
  124. settings: {
  125. placeholder: 'Auto',
  126. },
  127. category,
  128. });
  129. if (!opts.calculate) {
  130. builder.addRadio({
  131. path: 'rowsFrame.layout',
  132. name: 'Tick alignment',
  133. defaultValue: defaultPanelOptions.rowsFrame?.layout ?? HeatmapCellLayout.auto,
  134. category,
  135. settings: {
  136. options: [
  137. { label: 'Auto', value: HeatmapCellLayout.auto },
  138. { label: 'Top (LE)', value: HeatmapCellLayout.le },
  139. { label: 'Middle', value: HeatmapCellLayout.unknown },
  140. { label: 'Bottom (GE)', value: HeatmapCellLayout.ge },
  141. ],
  142. },
  143. });
  144. }
  145. builder.addBooleanSwitch({
  146. path: 'yAxis.reverse',
  147. name: 'Reverse',
  148. defaultValue: defaultPanelOptions.yAxis.reverse === true,
  149. category,
  150. });
  151. category = ['Colors'];
  152. builder.addRadio({
  153. path: `color.mode`,
  154. name: 'Mode',
  155. defaultValue: defaultPanelOptions.color.mode,
  156. category,
  157. settings: {
  158. options: [
  159. { label: 'Scheme', value: HeatmapColorMode.Scheme },
  160. { label: 'Opacity', value: HeatmapColorMode.Opacity },
  161. ],
  162. },
  163. });
  164. builder.addColorPicker({
  165. path: `color.fill`,
  166. name: 'Color',
  167. defaultValue: defaultPanelOptions.color.fill,
  168. category,
  169. showIf: (opts) => opts.color.mode === HeatmapColorMode.Opacity,
  170. });
  171. builder.addRadio({
  172. path: `color.scale`,
  173. name: 'Scale',
  174. defaultValue: defaultPanelOptions.color.scale,
  175. category,
  176. settings: {
  177. options: [
  178. { label: 'Exponential', value: HeatmapColorScale.Exponential },
  179. { label: 'Linear', value: HeatmapColorScale.Linear },
  180. ],
  181. },
  182. showIf: (opts) => opts.color.mode === HeatmapColorMode.Opacity,
  183. });
  184. builder.addSliderInput({
  185. path: 'color.exponent',
  186. name: 'Exponent',
  187. defaultValue: defaultPanelOptions.color.exponent,
  188. category,
  189. settings: {
  190. min: 0.1, // 1 for on/off?
  191. max: 2,
  192. step: 0.1,
  193. },
  194. showIf: (opts) =>
  195. opts.color.mode === HeatmapColorMode.Opacity && opts.color.scale === HeatmapColorScale.Exponential,
  196. });
  197. builder.addSelect({
  198. path: `color.scheme`,
  199. name: 'Scheme',
  200. description: '',
  201. defaultValue: defaultPanelOptions.color.scheme,
  202. category,
  203. settings: {
  204. options: colorSchemes.map((scheme) => ({
  205. value: scheme.name,
  206. label: scheme.name,
  207. //description: 'Set a geometry field based on the results of other fields',
  208. })),
  209. },
  210. showIf: (opts) => opts.color.mode !== HeatmapColorMode.Opacity,
  211. });
  212. builder
  213. .addSliderInput({
  214. path: 'color.steps',
  215. name: 'Steps',
  216. defaultValue: defaultPanelOptions.color.steps,
  217. category,
  218. settings: {
  219. min: 2,
  220. max: 128,
  221. step: 1,
  222. },
  223. })
  224. .addCustomEditor({
  225. id: '__scale__',
  226. path: `__scale__`,
  227. name: '',
  228. category,
  229. editor: () => {
  230. const palette = quantizeScheme(opts.color, config.theme2);
  231. return (
  232. <div>
  233. <ColorScale colorPalette={palette} min={1} max={100} />
  234. </div>
  235. );
  236. },
  237. });
  238. builder
  239. .addNumberInput({
  240. path: 'color.min',
  241. name: 'Start color scale from value',
  242. defaultValue: defaultPanelOptions.color.min,
  243. settings: {
  244. placeholder: 'Auto (min)',
  245. },
  246. category,
  247. })
  248. .addNumberInput({
  249. path: 'color.max',
  250. name: 'End color scale at value',
  251. defaultValue: defaultPanelOptions.color.max,
  252. settings: {
  253. placeholder: 'Auto (max)',
  254. },
  255. category,
  256. });
  257. category = ['Cell display'];
  258. if (!opts.calculate) {
  259. builder.addTextInput({
  260. path: 'rowsFrame.value',
  261. name: 'Value name',
  262. defaultValue: defaultPanelOptions.rowsFrame?.value,
  263. settings: {
  264. placeholder: 'Value',
  265. },
  266. category,
  267. });
  268. }
  269. builder
  270. .addUnitPicker({
  271. category,
  272. path: 'cellValues.unit',
  273. name: 'Unit',
  274. defaultValue: undefined,
  275. })
  276. .addNumberInput({
  277. category,
  278. path: 'cellValues.decimals',
  279. name: 'Decimals',
  280. settings: {
  281. placeholder: 'Auto',
  282. },
  283. });
  284. builder
  285. // .addRadio({
  286. // path: 'showValue',
  287. // name: 'Show values',
  288. // defaultValue: defaultPanelOptions.showValue,
  289. // category,
  290. // settings: {
  291. // options: [
  292. // { value: VisibilityMode.Auto, label: 'Auto' },
  293. // { value: VisibilityMode.Always, label: 'Always' },
  294. // { value: VisibilityMode.Never, label: 'Never' },
  295. // ],
  296. // },
  297. // })
  298. .addSliderInput({
  299. name: 'Cell gap',
  300. path: 'cellGap',
  301. defaultValue: defaultPanelOptions.cellGap,
  302. category,
  303. settings: {
  304. min: 0,
  305. max: 25,
  306. },
  307. })
  308. .addNumberInput({
  309. path: 'filterValues.le',
  310. name: 'Hide cells with values <=',
  311. defaultValue: defaultPanelOptions.filterValues?.le,
  312. settings: {
  313. placeholder: 'None',
  314. },
  315. category,
  316. })
  317. .addNumberInput({
  318. path: 'filterValues.ge',
  319. name: 'Hide cells with values >=',
  320. defaultValue: defaultPanelOptions.filterValues?.ge,
  321. settings: {
  322. placeholder: 'None',
  323. },
  324. category,
  325. });
  326. // .addSliderInput({
  327. // name: 'Cell radius',
  328. // path: 'cellRadius',
  329. // defaultValue: defaultPanelOptions.cellRadius,
  330. // category,
  331. // settings: {
  332. // min: 0,
  333. // max: 100,
  334. // },
  335. // })
  336. category = ['Tooltip'];
  337. builder.addBooleanSwitch({
  338. path: 'tooltip.show',
  339. name: 'Show tooltip',
  340. defaultValue: defaultPanelOptions.tooltip.show,
  341. category,
  342. });
  343. builder.addBooleanSwitch({
  344. path: 'tooltip.yHistogram',
  345. name: 'Show histogram (Y axis)',
  346. defaultValue: defaultPanelOptions.tooltip.yHistogram,
  347. category,
  348. showIf: (opts) => opts.tooltip.show,
  349. });
  350. category = ['Legend'];
  351. builder.addBooleanSwitch({
  352. path: 'legend.show',
  353. name: 'Show legend',
  354. defaultValue: defaultPanelOptions.legend.show,
  355. category,
  356. });
  357. category = ['Exemplars'];
  358. builder.addColorPicker({
  359. path: 'exemplars.color',
  360. name: 'Color',
  361. defaultValue: defaultPanelOptions.exemplars.color,
  362. category,
  363. });
  364. })
  365. .setSuggestionsSupplier(new HeatmapSuggestionsSupplier());