@grafana/data#getColorFromHexRgbOrName TypeScript Examples

The following examples show how to use @grafana/data#getColorFromHexRgbOrName. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: ThresholdsEditor.tsx    From grafana-chinese with Apache License 2.0 6 votes vote down vote up
render() {
    const { steps } = this.state;

    return (
      <PanelOptionsGroup title="Thresholds">
        <ThemeContext.Consumer>
          {theme => (
            <>
              <div className="thresholds">
                {steps
                  .slice(0)
                  .reverse()
                  .map(threshold => {
                    return (
                      <div className="thresholds-row" key={`${threshold.key}`}>
                        <div className="thresholds-row-add-button" onClick={() => this.onAddThresholdAfter(threshold)}>
                          <i className="fa fa-plus" />
                        </div>
                        <div
                          className="thresholds-row-color-indicator"
                          style={{ backgroundColor: getColorFromHexRgbOrName(threshold.color, theme.type) }}
                        />
                        <div className="thresholds-row-input">{this.renderInput(threshold)}</div>
                      </div>
                    );
                  })}
              </div>
            </>
          )}
        </ThemeContext.Consumer>
      </PanelOptionsGroup>
    );
  }
Example #2
Source File: renderer.ts    From grafana-chinese with Apache License 2.0 6 votes vote down vote up
getColorForValue(value: number, style: ColumnStyle) {
    if (!style.thresholds || !style.colors) {
      return null;
    }
    for (let i = style.thresholds.length; i > 0; i--) {
      if (value >= style.thresholds[i - 1]) {
        return getColorFromHexRgbOrName(style.colors[i], this.theme);
      }
    }
    return getColorFromHexRgbOrName(_.first(style.colors), this.theme);
  }
Example #3
Source File: rendering.ts    From grafana-chinese with Apache License 2.0 6 votes vote down vote up
getCardColor(d: { count: any }) {
    if (this.panel.color.mode === 'opacity') {
      return getColorFromHexRgbOrName(
        this.panel.color.cardColor,
        contextSrv.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark
      );
    } else {
      return this.colorScale(d.count);
    }
  }
Example #4
Source File: time_region_manager.ts    From grafana-chinese with Apache License 2.0 6 votes vote down vote up
function getColor(timeRegion: any, theme: GrafanaThemeType): TimeRegionColorDefinition {
  if (Object.keys(colorModes).indexOf(timeRegion.colorMode) === -1) {
    timeRegion.colorMode = 'red';
  }

  if (timeRegion.colorMode === 'custom') {
    return {
      fill: timeRegion.fill && timeRegion.fillColor ? getColorFromHexRgbOrName(timeRegion.fillColor, theme) : null,
      line: timeRegion.line && timeRegion.lineColor ? getColorFromHexRgbOrName(timeRegion.lineColor, theme) : null,
    };
  }

  const colorMode = colorModes[timeRegion.colorMode];

  if (colorMode.themeDependent === true) {
    return theme === GrafanaThemeType.Light ? colorMode.lightColor : colorMode.darkColor;
  }

  return {
    fill: timeRegion.fill ? getColorFromHexRgbOrName(colorMode.color.fill, theme) : null,
    line: timeRegion.fill ? getColorFromHexRgbOrName(colorMode.color.line, theme) : null,
  };
}
Example #5
Source File: data_processor.ts    From grafana-chinese with Apache License 2.0 6 votes vote down vote up
private toTimeSeries(
    field: Field,
    alias: string,
    dataFrameIndex: number,
    fieldIndex: number,
    datapoints: any[][],
    index: number,
    range?: TimeRange
  ) {
    const colorIndex = index % colors.length;
    const color = this.panel.aliasColors[alias] || colors[colorIndex];

    const series = new TimeSeries({
      datapoints: datapoints || [],
      alias: alias,
      color: getColorFromHexRgbOrName(color, config.theme.type),
      unit: field.config ? field.config.unit : undefined,
      dataFrameIndex,
      fieldIndex,
    });

    if (datapoints && datapoints.length > 0 && range) {
      const last = datapoints[datapoints.length - 1][1];
      const from = range.from;

      if (last - from.valueOf() < -10000) {
        series.isOutsideRange = true;
      }
    }
    return series;
  }
Example #6
Source File: utils.test.ts    From grafana-chinese with Apache License 2.0 6 votes vote down vote up
function getFixedThemedColor(field: Field): string {
  return getColorFromHexRgbOrName(field.config.color!.fixedColor!, GrafanaThemeType.Dark);
}
Example #7
Source File: Gauge.tsx    From grafana-chinese with Apache License 2.0 6 votes vote down vote up
getFormattedThresholds(): Threshold[] {
    const { field, theme } = this.props;
    const thresholds = field.thresholds ?? Gauge.defaultProps.field?.thresholds!;
    const isPercent = thresholds.mode === ThresholdsMode.Percentage;
    const steps = thresholds.steps;
    let min = field.min!;
    let max = field.max!;
    if (isPercent) {
      min = 0;
      max = 100;
    }

    const first = getActiveThreshold(min, steps);
    const last = getActiveThreshold(max, steps);
    const formatted: Threshold[] = [];
    formatted.push({ value: min, color: getColorFromHexRgbOrName(first.color, theme.type) });
    let skip = true;
    for (let i = 0; i < steps.length; i++) {
      const step = steps[i];
      if (skip) {
        if (first === step) {
          skip = false;
        }
        continue;
      }
      const prev = steps[i - 1];
      formatted.push({ value: step.value, color: getColorFromHexRgbOrName(prev!.color, theme.type) });
      if (step === last) {
        break;
      }
    }
    formatted.push({ value: max, color: getColorFromHexRgbOrName(last.color, theme.type) });
    return formatted;
  }
Example #8
Source File: SpectrumPalette.tsx    From grafana-chinese with Apache License 2.0 6 votes vote down vote up
SpectrumPalette: React.FunctionComponent<SpectrumPaletteProps> = ({ color, onChange, theme }) => {
  return (
    <div>
      <SpectrumPicker
        color={tinycolor(getColorFromHexRgbOrName(color)).toRgb()}
        onChange={(a: ColorResult) => {
          onChange(tinycolor(a.rgb).toString());
        }}
        theme={theme}
      />
      <ColorInput theme={theme} color={color} onChange={onChange} style={{ marginTop: '16px' }} />
    </div>
  );
}
Example #9
Source File: ColorPickerPopover.tsx    From grafana-chinese with Apache License 2.0 6 votes vote down vote up
handleChange = (color: any) => {
    const { onColorChange, onChange, enableNamedColors, theme } = this.props;
    const changeHandler = onColorChange || onChange;

    if (enableNamedColors) {
      return changeHandler(color);
    }
    changeHandler(getColorFromHexRgbOrName(color, theme.type));
  };
Example #10
Source File: BigValueLayout.tsx    From grafana-chinese with Apache License 2.0 6 votes vote down vote up
constructor(private props: Props) {
    const { width, height, value, alignmentFactors, theme } = props;

    this.valueColor = getColorFromHexRgbOrName(value.color || 'green', theme.type);
    this.justifyCenter = shouldJustifyCenter(props);
    this.panelPadding = height > 100 ? 12 : 8;
    this.titleToAlignTo = alignmentFactors ? alignmentFactors.title : value.title;
    this.valueToAlignTo = formattedValueToString(alignmentFactors ? alignmentFactors : value);

    this.titleFontSize = 14;
    this.valueFontSize = 14;
    this.chartHeight = 0;
    this.chartWidth = 0;
    this.maxTextWidth = width - this.panelPadding * 2;
    this.maxTextHeight = height - this.panelPadding * 2;
  }
Example #11
Source File: BarGauge.tsx    From grafana-chinese with Apache License 2.0 6 votes vote down vote up
/**
 * Only exported to for unit test
 */
export function getValueColor(props: Props): string {
  const { theme, value } = props;
  if (value.color) {
    return value.color;
  }
  return getColorFromHexRgbOrName('gray', theme.type);
}
Example #12
Source File: BarGauge.tsx    From grafana-chinese with Apache License 2.0 5 votes vote down vote up
/**
 * Only exported to for unit test
 */
export function getBarGradient(props: Props, maxSize: number): string {
  const { field, value, orientation } = props;
  const cssDirection = isVertical(orientation) ? '0deg' : '90deg';
  const minValue = field.min!;
  const maxValue = field.max!;

  let gradient = '';
  let lastpos = 0;

  if (field.color && field.color.mode === FieldColorMode.Scheme) {
    const schemeSet = (d3 as any)[`scheme${field.color.schemeName}`] as any[];
    if (!schemeSet) {
      // Error: unknown scheme
      const color = '#F00';
      gradient = `linear-gradient(${cssDirection}, ${color}, ${color}`;
      gradient += ` ${maxSize}px, ${color}`;
      return gradient + ')';
    }
    // Get the scheme with as many steps as possible
    const scheme = schemeSet[schemeSet.length - 1] as string[];
    for (let i = 0; i < scheme.length; i++) {
      const color = scheme[i];
      const valuePercent = i / (scheme.length - 1);
      const pos = valuePercent * maxSize;
      const offset = Math.round(pos - (pos - lastpos) / 2);

      if (gradient === '') {
        gradient = `linear-gradient(${cssDirection}, ${color}, ${color}`;
      } else {
        lastpos = pos;
        gradient += ` ${offset}px, ${color}`;
      }
    }
  } else {
    const thresholds = field.thresholds!;

    for (let i = 0; i < thresholds.steps.length; i++) {
      const threshold = thresholds.steps[i];
      const color = getColorFromHexRgbOrName(threshold.color);
      const valuePercent = getValuePercent(threshold.value, minValue, maxValue);
      const pos = valuePercent * maxSize;
      const offset = Math.round(pos - (pos - lastpos) / 2);

      if (gradient === '') {
        gradient = `linear-gradient(${cssDirection}, ${color}, ${color}`;
      } else if (value.numeric < threshold.value) {
        break;
      } else {
        lastpos = pos;
        gradient += ` ${offset}px, ${color}`;
      }
    }
  }

  return gradient + ')';
}
Example #13
Source File: module.ts    From grafana-chinese with Apache License 2.0 5 votes vote down vote up
onColorChange = (series: any, color: string) => {
    series.setColor(getColorFromHexRgbOrName(color, config.theme.type));
    this.panel.aliasColors[series.alias] = color;
    this.render();
  };
Example #14
Source File: ColorPicker.tsx    From grafana-chinese with Apache License 2.0 5 votes vote down vote up
colorPickerFactory = <T extends ColorPickerProps>(
  popover: React.ComponentType<T>,
  displayName = 'ColorPicker'
) => {
  return class ColorPicker extends Component<T & { children?: ColorPickerTriggerRenderer }, any> {
    static displayName = displayName;
    pickerTriggerRef = createRef<any>();

    onColorChange = (color: string) => {
      const { onColorChange, onChange } = this.props;
      const changeHandler = (onColorChange || onChange) as ColorPickerChangeHandler;

      return changeHandler(color);
    };

    render() {
      const { theme, children } = this.props;
      const popoverElement = React.createElement(popover, {
        ...omit(this.props, 'children'),
        onChange: this.onColorChange,
      });

      return (
        <PopoverController content={popoverElement} hideAfter={300}>
          {(showPopper, hidePopper, popperProps) => {
            return (
              <>
                {this.pickerTriggerRef.current && (
                  <Popover
                    {...popperProps}
                    referenceElement={this.pickerTriggerRef.current}
                    wrapperClassName="ColorPicker"
                    onMouseLeave={hidePopper}
                    onMouseEnter={showPopper}
                  />
                )}

                {children ? (
                  // Children have a bit weird type due to intersection used in the definition so we need to cast here,
                  // but the definition is correct and should not allow to pass a children that does not conform to
                  // ColorPickerTriggerRenderer type.
                  (children as ColorPickerTriggerRenderer)({
                    ref: this.pickerTriggerRef,
                    showColorPicker: showPopper,
                    hideColorPicker: hidePopper,
                  })
                ) : (
                  <ColorPickerTrigger
                    ref={this.pickerTriggerRef}
                    onClick={showPopper}
                    onMouseLeave={hidePopper}
                    color={getColorFromHexRgbOrName(this.props.color || '#000000', theme.type)}
                  />
                )}
              </>
            );
          }}
        </PopoverController>
      );
    }
  };
}
Example #15
Source File: color_legend.ts    From grafana-chinese with Apache License 2.0 5 votes vote down vote up
function drawSimpleOpacityLegend(elem: JQuery, options: { colorScale: string; exponent: number; cardColor: string }) {
  const legendElem = $(elem).find('svg');
  clearLegend(elem);

  const legend = d3.select(legendElem.get(0));
  const legendWidth = Math.floor(legendElem.outerWidth());
  const legendHeight = legendElem.attr('height');

  if (legendWidth) {
    let legendOpacityScale: any;
    if (options.colorScale === 'linear') {
      legendOpacityScale = d3
        .scaleLinear()
        .domain([0, legendWidth])
        .range([0, 1]);
    } else if (options.colorScale === 'sqrt') {
      legendOpacityScale = d3
        .scalePow()
        .exponent(options.exponent)
        .domain([0, legendWidth])
        .range([0, 1]);
    }

    const rangeStep = 10;
    const valuesRange = d3.range(0, legendWidth, rangeStep);
    const legendRects = legend.selectAll('.heatmap-opacity-legend-rect').data(valuesRange);

    legendRects
      .enter()
      .append('rect')
      .attr('x', d => d)
      .attr('y', 0)
      .attr('width', rangeStep)
      .attr('height', legendHeight)
      .attr('stroke-width', 0)
      .attr(
        'fill',
        getColorFromHexRgbOrName(
          options.cardColor,
          contextSrv.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark
        )
      )
      .style('opacity', d => legendOpacityScale(d));
  }
}
Example #16
Source File: threshold_manager.ts    From grafana-chinese with Apache License 2.0 4 votes vote down vote up
addFlotOptions(options: any, panel: any) {
    if (!panel.thresholds || panel.thresholds.length === 0) {
      return;
    }

    let gtLimit = Infinity;
    let ltLimit = -Infinity;
    let i, threshold, other;

    for (i = 0; i < panel.thresholds.length; i++) {
      threshold = panel.thresholds[i];
      if (!_.isNumber(threshold.value)) {
        continue;
      }

      let limit;
      switch (threshold.op) {
        case 'gt': {
          limit = gtLimit;
          // if next threshold is less then op and greater value, then use that as limit
          if (panel.thresholds.length > i + 1) {
            other = panel.thresholds[i + 1];
            if (other.value > threshold.value) {
              limit = other.value;
              ltLimit = limit;
            }
          }
          break;
        }
        case 'lt': {
          limit = ltLimit;
          // if next threshold is less then op and greater value, then use that as limit
          if (panel.thresholds.length > i + 1) {
            other = panel.thresholds[i + 1];
            if (other.value < threshold.value) {
              limit = other.value;
              gtLimit = limit;
            }
          }
          break;
        }
      }

      let fillColor, lineColor;

      switch (threshold.colorMode) {
        case 'critical': {
          fillColor = 'rgba(234, 112, 112, 0.12)';
          lineColor = 'rgba(237, 46, 24, 0.60)';
          break;
        }
        case 'warning': {
          fillColor = 'rgba(235, 138, 14, 0.12)';
          lineColor = 'rgba(247, 149, 32, 0.60)';
          break;
        }
        case 'ok': {
          fillColor = 'rgba(11, 237, 50, 0.090)';
          lineColor = 'rgba(6,163,69, 0.60)';
          break;
        }
        case 'custom': {
          fillColor = threshold.fillColor;
          lineColor = threshold.lineColor;
          break;
        }
      }

      // fill
      if (threshold.fill) {
        if (threshold.yaxis === 'right' && this.hasSecondYAxis) {
          options.grid.markings.push({
            y2axis: { from: threshold.value, to: limit },
            color: getColorFromHexRgbOrName(fillColor),
          });
        } else {
          options.grid.markings.push({
            yaxis: { from: threshold.value, to: limit },
            color: getColorFromHexRgbOrName(fillColor),
          });
        }
      }
      if (threshold.line) {
        if (threshold.yaxis === 'right' && this.hasSecondYAxis) {
          options.grid.markings.push({
            y2axis: { from: threshold.value, to: threshold.value },
            color: getColorFromHexRgbOrName(lineColor),
          });
        } else {
          options.grid.markings.push({
            yaxis: { from: threshold.value, to: threshold.value },
            color: getColorFromHexRgbOrName(lineColor),
          });
        }
      }
    }
  }
Example #17
Source File: module.ts    From grafana-chinese with Apache License 2.0 4 votes vote down vote up
link(scope: any, elem: JQuery, attrs: any, ctrl: any) {
    const $location = this.$location;
    const linkSrv = this.linkSrv;
    const $timeout = this.$timeout;
    const $sanitize = this.$sanitize;
    const panel = ctrl.panel;
    const templateSrv = this.templateSrv;
    let linkInfo: LinkModel<any> | null = null;
    elem = elem.find('.singlestat-panel');

    function getPanelContainer() {
      return elem.closest('.panel-container');
    }

    function applyColoringThresholds(valueString: string) {
      const data = ctrl.data;
      const color = getColorForValue(data, data.value);
      if (color) {
        return '<span style="color:' + color + '">' + valueString + '</span>';
      }

      return valueString;
    }

    function getSpan(className: string, fontSizePercent: string, applyColoring: any, value: string) {
      value = $sanitize(templateSrv.replace(value, ctrl.data.scopedVars));
      value = applyColoring ? applyColoringThresholds(value) : value;
      const pixelSize = (parseInt(fontSizePercent, 10) / 100) * BASE_FONT_SIZE;
      return '<span class="' + className + '" style="font-size:' + pixelSize + 'px">' + value + '</span>';
    }

    function getBigValueHtml() {
      const data: ShowData = ctrl.data;
      let body = '<div class="singlestat-panel-value-container">';

      if (panel.prefix) {
        body += getSpan('singlestat-panel-prefix', panel.prefixFontSize, panel.colorPrefix, panel.prefix);
      }

      body += getSpan(
        'singlestat-panel-value',
        panel.valueFontSize,
        panel.colorValue,
        formattedValueToString(data.display)
      );

      if (panel.postfix) {
        body += getSpan('singlestat-panel-postfix', panel.postfixFontSize, panel.colorPostfix, panel.postfix);
      }

      body += '</div>';

      return body;
    }

    function getValueText() {
      const data: ShowData = ctrl.data;
      let result = panel.prefix ? templateSrv.replace(panel.prefix, data.scopedVars) : '';
      result += formattedValueToString(data.display);
      result += panel.postfix ? templateSrv.replace(panel.postfix, data.scopedVars) : '';

      return result;
    }

    function addGauge() {
      const data: ShowData = ctrl.data;
      const width = elem.width();
      const height = elem.height();
      // Allow to use a bit more space for wide gauges
      const dimension = Math.min(width, height * 1.3);

      ctrl.invalidGaugeRange = false;
      if (panel.gauge.minValue > panel.gauge.maxValue) {
        ctrl.invalidGaugeRange = true;
        return;
      }

      const plotCanvas = $('<div></div>');
      const plotCss = {
        top: '5px',
        margin: 'auto',
        position: 'relative',
        height: height * 0.9 + 'px',
        width: dimension + 'px',
      };

      plotCanvas.css(plotCss);

      const thresholds = [];

      for (let i = 0; i < data.thresholds.length; i++) {
        thresholds.push({
          value: data.thresholds[i],
          color: data.colorMap[i],
        });
      }
      thresholds.push({
        value: panel.gauge.maxValue,
        color: data.colorMap[data.colorMap.length - 1],
      });

      const bgColor = config.bootData.user.lightTheme ? 'rgb(230,230,230)' : 'rgb(38,38,38)';

      const fontScale = parseInt(panel.valueFontSize, 10) / 100;
      const fontSize = Math.min(dimension / 5, 100) * fontScale;
      // Reduce gauge width if threshold labels enabled
      const gaugeWidthReduceRatio = panel.gauge.thresholdLabels ? 1.5 : 1;
      const gaugeWidth = Math.min(dimension / 6, 60) / gaugeWidthReduceRatio;
      const thresholdMarkersWidth = gaugeWidth / 5;
      const thresholdLabelFontSize = fontSize / 2.5;

      const options: any = {
        series: {
          gauges: {
            gauge: {
              min: panel.gauge.minValue,
              max: panel.gauge.maxValue,
              background: { color: bgColor },
              border: { color: null },
              shadow: { show: false },
              width: gaugeWidth,
            },
            frame: { show: false },
            label: { show: false },
            layout: { margin: 0, thresholdWidth: 0 },
            cell: { border: { width: 0 } },
            threshold: {
              values: thresholds,
              label: {
                show: panel.gauge.thresholdLabels,
                margin: thresholdMarkersWidth + 1,
                font: { size: thresholdLabelFontSize },
              },
              show: panel.gauge.thresholdMarkers,
              width: thresholdMarkersWidth,
            },
            value: {
              color: panel.colorValue ? getColorForValue(data, data.display.numeric) : null,
              formatter: () => {
                return getValueText();
              },
              font: {
                size: fontSize,
                family: config.theme.typography.fontFamily.sansSerif,
              },
            },
            show: true,
          },
        },
      };

      elem.append(plotCanvas);

      const plotSeries = {
        data: [[0, data.value]],
      };

      $.plot(plotCanvas, [plotSeries], options);
    }

    function addSparkline() {
      const data: ShowData = ctrl.data;
      const width = elem.width();
      if (width < 30) {
        // element has not gotten it's width yet
        // delay sparkline render
        setTimeout(addSparkline, 30);
        return;
      }
      if (!data.sparkline || !data.sparkline.length) {
        // no sparkline data
        return;
      }

      const height = ctrl.height;
      const plotCanvas = $('<div></div>');
      const plotCss: any = {};
      plotCss.position = 'absolute';
      plotCss.bottom = '0px';

      if (panel.sparkline.full) {
        plotCss.left = '0px';
        plotCss.width = width + 'px';
        const dynamicHeightMargin = height <= 100 ? 5 : Math.round(height / 100) * 15 + 5;
        plotCss.height = height - dynamicHeightMargin + 'px';
      } else {
        plotCss.left = '0px';
        plotCss.width = width + 'px';
        plotCss.height = Math.floor(height * 0.25) + 'px';
      }

      plotCanvas.css(plotCss);

      const options = {
        legend: { show: false },
        series: {
          lines: {
            show: true,
            fill: 1,
            lineWidth: 1,
            fillColor: getColorFromHexRgbOrName(panel.sparkline.fillColor, config.theme.type),
            zero: false,
          },
        },
        yaxis: {
          show: false,
          min: panel.sparkline.ymin,
          max: panel.sparkline.ymax,
        },
        xaxis: {
          show: false,
          mode: 'time',
          min: ctrl.range.from.valueOf(),
          max: ctrl.range.to.valueOf(),
        },
        grid: { hoverable: false, show: false },
      };

      elem.append(plotCanvas);

      const plotSeries = {
        data: data.sparkline,
        color: getColorFromHexRgbOrName(panel.sparkline.lineColor, config.theme.type),
      };

      $.plot(plotCanvas, [plotSeries], options);
    }

    function render() {
      if (!ctrl.data) {
        return;
      }
      const { data, panel } = ctrl;

      // get thresholds
      data.thresholds = panel.thresholds
        ? panel.thresholds.split(',').map((strVale: string) => {
            return Number(strVale.trim());
          })
        : [];

      // Map panel colors to hex or rgb/a values
      if (panel.colors) {
        data.colorMap = panel.colors.map((color: string) => getColorFromHexRgbOrName(color, config.theme.type));
      }

      const body = panel.gauge.show ? '' : getBigValueHtml();

      if (panel.colorBackground) {
        const color = getColorForValue(data, data.display.numeric);
        if (color) {
          getPanelContainer().css('background-color', color);
          if (scope.fullscreen) {
            elem.css('background-color', color);
          } else {
            elem.css('background-color', '');
          }
        } else {
          getPanelContainer().css('background-color', '');
          elem.css('background-color', '');
        }
      } else {
        getPanelContainer().css('background-color', '');
        elem.css('background-color', '');
      }

      elem.html(body);

      if (panel.sparkline.show) {
        addSparkline();
      }

      if (panel.gauge.show) {
        addGauge();
      }

      elem.toggleClass('pointer', panel.links.length > 0);

      if (panel.links.length > 0) {
        linkInfo = linkSrv.getDataLinkUIModel(panel.links[0], data.scopedVars, {});
      } else {
        linkInfo = null;
      }
    }

    function hookupDrilldownLinkTooltip() {
      // drilldown link tooltip
      const drilldownTooltip = $('<div id="tooltip" class="">hello</div>"');

      elem.mouseleave(() => {
        if (panel.links.length === 0) {
          return;
        }
        $timeout(() => {
          drilldownTooltip.detach();
        });
      });

      elem.click(evt => {
        if (!linkInfo) {
          return;
        }
        // ignore title clicks in title
        if ($(evt).parents('.panel-header').length > 0) {
          return;
        }

        if (linkInfo.target === '_blank') {
          window.open(linkInfo.href, '_blank');
          return;
        }

        if (linkInfo.href.indexOf('http') === 0) {
          window.location.href = linkInfo.href;
        } else {
          $timeout(() => {
            $location.url(locationUtil.stripBaseFromUrl(linkInfo.href));
          });
        }

        drilldownTooltip.detach();
      });

      elem.mousemove(e => {
        if (!linkInfo) {
          return;
        }

        drilldownTooltip.text('click to go to: ' + linkInfo.title);
        drilldownTooltip.place_tt(e.pageX, e.pageY - 50);
      });
    }

    hookupDrilldownLinkTooltip();

    this.events.on(PanelEvents.render, () => {
      render();
      ctrl.renderingCompleted();
    });
  }