Legend.tsx 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. import { css } from '@emotion/css';
  2. import { identity } from 'lodash';
  3. import React, { useCallback } from 'react';
  4. import { Field, FieldColorModeId, GrafanaTheme } from '@grafana/data';
  5. import { LegendDisplayMode } from '@grafana/schema';
  6. import { Icon, useStyles, useTheme, VizLegend, VizLegendItem, VizLegendListItem } from '@grafana/ui';
  7. import { Config } from './layout';
  8. import { NodeDatum } from './types';
  9. function getStyles() {
  10. return {
  11. item: css`
  12. label: LegendItem;
  13. flex-grow: 0;
  14. `,
  15. legend: css`
  16. label: Legend;
  17. pointer-events: all;
  18. `,
  19. };
  20. }
  21. interface Props {
  22. nodes: NodeDatum[];
  23. onSort: (sort: Config['sort']) => void;
  24. sort?: Config['sort'];
  25. sortable: boolean;
  26. }
  27. export const Legend = function Legend(props: Props) {
  28. const { nodes, onSort, sort, sortable } = props;
  29. const theme = useTheme();
  30. const styles = useStyles(getStyles);
  31. const colorItems = getColorLegendItems(nodes, theme);
  32. const onClick = useCallback(
  33. (item) => {
  34. onSort({
  35. field: item.data!.field,
  36. ascending: item.data!.field === sort?.field ? !sort?.ascending : false,
  37. });
  38. },
  39. [sort, onSort]
  40. );
  41. return (
  42. <VizLegend<ItemData>
  43. className={styles.legend}
  44. displayMode={LegendDisplayMode.List}
  45. placement={'bottom'}
  46. items={colorItems}
  47. itemRenderer={(item) => {
  48. return (
  49. <>
  50. <VizLegendListItem item={item} className={styles.item} onLabelClick={sortable ? onClick : undefined} />
  51. {sortable &&
  52. (sort?.field === item.data!.field ? <Icon name={sort!.ascending ? 'arrow-up' : 'arrow-down'} /> : '')}
  53. </>
  54. );
  55. }}
  56. />
  57. );
  58. };
  59. interface ItemData {
  60. field: Field;
  61. }
  62. function getColorLegendItems(nodes: NodeDatum[], theme: GrafanaTheme): Array<VizLegendItem<ItemData>> {
  63. if (!nodes.length) {
  64. return [];
  65. }
  66. const fields = [nodes[0].mainStat, nodes[0].secondaryStat].filter(identity) as Field[];
  67. const node = nodes.find((n) => n.arcSections.length > 0);
  68. if (node) {
  69. if (node.arcSections[0]!.config?.color?.mode === FieldColorModeId.Fixed) {
  70. // We assume in this case we have a set of fixed colors which map neatly into a basic legend.
  71. // Lets collect and deduplicate as there isn't a requirement for 0 size arc section to be defined
  72. fields.push(...new Set(nodes.map((n) => n.arcSections).flat()));
  73. }
  74. }
  75. if (nodes[0].color) {
  76. fields.push(nodes[0].color);
  77. }
  78. return fields.map((f) => {
  79. const item: VizLegendItem = {
  80. label: f.config.displayName || f.name,
  81. yAxis: 0,
  82. data: { field: f },
  83. };
  84. if (f.config.color?.mode === FieldColorModeId.Fixed && f.config.color?.fixedColor) {
  85. item.color = theme.visualization.getColorByName(f.config.color?.fixedColor || '');
  86. } else if (f.config.color?.mode) {
  87. item.gradient = f.config.color?.mode;
  88. }
  89. if (!(item.color || item.gradient)) {
  90. // Defaults to gray color
  91. item.color = theme.visualization.getColorByName('');
  92. }
  93. return item;
  94. });
  95. }