在d3 js中移动y轴刻度线

时间:2018-05-29 10:31:49

标签: d3.js

我的分组条形图如下所示,带有刻度线。

$(document).ready(function() {
  render_chart();
});

function render_chart() {

  var dataset = {
    "colors": [
      "#1f77b4",
      "#ffbb78",
      "#e74c3c",
      "#2ecc71",
      "#aec7e8",
      "#ff7f0e",
      "#2ca02c",
      "#98df8a",
      "#9467bd",
      "#3498db",
    ],
    "operators": [
      "Client1",
      "Client2",
      "Client3",
      "Client4"
    ],
    "days": [
      "Monday",
      "Tuesday",
      "Wednesday",
      "Thursday",
      "Friday",
      "Saturday",
      "Sunday"
    ],
    "schedules": [{
        "fromHours": "00:00:00",
        "toHours": "08:00:00",
        "operator": "Client1",
        "day": "Monday",
        "color": "#1f77b4"
      },
      {
        "fromHours": "00:00:00",
        "toHours": "08:00:00",
        "operator": "Client2",
        "day": "Monday",
        "color": "#ffbb78"
      },
      {
        "fromHours": "08:00:00",
        "toHours": "14:00:00",
        "operator": "Client2",
        "day": "Monday",
        "color": "#ffbb78"
      },
      {
        "fromHours": "14:00:00",
        "toHours": "18:00:00",
        "operator": "Client3",
        "day": "Monday",
        "color": "#e74c3c"
      },
      {
        "fromHours": "18:00:00",
        "toHours": "23:59:59",
        "operator": "Client4",
        "day": "Monday",
        "color": "#2ecc71"
      },
      {
        "fromHours": "02:00:00",
        "toHours": "16:00:00",
        "operator": "Client4",
        "day": "Tuesday",
        "color": "#2ecc71"
      },
      {
        "fromHours": "16:00:00",
        "toHours": "20:00:00",
        "operator": "Client3",
        "day": "Tuesday",
        "color": "#e74c3c"
      },
      {
        "fromHours": "16:00:00",
        "toHours": "23:59:59",
        "operator": "Client1",
        "day": "Tuesday",
        "color": "#1f77b4"
      },
      {
        "fromHours": "00:00:00",
        "toHours": "17:00:00",
        "operator": "Client4",
        "day": "Wednesday",
        "color": "#2ecc71"
      },
      {
        "fromHours": "17:00:00",
        "toHours": "23:59:59",
        "operator": "Client3",
        "day": "Wednesday",
        "color": "#e74c3c"
      }
    ]
  };

  var schedulesDataset = dataset["schedules"];
  var daysDataset = dataset["days"];
  dataset.layers = new Array();
  var mondayData = new Array();
  var tuesdayData = new Array();
  var wednesdayData = new Array();
  var thursdayData = new Array();
  var fridayData = new Array();
  var saturdayData = new Array();
  var sundayData = new Array();

  schedulesDataset.forEach(function(schedule) {
    switch (schedule.day) {
      case "Monday":
        mondayData.push({
          "fromHours": schedule.fromHours,
          "toHours": schedule.toHours,
          "operator": schedule.operator,
          "day": schedule.day,
          "color": schedule.color
        });
        break;
      case "Tuesday":
        tuesdayData.push({
          "fromHours": schedule.fromHours,
          "toHours": schedule.toHours,
          "operator": schedule.operator,
          "day": schedule.day,
          "color": schedule.color
        });
        break;
      case "Wednesday":
        wednesdayData.push({
          "fromHours": schedule.fromHours,
          "toHours": schedule.toHours,
          "operator": schedule.operator,
          "day": schedule.day,
          "color": schedule.color
        });
        break;
      case "Thursday":
        thursdayData.push({
          "fromHours": schedule.fromHours,
          "toHours": schedule.toHours,
          "operator": schedule.operator,
          "day": schedule.day,
          "color": schedule.color
        });
        break;
      case "Friday":
        fridayData.push({
          "fromHours": schedule.fromHours,
          "toHours": schedule.toHours,
          "operator": schedule.operator,
          "day": schedule.day,
          "color": schedule.color
        });
        break;
      case "Saturday":
        saturdayData.push({
          "fromHours": schedule.fromHours,
          "toHours": schedule.toHours,
          "operator": schedule.operator,
          "day": schedule.day,
          "color": schedule.color
        });
        break;
      case "Sunday":
        sundayData.push({
          "fromHours": schedule.fromHours,
          "toHours": schedule.toHours,
          "operator": schedule.operator,
          "day": schedule.day,
          "color": schedule.color
        });
        break;
    }
  });

  dataset.layers.push(mondayData);
  dataset.layers.push(tuesdayData);
  dataset.layers.push(wednesdayData);
  dataset.layers.push(thursdayData);
  dataset.layers.push(fridayData);
  dataset.layers.push(saturdayData);
  dataset.layers.push(sundayData);

  var numberOfOperators = dataset["operators"].length;

  var today = new Date();
  today.setHours(0, 0, 0, 0);
  todayMillis = today.getTime();
  var layersData = dataset["layers"];
  layersData.forEach(function(layer) {
    layer.forEach(function(innerLayer) {
      var fromHourParts = innerLayer.fromHours.split(/:/);
      var toHourParts = innerLayer.toHours.split(/:/);

      innerLayer.fromHours = new Date();
      innerLayer.fromHours.setTime(todayMillis + getTimePeriodMillis(fromHourParts));

      innerLayer.toHours = new Date();
      innerLayer.toHours.setTime(todayMillis + getTimePeriodMillis(toHourParts));
    })
  });

  function getTimePeriodMillis(parts) {
    return (parseInt(parts[0], 10) * 60 * 60 * 1000) +
      (parseInt(parts[1], 10) * 60 * 1000) +
      (parseInt(parts[2], 10) * 1000);
  }

  function appendExtraZeroToSingleValues(inputValue) {
    if (inputValue === 0) {
      return inputValue + "0";
    }
    var parsedInputValue = inputValue.toString();
    if (parsedInputValue.length === 1) {
      return "0" + inputValue;
    }
    return inputValue;
  }

  var offsetWidth = 1000;
  var margin = {
      top: 50,
      right: 50,
      bottom: 50,
      left: 100
    },
    width = offsetWidth - margin.left - margin.right,
    height = 600 - margin.top - margin.bottom;

  var svg = d3.select("#groupchart").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  var xScale = d3.scaleTime()
    .domain([new Date(), new Date()])
    .nice(d3.timeDay, 1)
    .range([0, width - margin.left]);

  var yScale = d3.scaleBand()
    .domain(dataset["days"])
    .rangeRound([0, height])
    .padding(.08);

  var xAxis = d3.axisBottom(xScale)
    .ticks(24)
    .tickSize(-height)
    .tickFormat(d3.timeFormat("%H"));

  var yAxis = d3.axisLeft(yScale)
    .tickSize(-(width - margin.left))
    .tickPadding(5);

  var layer = svg.selectAll(".layer")
    .data(dataset["layers"])
    .enter().append("g")
    .attr("class", "layer");

  var rect = layer.selectAll("rect")
    .data(function(d, i) {
      return d;
    })
    .enter()
    .append("rect")
    .transition()
    .duration(500)
    .delay(function(d, i) {
      return i * 100;
    })
    .attr("y", function(d, i) {
      return yScale(d.day) + yScale.bandwidth() / 7 * i;
    })
    .attr("height", yScale.bandwidth() / 7)
    .transition()
    .attr("x", function(d) {
      return xScale(d.fromHours)
    })
    .attr("width", function(d) {
      return xScale(Math.abs(d.toHours)) - xScale(Math.abs(d.fromHours));
    })
    .attr("class", "bar")
    .style("fill", function(d, i) {
      return d.color;
    });

  svg.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
    .call(xAxis);

  svg.select("g")
    .attr("class", "y axis")
    .call(yAxis);

  var legend = svg.append("g")
    .attr("class", "legend")

  legend.selectAll('text')
    .data(dataset["operators"])
    .enter()
    .append("rect")
    .attr("x", function(d, i) {
      return (i * (offsetWidth / 10)) + (width / numberOfOperators);
    })
    .attr("y", function(d, i) {
      return height + 30;
    })
    .attr("width", 10)
    .attr("height", 10)
    .style("fill", function(d, i) {
      return dataset.colors[i];
    })

  legend.selectAll('text')
    .data(dataset["operators"])
    .enter()
    .append("text")
    .attr("x", function(d, i) {
      return (i * (offsetWidth / 10)) + (width / numberOfOperators) + 12;
    })
    .attr("y", function(d, i) {
      return height + 40;
    })
    .text(function(d) {
      return d;
    });

  var tooltip = d3.select("body")
    .append('div')
    .attr('class', 'tooltip');

  tooltip.append('div')
    .attr('class', 'operator');
  tooltip.append('div')
    .attr('class', 'timeRange');

  svg.selectAll("rect")
    .on('mouseover', function(d, i) {
      if (!d.day) return null;

      tooltip.select('.operator').html("<b>" + d.operator + "</b>");
      tooltip.select('.timeRange').html(appendExtraZeroToSingleValues(d.fromHours.getHours()) + ":" +
        appendExtraZeroToSingleValues(d.fromHours.getMinutes()) + " Hours to " +
        appendExtraZeroToSingleValues(d.toHours.getHours()) + ":" +
        appendExtraZeroToSingleValues(d.toHours.getMinutes()) + " Hours");

      tooltip.style('display', 'block');
      tooltip.style('opacity', 2);

    })
    .on('mousemove', function(d) {
      if (!d.day) return null;

      tooltip.style('top', (d3.event.layerY + 10) + 'px')
        .style('left', (d3.event.layerX - 25) + 'px');
    })
    .on('mouseout', function() {
      tooltip.style('display', 'none');
      tooltip.style('opacity', 0);
    });

}
.axis .tick line {
        stroke-width: 1;
        stroke: rgba(0, 0, 0, 0.2);
    }
    .axis path,
    .axis line {
      fill: none;
      font: 10px sans-serif;
      stroke: #000;
      shape-rendering: crispEdges;
    }

    .legend {
        padding: 5px;
        font-size: 15px;
        font-family: 'Roboto', sans-serif;
        background: yellow;
        box-shadow: 2px 2px 1px #888;
    }

    .tooltip {
        background: #eee;
        box-shadow: 0 0 5px #999999;
        color: #333;
        font-size: 12px;
        left: 130px;
        padding: 10px;
        position: absolute;
        text-align: center;
        top: 95px;
        z-index: 10;
        display: block;
        opacity: 0;
    }
<!DOCTYPE html>
<html>
	<head>
    <meta charset="UTF-8">
		<title>Bar Graph</title>

    <script
  src="https://code.jquery.com/jquery-3.3.1.min.js"
  integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
  crossorigin="anonymous"></script>
    <script src="https://d3js.org/d3.v4.min.js"></script>
	</head>
	<body>
    <div id="groupchart" class="chart"></div>
	</body>
</html>

由于某些条形图不是从0位置开始,因此它看起来不像是一个组。我想将这些y轴刻度线向上移动half of the distance between current tick position and the next tick position,因此分组的条形显示在2条黑线内。我认为在这种情况下看起来会更好,也许我们可以用一些css来区分每个相邻的组。

我不知道该怎么做,所以我四处寻找并没有找到类似的东西。有可能实现吗?

非常感谢任何帮助:)

1 个答案:

答案 0 :(得分:1)

这些线对应于轴'刻度线,它们由轴生成器自动创建和定位。

如果没有太多的重构或自定义,您可以在创建它们之后将其移动到

d3.selectAll(".y.axis .tick line").each(function(){
    d3.select(this)
        .attr("transform", "translate(0," + yScale.bandwidth()/2 + ")")
});

以下是具有该更改的代码:

$(document).ready(function() {
  render_chart();
});

function render_chart() {

  var dataset = {
    "colors": [
      "#1f77b4",
      "#ffbb78",
      "#e74c3c",
      "#2ecc71",
      "#aec7e8",
      "#ff7f0e",
      "#2ca02c",
      "#98df8a",
      "#9467bd",
      "#3498db",
    ],
    "operators": [
      "Client1",
      "Client2",
      "Client3",
      "Client4"
    ],
    "days": [
      "Monday",
      "Tuesday",
      "Wednesday",
      "Thursday",
      "Friday",
      "Saturday",
      "Sunday"
    ],
    "schedules": [{
        "fromHours": "00:00:00",
        "toHours": "08:00:00",
        "operator": "Client1",
        "day": "Monday",
        "color": "#1f77b4"
      },
      {
        "fromHours": "00:00:00",
        "toHours": "08:00:00",
        "operator": "Client2",
        "day": "Monday",
        "color": "#ffbb78"
      },
      {
        "fromHours": "08:00:00",
        "toHours": "14:00:00",
        "operator": "Client2",
        "day": "Monday",
        "color": "#ffbb78"
      },
      {
        "fromHours": "14:00:00",
        "toHours": "18:00:00",
        "operator": "Client3",
        "day": "Monday",
        "color": "#e74c3c"
      },
      {
        "fromHours": "18:00:00",
        "toHours": "23:59:59",
        "operator": "Client4",
        "day": "Monday",
        "color": "#2ecc71"
      },
      {
        "fromHours": "02:00:00",
        "toHours": "16:00:00",
        "operator": "Client4",
        "day": "Tuesday",
        "color": "#2ecc71"
      },
      {
        "fromHours": "16:00:00",
        "toHours": "20:00:00",
        "operator": "Client3",
        "day": "Tuesday",
        "color": "#e74c3c"
      },
      {
        "fromHours": "16:00:00",
        "toHours": "23:59:59",
        "operator": "Client1",
        "day": "Tuesday",
        "color": "#1f77b4"
      },
      {
        "fromHours": "00:00:00",
        "toHours": "17:00:00",
        "operator": "Client4",
        "day": "Wednesday",
        "color": "#2ecc71"
      },
      {
        "fromHours": "17:00:00",
        "toHours": "23:59:59",
        "operator": "Client3",
        "day": "Wednesday",
        "color": "#e74c3c"
      }
    ]
  };

  var schedulesDataset = dataset["schedules"];
  var daysDataset = dataset["days"];
  dataset.layers = new Array();
  var mondayData = new Array();
  var tuesdayData = new Array();
  var wednesdayData = new Array();
  var thursdayData = new Array();
  var fridayData = new Array();
  var saturdayData = new Array();
  var sundayData = new Array();

  schedulesDataset.forEach(function(schedule) {
    switch (schedule.day) {
      case "Monday":
        mondayData.push({
          "fromHours": schedule.fromHours,
          "toHours": schedule.toHours,
          "operator": schedule.operator,
          "day": schedule.day,
          "color": schedule.color
        });
        break;
      case "Tuesday":
        tuesdayData.push({
          "fromHours": schedule.fromHours,
          "toHours": schedule.toHours,
          "operator": schedule.operator,
          "day": schedule.day,
          "color": schedule.color
        });
        break;
      case "Wednesday":
        wednesdayData.push({
          "fromHours": schedule.fromHours,
          "toHours": schedule.toHours,
          "operator": schedule.operator,
          "day": schedule.day,
          "color": schedule.color
        });
        break;
      case "Thursday":
        thursdayData.push({
          "fromHours": schedule.fromHours,
          "toHours": schedule.toHours,
          "operator": schedule.operator,
          "day": schedule.day,
          "color": schedule.color
        });
        break;
      case "Friday":
        fridayData.push({
          "fromHours": schedule.fromHours,
          "toHours": schedule.toHours,
          "operator": schedule.operator,
          "day": schedule.day,
          "color": schedule.color
        });
        break;
      case "Saturday":
        saturdayData.push({
          "fromHours": schedule.fromHours,
          "toHours": schedule.toHours,
          "operator": schedule.operator,
          "day": schedule.day,
          "color": schedule.color
        });
        break;
      case "Sunday":
        sundayData.push({
          "fromHours": schedule.fromHours,
          "toHours": schedule.toHours,
          "operator": schedule.operator,
          "day": schedule.day,
          "color": schedule.color
        });
        break;
    }
  });

  dataset.layers.push(mondayData);
  dataset.layers.push(tuesdayData);
  dataset.layers.push(wednesdayData);
  dataset.layers.push(thursdayData);
  dataset.layers.push(fridayData);
  dataset.layers.push(saturdayData);
  dataset.layers.push(sundayData);

  var numberOfOperators = dataset["operators"].length;

  var today = new Date();
  today.setHours(0, 0, 0, 0);
  todayMillis = today.getTime();
  var layersData = dataset["layers"];
  layersData.forEach(function(layer) {
    layer.forEach(function(innerLayer) {
      var fromHourParts = innerLayer.fromHours.split(/:/);
      var toHourParts = innerLayer.toHours.split(/:/);

      innerLayer.fromHours = new Date();
      innerLayer.fromHours.setTime(todayMillis + getTimePeriodMillis(fromHourParts));

      innerLayer.toHours = new Date();
      innerLayer.toHours.setTime(todayMillis + getTimePeriodMillis(toHourParts));
    })
  });

  function getTimePeriodMillis(parts) {
    return (parseInt(parts[0], 10) * 60 * 60 * 1000) +
      (parseInt(parts[1], 10) * 60 * 1000) +
      (parseInt(parts[2], 10) * 1000);
  }

  function appendExtraZeroToSingleValues(inputValue) {
    if (inputValue === 0) {
      return inputValue + "0";
    }
    var parsedInputValue = inputValue.toString();
    if (parsedInputValue.length === 1) {
      return "0" + inputValue;
    }
    return inputValue;
  }

  var offsetWidth = 1000;
  var margin = {
      top: 50,
      right: 50,
      bottom: 50,
      left: 100
    },
    width = offsetWidth - margin.left - margin.right,
    height = 600 - margin.top - margin.bottom;

  var svg = d3.select("#groupchart").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  var xScale = d3.scaleTime()
    .domain([new Date(), new Date()])
    .nice(d3.timeDay, 1)
    .range([0, width - margin.left]);

  var yScale = d3.scaleBand()
    .domain(dataset["days"])
    .rangeRound([0, height])
    .padding(.08);

  var xAxis = d3.axisBottom(xScale)
    .ticks(24)
    .tickSize(-height)
    .tickFormat(d3.timeFormat("%H"));

  var yAxis = d3.axisLeft(yScale)
    .tickSize(-(width - margin.left))
    .tickPadding(5);

  var layer = svg.selectAll(".layer")
    .data(dataset["layers"])
    .enter().append("g")
    .attr("class", "layer");

  var rect = layer.selectAll("rect")
    .data(function(d, i) {
      return d;
    })
    .enter()
    .append("rect")
    .transition()
    .duration(500)
    .delay(function(d, i) {
      return i * 100;
    })
    .attr("y", function(d, i) {
      return yScale(d.day) + yScale.bandwidth() / 7 * i;
    })
    .attr("height", yScale.bandwidth() / 7)
    .transition()
    .attr("x", function(d) {
      return xScale(d.fromHours)
    })
    .attr("width", function(d) {
      return xScale(Math.abs(d.toHours)) - xScale(Math.abs(d.fromHours));
    })
    .attr("class", "bar")
    .style("fill", function(d, i) {
      return d.color;
    });

  svg.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
    .call(xAxis);

  svg.select("g")
    .attr("class", "y axis")
    .call(yAxis);
    
  d3.selectAll(".y.axis .tick line").each(function(){
  d3.select(this).attr("transform", "translate(0," + yScale.bandwidth()/2 + ")")
  });

  var legend = svg.append("g")
    .attr("class", "legend")

  legend.selectAll('text')
    .data(dataset["operators"])
    .enter()
    .append("rect")
    .attr("x", function(d, i) {
      return (i * (offsetWidth / 10)) + (width / numberOfOperators);
    })
    .attr("y", function(d, i) {
      return height + 30;
    })
    .attr("width", 10)
    .attr("height", 10)
    .style("fill", function(d, i) {
      return dataset.colors[i];
    })

  legend.selectAll('text')
    .data(dataset["operators"])
    .enter()
    .append("text")
    .attr("x", function(d, i) {
      return (i * (offsetWidth / 10)) + (width / numberOfOperators) + 12;
    })
    .attr("y", function(d, i) {
      return height + 40;
    })
    .text(function(d) {
      return d;
    });

  var tooltip = d3.select("body")
    .append('div')
    .attr('class', 'tooltip');

  tooltip.append('div')
    .attr('class', 'operator');
  tooltip.append('div')
    .attr('class', 'timeRange');

  svg.selectAll("rect")
    .on('mouseover', function(d, i) {
      if (!d.day) return null;

      tooltip.select('.operator').html("<b>" + d.operator + "</b>");
      tooltip.select('.timeRange').html(appendExtraZeroToSingleValues(d.fromHours.getHours()) + ":" +
        appendExtraZeroToSingleValues(d.fromHours.getMinutes()) + " Hours to " +
        appendExtraZeroToSingleValues(d.toHours.getHours()) + ":" +
        appendExtraZeroToSingleValues(d.toHours.getMinutes()) + " Hours");

      tooltip.style('display', 'block');
      tooltip.style('opacity', 2);

    })
    .on('mousemove', function(d) {
      if (!d.day) return null;

      tooltip.style('top', (d3.event.layerY + 10) + 'px')
        .style('left', (d3.event.layerX - 25) + 'px');
    })
    .on('mouseout', function() {
      tooltip.style('display', 'none');
      tooltip.style('opacity', 0);
    });

}
.axis .tick line {
        stroke-width: 1;
        stroke: rgba(0, 0, 0, 0.2);
    }
    .axis path,
    .axis line {
      fill: none;
      font: 10px sans-serif;
      stroke: #000;
      shape-rendering: crispEdges;
    }

    .legend {
        padding: 5px;
        font-size: 15px;
        font-family: 'Roboto', sans-serif;
        background: yellow;
        box-shadow: 2px 2px 1px #888;
    }

    .tooltip {
        background: #eee;
        box-shadow: 0 0 5px #999999;
        color: #333;
        font-size: 12px;
        left: 130px;
        padding: 10px;
        position: absolute;
        text-align: center;
        top: 95px;
        z-index: 10;
        display: block;
        opacity: 0;
    }
<!DOCTYPE html>
<html>
	<head>
    <meta charset="UTF-8">
		<title>Bar Graph</title>

    <script
  src="https://code.jquery.com/jquery-3.3.1.min.js"
  integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
  crossorigin="anonymous"></script>
    <script src="https://d3js.org/d3.v4.min.js"></script>
	</head>
	<body>
    <div id="groupchart" class="chart"></div>
	</body>
</html>

如果你采用这种方法,最后一个(星期日)线看起来不会很好......但你可以简单地删除它。