微信小程序canvas画价格走势图(四)

上一篇讲到了把折线图和拐点都画好了,这一篇我们来更进一步,绘制出拐点处的更多信息,价格信息和拼团信息。

效果如下图所示。(不包括下面的日期和刻度线)

一、普通情况下的价格和拼团人数的绘制

很容易就看出,图上第二个拐点不仅圆圈更大,而且价格和拼团人数的样式明显不同。我们先考虑更简单的情况。

首先,思考一个问题,拐点与这些信息的位置有什么关系?根据图得知,价格位于拐点上方不远处,拼团人数则位于价格的上方不远处。因此价格和拼团人数的位置应该与拐点坐标有一定的关系。所以我绘制这两个信息的时候,会把拐点信息先拿到。

然后,根据拐点与这些信息的位置关系,计算出绘制文字的正确位置。虽然在我们习惯的坐标系中,越往上的位置,y坐标更大,但是在canvas中,越往上,y是越小的。所以价格文字的y坐标应该是拐点y坐标减去某个合适的值来得到,拼团人数的y坐标是是拐点y坐标减去一个更大的值。价格和拼团人数的x坐标就暂时为拐点的x坐标。

现在,我们让价格和拼团文字与拐点位置水平居中。当前,我们的文字就写在拐点圆心处,我们很容易就能想到,让文字往左平移文字本身宽度的一半,就可以实现水平居中对齐。那么,怎么知道文字的宽度呢?我们可以在设置好文字的字体样式后,调用context.measureText(text)方法来获取(参数是要测量的文本,返回值是一个对象,里面有一个width属性,值为数字,单位是px)

综上所述,它们的x坐标为为拐点的x坐标减去文字内容宽度的一半,y坐标为拐点的y坐标减去一个合适的常量。做好以上三步后,价格和拼团人数就能正常显示啦。

贴一下我写的相关代码:(获取文字宽度的代码我封装到函数里去了,因为返回值是一个对象,在我封装的函数里,返回的是这个对象里的width属性,这才是我们需要的)

  /**
   * 计算文字的宽度,返回计算结果
   * @param {*} context 上下文对象
   * @param {*} text 要计算的文字
   */
   function getTextWidth(context, text) {
     return context.measureText(text).width;
   }
   
    /**
     * 绘制普通价格
     * @param {*} context 上下文对象
     * @param {*} center 拐点信息
     */
     function drawNormalPrice(context, center) {
       // 写价格
       context.beginPath();
       context.setFontSize(12);
       context.fillStyle = "#333333";
       // 写出价格,注意x的偏移量是文字宽度的一半
       context.fillText('¥' + center.value, center.x - getTextWidth(context, '¥' + center.value) / 2, center.y - 30 * _this.data.toPx);
       context.draw(true);
    }

     /**
      * 绘制普通拼团人数
      * @param {*} context 上下文对象
      * @param {*} center 拐点信息
      */
    function drawNormalTeamNumber(context, center) {
      context.beginPath();
      context.setFontSize(12);
      context.fillStyle = "#999999";
      context.fillText('(' + center.teammateNumber + '人团)', center.x - getTextWidth(context, '(' + center.teammateNumber + '人团)') / 2, center.y - 64 * _this.data.toPx);
      context.draw(true);
    }

二、特殊情况下的价格和拼团人数的绘制

第二个点是特殊点,它的价格和拼团人数有哪些特殊之处呢?首先是位置,它的价格写到了拐点下方,拼团人数写到了拐点右方。然后是字体的颜色,价格变成了白色,并且带有渐变色填充的背景,拼团人数是橙色的。还有字体的大小也略有不同。

我们先来看看,特殊拐点处的拼团价格怎么绘制。

首先,先绘制出这样的一个胶囊形轮廓。流程说起来很简单——“画一条向右的直线,然后画右半边圆弧,再然后画一条向左的直线,最后画一条左半边圆弧”

但我个人感觉这不太容易,要注意计算,如果算错了一个点的坐标,整个效果就成了车祸现场。我最后得出来的方法是:

①确定好胶囊的宽度w和半圆的半径r

②找到一个合适的点,调用context.moveTo,移动到这里

③调用context.lineTo,画条直线到第二个点,第二个点与第一个点的关系是:y坐标不变,x坐标加上胶囊的宽度w

④找到圆心,调用context.arc来绘制右半边圆。圆心的坐标是:x坐标等于第二个点的x坐标,y坐标等于第二个点的y坐标加上圆弧半径r注意圆弧的起点和终点,由于我们画的是右半边的圆弧,要记得右边的方向的弧度是0或2π,所以上方的弧度是1.5π,下方的弧度是2.5π,所以我们要绘制的圆弧是顺时针的1.5π到2.5π;(这里我折腾了好久的)

弧度见下图:

弧/曲线

⑤调用context.lineTo方法,画直线到第三个点,第三个点的位置是:x坐标与第一个点的x坐标相同,y坐标为第一个点的x坐标加上两边的圆弧半径r

⑥找到圆心,调用context.arc来绘制右半边圆。圆心的坐标是:x坐标等于第三个点的x坐标,y坐标等于之前的那个圆弧的圆心y坐标注意圆弧的起点和终点,由于我们画的是左半边的圆弧,所以我们要绘制的圆弧是顺时针的0.5π到1.5π

做好了以上六步,恭喜你,胶囊形就绘制完毕了。

现在,我们要弄一个渐变色来填充胶囊。渐变色在canvas中如何绘制呢?渐变色也是一种填充,所以是先设置出一种渐变,再作为路径的填充风格。使用context.createLinearGradient()创建渐变对象,参数是渐变的起止位置。然后使用渐变对象的addColorStop(颜色,位置)来设置渐变的过程有哪些颜色,位置的值位于0到1之间,0表示起点,1表示终点。之后,就把context的fillStyle设为这个渐变对象,然后调用context.fill和context.draw(true)即可。

最后,注意一个细节问题,胶囊形的宽度定为多少合适?位置在哪里合适?显然是比文字宽度稍微大一点,并且与白色的文字水平居中对齐合适。要怎么做到这些呢?

第一步,来获取白色文字的宽度。让我非常惊喜的是, 这个API可以告诉我文本的宽度——context.measureText ,参数是要计算宽度的文本,返回值是一个对象,里面有weight属性,数字类型的。注意,要先设置好文字的样式和大小以后再来计算哦。因为样式和文本大小决定了一个字有多宽,所以肯定会影响到文本写出来的宽度。我上次就因为这个,导致做出来的结果总是不如预期效果,难受了好久。

第二步,准备一个常量来存储胶囊宽度。计算好了文字宽度后,胶囊的宽度也就呼之欲出了,我们再次基础上加上一个比较小的值作为胶囊宽度,把它存入常量中。然后把这个常量用到上面那个画胶囊形的公式里去。这样胶囊的宽度就是正好的。

第三步,确定白色文字和胶囊的位置。这个位置肯定与特殊点x坐标有关系。假如白色文字的左边缘x坐标等于特殊点的x坐标,那么文字显然是偏右的,要向左移动(把特殊点的x坐标减去)文字宽度的一半才可以。同理,胶囊形的x坐标也是把特殊点的x坐标减去胶囊宽度的一半。它们的位置显然比特殊点更高,所以y应该是更小,适当减少特殊点坐标y的值,赋值给它们就可以了。把胶囊形底边的y坐标也代入前面画胶囊的公式里去。

做完这三步以后,恭喜你,你已经把拼团人数和价格都画完了。

最后贴一下这一步相关的代码:

 /**
     * 绘制普通价格
     * @param {*} context 上下文对象
     * @param {*} center 拐点信息
     */
    function drawNormalPrice(context, center) {
      // 写价格
      context.beginPath();
      context.setFontSize(12);
      context.fillStyle = "#333333";
      // 写出价格,注意x的偏移量是文字宽度的一半
      context.fillText('¥' + center.value, center.x - getTextWidth(context, '¥' + center.value) / 2, center.y - 30 * _this.data.toPx);
      context.draw(true);
    }

    // 
    /**
     * 绘制特殊情况的拼团价格
     * @param {*} context 上下文对象
     * @param {*} center 拐点信息
     */
    function drawSpecialPrice(context, center) {
      context.beginPath();
      context.setFontSize(30 * _this.data.toPx);
      // 计算白字的宽度
      const textWidth = getTextWidth(context, '¥' + center.value);
      // 先画胶囊形状
      // 根据白字的宽度,来计算胶囊的宽度,才能确定胶囊的位置和宽度
      const capsultWidth = textWidth + 10 * _this.data.toPx;
      context.moveTo(center.x - capsultWidth / 2, center.y + 42 * _this.data.toPx);
      context.lineTo(center.x + capsultWidth / 2, center.y + 42 * _this.data.toPx);
      context.arc(center.x + capsultWidth / 2, center.y + 70 * _this.data.toPx, 28 * _this.data.toPx, 1.5 * Math.PI, 2.5 * Math.PI, false);
      context.lineTo(center.x - capsultWidth / 2, center.y + 98 * _this.data.toPx);
      context.arc(center.x - capsultWidth / 2, center.y + 70 * _this.data.toPx, 28 * _this.data.toPx, 0.5 * Math.PI, 1.5 * Math.PI, false);
      context.closePath();
      // 准备一个渐变
      const grd = context.createLinearGradient(center.x - (capsultWidth / 2 - 28 * _this.data.toPx), 0, center.x + (capsultWidth / 2 + 28 * _this.data.toPx), 0)
      grd.addColorStop(0, '#FE7301');
      grd.addColorStop(1, '#FF4800');
      context.fillStyle = grd;
      // 胶囊填充的是渐变色
      context.fill();
      context.draw(true);
      // 写白色的字
      context.beginPath();
      context.fillStyle = "#ffffff";
      context.fillText('¥' + center.value, center.x - textWidth / 2, center.y + 82 * _this.data.toPx);
      context.draw(true);
    }

    /**
     * 绘制普通拼团人数
     * @param {*} context 上下文对象
     * @param {*} center 拐点信息
     */
    function drawNormalTeamNumber(context, center) {
      context.beginPath();
      context.setFontSize(12);
      context.fillStyle = "#999999";
      context.fillText('(' + center.teammateNumber + '人团)', center.x - getTextWidth(context, '(' + center.teammateNumber + '人团)') / 2, center.y - 64 * _this.data.toPx);
      context.draw(true);
    }

    /**
     * 绘制特殊情况的拼团人数
     * @param {*} context 上下文对象
     * @param {*} center 拐点信息
     * @param {*} color 颜色
     */
    function drawSpecialTeamNumber(context, center, color) {
      context.beginPath();
      context.setFontSize(13);
      context.fillStyle = color;
      context.fillText('(' + center.teammateNumber + '人团)', center.x + 28, center.y + 5);
      context.draw(true);
    }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值