d3js 折线图+柱图

 

<!DOCTYPE html>
<html>
<body>
<div id="vis"><svg></svg></div>
<div id="text"></div>

<style>
  div.CCMixed-tooltip {
    border-radius: 5px;
    visibility:hidden;
    background: rgba(255,255,255,0.9);
    position: absolute;
    padding: 8px;
    box-shadow: 0px 0px 5px #888888;
    font-family: Arial, serif;
    font-size: 12px;
    color: #777;
  }

  .CCMixed-axis, .legend {
    font-family: Arial, serif;
    font-size: 12px;
    fill: #777;
  }

  .CCMixed-axis path,
  .CCMixed-axis line {
    fill: none;
    stroke: #DDD;
    stroke-width: 2;
  }
</style>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://code.jquery.com/jquery-3.1.1.min.js"></script>
<script>

  var CCMixedChart = {}; 

  CCMixedChart.draw = function(elem, config){
    var colorFunction = config.colorFunction;

    var canvas = d3.select("#" + elem);
    canvas.select("svg").selectAll("*").remove();
    var margin = {top: 20, right: 40, bottom: 40, left: 50};
    var width = +config.width - margin.left - margin.right;
    var height = +config.height - margin.top - margin.bottom - config.addtionalXAxisSpace;

    var svg = canvas.select("svg")
      .attr("width", config.width)
      .attr("height", config.height + config.addtionalXAxisSpace)
      .append("g").attr("class", "canvas")
      .attr("transform", "translate(" +  margin.left + "," + margin.top + ")");

    // draw legends
    if(config.showLegend){
      height = height - drawLegends() * 20 - 10;
    }

    var x = d3.scale.ordinal().rangeRoundBands([0, width], 0.5).domain(config.xAxis);
    var yLeftMax = 0;
    var yRightMax = 0;
    for(var i=0; i<config.series.length; i++){
      config.series[i].visible = true;
      for(var j=0; j<config.series[i].data.length; j++){
        if(config.series[i].yAxis == "left" && yLeftMax < config.series[i].data[j]){
          yLeftMax = config.series[i].data[j];
        }
        if(config.series[i].yAxis == "right" && yRightMax < config.series[i].data[j]){
          yRightMax = config.series[i].data[j];
        }
      }
    }

    var yLeft = d3.scale.linear().range([height, 0]).domain([0, yLeftMax]);
    var yRight = d3.scale.linear().range([height, 0]).domain([0, yRightMax]);

    // draw xAxis
    drawXAxis(x);

    // draw left yAxis
    drawYAxis(yLeft, "left", config.yAxisLeft);
    drawYAxis(yRight, "right", config.yAxisRight);

    // draw charts
    for(var i=0; i<config.series.length; i++){
      var chart = config.series[i];
      if(chart.type == 'bar'){
        drawBarChart(chart);
      } else if(chart.type == 'line'){
        drawLineChart(chart)
      }
    }

    // draw tooltip
    var tooltip = canvas.append("div").attr("class", "CCMixed-tooltip");

    // draw invisible bars for hover/click events
    svg.append("g").attr("class", "bars bars-action").selectAll(".bar-hover")
      .data(config.xAxis)
      .enter().append("rect")
        .attr("class", "bar-hover")
        .attr("x", function(d) { return x(d)-x.rangeBand()/2; })
        .attr("y", 0)
        .attr("width", x.rangeBand()*2)
        .attr("height", height)
        .style("opacity", "0")
        .style("fill", "gold")
        .style("cursor", "pointer")
        .on("mouseover", function(d, i){mouseOver(this, d.replace(/ /g, '_'), i)})
        .on("mouseout", function(d, i){mouseOut(this, d.replace(/ /g, '_'), i)})
        .on("mousemove", function(){updateTooltipPos(d3.event)})
        .on("click", function(d, i){
          if(config.clickFunction){
            config.clickFunction(config, d, i);
          }
          // stop the propagation of event
          d3.event.stopPropagation();
        });

    function drawLegends(){
      var legend = svg.append("g").attr("class", "legends CCMixed-legend");
  
      var lx = 0;
      var ly = 0;
      var rows = 1;
      
      for(var i=0; i<config.series.length; i++){
        var chart = config.series[i];
        if(chart.type == 'bar'){
          drawBarLegend(chart);
        } else if(chart.type == 'line'){
          drawLineLegend(chart)
        }
      }

      var tx = 0;
      if(rows == 1){
        tx = (width - lx) / 2;
      }
      legend.attr("transform", "translate(" + tx + ", "+ (config.height+config.addtionalXAxisSpace-(rows+1)*20) + ")");

      function drawBarLegend(chart){
        var barLegend = legend.append("g")
          .attr("class", "legend legend-"+chart.label.replace(/ /g, '_'))
          .style("cursor", "pointer")
          .attr("transform", getLegendTransfrom(chart))
          .style("fill", colorFunction(chart.colorKey));
        barLegend.append("rect")
          .attr("x",  0)
          .attr("y",  0)
          .attr("width", 12)
          .attr("height", 12);
        barLegend.append("text")
          .attr("x",  10)
          .attr("y",  0)
          .attr("dy", "0.85em")
          .attr("dx", "0.4em")
          .style("text-anchor", "begin")
          .text(chart.label);
        barLegend.on("mouseover", function(){
          svg.select(".bars-"+chart.label.replace(/ /g, '_')).selectAll(".bar")
            .style("stroke","gold").style("stroke-width", 3);
        }).on("mouseout", function(){
          svg.select(".bars-"+chart.label.replace(/ /g, '_')).selectAll(".bar")
            .style("stroke-width", 0);
        }).on("click", function(){
          toggleVisibility(chart);
        });
      }

      function drawLineLegend(chart){
        var lineLegend = legend.append("g")
          .attr("class", "legend legend-"+chart.label.replace(/ /g, '_'))
          .style("cursor", "pointer")
          .style("fill", colorFunction(chart.colorKey))
          .attr("transform", getLegendTransfrom(chart));
        lineLegend.append("circle")
          .attr("r", 4)
          .attr("cx", 6)
          .attr("cy", 6)
        lineLegend.append("line")
          .attr("x1", 0)
          .attr("y1", 6)
          .attr("x2", 12)
          .attr("y2", 6)
          .style("stroke", colorFunction(chart.colorKey))
          .style("stroke-width", 2)
        lineLegend.append("text")
          .attr("x",   10)
          .attr("y",  0)
          .attr("dy", "0.85em")
          .attr("dx", "0.4em")
          .style("text-anchor", "begin")
          .text(chart.label)
        lineLegend.on("mouseover", function(d){
          svg.select(".line-"+chart.label.replace(/ /g, '_'))
            .style("stroke-width", 4);
          svg.select(".circles-"+chart.label.replace(/ /g, '_')).selectAll(".circle")
            .attr("r", 6).style("stroke","gold").style("stroke-width", 2);
        }).on("mouseout", function(d){
          svg.select(".line-"+chart.label.replace(/ /g, '_'))
            .style("stroke-width", 2);
          svg.select(".circles-"+chart.label.replace(/ /g, '_')).selectAll(".circle")
            .attr("r", 4).style("stroke-width", 0);
        }).on("click", function(){
          toggleVisibility(chart);
        });
      }

      function toggleVisibility (chart){
        if(chart.type == "bar"){
          var elem = svg.select(".bars-"+chart.label.replace(/ /g, '_'));
          toggle(elem, chart);
        } else if(chart.type == "line"){
          var elem = svg.select(".line-"+chart.label.replace(/ /g, '_'));
          toggle(elem, chart);
          elem = svg.select(".circles-"+chart.label.replace(/ /g, '_'));
          toggle(elem, chart);
        }

        var showLeft = false;
        var showRight = false;
        for(var i=0; i<config.series.length; i++){
          if(config.series[i].yAxis == "left" && config.series[i].visible){
            showLeft = true;
          }
          if(config.series[i].yAxis == "right" && config.series[i].visible){
            showRight = true;
          }
        }

        if(showLeft){
          svg.select(".axis-left").selectAll(".tick").select("text").style("visibility", "visible");
          svg.select(".axis-left").select(".title").style("visibility", "visible");
        } else {
          svg.select(".axis-left").selectAll(".tick").select("text").style("visibility", "hidden");
          svg.select(".axis-left").select(".title").style("visibility", "hidden");
        }

        if(showRight){
          svg.select(".axis-right").selectAll(".tick").select("text").style("visibility", "visible");
          svg.select(".axis-right").select(".title").style("visibility", "visible");
        } else {
          svg.select(".axis-right").selectAll(".tick").select("text").style("visibility", "hidden");
          svg.select(".axis-right").select(".title").style("visibility", "hidden");
        }
        
        function toggle(elem, chart){
          if(elem.style("visibility") == "visible"){
            elem.style("visibility", "hidden");
            chart.visible = false;
            var legend = svg.select(".legends").select(".legend-"+chart.label.replace(/ /g, '_')).style("fill", "#777");
            if(chart.type == "line"){
              legend.select("line").style("stroke", "#777");
            }
          } else {
            elem.style("visibility", "visible");
            chart.visible = true;
            var legend = svg.select(".legends").select(".legend-"+chart.label.replace(/ /g, '_')).style("fill", colorFunction(chart.colorKey));
            if(chart.type == "line"){
              legend.select("line").style("stroke", colorFunction(chart.colorKey));
            }
          }
        }  
      };

      function getLegendTransfrom(chart){
        var translate = "translate(" +  lx + "," + ly + ")";
        lx = lx + chart.label.length * 5 + 30;
        if(chart.type == "line"){
          lx = lx + 15;
        } else if(chart.type == "bar"){
          lx = lx - 10;
        }
        if(lx + chart.label.length * 5  >= width){
          lx = 0;
          ly = ly + 20;
          rows = rows + 1;
        }
        return translate;
      }

      return rows;
    }

    function drawBarChart(chart){
      var yScale = yLeft;
      if(chart.yAxis == "right"){
        yScale = yRight;
      }

      svg.append("g")
        .attr("class", function(d, i){return "bars bars-"+chart.label.replace(/ /g, '_');})
      .selectAll(".bar")
        .data(chart.data)
        .enter().append("rect")
          .attr("class", function(d, i){return "bar bar-"+config.xAxis[i].replace(/ /g, '_');})
          .attr("x", function(d, i) { return x(config.xAxis[i]); })
          .attr("y", function(d) { return yScale(d); })
          .attr("width", x.rangeBand())
          .attr("height", function(d) { return height - yScale(d); })
          .style("fill", colorFunction(chart.colorKey));
    }

    function drawLineChart(chart){
      var yScale = yLeft;
      if(chart.yAxis == "right"){
        yScale = yRight;
      }

      var line = d3.svg.line()
        .x(function(d, i) {
          return x(config.xAxis[i]) + x.rangeBand()/2;
        })
        .y(function(d) {
          return yScale(d);
        });

      svg.append("g").append("path")
        .style("stroke", colorFunction(chart.colorKey))
        .attr("class", function(d, i){return "line-"+chart.label.replace(/ /g, '_');})
        .style("stroke-width", 2)
        .style("fill", "none")
        .attr("d", line(chart.data));

      svg.append("g")
        .attr("class", function(d, i){return "circles circles-"+chart.label.replace(/ /g, '_');})
      .selectAll(".circle")
        .data(chart.data)
        .enter().append("circle")
        .attr("class", function(d, i){return "circle circle-"+config.xAxis[i].replace(/ /g, '_');})
        .attr("r", 4)
        .style("fill", colorFunction(chart.colorKey))
        .attr("cx", function(d, i){return x(config.xAxis[i]) + x.rangeBand()/2;})
        .attr("cy", function(d){return yScale(d);});

    }

    function mouseOver(elem, d, i){
      d3.select(elem).style("opacity", "0.3");
      svg.select(".axis-x").select(".axis-"+d).style("font-weight","bold").style("font-size","14px");
      svg.selectAll(".circles").select(".circle-"+d).attr("r", 7).style("stroke","gold").style("stroke-width",2);
      svg.selectAll(".bars").select(".bar-"+d).style("stroke","gold").style("stroke-width", 2);

      var html;
      if(config.tooltipFunction){
        html = config.tooltipFunction(config, d, i);
      } else {
        html = getTooltips(d, i);
      }

      tooltip.html(html).style("visibility", "visible");
      
      function getTooltips(d, i){
        var html = d + "<br/>";
        for(var j=0; j<config.series.length; j++){
          html = html + "<b>" + config.series[j].label + "</b>: " + config.series[j].data[i] + "<br/>";
        }
        return html;
      }
    };

    function mouseOut(elem, d, i){
      d3.select(elem).style("opacity", "0");
      svg.select(".axis-x").select(".axis-"+d).style("font-weight","normal").style("font-size","12px");
      svg.selectAll(".circles").select(".circle-"+d).attr("r", 4).style("stroke-width",0);
      svg.selectAll(".bars").select(".bar-"+d).style("stroke-width",0);
      tooltip.style("visibility", "hidden");
    }

    function updateTooltipPos (e){
      tooltip.style("top", (e.pageY + 15) + "px")
        .style("left", (e.pageX + 15) + "px");
    };

    function drawXAxis(x){
      // draw x
      svg.append("g")
        .attr("class", "CCMixed-axis axis-x")
        .attr("transform", "translate(0," + height + ")")
        .call(customXAxis)
      .selectAll("text")
        .data(config.xAxis)
        .attr("class", function(d, i){return "axis-"+config.xAxis[i].replace(/ /g, '_');})
        .style("text-anchor", "end")
        .attr("dy", "0.3em")
        .attr("dx", "-0.7em")
        .attr("transform", "rotate(-45)")
        .text(function(d, i){
          // skip one if too many ticks
          //alert(config.xAxis.length);
          if(config.xAxis.length >= 10 && i % 2 == 1){
            return "";
          } else {
            return d
          }
        });
      function customXAxis(g) {
        var xAxis = d3.svg.axis()
          .scale(x)
          .orient("bottom");
        g.call(xAxis);
        g.select(".domain").remove();
      }
    }

    function drawYAxis(y, orient, setting){
      var axisY = svg.append("g")
        .attr("class", "CCMixed-axis axis-"+orient)
        .style("fill", colorFunction(setting.colorKey))
        .call(customYAxis)
      if(orient == 'right'){
        axisY.attr("transform", "translate(" + width + ", 0)")
      }
      if(setting.title){
        var axisYLabel = axisY.append("text")
          .text(setting.title)
          .attr("class", "title")
        if(orient == "left"){
          axisYLabel.attr("transform", "rotate(-90)")
            .attr("y", -30)
            .attr("x", -1*height/2)
            .attr("dx", setting.title.length * 0.25 + "em")
            .style("text-anchor", "end")
        } else {
          axisYLabel.attr("transform", "rotate(90)")
          .attr("y", -30)
          .attr("x", height/2)
          .attr("dx", -1 * setting.title.length * 0.25 + "em")
          .style("text-anchor", "begin")
        }
      }

      function customYAxis(g) {
        var yAxis = d3.svg.axis()
          .scale(y)
          .orient(orient)
          .tickSize(orient=="left"? -1*width : 0)
          .ticks(config.yAxisTicks);
        g.call(yAxis);
        g.select(".domain").remove();
        g.selectAll(".tick:not(:first-of-type) line").attr("stroke", "#030303");
      }
    }
  };

  var colorFunction1 = function(key){
    return key;
  }

  var clickFunction1 = function(config, d, i){
    var html = d + "<br/>";
    for(var j=0; j<config.series.length; j++){
      html = html + "<b>" + config.series[j].label + "</b>: " + config.series[j].data[i] + "<br/>";
    }
    $("#text").html(html);
  }

  $(document).ready(function(){
    var vis = $("#vis");

    var config = {
      "addtionalXAxisSpace": 0,  // OPTIONAL. if the labels for xAxis is too long can add more space between xAxis and legend
      "width": 600, // width of canvas
      "height": 400, // height of canvas
      "showLegend": true, // OPTIONAL.
      "colorFunction": colorFunction1,  // color function, accept a key and return a color, can use d3.scale.category20()
      "clickFunction": clickFunction1,  // OPTIONAL. handles click event when clicking a day
      //"tooltipFunction": tooltipFunction  // OPTIONAL. if not provided default function will be used
      "xAxis": ["1-Jul", "2-Jul", "3-Jul", "4-Jul", "5-Jul"],
      "xAxisData": [1, 2, 3, 4, 5],  // OPTIONAL. can store additional data to be used by the click event. e.g. xAxis is the date string but xAxisData is the timestamp
      "yAxisTicks": 5, // number of horizontal lines
      "yAxisLeft": {  // config the left y axis, MUST if any series need to use this axis
        "title": "Number of Non-responding Nodes",
        "colorKey": "cornflowerblue"
      },
      "yAxisRight": { // config the right y axis, MUST if any series need to use this axis
        "title": "Percentage (%)",
        "colorKey": "yellowgreen"
      },
      "series":[  // array of chart config. one element correspond to one chart. bar charts are NOT stacked and will OVERLAP
        {
          "colorKey": " cornflowerblue",
          "type": "bar",  // chart type - bar or line
          "data": [10, 20, 50, 100, 30],
          "yAxis": "left",   // associate the chart to left or right axis 
          "label": "label 1"  // used in legends and tooltips
        },
        {
          "colorKey": "yellowgreen",
          "type": "line",
          "data": [10, 20, 50, 100, 30],
          "yAxis": "right",
          "label": "label 2"
        },
        {
          "colorKey": "yellow",
          "type": "line",
          "data": [30, 24, 64, 40, 39],
          "yAxis": "right",
          "label": "label 3"
        }
      ]
    };

    CCMixedChart.draw("vis", config);
  });


</script>
</body>
</html>
posted @ 2019-10-10 15:49  大飞90  阅读(383)  评论(0编辑  收藏  举报