代码之家  ›  专栏  ›  技术社区  ›  The Old County

d3液态水图表从d3v3到v4的转换

  •  0
  • The Old County  · 技术社区  · 4 年前

    我已经构建了一个d3.js liquid chart v3,它将它与旧演示中使用的插件分离。 http://bl.ocks.org/brattonc/5e5ce9beee483220e2f6

    enter image description here

    d3js—版本3—液体图表 http://jsfiddle.net/dz36kjvc/

    12月20日 http://jsfiddle.net/f975pnta/

      var $this = $('.fillgauge');
    
          var w = $this.data("width");
          var h = $this.data("height");
          var ir = $this.data("innerradius");
          var r = $this.data("radius");
    
    
    
    
      function liquidChart(el, index){
    
    
          var width = $(el).data("width");
          var height = $(el).data("height");
    
          var radius = width/2;
    
            var displayText = $(el).data("display-text");
            var numberCurrent = $(el).data("number-current");
            var numberMax = $(el).data("number-max");
            var displayPercent = $(el).data("display-percent");
    
              var circleColor = $(el).data("circle-color");
              var textColor = $(el).data("text-color");
              var waveTextColor = $(el).data("wave-text-color");
              var waveColor = $(el).data("wave-color");
              var textVertPosition = $(el).data("text-vert-position");
              var textHorzPosition = $(el).data("text-horz-position");
              var waveAnimateTime = $(el).data("wave-animate-time");
              var overlayImage = $(el).data("overlay-image");
              var overlayImageHeight = $(el).data("overlay-image-height");
              var overlayImageWidth = $(el).data("overlay-image-width");
    
              var chartPaddingLeft = $(el).data("chart-padding-left");
              /*off center in case a handle is ever given to a cup*/
    
          var options = {
            "displayPercent": displayPercent,
              "displayText": displayText,
              "circleColor": circleColor,
              "textColor": textColor,
              "waveTextColor":waveTextColor,
              "waveColor":waveColor,
              "circleThickness":0,
              "textVertPosition":textVertPosition,
              "textHorzPosition":textHorzPosition,
              "waveAnimateTime":waveAnimateTime
          }   
    
          var config1 = liquidFillGaugeDefaultSettings();
          config1=  $.extend(config1, options);
    
          //gauge1.update(NewValue());
    
    
        var chart = d3.select(el).append("svg:svg")
            .attr("class", "chart")
            .attr("width", width)
            .attr("height", height)
            .style("padding-left", chartPaddingLeft+"px")
            .attr("id", "fillgauge"+index);
            
    
              var circle = chart.append("circle")
          .attr("cx", width/2)
          .attr("cy", height/2)
          .attr("r", radius)
          .attr("fill", circleColor);
                  
             
          var gauge1= loadLiquidFillGauge("fillgauge"+index, numberCurrent, numberMax, config1);
        
        /*
        var imgs = chart.append("svg:image")
                    .attr("xlink:href", overlayImage)
                    .attr("x", "0")
                    .attr("y", "0")
                    .attr("width", overlayImageHeight)
                    .attr("height", overlayImageWidth);*/
    
          function NewValue(){
              if(Math.random() > .5){
                  return Math.round(Math.random()*100);
              } else {
                  return (Math.random()*100).toFixed(1);
              }
          }
      }
    
    
      liquidChart($this[0], 0);
      
    
    
      /*!
       * @license Open source under BSD 2-clause (http://choosealicense.com/licenses/bsd-2-clause/)
       * Copyright (c) 2015, Curtis Bratton
       * All rights reserved.
       *
       * Liquid Fill Gauge v1.1
       */
      function liquidFillGaugeDefaultSettings(){
          return {
              minValue: 0, // The gauge minimum value.
              maxValue: 100, // The gauge maximum value.
              circleThickness: 0.05, // The outer circle thickness as a percentage of it's radius.
              circleFillGap: 0.05, // The size of the gap between the outer circle and wave circle as a percentage of the outer circles radius.
              circleColor: "#178BCA", // The color of the outer circle.
              waveHeight: 0.05, // The wave height as a percentage of the radius of the wave circle.
              waveCount: 1, // The number of full waves per width of the wave circle.
              waveRiseTime: 1000, // The amount of time in milliseconds for the wave to rise from 0 to it's final height.
              waveAnimateTime: 18000, // The amount of time in milliseconds for a full wave to enter the wave circle.
              waveRise: true, // Control if the wave should rise from 0 to it's full height, or start at it's full height.
              waveHeightScaling: true, // Controls wave size scaling at low and high fill percentages. When true, wave height reaches it's maximum at 50% fill, and minimum at 0% and 100% fill. This helps to prevent the wave from making the wave circle from appear totally full or empty when near it's minimum or maximum fill.
              waveAnimate: true, // Controls if the wave scrolls or is static.
              waveColor: "#178BCA", // The color of the fill wave.
              waveOffset: 0, // The amount to initially offset the wave. 0 = no offset. 1 = offset of one full wave.
              textVertPosition: .5, // The height at which to display the percentage text withing the wave circle. 0 = bottom, 1 = top.
              textSize: 1, // The relative height of the text to display in the wave circle. 1 = 50%
              valueCountUp: true, // If true, the displayed value counts up from 0 to it's final value upon loading. If false, the final value is displayed.
              displayPercent: true, // If true, a % symbol is displayed after the value.
              textColor: "#045681", // The color of the value text when the wave does not overlap it.
              waveTextColor: "#A4DBf8" // The color of the value text when the wave overlaps it.
          };
      }
    
      function loadLiquidFillGauge(elementId, numberCurrent, numberMax, config) {
          if(config == null) config = liquidFillGaugeDefaultSettings();
    
          //value as it is
    
          var value = (numberCurrent/numberMax) * 100;;
    
          var textValue = numberCurrent;
          
          if(config.displayPercent){
            textValue = (numberCurrent/numberMax) * 100;
          }
    
    
          var gauge = d3.select("#" + elementId);
          var radius = Math.min(parseInt(gauge.style("width")), parseInt(gauge.style("height")))/2;
          var locationX = parseInt(gauge.style("width"))/2 - radius;
          var locationY = parseInt(gauge.style("height"))/2 - radius;
          var fillPercent = Math.max(config.minValue, Math.min(config.maxValue, value))/config.maxValue;
    
          var waveHeightScale;
          if(config.waveHeightScaling){
              waveHeightScale = d3.scale.linear()
                  .range([0,config.waveHeight,0])
                  .domain([0,50,100]);
          } else {
              waveHeightScale = d3.scale.linear()
                  .range([config.waveHeight,config.waveHeight])
                  .domain([0,100]);
          }
    
          var textPixels = (config.textSize*radius/2);
          var textFinalValue = parseFloat(textValue).toFixed(2);
          var textStartValue = config.valueCountUp?config.minValue:textFinalValue;
          var percentText = config.displayPercent?"%":"";
          var circleThickness = config.circleThickness * radius;
          var circleFillGap = config.circleFillGap * radius;
          var fillCircleMargin = circleThickness + circleFillGap;
          var fillCircleRadius = radius - fillCircleMargin;
          var waveHeight = fillCircleRadius*waveHeightScale(fillPercent*100);
    
          var waveLength = fillCircleRadius*2/config.waveCount;
          var waveClipCount = 1+config.waveCount;
          var waveClipWidth = waveLength*waveClipCount;
    
          // Rounding functions so that the correct number of decimal places is always displayed as the value counts up.
          var textRounder = function(value){ return Math.round(value); };
          if(parseFloat(textFinalValue) != parseFloat(textRounder(textFinalValue))){
              textRounder = function(value){ return parseFloat(value).toFixed(1); };
          }
          if(parseFloat(textFinalValue) != parseFloat(textRounder(textFinalValue))){
              textRounder = function(value){ return parseFloat(value).toFixed(2); };
          }
    
          // Data for building the clip wave area.
          var data = [];
          for(var i = 0; i <= 40*waveClipCount; i++){
              data.push({x: i/(40*waveClipCount), y: (i/(40))});
          }
    
          // Scales for drawing the outer circle.
          var gaugeCircleX = d3.scale.linear().range([0,2*Math.PI]).domain([0,1]);
          var gaugeCircleY = d3.scale.linear().range([0,radius]).domain([0,radius]);
    
          // Scales for controlling the size of the clipping path.
          var waveScaleX = d3.scale.linear().range([0,waveClipWidth]).domain([0,1]);
          var waveScaleY = d3.scale.linear().range([0,waveHeight]).domain([0,1]);
    
          // Scales for controlling the position of the clipping path.
          var waveRiseScale = d3.scale.linear()
              // The clipping area size is the height of the fill circle + the wave height, so we position the clip wave
              // such that the it will overlap the fill circle at all when at 0%, and will totally cover the fill
              // circle at 100%.
              .range([(fillCircleMargin+fillCircleRadius*2+waveHeight),(fillCircleMargin-waveHeight)])
              .domain([0,1]);
          var waveAnimateScale = d3.scale.linear()
              .range([0, waveClipWidth-fillCircleRadius*2]) // Push the clip area one full wave then snap back.
              .domain([0,1]);
    
          // Scale for controlling the position of the text within the gauge.
          var textRiseScaleY = d3.scale.linear()
              .range([fillCircleMargin+fillCircleRadius*2,(fillCircleMargin+textPixels*0.7)])
              .domain([0,1]);
    
          // Center the gauge within the parent SVG.
          var gaugeGroup = gauge.append("g")
              .attr('transform','translate('+locationX+','+locationY+')');
    
          // Draw the outer circle.
          var gaugeCircleArc = d3.svg.arc()
              .startAngle(gaugeCircleX(0))
              .endAngle(gaugeCircleX(1))
              .outerRadius(gaugeCircleY(radius))
              .innerRadius(gaugeCircleY(radius-circleThickness));
          gaugeGroup.append("path")
              .attr("d", gaugeCircleArc)
              .style("fill", config.circleColor)
              .attr('transform','translate('+radius+','+radius+')');
    
          var textLeftPlacement = parseInt(radius + config.textHorzPosition, 10);
    
          if(config.displayText){
            // Text where the wave does not overlap.
            var text1 = gaugeGroup.append("text")
                .text(textRounder(textStartValue) + percentText)
                .attr("class", "liquidFillGaugeText")
                .attr("text-anchor", "middle")
                .attr("font-size", textPixels + "px")
                .style("fill", config.textColor)
                .attr('transform','translate('+textLeftPlacement+','+textRiseScaleY(config.textVertPosition)+')');
          } 
    
          // The clipping wave area.
          var clipArea = d3.svg.area()
              .x(function(d) { return waveScaleX(d.x); } )
              .y0(function(d) { return waveScaleY(Math.sin(Math.PI*2*config.waveOffset*-1 + Math.PI*2*(1-config.waveCount) + d.y*2*Math.PI));} )
              .y1(function(d) { return (fillCircleRadius*2 + waveHeight); } );
          var waveGroup = gaugeGroup.append("defs")
              .append("clipPath")
              .attr("id", "clipWave" + elementId);
          var wave = waveGroup.append("path")
              .datum(data)
              .attr("d", clipArea)
              .attr("T", 0);
    
          // The inner circle with the clipping wave attached.
          var fillCircleGroup = gaugeGroup.append("g")
              .attr("clip-path", "url(#clipWave" + elementId + ")");
          fillCircleGroup.append("circle")
              .attr("cx", radius)
              .attr("cy", radius)
              .attr("r", fillCircleRadius)
              .style("fill", config.waveColor);
    
    
          if(config.displayText){
            // Text where the wave does overlap.
            var text2 = fillCircleGroup.append("text")
                .text(textRounder(textStartValue) + percentText)
                .attr("class", "liquidFillGaugeText")
                .attr("text-anchor", "middle")
                .attr("font-size", textPixels + "px")
                .style("fill", config.waveTextColor)
                .attr('transform','translate('+textLeftPlacement+','+textRiseScaleY(config.textVertPosition)+')');
          }
    
          // Make the value count up.
          if(config.valueCountUp){
              var textTween = function(){
                  var i = d3.interpolate(this.textContent, textFinalValue);
                  return function(t) { this.textContent = textRounder(i(t)) + percentText; }
              };
              if(config.displayText){
                text1.transition()
                    .duration(config.waveRiseTime)
                    .tween("text", textTween);
                text2.transition()
                    .duration(config.waveRiseTime)
                    .tween("text", textTween);
              }
          }
    
          // Make the wave rise. wave and waveGroup are separate so that horizontal and vertical movement can be controlled independently.
          var waveGroupXPosition = fillCircleMargin+fillCircleRadius*2-waveClipWidth;
          if(config.waveRise){
              waveGroup.attr('transform','translate('+waveGroupXPosition+','+waveRiseScale(0)+')')
                  .transition()
                  .duration(config.waveRiseTime)
                  .attr('transform','translate('+waveGroupXPosition+','+waveRiseScale(fillPercent)+')')
                  .each("start", function(){ wave.attr('transform','translate(1,0)'); }); // This transform is necessary to get the clip wave positioned correctly when waveRise=true and waveAnimate=false. The wave will not position correctly without this, but it's not clear why this is actually necessary.
          } else {
              waveGroup.attr('transform','translate('+waveGroupXPosition+','+waveRiseScale(fillPercent)+')');
          }
    
          if(config.waveAnimate) animateWave();
    
          function animateWave() {
              wave.attr('transform','translate('+waveAnimateScale(wave.attr('T'))+',0)');
              wave.transition()
                  .duration(config.waveAnimateTime * (1-wave.attr('T')))
                  .ease('linear')
                  .attr('transform','translate('+waveAnimateScale(1)+',0)')
                  .attr('T', 1)
                  .each('end', function(){
                      wave.attr('T', 0);
                      animateWave(config.waveAnimateTime);
                  });
          }
    
          function GaugeUpdater(){
              this.update = function(value){
                  var newFinalValue = parseFloat(value).toFixed(2);
                  var textRounderUpdater = function(value){ return Math.round(value); };
                  if(parseFloat(newFinalValue) != parseFloat(textRounderUpdater(newFinalValue))){
                      textRounderUpdater = function(value){ return parseFloat(value).toFixed(1); };
                  }
                  if(parseFloat(newFinalValue) != parseFloat(textRounderUpdater(newFinalValue))){
                      textRounderUpdater = function(value){ return parseFloat(value).toFixed(2); };
                  }
    
                  var textTween = function(){
                      var i = d3.interpolate(this.textContent, parseFloat(value).toFixed(2));
                      return function(t) { this.textContent = textRounderUpdater(i(t)) + percentText; }
                  };
    
                  text1.transition()
                      .duration(config.waveRiseTime)
                      .tween("text", textTween);
                  text2.transition()
                      .duration(config.waveRiseTime)
                      .tween("text", textTween);
    
                  var fillPercent = Math.max(config.minValue, Math.min(config.maxValue, value))/config.maxValue;
                  var waveHeight = fillCircleRadius*waveHeightScale(fillPercent*100);
                  var waveRiseScale = d3.scale.linear()
                      // The clipping area size is the height of the fill circle + the wave height, so we position the clip wave
                      // such that the it will overlap the fill circle at all when at 0%, and will totally cover the fill
                      // circle at 100%.
                      .range([(fillCircleMargin+fillCircleRadius*2+waveHeight),(fillCircleMargin-waveHeight)])
                      .domain([0,1]);
                  var newHeight = waveRiseScale(fillPercent);
                  var waveScaleX = d3.scale.linear().range([0,waveClipWidth]).domain([0,1]);
                  var waveScaleY = d3.scale.linear().range([0,waveHeight]).domain([0,1]);
                  var newClipArea;
                  if(config.waveHeightScaling){
                      newClipArea = d3.svg.area()
                          .x(function(d) { return waveScaleX(d.x); } )
                          .y0(function(d) { return waveScaleY(Math.sin(Math.PI*2*config.waveOffset*-1 + Math.PI*2*(1-config.waveCount) + d.y*2*Math.PI));} )
                          .y1(function(d) { return (fillCircleRadius*2 + waveHeight); } );
                  } else {
                      newClipArea = clipArea;
                  }
    
                  var newWavePosition = config.waveAnimate?waveAnimateScale(1):0;
                  wave.transition()
                      .duration(0)
                      .transition()
                      .duration(config.waveAnimate?(config.waveAnimateTime * (1-wave.attr('T'))):(config.waveRiseTime))
                      .ease('linear')
                      .attr('d', newClipArea)
                      .attr('transform','translate('+newWavePosition+',0)')
                      .attr('T','1')
                      .each("end", function(){
                          if(config.waveAnimate){
                              wave.attr('transform','translate('+waveAnimateScale(0)+',0)');
                              animateWave(config.waveAnimateTime);
                          }
                      });
                  waveGroup.transition()
                      .duration(config.waveRiseTime)
                      .attr('transform','translate('+waveGroupXPosition+','+newHeight+')')
              }
          }
    
          return new GaugeUpdater();
      }
    
     
      <div class="fillgauge" 
              data-role="fillgauge" 
              data-height="155"
              data-width="155"
              data-chart-padding-left="0"
              data-display-percent=false
              data-display-text=false
              data-number-current=20
              data-number-max=70
              data-circle-color="grey" 
              data-text-color="black" 
              data-wave-text-color="black" 
              data-wave-color="gold" 
              data-text-vert-position=1
              data-text-horz-position=20
              data-wave-animate-time=1000
              data-overlay-image="./beer.png"
              data-overlay-image-height="155"
              data-overlay-image-width="155"
          ></div>
    
    
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
    <script src="https://d3js.org/d3.v3.min.js"></script>

    有一个版本4的作品-但我觉得它错过了很多功能版本3。我们可以从旧的代码库中导入其他属性-还有很多其他的东西,比如改变水的颜色、速度、持续时间,其中一些在配置对象中被重写-我们还可以将变量名从alpha0、beta0改为更容易理解的部分-这是否只创建了一个wave对象?

    所以开始的时候,水是紫色的,然后变成蓝色,但是也从0上升到它需要的地方,就像温度计一样,开始的时候,而不是波浪刚开始的时候,它有相同的部分,我认为它上升的动画可能需要一些放松或者其他的东西,它只是向上分流,如果它的水和它有一些重量的话对它-它会冲进更顺利?我也认为一些持续时间应该是毫秒,例如动画时间=1000--这100000个常数代表什么10万吨

    我仍然感觉到它缺少了一些旧的功能--“将这个旧的水图从v3升级到4--清理代码库--这样它就不那么依赖旧的函数代码--真正地将它改编成一些灵活的东西--所以生成一个矩形或圆形的shell--清理各种数据属性--以前使用的defaultConfig对象有点混乱至于什么应该是可编辑的或固定的。创建一个工作示例,详细说明如何创建/更改波浪-”

    “-将变量重命名为更容易理解的单词-例如A0-高度,Alpha2-水波高度等。如果是这样的话,版本3的代码很高,有很多不同的配置部分-并且使用了更多毫秒的持续时间设置-所以我很想继承它的属性/自定义,但是让它在v4上工作-甚至准备好让它可以选择多个水波在彼此后面,使它看起来像一个油圈-不同的油-颜色/粘度等…“

    --

    d3——版本4——液体图表 http://jsfiddle.net/yrkcpgj9

    $(document).ready(function() {
      let width = 480;
      const $this = $('#demo_container');
      let container = d3.select($this[0]);
      let svg = container.select('svg');
      let xmin = -10;
      let xmax = 10;
      let dx = 1;
      /**
       * Height of Container decided based on ymin and ymax values
       * Container Height = (startymax - startymin) * 24px
       * So, if startymin = 0 and startymax = 20 then Container Height will be 480px
       * 
       * Other thing ymin and ymax define are the height of full and empty part of container
       * startymax = 20 means Container is total empty
       * startymin = 0 means Container has no fluid
       *
       * if, startymin = -16 means Container is 80% full
       * startymax = 4 means Container is 20% empty
       */
      let startymin = ymin = 0; // Will animate to -16
      let startymax = ymax = 20; // Will animate to 4
      /**
       * Let's we want to animate from ymin 0 to -16 means fluid height from 0% to 80%
       * and ymax 20 to 4 means 100% empty to 20% empty container
       *
       * Total duration for this animation will be 10 Seconds
       */
      let endymin = -16;
      let endymax = 4;
      let duration = 10; // Seconds
      
      // How much fluid will change per millisecond
      let changePerMillisecond = ((startymin - endymin) / (1000 * duration));
      
      /**
       * Color Scheme
       * Start RGB - 800080 - Purple
       * End RGB - 0000FF - Blue
       *
       * So, R will go from 128 to 256
       * and B will go from 128 to 0
       */
      let startR = R = 128;
      let startB = B = 128;
      let color = `#${R.toString(16)}00${B.toString(16)}`;
      
      let colorChangePerMillisecond = ((startR - 0) / (1000 * duration));
      
      let dy = 1;
      let aspect = (ymax - ymin) / (xmax - xmin);
    
      let grid = d3
        .range(ymin, dy, dy)
        .map(y => d3.range(xmin - dx, xmax + 2 * dx, dx).map(x => [x, y]));
      grid = grid.reduce(function(accumulated, currentValue) {
        return accumulated.concat(currentValue);
      }, []);
    
      var HEIGHT_OF_WAVE = 0.2; // Height
      var SPACE_BW_WAVES = 0.42; // Space between waves
      var WAVE_SPEED = 6; // Speed
    
      let t0 = 0;
      let pts = grid.map(xy => p(HEIGHT_OF_WAVE, SPACE_BW_WAVES, WAVE_SPEED, xy[0], xy[1], t0));
      let outline = pts.slice(pts.length - (xmax - xmin + 3));
      outline.push([xmax, ymax]);
      outline.push([xmin, ymin]);
    
      let xScale, yScale, rScale, pts_to_path;
      function setup() {
        svg.selectAll('*').remove();
        let height = aspect * width;
        svg.attr('height', height);
        xScale = d3
          .scaleLinear()
          .domain([xmin, xmax])
          .range([0, width]);
        rScale = d3
          .scaleLinear()
          .domain([0, xmax - xmin])
          .range([0, width]);
        yScale = d3
          .scaleLinear()
          .domain([ymin, ymax])
          .range([height, 0]);
        pts_to_path = d3
          .line()
          .x(function(d) {
            return xScale(d[0]);
          })
          .y(function(d) {
            return yScale(d[1]);
          });
    
        svg
          .append("path")
          .attr('d', pts_to_path(outline))
          .attr('fill', color);
      }
    
      function start(t) {
        let changed = false;
    
        if (ymin >= -16) {
          ymin = startymin - (t * changePerMillisecond);
          changed = true;
        }
    
        if (ymax >= 4) {
          ymax = startymax - (t * changePerMillisecond);
          changed = true;
        }
        
        if (R > 1) {
          R = parseInt(startR - (t * colorChangePerMillisecond));
        }
        
        if (B < 254) {
          B = parseInt(startB + (t * colorChangePerMillisecond));
        }
        
        color = `#${R > 15 ? R.toString(16) : `0${R.toString(16)}`}00${B.toString(16)}`;
        
        pts = grid.map(xy => p(HEIGHT_OF_WAVE, SPACE_BW_WAVES, WAVE_SPEED, xy[0], xy[1], t / 1000));
        outline = pts.slice(grid.length - (xmax - xmin + 3));
        outline.push([xmax, ymin]);
        outline.push([xmin, ymin]);
        
        if (changed) {
          setup();
        }
        
        svg
          .selectAll("path")
          .transition()
          .duration(0)
          .attr('d', pts_to_path(outline));
        svg
          .selectAll("circle")
          .data(pts)
          .transition()
          .duration(0)
          .attr("cx", function(d) {
            return xScale(d[0]);
          })
          .attr("cy", function(d) {
            return yScale(d[1]);
          });
        
      }
    
      function p(A, alpha, beta, x0, y0, t) {
        let r = A * Math.exp(alpha * y0);
        let arg = alpha * x0 - beta * t;
        let x = x0 + r * Math.cos(arg);
        let y = y0 + r * Math.sin(arg);
        return [x, y];
      }
    
      setup();
      let timer = d3.timer(start);
    });
    div.demo_container {
      background-color: #f1f1f1;
    }
    div.svg_container {
      width: 96%;
      margin: 2%;
    }
    svg.demo {
      background-color: white;
      border: solid 1px lightslategray;
    }
    <div id="demo_container" class="demo_container">
      <div id="water" class="svg_container">
        <svg id="water" class="demo" width=480></svg>
      </div>
    </div>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
    <script src="https://d3js.org/d3.v4.min.js"></script>
    0 回复  |  直到 4 年前