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