How can I draw compass shape in d3.js and colour each triangle differently?

Viewed 706

I have drawn this shape in d3.js using a line generator - the idea is to make it look like a compass.

Left image: what I have
Right image: what I want (just the middle red/black, not the whole compass).

enter image description here enter image description here

What I used to make this is:

function drawArrowCompass() { //draw arrow

    var dataArrow = [
        [35, 50],
        [50, 0],
        [65, 50],
        [50, 100],
        [35, 50]
    ];
    var lineGenerator = d3.line();
    var pathString = lineGenerator(dataArrow);

    var compassArrow = d3.select("#noun_compass");

    //draw arrow(s)
    compassArrow.append("path")
        .attr('d', pathString);

}

With the corresponding HTML:

<div id="compass">
        <svg width="100" height="100" id="noun_compass"></svg>
</div>

However, the way I've done it, I can't see a way to add two different colour to the path. Does anyone know how I can draw such a shape in d3.js? Thanks in advance!!

2 Answers

The way I'd do it, is create a path data (d attribute) for single arrow where path's origin is at the base of the arrow, and then make two paths with that same data, but different transform. This is robust, and allows easily to make changes to the position, orientation, and count of the arrows.

<!DOCTYPE html>

<head>
  <meta charset="utf-8">
  <script src="https://d3js.org/d3.v4.min.js"></script>
  <style>
    body {
      margin: 0;
      position: fixed;
      top: 0;
      right: 0;
      bottom: 0;
      left: 0;
    }
  </style>
</head>

<body>
  <input type="range" min="0" max="360" value="0" id="slider" oninput="updateAngle(this.value)">
  <script>
    // Feel free to change or delete any of the code you see in this editor!
    var svg = d3.select("body").append("svg")
      .attr("width", 200)
      .attr("height", 200)
      .attr('viewBox', '-50 -50 100 100')

    var path_d = "M 0,0 L 0,-10 L 50,0 L 0,10 Z";

    function updateAngle(value) {
      var angle = parseInt(value);
      var data = [{
        angle: angle,
        color: 'black'
      }, {
        angle: (180 + angle) % 360,
        color: 'red'
      }];

      paths = svg.selectAll('path')
        .data(data);

      paths.enter()
        .append('path')
        .attr('d', path_d)
        .merge(paths)
        .style('fill', d => d.color)
        .attr('transform', d => `rotate(${d.angle})`);

      paths.exit().remove();
    }

    updateAngle(0);
  </script>
</body>

A simple solution is using a linearGradient with the stop-colours meeting at 50%, like this:

var gradient = svg.append("defs")
  .append("linearGradient")
  .attr("id", "gradient");
gradient.append("stop")
  .attr("offset", "50%")
  .attr("stop-color", "red");
gradient.append("stop")
  .attr("offset", "50%")
  .attr("stop-color", "black");

Given you hard coded the points forming the path, you have to rotate it to keep the gradient's direction, like this:

var svg = d3.select("svg");
var gradient = svg.append("defs")
  .append("linearGradient")
  .attr("y1", "0%")
  .attr("y2", "100%")
  .attr("x1", "50%")
  .attr("x2", "50%")
  .attr("id", "gradient");
gradient.append("stop")
  .attr("offset", "50%")
  .attr("stop-color", "red");
gradient.append("stop")
  .attr("offset", "50%")
  .attr("stop-color", "black");
drawArrowCompass();

function drawArrowCompass() {
  var dataArrow = [
    [35, 50],
    [50, 0],
    [65, 50],
    [50, 100],
    [35, 50]
  ];
  var lineGenerator = d3.line();
  var pathString = lineGenerator(dataArrow);
  var compassArrow = d3.select("#noun_compass");
  compassArrow.append("path")
    .attr('d', pathString)
    .attr("fill", "url(#gradient)")
    .attr("transform", "rotate(80, 50, 50)");
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="compass">
  <svg width="100" height="100" id="noun_compass"></svg>
</div>

Related