Rajkishan Swami
Rajkishan Swami

Reputation: 3759

Moving y-axis tick lines in d3 js

I have the grouped bar chart like below with tick lines.

$(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>

Since some of the bars does not start from 0 position, it is does not appear like a group. I want to move those y-axis tick lines up by half of the distance between current tick position and the next tick position, so it the grouped bars appear inside 2 black lines. I think it will look better in that case and maybe we can appply some css to distinguish each adjacent group.

I do not know how to do that, so i searched around and didn't find anything similar. Is it possible to achieve?

Any help is greatly appreciated :)

Upvotes: 4

Views: 1431

Answers (1)

Gerardo Furtado
Gerardo Furtado

Reputation: 102174

Those lines correspond to the axis' ticks, and they are automatically created and positioned by the axis generator.

Without too much refactor or customisation, you can simply move them down after they were created:

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

Here is the code with that change:

$(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>

If you follow this approach, the last (Sunday) line won't look good... but you can simply remove it.

Upvotes: 2

Related Questions