Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
238 views
in Technique[技术] by (71.8m points)

javascript - d3.js chemical tube bar chart

enter image description here

I am interested in creating this liquid bar chart of sorts. With the pointers/dotted markers pulling off to the side like shown.

22 April - latest code to work with dynamic data http://jsfiddle.net/NYEaX/1855/


latest bar chart code http://jsfiddle.net/NYEaX/1827/

latest cleaned up water wave code http://jsfiddle.net/Qh9X5/10331/


//I want to strip down this waterwave code http://jsfiddle.net/Qh9X5/10091/

//progress chart http://jsfiddle.net/NYEaX/1740/


latest base code for this bar chart. http://jsfiddle.net/NYEaX/1822/

var $this = $("#checmicalbars");


var data = [{
  "label": "Rendering",
  "value": 90,      
  "startcolor": "#c3da54",
  "endcolor": "#c1e500"
},
{
  "label": "Character Design",
  "value": 95,      
  "startcolor": "#e94adc",
  "endcolor": "#aae3dd"
},
{
  "label": "Sketching",
  "value": 80,      
  "startcolor": "#c3da54",
  "endcolor": "#fa5283"
},
{
  "label": "Story Boarding",
  "value": 90,      
  "startcolor": "#e94adc",
  "endcolor": "#f83b03"
},
{
  "label": "Drawing",
  "value": 82,      
  "startcolor": "#c3da54",
  "endcolor": "#f88504"
},
{
  "label": "Painting",
  "value": 90,      
  "startcolor": "#e94adc",
  "endcolor": "#f7d200"
}];


var h = 150;
var w = 300;

    var options = {
  minlimit: 0,
  maxlimit: 100
}


// setup scales
var x = d3.scale.ordinal()
  .rangeRoundBands([0, w], .1);

var y = d3.scale.linear()
  .range([h, 0]);

var xAxis = d3.svg.axis()
  .scale(this.x)
  .orient("bottom");

var yAxis = d3.svg.axis()
  .scale(this.y)
  .orient("left");
// setup scales


// chart container
var progresschart = d3.select($this[0]).append("svg")
  .attr("width", w)
  .attr("height", h)
  .append("g")
  .attr("transform", "translate(0,5)");

var barrectsholder = progresschart.append("g")
  .attr("class", "chart")
  .attr("transform", "translate(15,0)");
// chart container


y.domain([options["minlimit"], options["maxlimit"]]);


//__ bars
var bar = barrectsholder.selectAll("rect")
  .data(data);

//__ enter
bar.enter()
  .append("rect")
  .attr("class", "bar")
  .attr("y", h);

//__ update   
bar
  .attr("y", h)
  .attr("height", 0)
  .style("fill", function(d){
    return d.startcolor;
  })
  .transition()
  .duration(2500)
  .style("fill", function(d){
    return d.endcolor;
  })
  .attr("width", 20)
  .attr("x", function(d, i) {
    return 30 * i;
  })
  .attr("y", function(d) {
    return y(d.value);
  })
  .attr("height", function(d) {
    return h - y(d.value);
  })

//__ exit
bar.exit()
  .transition()
  .duration(250)
  .attr("y", 0)
  .attr("height", 0)
  .remove();
//__ bars
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I've merged the two charts together - but the watercode is translated correctly if its a separate svg - be good to get this code cleaned up/reviewed. Also ensuring the pointers/labels adjust/adapt with more/less data sets.

enter image description here

latest jsfiddle http://jsfiddle.net/NYEaX/1843/

    var $this = $("#checmicalbars");


    var data = [{
      "label": "Rendering",
      "value": 90,      
      "startcolor": "#c3da54",
      "endcolor": "#c1e500"
    },
    {
      "label": "Character Design",
      "value": 95,      
      "startcolor": "#e94adc",
      "endcolor": "#aae3dd"
    },
    {
      "label": "Sketching",
      "value": 80,      
      "startcolor": "#c3da54",
      "endcolor": "#fa5283"
    },
    {
      "label": "Story Boarding",
      "value": 90,      
      "startcolor": "#e94adc",
      "endcolor": "#f83b03"
    },
    {
      "label": "Drawing",
      "value": 82,      
      "startcolor": "#c3da54",
      "endcolor": "#f88504"
    },
    {
      "label": "Painting",
      "value": 90,      
      "startcolor": "#e94adc",
      "endcolor": "#f7d200"
    }];


    var h = 450;
    var w = 400;

    var barHeight = 150;
    var barWidth = 180;

        var options = {
      minlimit: 0,
      maxlimit: 100
    }


    // setup scales
    var x = d3.scale.ordinal()
      .rangeRoundBands([0, barWidth], .1);

    var y = d3.scale.linear()
      .range([barHeight, 0]);

    var xAxis = d3.svg.axis()
      .scale(this.x)
      .orient("bottom");

    var yAxis = d3.svg.axis()
      .scale(this.y)
      .orient("left");
    // setup scales


    // chart container
    var progresschart = d3.select($this[0]).append("svg")
      .attr("width", w)
      .attr("height", h)
      .append("g")
      .attr("transform", "translate(0,5)");

    var barrectsholder = progresschart.append("g")
      .attr("class", "barrectsholder")
      .attr("transform", "translate(15,0)");

        var labelsholder = progresschart.append("g")
      .attr("class", "labelsholder")
      .attr("transform", "translate(10,"+(barHeight+ 20)+")");      

      var lineholder = progresschart.append("g")
      .attr("class", "lineholder")
      .attr("transform", "translate(25,"+(barHeight+ 15)+")");
    // chart container


    y.domain([options["minlimit"], options["maxlimit"]]);





/*
  var gauge = barrectsholder
    .append("g")
    .attr("width", config.w)
    .attr("height", config.h)
    .append("g");

  liquidBar(gauge, config);
*/

var bar = barrectsholder.selectAll("svg")
      .data(data);

 bar.enter()
       .append("svg")
       .attr("class", function(d, i) {
        return "bar"+i;
         })
      .attr("width", 20)
      .attr("x", function(d, i) {
        return 30 * i;
      })
      .attr("y", function(d) {
        return y(d.value);
      })
      .attr("height", function(d) {
        return barHeight - y(d.value);
      })


$.each(data, function( index, value ) {
  //alert( index + ": " + value );


  var config = {
    w: 20,
    h: barHeight,
    value: value.value,
    amplitude: 0.02, // The wave height as a percentage of the radius of the wave circle.
    countPerWidth: 1, // The number of full waves per width of the wave circle.
    riseTime: 1000, // The amount of time in milliseconds for the wave to rise from 0 to it's final height.
    animateTime: 1000, // The amount of time in milliseconds for a full wave to enter the wave circle.
    rise: true, // Control if the wave should rise from 0 to it's full height, or start at it's full height. 
    colorTransition: 1000,
    colorBefore: value.startcolor, // The color before of the fill wave.
    colorAfter: value.endcolor, // The color after of the fill wave.
    offset: 0 // The amount to initially offset the wave. 0 = no offset. 1 = offset of one full wave.
  };

  var gauge = barrectsholder.selectAll("svg.bar"+index)
   liquidBar(gauge, config);

});      


/*
    //__ bars
    var bar = barrectsholder.selectAll("rect")
      .data(data);

    //__ enter
    bar.enter()
      .append("rect")
      .attr("class", "bar")
      .attr("y", barHeight);

    //__ update   
    bar
      .attr("y", barHeight)
      .attr("height", 0)
      .style("fill", function(d){
        return d.startcolor;
      })
      .transition()
      .duration(2500)
      .style("fill", function(d){
        return d.endcolor;
      })
      .attr("width", 20)
      .attr("x", function(d, i) {
        return 30 * i;
      })
      .attr("y", function(d) {
        return y(d.value);
      })
      .attr("height", function(d) {
        return barHeight - y(d.value);
      })

    //__ exit
    bar.exit()
      .transition()
      .duration(250)
      .attr("y", 0)
      .attr("height", 0)
      .remove();
    //__ bars

*/


    //__ labels
    var labels = labelsholder.selectAll("text")
      .data(data);

    labels.enter()
      .append("text")
      .attr("class", "barlabels")
      .attr("x", 200)
      .attr("y", function(d, i) {
        return 20 * i;
      })  
      .text(function(d) {
        return d.label; 
      })


 var lines = lineholder.selectAll("text")
      .data(data);

  lines.enter()
    .append("line")// attach a line
    .style("stroke-dasharray", ("3, 3"))    
    .style("stroke", "black")// colour the line
    .attr("x1", function(d, i) {
      return barWidth-(30 * (i+1));
    })//x pos of the 1st end of the line
    .attr("y1", function(d, i) {
      return 20 * i;
    })//y pos of the 1st end of the line
    .attr("x2", function(d, i) {

      return barWidth;
    })//x pos of the 2nd end of the line
    .attr("y2", function(d, i) {
      return 20 * i;
    });//y pos of the 2nd end of the line


    var lineHeights = 100;

  lines.enter()
    .append("line")// attach a line
    .style("stroke-dasharray", ("3, 3"))    
    .style("stroke", "black")// colour the line
    .attr("x1", function(d, i) {
      return 30 * i;
    })//x pos of the 1st end of the line
    .attr("y1", function(d, i) {
      return lineHeights - (20 * i);
    })//y pos of the 1st end of the line
    .attr("x2", function(d, i) {
      return 30 * i;
    })//x pos of the 2nd end of the line
    .attr("y2", function(d, i) {
      return -15;
    });//y pos of the 2nd end of the line









  function liquidBar(gauge, config) {

    var fillPercent = Math.max(0, Math.min(100, config.value)) / 100;

    var waveHeightScale = d3.scale.linear()
      .range([0, config.amplitude, 0])
      .domain([0, 50, 100]);

    var waveHeight = (config.h / 2) * waveHeightScale(fillPercent * 100);
    var waveLength = config.w / config.countPerWidth;
    var waveClipCount = 1 + config.countPerWidth;
    var waveClipWidth = waveLength * waveClipCount;

    // 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 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([(config.h + waveHeight), (waveHeight)])
      .domain([0, 1]);
    var waveAnimateScale = d3.scale.linear()
      .range([0, waveClipWidth - config.w]) // Push the clip area one full wave then snap back.
      .domain([0, 1]);


    // Center the gauge within the parent SVG.
    var gaugeGroup = gauge.append("g")
      .attr("class", "gaugeGroup")
      .attr("transform", "translate(0,0)");


    var randomId = Math.floor(Math.random() * 26) + Date.now();

    // 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.offset * -1 + Math.PI * 2 * (1 - config.countPerWidth) + d.y * 2 * Math.PI));
      })
      .y1(function(d) {
        return (config.h + waveHeight);
      });

    var waveGroup = gaugeGroup.append("defs")
      .append("clipPath")
      .attr("id", "clipWave" + randomId);

    var wave = waveGroup.append("path")
      .datum(data)
      .attr("d", clipArea)
      .attr("T", 0);

    // The inner circle with the clipping wave attached.
    var fillGroup = gaugeGroup.append("g")
      .attr("clip-path", "url(#clipWave" + randomId + ")");

    fillGroup.append("rect")
      .attr("x", 0)
      .attr("y", 0)
      .attr("width", config.w)
      .attr("height", config.h)
      .style("fill", config.colorBefore)
      .transition()
      .duration(config.colorTransition)
      .style("fill", config.colorAfter);


    // Make the wave rise. wave and waveGroup are separate so that horizontal and vertical movement can be controlled independently.
    var waveGroupXPosition = config.w - waveClipWidth;
    if (config.rise) {
      waveGroup.attr('transform', 'translate(' + waveGroupXPosition + ',' + waveRiseScale(0) + ')')
        .transition()
        .duration(config.riseTime)
        .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) + ')');
    }


        animateWave();

    function animateWave() {
      wave.attr('transform', 'translate(' + waveAnimateScale(wave.attr('T')) + ',0)');
      wave.transition()
        .duration(config.animateTime * (1 - wave.attr('T')))
        .ease('linear')
        .attr('transform', 'translate(' + waveAnimateScale(1) + ',0)')
        .attr('T', 1)
        .each('end', function() {
          wave.attr('T', 0);
          animateWave(config.animateTime);
        });
    }

  }

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...