LineStyleEditor.tsx 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. import React, { useMemo } from 'react';
  2. import { FieldOverrideEditorProps, SelectableValue } from '@grafana/data';
  3. import { LineStyle } from '@grafana/schema';
  4. import { HorizontalGroup, IconButton, RadioButtonGroup, Select } from '@grafana/ui';
  5. type LineFill = 'solid' | 'dash' | 'dot';
  6. const lineFillOptions: Array<SelectableValue<LineFill>> = [
  7. {
  8. label: 'Solid',
  9. value: 'solid',
  10. },
  11. {
  12. label: 'Dash',
  13. value: 'dash',
  14. },
  15. {
  16. label: 'Dots',
  17. value: 'dot',
  18. },
  19. ];
  20. const dashOptions: Array<SelectableValue<string>> = [
  21. '10, 10', // default
  22. '10, 15',
  23. '10, 20',
  24. '10, 25',
  25. '10, 30',
  26. '10, 40',
  27. '15, 10',
  28. '20, 10',
  29. '25, 10',
  30. '30, 10',
  31. '40, 10',
  32. '50, 10',
  33. '5, 10',
  34. '30, 3, 3',
  35. ].map((txt) => ({
  36. label: txt,
  37. value: txt,
  38. }));
  39. const dotOptions: Array<SelectableValue<string>> = [
  40. '0, 10', // default
  41. '0, 20',
  42. '0, 30',
  43. '0, 40',
  44. '0, 3, 3',
  45. ].map((txt) => ({
  46. label: txt,
  47. value: txt,
  48. }));
  49. export const LineStyleEditor: React.FC<FieldOverrideEditorProps<LineStyle, any>> = ({ value, onChange }) => {
  50. const options = useMemo(() => (value?.fill === 'dash' ? dashOptions : dotOptions), [value]);
  51. const current = useMemo(() => {
  52. if (!value?.dash?.length) {
  53. return options[0];
  54. }
  55. const str = value.dash?.join(', ');
  56. const val = options.find((o) => o.value === str);
  57. if (!val) {
  58. return {
  59. label: str,
  60. value: str,
  61. };
  62. }
  63. return val;
  64. }, [value, options]);
  65. return (
  66. <HorizontalGroup>
  67. <RadioButtonGroup
  68. value={value?.fill || 'solid'}
  69. options={lineFillOptions}
  70. onChange={(v) => {
  71. let dash: number[] | undefined = undefined;
  72. if (v === 'dot') {
  73. dash = parseText(dotOptions[0].value!);
  74. } else if (v === 'dash') {
  75. dash = parseText(dashOptions[0].value!);
  76. }
  77. onChange({
  78. ...value,
  79. fill: v!,
  80. dash,
  81. });
  82. }}
  83. />
  84. {value?.fill && value?.fill !== 'solid' && (
  85. <>
  86. <Select
  87. allowCustomValue={true}
  88. options={options}
  89. value={current}
  90. width={20}
  91. onChange={(v) => {
  92. onChange({
  93. ...value,
  94. dash: parseText(v.value ?? ''),
  95. });
  96. }}
  97. formatCreateLabel={(t) => `Segments: ${parseText(t).join(', ')}`}
  98. />
  99. <div>
  100. &nbsp;
  101. <a
  102. title="The input expects a segment list"
  103. href="https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash#Parameters"
  104. target="_blank"
  105. rel="noreferrer"
  106. >
  107. <IconButton name="question-circle" />
  108. </a>
  109. </div>
  110. </>
  111. )}
  112. </HorizontalGroup>
  113. );
  114. };
  115. function parseText(txt: string): number[] {
  116. const segments: number[] = [];
  117. for (const s of txt.split(/(?:,| )+/)) {
  118. const num = Number.parseInt(s, 10);
  119. if (!isNaN(num)) {
  120. segments.push(num);
  121. }
  122. }
  123. return segments;
  124. }