画布 - 如何在饼图中独立于形状旋转绘制的图像

时间:2017-11-30 03:28:19

标签: javascript html html5 canvas html5-canvas

我使用我在Github上找到的James Alvarez的Draggable Pie Chart插件创建了一个交互式饼图。我决定使用图像作为每个饼图的标签而不是文本。我有饼图显示和按预期运行,但我对标签图像的显示方式有疑问。

问题在于标签图像必须与饼图一起旋转/翻译,以便它们具有正确的位置,这会导致徽标显示为颠倒。

我希望一切都保持原样,但标志始终是正确的。这可能在canvas元素中吗?

这是我的代码: https://jsfiddle.net/uwx3vv7c/

HTML:

<div id="piechart-controls">
    <canvas id="piechart" width="400" height="400">Your browser is too old!</canvas>
    <div>
        <div class="percentWrapper" style="display: inline-block;">
            <img src="https://cdn4.iconfinder.com/data/icons/seo-web-15/465/web-user-interface_49-128.png">
            <span class="mixPercentage">33%</span>
        </div>
        <div class="percentWrapper" style="display: inline-block;">
            <img src="https://cdn3.iconfinder.com/data/icons/picons-social/57/46-facebook-128.png">
            <span class="mixPercentage">33%</span>
        </div>
        <div class="percentWrapper" style="display: inline-block;">
            <img src="https://cdn3.iconfinder.com/data/icons/picons-social/57/43-twitter-128.png">
            <span class="mixPercentage">34%</span>
        </div>
    </div>
</div>

JS:

(function($){


    $(window).ready(setupPieChart);


    function setupPieChart() {


        let proportions = [
            { proportion: 45, format: { image: "https://cdn4.iconfinder.com/data/icons/seo-web-15/465/web-user-interface_49-128.png" }},
            { proportion: 30, format: { image: "https://cdn3.iconfinder.com/data/icons/picons-social/57/46-facebook-128.png" }},
            { proportion: 25, format: { image: "https://cdn3.iconfinder.com/data/icons/picons-social/57/43-twitter-128.png" }} 
        ];

        let setup = {
            canvas: document.getElementById('piechart'),
            radius: 0.9,
            collapsing: false,
            proportions: proportions,
            drawSegment: drawSegmentOutlineOnly,
            onchange: onPieChartChange,
            minAngle: 1.575
        };

        let newPie = new DraggablePiechart(setup);

        // initial drawing function for pie chart
        function drawSegmentOutlineOnly(context, piechart, centerX, centerY, radius, startingAngle, arcSize, format, collapsed) {

            if (collapsed) { return; }

            // Draw segment
            context.save();
            let endingAngle = startingAngle + arcSize;
            context.beginPath();
            context.moveTo(centerX, centerY);
            context.arc(centerX, centerY, radius, startingAngle, endingAngle, false);
            context.closePath();

            context.fillStyle = '#666';
            context.fill();
            context.stroke();
            context.restore();

            // Draw image
            context.save();
            context.translate(centerX, centerY);
            context.rotate(startingAngle);

            let iconHeight = Math.floor(context.canvas.height / 5);
            let iconWidth = Math.floor(context.canvas.width / 5);
            let dx = (radius / 2) - (iconWidth/2);
            let dy = (radius / 2) - (iconHeight/2);

            let flavorImage = new Image();
            flavorImage.src = format.image;
            context.drawImage(flavorImage, dx, dy, iconWidth, iconHeight);
            context.restore();
        }

        // update the percentages when the pieces are adjusted
        function onPieChartChange(piechart) {

            let percentages = piechart.getAllSliceSizePercentages();
            let percentLabels = $(".mixPercentage");

            for (let i = 0; i < percentages.length; i++) {
                percentLabels.eq(i).html(percentages[i].toFixed(0) + "%");
            }

        }

    }

})(jQuery);

我尝试了几件事,包括:

  • 不能旋转和翻译图像,但会导致图像无法正常显示

  • 使用图像作为背景图案,而不是将它们实际绘制为画布上下文的一部分,但它可以平铺图像或将其拉伸到完整饼图的大小。

奖金问题 - 在饼图中垂直和水平居中的任何方式?

2 个答案:

答案 0 :(得分:0)

绘制比例,通过其中心旋转图像。

要绘制旋转和缩放的图像,请使用以下功能

// ctx is the 2D context
// image is the image to draw
// x,y where on the canvas the image center will be
// the scale 1 is no change, < 1 is smaller,  > 1 is larger    
// angle in radians to rotate the image
function drawImage(ctx, image, x, y, scale, angle){
    ctx.setTransform(scale, 0, 0, scale, x, y);
    ctx.rotate(angle);
    ctx.drawImage(image, -image.width / 2, -image.height / 2);
    ctx.setTransform(1, 0, 0, 1, 0, 0);  // restore default transform
                                    // not really needed if you call this
                                    // function many times in a row.
                                    // You can restore the default afterwards
}

如果您需要镜像图像,以下功能将有所帮助,只需将图像轴的比例设置为负片,例如drawImage(ctx,image,100,100,1,-1,0);将图像上下颠倒。

// ctx is the 2D context
// image is the image to draw
// x,y where on the canvas the image center will be
// the scaleX, scaleY 1 is no change, < 1 is smaller,  > 1 is larger    
// angle in radians to rotate the image
function drawImageMirror(ctx, image, x, y, scaleX, scaleY, angle){
    ctx.setTransform(scaleX, 0, 0, scaleY, x, y);
    ctx.rotate(angle);
    ctx.drawImage(image, -image.width / 2, -image.height / 2);
    ctx.setTransform(1, 0, 0, 1, 0, 0);  // restore default transform
                                    // not really needed if you call this
                                    // function many times in a row.
                                    // You can restore the default afterwards
}

在饼图的中心绘制,用

绘制外部曲线
       context.arc(centerX, centerY, radius, startingAngle, endingAngle, false);

使用第一个功能

const scale = Math.min(canvas.width / 5, canvas.height / 5) / flavorImage.width;
const imgSize = flavorImage.width * scale;
const centerAngle = (startingAngle + endingAngle) / 2;
const angle = 0;  // for image directions see comment below
drawImage(
    context,
    flavorImage,
    Math.cos(centerAngle) * (radius - imgSize) + centerX, 
    Math.sin(centerAngle) * (radius - imgSize) + centerY, 
    scale,
    angle,
 );

// or if you want the top of image to face center use next line
// const angle = centerAngle - Math.PI / 2;
// or if you want the bottom of image to face center use next line
// const angle = centerAngle + Math.PI / 2;

答案 1 :(得分:0)

您的库为您提供了必须使用arc()方法的起始角度。它还为您提供了arcLength和中心坐标。

您似乎已经能够绘制弧段,但要绘制图像,您需要进行一些调整。

首先,您将上下文的矩阵移动到饼图的中心, 然后您将找到旋转角度,以便您面向当前线段的中间,然后在Y轴上移动到图像的中心位置,最后,您将旋转线段旋转的倒数来制作图像正常面对。

// you've got starting angle for arc() method,
// which itself starts at 3'oclock, while the matrix is noon based
// so we subtract half PI from the starting angle and add half arcSize to get to the middle
let rotationAngle = -Math.PI / 2 + (startingAngle + (arcSize / 2));
// distance from center
let distance = (radius / 2);
// position our pointer to the center of the canvas
context.translate(centerX, centerY);
// rotate it so we face the middle of the arc
context.rotate(rotationAngle);
// move on the Y axis
context.translate(0, distance);

// now we are at the center X&Y of our arc segment
// inverse current rotation
context.rotate(-rotationAngle);
// draw the image
context.drawImage(format.image, -iconWidth / 2, -iconHeight / 2, iconWidth, iconHeight);

(function($) {
  $(window).ready(setupPieChart);

  function setupPieChart() {

    // Instead of storing only the images' src, store directly the HTMLImg elements.
    // Will avoid huge amount of useless loadings
    let proportions = [{
        proportion: 45,
        format: {
          image: Object.assign(new Image, {
            src: "https://cdn4.iconfinder.com/data/icons/seo-web-15/465/web-user-interface_49-128.png"
          })
        }
      },
      {
        proportion: 30,
        format: {
          image: Object.assign(new Image, {
            src: "https://cdn3.iconfinder.com/data/icons/picons-social/57/46-facebook-128.png"
          })
        }
      },
      {
        proportion: 25,
        format: {
          image: Object.assign(new Image, {
            src: "https://cdn3.iconfinder.com/data/icons/picons-social/57/43-twitter-128.png"
          })
        }
      }
    ];

    let setup = {
      canvas: document.getElementById('piechart'),
      radius: 0.9,
      collapsing: false,
      proportions: proportions,
      drawSegment: drawSegmentOutlineOnly,
      onchange: onPieChartChange,
      minAngle: 1.575
    };

    let newPie = new DraggablePiechart(setup);

    // initial drawing function for pie chart
    function drawSegmentOutlineOnly(context, piechart, centerX, centerY, radius, startingAngle, arcSize, format, collapsed) {
      if (collapsed) {
        return;
      }

      // because the library needs it for anchors...
      context.save();

      // reset context's matrix
      context.setTransform(1, 0, 0, 1, 0, 0);
      // Draw segment
      let endingAngle = startingAngle + arcSize;
      context.beginPath();
      context.moveTo(centerX, centerY);
      context.arc(centerX, centerY, radius, startingAngle, endingAngle, false);
      context.closePath();

      context.fillStyle = '#666';
      context.fill();
      context.stroke();

      // Draw image
      // you've got starting angle for arc() method, which itself starts at 3'oclock, while the matrix is noon based
      // so we subtract half PI from the starting angle and add half arcSize to get to the middle
      let rotationAngle = -Math.PI / 2 + (startingAngle + (arcSize / 2));
      let iconHeight = Math.floor(context.canvas.height / 5);
      let iconWidth = Math.floor(context.canvas.width / 5);
      // distance from center
      let distance = (radius / 2);
      // position our pointer to the center of the canvas
      context.translate(centerX, centerY);
      // rotate it so we face the middle of the arc
      context.rotate(rotationAngle);
      // move on the Y axis
      context.translate(0, distance);

      // now we are at the center X&Y of our arc segment
      // inverse current rotation
      context.rotate(-rotationAngle);
      // draw the image
      context.drawImage(format.image, -iconWidth / 2, -iconHeight / 2, iconWidth, iconHeight);

      // because the library needs it for anchors...
      context.restore();
    }

    // update the percentages when the pieces are adjusted
    function onPieChartChange(piechart) {

      let percentages = piechart.getAllSliceSizePercentages();
      let percentLabels = $(".mixPercentage");

      for (let i = 0; i < percentages.length; i++) {
        percentLabels.eq(i).html(percentages[i].toFixed(0) + "%");
      }

    }

  }
})(jQuery);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://rawgit.com/jamesalvarez/draggable-piechart/master/draggable-piechart.js?ver=4.9"></script>
<div id="piechart-controls">
  <canvas id="piechart" width="400" height="400">Your browser is too old!</canvas>
  <div>
    <div class="percentWrapper" style="display: inline-block;">
      <img src="https://cdn4.iconfinder.com/data/icons/seo-web-15/465/web-user-interface_49-128.png">
      <span class="mixPercentage">33%</span>
    </div>
    <div class="percentWrapper" style="display: inline-block;">
      <img src="https://cdn3.iconfinder.com/data/icons/picons-social/57/46-facebook-128.png">
      <span class="mixPercentage">33%</span>
    </div>
    <div class="percentWrapper" style="display: inline-block;">
      <img src="https://cdn3.iconfinder.com/data/icons/picons-social/57/43-twitter-128.png">
      <span class="mixPercentage">34%</span>
    </div>
  </div>
</div>