Highcharts Highstock如何在x轴Navigator上面拖动时显示日期tooltip

当前公司在使用HighCharts来做图表展示大量数据. 其中HighStock是很好的展示时间轴图表的工具. 最近一个需求是要求在图表的Navigator上面拖动的时候显示日期tooltip, 好让用户知道拖到了哪里. 在网上搜索找到了一个别人写的三年前的highcharts插件库, 已经不能用了, 但是思路可以借鉴.

首先借鉴

  1. 首先把上面提到的人家写的库的代码拿出来复制到自己的项目内
  2. 你会发现很多变量已经变了(Highcharts正常更新, 这个插件库年久失修, 很多变量很多方法都发生了翻天覆地的变化), 把已经不存在的变量修复成为当前的变量
  3. 修改插件中定义的各种位置数值(很多位置不对, 要自己慢慢调整)

修改完成后的代码

(function(H) {
  const widthMap = {
    left: 70,
    right: 70,
    center: 150,
  }

  /**
   * Render tooltip into navigatorGroup
   *
   * @name renderTooltip
   * @param {Scroller} scroller
   * @param {String} position: position of tooltip [left, right, center]
   * @param {String} str: string to initially display in tooltip
   * @returns {undefined}
   */
  const renderTooltip = function(scroller, position, str) {
    const {
      chart: { renderer },
    } = scroller;
    scroller[`${position}Tooltip`] = renderer.rect(0, 0, 95, 40, 3, 0).add(scroller.navigatorGroup);
    scroller[`${position}TooltipText`] = renderer.text(str, 5, 15).add(scroller.navigatorGroup);
    scroller[`${position}TooltipArrow`] = renderer
    .path(['M', 0, 0, 'H', 10, 'L', 5, 5, 'L', 0, 0])
    .add(scroller.navigatorGroup);
  };

  /**
   * Fade in/out tooltip
   *
   * @name fade
   * @param {String} direction: direction of fade [in, out]
   * @returns {Function}
   */
  const fade = direction => {
    return function(scroller, position) {
      const { [`${position}Tooltip`]: tooltip, [`${position}TooltipText`]: text, [`${position}TooltipArrow`]: tooltipArrow } = scroller;
      const fadeClass = `fade-${direction}`;

      tooltip.attr('class', fadeClass);
      text.attr('class', fadeClass);
      tooltipArrow.attr('class', fadeClass);
    };
  };

  const fadeInTooltip = fade('in');
  const fadeOutTooltip = fade('out');

  const debounceFadeOut = _.debounce(fadeOutTooltip, 1000);

  const tooltipPadding = 12;

  /**
   * Find the correct position for the tooltip and the tooltip's arrow
   *
   * @name findTooltipPosition
   * @param {Scroller} scroller
   * @param {String} position: position of tooltip [left, right, center]
   * @param {String} tooltipWidth
   * @returns {Object}
   *   @returns {Number} x
   *   @returns {Number} y
   *   @returns {Number} arrow
   *     @returns {Number} x
   *     @returns {Number} y
   */
  const findTooltipPosition = function(scroller, position, tooltipWidth) {
    const { size, left, range } = scroller;
    const handleIndex = { left: 0, right: 1, center: 0 };
    const handle = scroller.handles[handleIndex[position]];
    const offset = (tooltipWidth + tooltipPadding * 2) / 2;
    let x = handle.translateX - offset;
    const arrow = {
      x: handle.translateX - 5,
      y: handle.translateY - 13,
    };

    if (position === 'center') {
      x = handle.translateX + (range - tooltipWidth) / 2;
      arrow.x = x + tooltipWidth / 2 - 5;
    }

    // tooltip is outside right edge of minimap
    if (x + tooltipWidth > size) {
      x = size - tooltipWidth;
      arrow.x = size - 5;
    }

    // tooltip is outside of left edge of minimap
    if (x < left) {
      x = left;
      arrow.x = x;
    }

    return { x, y: handle.translateY - 25, arrow };
  };

  const tooltipFill = THEME.fifth;

  /**
   * Adjust the position, width of the tooltip
   *
   * @name adjustTooltip
   * @param {Scroller} scroller
   * @param {String} position: position of tooltip [left, right, center]
   * @param {String} str: string to initially display in tooltip
   * @returns {undefined}
   */
  const adjustTooltip = function(scroller, position, str) {
    const { [`${position}TooltipArrow`]: arrow, [`${position}Tooltip`]: tooltip, [`${position}TooltipText`]: text } = scroller;
    text.attr({ text: str });
    // const { clientWidth: textWidth, clientHeight: textHeight } = text.element;
    const textWidth = widthMap[position];
    const textHeight = 14;

    const {
      x,
      y,
      arrow: { x: arrowX, y: arrowY },
    } = findTooltipPosition(scroller, position, textWidth);

    tooltip.attr({
      width: textWidth + tooltipPadding * 2,
      height: textHeight + tooltipPadding * 2,
      fill: tooltipFill,
      x,
      y: y - textHeight - tooltipPadding,
    });

    text.attr({ text: str, x: x + tooltipPadding, y: y }).css({
      color: THEME.primary,
      fontSize: '14px',
    });

    arrow.attr({
      d: ['M', arrowX, arrowY, 'H', arrowX + 10, 'L', arrowX + 5, arrowY + 5, 'L', arrowX, arrowY],
      fill: tooltipFill,
    });
  };

  /**
   * Render the scroller as it changes through interaction
   *
   * @name render
   * @param {Function} proceed: effectively `_super`
   * @param {Number} min: min xAxis value of currently represented by the navigator
   * @param {Number} max: max xAxis value of currently represented by the navigator
   * @param {Number} pxMin: min pixel position of current navigator selection
   * @param {Number} pxMax: max pixel position of current navigator selection
   * @returns {undefined}
   */
  H.wrap(H.Navigator.prototype, 'render', function(proceed, min, max, pxMin, pxMax) {
    const [, ...args] = arguments;
    proceed.call(this, ...args);

    const {
      navigatorOptions: { tooltipFormatter },
    } = this;

    if (!tooltipFormatter) return;

    const { chart } = this;
    const { dataMin, dataMax } = chart.xAxis[0];
    const diff = dataMax - dataMin;
    const unit = diff / chart.xAxis[0].width;
    const range = { min: dataMin + unit * this.zoomedMin, max: dataMin + unit * this.zoomedMax };

    const formattedTooltipText = tooltipFormatter(range.min, range.max, pxMin, pxMax);
    formattedTooltipText.center = `${formattedTooltipText.left} - ${formattedTooltipText.right}`;

    if (!this.tooltipRendered) {
      renderTooltip(this, 'left', formattedTooltipText.left);
      renderTooltip(this, 'right', formattedTooltipText.right);
      renderTooltip(this, 'center', formattedTooltipText.center);
      
      this.tooltipRendered = true;
    }

    if (!formattedTooltipText.left) return;
    const positions = ['left', 'right', 'center'];
    positions.forEach(position => {
      const capitalizedPosition = position.charAt(0).toUpperCase() + position.slice(1);
      const grabbed = this[`grabbed${capitalizedPosition}`];
      if (grabbed) {
        fadeInTooltip(this, position);
        adjustTooltip(this, position, formattedTooltipText[position]);
        debounceFadeOut(this, position);
      } else {
        fadeOutTooltip(this, position);
      }
    });
  });
})(Highcharts);

然后应用

进行完上面的修改调整之后, 就可以在调用的地方使用啦

  1. 首先, 把上面经过调整后的代码粘贴到你要使用Highcharts的地方的上面
  2. 在你要用的地方这样使用
navigator: {
        height: 32,
        margin: 20,
        xAxis: {
          labels: {
            enabled: true,
          },
        },
        tooltipFormatter: function(min, max) {
          return { left: formatter.dateFormatter(min), right: formatter.dateFormatter(max) };
        },
      },

当然你可以按照你自己的需要调整属性数值

最后的效果

拉动左边

拉动右边

拉动中间
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,383评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,522评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,852评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,621评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,741评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,929评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,076评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,803评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,265评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,582评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,716评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,395评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,039评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,027评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,488评论 2 361
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,612评论 2 350

推荐阅读更多精彩内容

  • 第一篇环境配置 & Hello World 一、前言 二、环境配置 三、Hello, React-Native 四...
    共田君阅读 631评论 0 3
  • 使用GSON库很久,但一直没有深入分析它的内部实现机理。正好为了建立团队java开发规范,参考了google的ja...
    zzuduoduo阅读 618评论 1 1