鸡尾酒配方数据可视化

3

我对数据可视化很感兴趣,最近开始熟悉JavaScript的D3库。我发现了这个不同鸡尾酒配方的可视化:Information is beautiful - Cocktails

我想知道这是如何完成的?有没有可能用D3js来完成这个任务,或者说这基本上是一项高度手动的任务?

为了将鸡尾酒中成分的相对数量进行可视化,可以使用堆栈布局。然而,我不知道该如何处理不同形状的鸡尾酒杯。

2个回答

4

那么,我们放下是否在d3中这样做是个好主意的讨论(这样有什么乐趣呢?),并且讨论如何在d3中实现这一点。下面的代码借用了这个SVG图像,然后应用了渐变填充来显示鸡尾酒的各个部分。最后,您可以使用一些标签将其包装起来...

<!DOCTYPE html>
<html>

<head>
  <script data-require="d3@3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
  <style>
    svg{
      font: 12px sans-serif;
    }
  </style>
</head>

<body>
  <script>
  
    // our cocktail glass
    var glassPath = "m 60,350 c -4.74218,-0.7989 -7.33654,-2.5714 -7.33654,-5.0124 0,-2.4848 2.03654,-4.0776 5.21357,-4.0776 1.54746,0 3.59496,-0.4138 4.55,-0.9197 1.97894,-1.0481 9.10335,-3.4326 24.23643,-8.1118 5.775,-1.7856 11.4,-3.6018 12.5,-4.0358 1.1,-0.434 4.3253,-1.3074 7.16733,-1.9408 5.71266,-1.2732 7.4813,-2.8015 9.21631,-7.9635 0.64176,-1.9094 2.38458,-4.7427 3.87294,-6.2962 l 2.70609,-2.8245 -0.2841,-27.7039 c -0.15625,-15.2371 -0.68535,-33.7788 -1.17577,-41.2038 -0.49042,-7.425 -1.20657,-21.825 -1.59145,-32 -0.94914,-25.0923 -1.70127,-27.1843 -15.45994,-43 -3.11003,-3.575 -7.5473,-9.326 -9.8606,-12.7799 -4.25363,-6.3511 -14.06748,-25.7631 -14.0826,-27.8558 -0.005,-0.6246 -0.68044,-2.4246 -1.50205,-4 -0.82161,-1.57534 -1.49661,-3.90636 -1.5,-5.18002 -0.003,-1.27366 -0.46743,-3.17763 -1.0312,-4.23105 -0.56377,-1.05341 -1.45572,-4.96133 -1.98212,-8.68426 -0.88933,-6.28986 -2.39223,-14.81377 -3.51606,-19.94188 -0.65359,-2.98237 -5.07693,-8.04956 -9.42012,-10.7913 -3.31741,-2.09419 -6.56863,-3.11638 -17.55666,-5.51985 -13.14833,-2.87601 -26.01544,-13.25971 -31.54627,-25.45769 -3.44329,-7.59403 -6.63083,-22.12177 -6.86935,-31.30823 l -0.0844,-3.25 131,0 131,0 -0.0844,3.25 c -0.23852,9.18646 -3.42606,23.7142 -6.86935,31.30823 -5.53083,12.19798 -18.39794,22.58168 -31.54627,25.45769 -10.98803,2.40347 -14.23925,3.42566 -17.55666,5.51985 -4.34319,2.74174 -8.76653,7.80893 -9.42012,10.7913 -1.12383,5.12811 -2.62673,13.65202 -3.51606,19.94188 -0.5264,3.72293 -1.41835,7.63085 -1.98212,8.68426 -0.56377,1.05342 -1.02781,2.95739 -1.0312,4.23105 -0.003,1.27366 -0.67839,3.60468 -1.5,5.18002 -0.82161,1.5754 -1.49753,3.3754 -1.50205,4 -0.0151,2.0927 -9.82897,21.5047 -14.0826,27.8558 -2.3133,3.4539 -6.75057,9.2049 -9.8606,12.7799 -13.75867,15.8157 -14.5108,17.9077 -15.45994,43 -0.38488,10.175 -1.10103,24.575 -1.59145,32 -0.49042,7.425 -1.01952,25.9667 -1.17577,41.2038 l -0.2841,27.7039 2.70609,2.8245 c 1.48836,1.5535 3.23118,4.3868 3.87294,6.2962 1.73501,5.162 3.50365,6.6903 9.21631,7.9635 2.84203,0.6334 6.06733,1.5068 7.16733,1.9408 1.1,0.434 6.725,2.2502 12.5,4.0358 15.13308,4.6792 22.25749,7.0637 24.23643,8.1118 0.95504,0.5059 3.00254,0.9197 4.55,0.9197 5.51258,0 7.05386,4.7477 2.50848,7.727 -2.52335,1.6539 -7.71319,1.7828 -77.25,1.9187 -40.9997,0.08 -76.41846,-0.17 -78.70837,-0.5557 z"
    
    // some drinks to show
    var data = [
      {
        drink: "Angel Face",
        parts: [
          { 
            unit: 3,
            name: "Calvados"
          },{ 
            unit: 3,
            name: "Apricot Brandy"
          },{ 
            unit: 3,
            name: "Gin"
          }
        ]
      }, {
        drink: "Aviation",
        parts: [
          { 
            unit: 1.5,
            name: "Maraschino"
          },{ 
            unit: 1.5,
            name: "Lemon Juice"
          },{ 
            unit: 4.5,
            name: "Gin"
          }
        ]
      }
    ];
    
    // 47 percent of our glass is where the liquid is
    var colorPercent = 47,
      // 3 percent is the empty spot on top
      // 50 percent is the stem
      startPercent = 50 - colorPercent,
      // width and height of the glass
      drinkWidth = 265,
      drinkHeight = 350,
      colors = d3.scale.category10();

    // calculate percentages...
    data.forEach(function(d0){
      var totPercent = startPercent,
          total = d3.sum(d0.parts, function(d1){ return d1.unit; });
      d0.gradient = [];
      d0.parts.forEach(function(d1){
        d1.startPercent = totPercent;
        d0.gradient.push({
          percent: totPercent,
          color: colors(d1.name)
        });
        totPercent += ((d1.unit / total) * colorPercent);
        d1.endPercent = totPercent;
        d0.gradient.push({
          percent: totPercent,
          color: colors(d1.name)
        });
      });
    });
    
    var svg = d3.select('body')
      .append('svg')
      .attr('width', (drinkWidth * data.length) + 5)
      .attr('height', drinkHeight + 20);
      

    // a g for each glass;
    var glass = svg.selectAll('.drink')
      .data(data)
      .enter()
      .append('g')
      .attr('class', 'drink')
      .attr('transform', function(d,i){
        return 'translate(' + (drinkWidth * i) + ',0)';
      })
    
    // the glass
    glass
      .append('path')
      .attr('d', glassPath)
      .style('stroke', 'black')
      .style('fill', function(d,i){
        return 'url(#grad' + i + ')';
      });
      
    // text labels of drink
    glass
      .append("text")
      .attr("x", drinkWidth / 2)
      .attr("y", drinkHeight)
      .text(function(d){
        return d.drink;
      })
      .attr("dy", "1em")
      .style("text-anchor", "middle")
      .style("font-size", "16");
      
    // text labels of drink parts
    glass.selectAll('.label')
      .data(function(d){
        return d.parts;
      })
      .enter()
      .append('text')
      .attr('class', 'label')
      .text(function(d){
        return d.unit + " " + d.name;
      })
      .style("fill", "black")
      .attr("x", drinkWidth / 2)
      .attr("y", function(d){
        return (((d.startPercent + d.endPercent) / 2) / 100) * drinkHeight;
      })
      .attr("dy", "1em")
      .style("text-anchor", "middle");
      
    // our gradients
    var grad = svg.append('defs')
      .selectAll('linearGradient')
      .data(data)
      .enter()
      .append('linearGradient')
      .attr('id', function(d,i){
        return "grad" + i;
      })
      .attr('x1', '0%')
      .attr('x2', '0%')
      .attr('y1', '0%')
      .attr('y2', '100%');
      
    // no liquid top of glass
    grad.append("stop")
      .attr("offset", "0%")
      .style("stop-color", "white");
      
    grad.append("stop")
      .attr("offset", startPercent + "%")
      .style("stop-color", "white");
    
    var e = grad.selectAll('.color')
      .data(function(d){
        return d.gradient
      })
      .enter();

    e.append("stop")
      .attr('id', function(d,i){
        return 'stop' + 1;
      })
      .attr("offset", function(d){
        return d.percent + '%';
      })
      .style("stop-color", function(d){
        return d.color;
      });

    // stem of glass
    grad.append("stop")
      .attr("offset", "50%")
      .style("stop-color", "black");
    
    grad.append("stop")
      .attr("offset", "100%")
      .style("stop-color", "black");
    
  </script>
</body>

</html>


2
在D3中做这样的事情就像用智能手机敲钉子一样 - 最终你可以完成任务,但你选择了错误的工具。
就像智能手机一样,D3有很多可能性,但这项任务需要很多视觉细节,而D3并不适合完成。
你知道,图形编辑工具存在是有原因的 - 它们为你提供了创建带有优秀字体、颜色、层等复合图像的可能性。Cocktails信息图表非常适合这样的任务 - 你可以轻松地在画布上添加小薄荷叶、桃子苦味和黑莓。
另一方面,在图形编辑器中制作甚至一个简单的条形图都很困难。我做过这个(别问我为什么),我必须进行一些数学计算才能绘制正确的比例。
每种工作都有相应的工具。重要的是要知道如何选择正确的工具。D3代表数据驱动文档 - 这说明了一切。每当你有数据时,请选择D3。
你可以看出在鸡尾酒信息图表中有数据。确实如此。但我想告诉你关于数据可视化中一个简单但非常重要的概念:信噪比(请查看解释该概念的这篇文章),它有点像你的数据中有多少噪声的质量指标(不要将设计与噪声混淆)。在回答这个问题时,我想出了信号与设计比率©(我没有任何研究支持这一点,所以请容忍我),它告诉你需要多少设计除了数据。在鸡尾酒信息图表中,你有很多设计而不是数据 - 因此D3不适合这项任务。 总之:每项任务都最适合特定的工具来完成。学会选择正确的工具来完成你的任务 - 这至少可以节省你的时间。

我们将您的“信号与设计比率”称为数据墨水比,这是Tufte的一个概念: http://www.infovis-wiki.net/index.php/Data-Ink_Ratio - Gerardo Furtado
我认为Tufte的数据墨水比是“信噪比”,而不是信号设计比。在他的书《定量信息的视觉显示》中,他谈到了展示大量数据的图表和其他可视化工具。在一个二维平面上,他成功地展示了数据的几个特征。我提出“信号设计比”的观点是,有些可视化工具的数据较少,但如果你只展示那些数据(每种成分的百分比),它不会像那些花哨的杯子和酒一样产生同样的效果。 - iulian
嗨,iulian,非常感谢您的回答。我认为您的答案是完全正确的。D3可能不是这个目的的理想工具。特别是当涉及到小细节,如水果碎片等时。因此,我对Mark的答案感到有些惊喜,它真的超出了我对D3能做到的期望。再次感谢您。 - verticoe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接