Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

D3 custom curve: bundle interpolation for areas

Consider this D3JS graph which uses a basis interpolation:

D3JS v3, basis interpolation

In D3JS v3, I could use bundle interpolation (.interpolate("bundle").tension(0)) on areas to achieve this type of rendering instead:

D3JS v3, bundle interpolation

Notice how each segment of the graph fits nicely with its neighbors. This is what I need.

With D3JS v4 and v5, the syntax for bundle interpolation is now this: .curve(d3.curveBundle). However, it's now "intended to work with d3.line, not d3.area."

I recently upgraded from v3 to v5, and so I'm trying to create a custom bundle curve that will work with areas too, to keep the interpolation type I enjoyed with v3.

I'm very close. This is what I have so far:

///////////////////// Custom curves.

/** Bundle-ish.
 * Trying to adapt curveBundle for use with areas…
 */
function myBundle(context, beta) {
	this._basis = new d3.curveBasis(context);
	this._beta = beta;

	this._context = context; // temporary. shouldn't be needed for bundle.
}
myBundle.prototype = {

	areaStart: function() {
		this._line = 0;
	},
	areaEnd: function() {
		this._line = NaN;
	},
	lineStart: function() {
		this._x = [];
		this._y = [];
		this._basis.lineStart();
	},
	lineEnd: function() {
		var x = this._x,
				y = this._y,
				j = x.length - 1;

		if (j > 0) {
			var x0 = x[0],
					y0 = y[0],
					dx = x[j] - x0,
					dy = y[j] - y0,
					i = -1,
					t;

			while (++i <= j) {
				t = i / j;
				this._basis.point(
					this._beta * x[i] + (1 - this._beta) * (x0 + t * dx),
					this._beta * y[i] + (1 - this._beta) * (y0 + t * dy)
				);
			}
		}

		this._x = this._y = null;
		this._basis.lineEnd();
	},
	point: function(x, y) {
		this._x.push(+x);
		this._y.push(+y);
		// console.log( this._x.push(+x), this._y.push(+y) );
	}
};
myCurveBundle = (function custom(beta) {
	function myCurveBundle(context) {
		return beta === 1 ? new myBasis(context) : new myBundle(context, beta);
	}

	myCurveBundle.beta = function(beta) {
		return custom(+beta);
	};

	return myCurveBundle;
})(0.85);






///////////////////// The chart.

	var width = 960;
	var height = 540;
	var data = [];
	data.prosody = [116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.578, 125.552, 134.888, 144.225, 153.561, 162.898, 172.235, 181.571, 190.908, 200.244, 209.581, 218.917, 227.715, 218.849, 209.591, 200.333, 191.076, 181.818, 172.560, 163.302, 154.044, 144.787, 135.529, 126.271, 117.013, 107.755, 98.498, 89.240, 97.511, 118.857, 140.202, 161.547, 182.893, 192.100, 188.997, 185.895, 182.792, 179.690, 176.587, 173.485, 170.382, 167.280, 164.177, 161.075, 157.972, 154.870, 151.767, 148.665, 145.562, 142.460, 139.357, 136.255, 133.152, 130.050, 126.947, 124.244, 122.275, 120.307, 118.338, 116.369, 114.400, 112.431, 110.462, 108.493, 106.524, 104.555, 102.586, 100.617, 98.648, 99.659, 101.531, 103.402, 105.273, 107.145, 109.016, 110.887, 112.758, 114.630, 116.501, 118.372, 120.244, 122.115, 123.986, 125.857, 127.729, 129.600, 131.471, 133.343, 135.214, 137.085, 138.956, 140.828, 142.699, 144.570, 146.442, 148.313, 150.184, 149.175, 146.384, 143.594, 140.803, 138.013, 135.222, 132.432, 129.642, 126.851, 124.061, 121.270, 118.480, 115.689, 112.899, 110.109, 107.318, 104.528, 101.737, 98.947, 96.156, 93.366, 90.576, 87.785, 84.995, 82.204, 79.414, 76.623, 0, 0, 0, 0, 0, 0, 76.601, 78.414, 80.227, 82.041, 83.854, 85.667, 87.480, 89.294, 91.107, 92.920, 94.733, 96.547, 98.360, 100.173, 101.986, 103.800, 105.613, 107.426, 109.239, 111.053, 112.866, 114.679, 116.492, 115.917, 114.338, 112.760, 111.181, 109.602, 108.023, 106.444, 104.865, 103.286, 101.707, 100.128, 98.549, 96.970, 95.391, 93.812, 92.233, 90.654, 89.075, 87.534, 88.055, 88.646, 89.237, 89.827, 90.418, 91.009, 91.600, 92.191, 92.782, 93.373, 93.964, 94.555, 95.146, 95.737, 96.328, 96.919, 97.509, 98.100, 98.691, 99.282, 99.873, 100.062, 98.230, 96.399, 94.567, 92.736, 90.904, 89.072, 87.241, 85.409, 83.578, 81.746, 79.914, 78.083, 78.839, 80.880, 82.922, 84.964, 87.006, 89.048, 91.090, 93.132, 95.174, 97.216, 99.257, 101.299, 103.341, 105.383, 107.425, 109.467, 111.509, 113.551, 112.633, 110.755, 108.877, 106.999, 105.121, 103.243, 101.365, 99.487, 97.609, 95.731, 93.853, 91.975, 90.097, 88.219, 86.341, 84.463, 82.585, 80.707, 78.829, 76.951, 78.067, 81.290, 84.513, 87.736, 90.958, 94.181, 97.404, 100.627, 103.849, 107.072, 110.295, 113.517, 116.740, 119.963, 123.186, 126.408, 129.631, 132.854, 136.077, 139.299, 142.522, 145.745, 148.968, 152.190, 155.413, 154.840, 152.899, 150.958, 149.017, 147.076, 145.135, 143.194, 141.253, 139.312, 137.371, 135.429, 133.488, 131.547, 129.606, 127.665, 125.724, 124.874, 126.734, 128.594, 130.454, 132.314, 134.174, 136.034, 137.894, 139.754, 141.614, 143.474, 145.334, 147.194, 149.054, 150.914, 152.774, 154.634, 156.494, 158.354, 160.214, 162.074, 163.934, 165.664, 161.795, 157.761, 153.726, 149.692, 145.658, 141.624, 137.589, 133.555, 129.521, 125.487, 121.452, 117.418, 113.384, 109.350, 105.316, 101.281, 97.247, 93.213, 89.179, 85.144, 81.110, 77.076, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
	data.TextGrid = {  "phone" : [ /** segment type, beginning, and end of each segment **/ [ "sp", 0.0124716553288, 0.271882086168 ], [ "M", 0.271882086168, 0.401587301587 ], [ "OW", 0.401587301587, 0.521315192744 ], [ "S", 0.521315192744, 0.660997732426 ], [ "T", 0.660997732426, 0.710884353741 ], [ "AH", 0.710884353741, 0.760770975057 ], [ "V", 0.760770975057, 0.820634920635 ], [ "DH", 0.820634920635, 0.860544217687 ], [ "IY", 0.860544217687, 0.940362811791 ], [ "AH", 0.940362811791, 0.980272108844 ], [ "D", 0.980272108844, 1.04013605442 ], [ "V", 1.04013605442, 1.10997732426 ], [ "EH", 1.10997732426, 1.21972789116 ], [ "N", 1.21972789116, 1.289569161 ], [ "CH", 1.289569161, 1.42925170068 ], [ "ER", 1.42925170068, 1.51904761905 ], [ "Z", 1.51904761905, 1.57891156463 ], [ "R", 1.57891156463, 1.66870748299 ], [ "AH", 1.66870748299, 1.69863945578 ], [ "K", 1.69863945578, 1.75850340136 ], [ "AO", 1.75850340136, 1.88820861678 ], [ "R", 1.88820861678, 1.91814058957 ], [ "D", 1.91814058957, 1.95804988662 ], [ "AH", 1.95804988662, 1.99795918367 ], [ "D", 1.99795918367, 2.07777777778 ], [ "AH", 2.07777777778, 2.10770975057 ], [ "N", 2.10770975057, 2.18752834467 ], [ "DH", 2.18752834467, 2.22743764172 ], [ "AH", 2.22743764172, 2.2873015873 ], [ "S", 2.2873015873, 2.42698412698 ], [ "B", 2.42698412698, 2.51678004535 ], [ "UH", 2.51678004535, 2.68639455782 ], [ "K", 2.68639455782, 2.79614512472 ], [ "sp", 2.79614512472, 2.81609977324 ], [ "R", 2.81609977324, 2.95578231293 ], [ "IY", 2.95578231293, 3.00566893424 ], [ "L", 3.00566893424, 3.09546485261 ], [ "IY", 3.09546485261, 3.23514739229 ], [ "AH", 3.23514739229, 3.27505668934 ], [ "K", 3.27505668934, 3.41473922902 ], [ "ER", 3.41473922902, 3.68412698413 ], [ "D", 3.68412698413, 3.75396825397 ], [ "sp", 3.75396825397, 4.01337868481 ] ] }


	/**
	 * Set up D3JS
	 */
	var x = d3.scaleLinear()
		.domain([0, 401])
		.range([0, width]);
	var y = d3.scaleLinear()
		.domain([0, 800])
		.range([height, 0]);

	/** Center the stream vertically **/
	var shift = d3.scaleLinear()
		.domain([0, 0])
		.range([-height/2, 0]);

	/** Draw a stream segment **/
	var pathGenerator = d3.area()
		.curve( myCurveBundle.beta(0) )
		.x(function(d, i) { return x(i); })
		.y1(function(d) { return y(d + 72 ); }) /** 72 is just some arbitrary thickess given to the graph **/
		.y0(function(d) { return y(d); });

	var svg = d3.select("body").append("svg")
		.attr("width", width)
		.attr("height", height);



	/**
	 * Render the chart
	 */
	
	/** Draw the stream, on a per-segment basis **/
	var path = svg.selectAll("path")
		.data(data.TextGrid.phone)
		.enter().append("path")
			.attr("transform", function(d, i) { return "translate(" + x(Math.floor(d[1]*100)) + ", " + shift(i) + ")"; })
			.attr("class", function(d) { return "segment " + d[0]; })
			.on('click', function(d, i) { playFromTo(Math.floor(d[1] * 1000), Math.floor(d[2] * 1000)); })
			.attr("d", function(d) { return pathGenerator(data.prosody.slice( Math.floor(d[1]*100), Math.floor(d[2]*100)+1)); });
.segment { fill: #ccc; }
.segment.sp { display: none; }

/** Adapted from Margaret Horrigan for American English **/
.segment.IY { fill: #7AC141; }
.segment.IH { fill: #F9C5DC; }
.segment.UH { fill: #FF00FF; }
.segment.UW { fill: #0153A5; }
.segment.EY { fill: #8B8C90; }
.segment.EH { fill: #E61A25; }
.segment.AX { fill: #DF5435; }
.segment.ER { fill: #805EAA; }
.segment.AO { fill: #E2A856; }
.segment.OY { fill: #2E3094; }
.segment.OW { fill: #FC2B1C; }
.segment.AE { fill: #21201E; }
.segment.AH { fill: #DF5435; }
.segment.AA { fill: #bf181f; }
.segment.AY { fill: #FFFFFF; }
.segment.AW { fill: #7C4540; }
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/d3.min.js"></script>

(The bundle code is adapted from bundle.js in d3-shape.)

I'm very close: if you inspect the SVG, you'll see that, even though nothing is shown, paths actually do get created.

If you look at the first "visible" segment (class segment M) you'll see that it contains a move command somewhere in the middle:

M31.122194513715712,398.532825

If I rename it to a line command, like so:

L31.122194513715712,398.532825

…then that segment will show.

I'm confused as to which part of the custom curve is responsible for that. How can I turn that M into an L?

The resulting paths also happen to lack final Zs. How would I go about handling that?

I haven't found much help regarding custom curves in D3JS. Any help is welcome.

like image 974
Fabien Snauwaert Avatar asked May 26 '18 12:05

Fabien Snauwaert


1 Answers

Calling areaStart and areaEnd:

It looks like you've copied the areaStart and areaEnd functions from d3.curveBasis into your custom curve, which is mainly drawn from d3.curveBundle.

You can't just copy the code from d3.curveBasis into d3.curveBundle because this refers to different things. this._basis (your instance of d3.curveBasis) can not access this._line (the variable it expects for areaStart and areaEnd, and is accessible via your customBundle).

You could change this._line to this._basis._line, but if you notice, all the line functions in d3.curveBundle call their respective this._basis functions (e.g. lineStart calls this._basis.lineStart()). If you do the same here it should be equivalent:

areaStart: {
    // this._basis._line = 0; // this should work, for now
    this._basis.areaStart(); // but this makes more sense semantically
},
areaEnd: {
    // this._basis._line = NaN; // this should work, for now
    this._basis.areaEnd(); // but this makes more sense semantically
}

The added benefit of doing things this way is that if d3.curveBasis changes its implementation in the future, this has a higher chance of being compatible.

No need for new:

As a side note, in your constructor you create a new instance of this._basis using the new operator:

this._basis = new d3.curveBasis(context);

The Basis constructor via new is used internally in the d3 modules, but in the bundled library, it's a functional constructor. It can just be:

this._basis = d3.curveBasis(context);

Though using new doesn't seem to be breaking anything. See https://stackoverflow.com/a/9468106/6184972 for more information.

Should you be using curve bundles to render areas?

As you note, d3.curveBundle is "intended to work with d3.line, not d3.area." It should be worth asking whether you should be using curveBundle to render areas, since the omission seems deliberate. From https://github.com/d3/d3-shape/issues/70, @mbostock writes:

Correct, d3.curveBundle is only intended to work with d3.line. It’s for hierarchical edge bundling, not for rendering areas.

See also https://github.com/d3/d3-shape#curveBundle.

You should probably compare to curveBundle with other interpolation methods to see whether using it distorts your area in a misleading manner.

Results:

All together, the changes can be seen in this fiddle: https://jsfiddle.net/g4ya2qso/

Alternatively:

Alternatively, since the functionality resembles d3.curveBundle so much, you can just add methods for .areaStart and .areaEnd, and omit all of the other custom code, like so:

var myCurveBundle = (function custom(beta) {
    function myCustomBundle(context) {
      var bundle = d3.curveBundle.beta(beta)(context);
      bundle.areaStart = function () {
        bundle._basis.areaStart();
      };
      bundle.areaEnd = function () {
        bundle._basis.areaEnd();
      };
      return bundle;
    }
    myCustomBundle.beta = function(newBeta) {
      return custom(+newBeta);
    };
    return myCustomBundle;
})(0.85);

///////////////////// Custom curves.

/** Bundle-ish.
 * Trying to adapt curveBundle for use with areas…
 */
var myCurveBundle = (function custom(beta) {
  function myCustomBundle(context) {
    var bundle = d3.curveBundle.beta(beta)(context);
    bundle.areaStart = function () {
      bundle._basis.areaStart();
    };
    bundle.areaEnd = function () {
      bundle._basis.areaEnd();
    };
    return bundle;
  }
  myCustomBundle.beta = function(newBeta) {
    return custom(+newBeta);
  };
  return myCustomBundle;
})(0.85);

///////////////////// The chart.

	var width = 960;
	var height = 540;
	var data = [];
	data.prosody = [116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.473, 116.578, 125.552, 134.888, 144.225, 153.561, 162.898, 172.235, 181.571, 190.908, 200.244, 209.581, 218.917, 227.715, 218.849, 209.591, 200.333, 191.076, 181.818, 172.560, 163.302, 154.044, 144.787, 135.529, 126.271, 117.013, 107.755, 98.498, 89.240, 97.511, 118.857, 140.202, 161.547, 182.893, 192.100, 188.997, 185.895, 182.792, 179.690, 176.587, 173.485, 170.382, 167.280, 164.177, 161.075, 157.972, 154.870, 151.767, 148.665, 145.562, 142.460, 139.357, 136.255, 133.152, 130.050, 126.947, 124.244, 122.275, 120.307, 118.338, 116.369, 114.400, 112.431, 110.462, 108.493, 106.524, 104.555, 102.586, 100.617, 98.648, 99.659, 101.531, 103.402, 105.273, 107.145, 109.016, 110.887, 112.758, 114.630, 116.501, 118.372, 120.244, 122.115, 123.986, 125.857, 127.729, 129.600, 131.471, 133.343, 135.214, 137.085, 138.956, 140.828, 142.699, 144.570, 146.442, 148.313, 150.184, 149.175, 146.384, 143.594, 140.803, 138.013, 135.222, 132.432, 129.642, 126.851, 124.061, 121.270, 118.480, 115.689, 112.899, 110.109, 107.318, 104.528, 101.737, 98.947, 96.156, 93.366, 90.576, 87.785, 84.995, 82.204, 79.414, 76.623, 0, 0, 0, 0, 0, 0, 76.601, 78.414, 80.227, 82.041, 83.854, 85.667, 87.480, 89.294, 91.107, 92.920, 94.733, 96.547, 98.360, 100.173, 101.986, 103.800, 105.613, 107.426, 109.239, 111.053, 112.866, 114.679, 116.492, 115.917, 114.338, 112.760, 111.181, 109.602, 108.023, 106.444, 104.865, 103.286, 101.707, 100.128, 98.549, 96.970, 95.391, 93.812, 92.233, 90.654, 89.075, 87.534, 88.055, 88.646, 89.237, 89.827, 90.418, 91.009, 91.600, 92.191, 92.782, 93.373, 93.964, 94.555, 95.146, 95.737, 96.328, 96.919, 97.509, 98.100, 98.691, 99.282, 99.873, 100.062, 98.230, 96.399, 94.567, 92.736, 90.904, 89.072, 87.241, 85.409, 83.578, 81.746, 79.914, 78.083, 78.839, 80.880, 82.922, 84.964, 87.006, 89.048, 91.090, 93.132, 95.174, 97.216, 99.257, 101.299, 103.341, 105.383, 107.425, 109.467, 111.509, 113.551, 112.633, 110.755, 108.877, 106.999, 105.121, 103.243, 101.365, 99.487, 97.609, 95.731, 93.853, 91.975, 90.097, 88.219, 86.341, 84.463, 82.585, 80.707, 78.829, 76.951, 78.067, 81.290, 84.513, 87.736, 90.958, 94.181, 97.404, 100.627, 103.849, 107.072, 110.295, 113.517, 116.740, 119.963, 123.186, 126.408, 129.631, 132.854, 136.077, 139.299, 142.522, 145.745, 148.968, 152.190, 155.413, 154.840, 152.899, 150.958, 149.017, 147.076, 145.135, 143.194, 141.253, 139.312, 137.371, 135.429, 133.488, 131.547, 129.606, 127.665, 125.724, 124.874, 126.734, 128.594, 130.454, 132.314, 134.174, 136.034, 137.894, 139.754, 141.614, 143.474, 145.334, 147.194, 149.054, 150.914, 152.774, 154.634, 156.494, 158.354, 160.214, 162.074, 163.934, 165.664, 161.795, 157.761, 153.726, 149.692, 145.658, 141.624, 137.589, 133.555, 129.521, 125.487, 121.452, 117.418, 113.384, 109.350, 105.316, 101.281, 97.247, 93.213, 89.179, 85.144, 81.110, 77.076, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
	data.TextGrid = {  "phone" : [ /** segment type, beginning, and end of each segment **/ [ "sp", 0.0124716553288, 0.271882086168 ], [ "M", 0.271882086168, 0.401587301587 ], [ "OW", 0.401587301587, 0.521315192744 ], [ "S", 0.521315192744, 0.660997732426 ], [ "T", 0.660997732426, 0.710884353741 ], [ "AH", 0.710884353741, 0.760770975057 ], [ "V", 0.760770975057, 0.820634920635 ], [ "DH", 0.820634920635, 0.860544217687 ], [ "IY", 0.860544217687, 0.940362811791 ], [ "AH", 0.940362811791, 0.980272108844 ], [ "D", 0.980272108844, 1.04013605442 ], [ "V", 1.04013605442, 1.10997732426 ], [ "EH", 1.10997732426, 1.21972789116 ], [ "N", 1.21972789116, 1.289569161 ], [ "CH", 1.289569161, 1.42925170068 ], [ "ER", 1.42925170068, 1.51904761905 ], [ "Z", 1.51904761905, 1.57891156463 ], [ "R", 1.57891156463, 1.66870748299 ], [ "AH", 1.66870748299, 1.69863945578 ], [ "K", 1.69863945578, 1.75850340136 ], [ "AO", 1.75850340136, 1.88820861678 ], [ "R", 1.88820861678, 1.91814058957 ], [ "D", 1.91814058957, 1.95804988662 ], [ "AH", 1.95804988662, 1.99795918367 ], [ "D", 1.99795918367, 2.07777777778 ], [ "AH", 2.07777777778, 2.10770975057 ], [ "N", 2.10770975057, 2.18752834467 ], [ "DH", 2.18752834467, 2.22743764172 ], [ "AH", 2.22743764172, 2.2873015873 ], [ "S", 2.2873015873, 2.42698412698 ], [ "B", 2.42698412698, 2.51678004535 ], [ "UH", 2.51678004535, 2.68639455782 ], [ "K", 2.68639455782, 2.79614512472 ], [ "sp", 2.79614512472, 2.81609977324 ], [ "R", 2.81609977324, 2.95578231293 ], [ "IY", 2.95578231293, 3.00566893424 ], [ "L", 3.00566893424, 3.09546485261 ], [ "IY", 3.09546485261, 3.23514739229 ], [ "AH", 3.23514739229, 3.27505668934 ], [ "K", 3.27505668934, 3.41473922902 ], [ "ER", 3.41473922902, 3.68412698413 ], [ "D", 3.68412698413, 3.75396825397 ], [ "sp", 3.75396825397, 4.01337868481 ] ] }

	/**
	 * Set up D3JS
	 */
	var x = d3.scaleLinear()
		.domain([0, 401])
		.range([0, width]);
	var y = d3.scaleLinear()
		.domain([0, 800])
		.range([height, 0]);

	/** Center the stream vertically **/
	var shift = d3.scaleLinear()
		.domain([0, 0])
		.range([-height/2, 0]);

	/** Draw a stream segment **/
	var pathGenerator = d3.area()
		.curve( myCurveBundle.beta(0) )
		.x(function(d, i) { return x(i); })
		.y1(function(d) { return y(d + 72 ); }) /** 72 is just some arbitrary thickess given to the graph **/
		.y0(function(d) { return y(d); });

	var svg = d3.select("body").append("svg")
		.attr("width", width)
		.attr("height", height);

	/**
	 * Render the chart
	 */
	
	/** Draw the stream, on a per-segment basis **/
	var path = svg.selectAll("path")
		.data(data.TextGrid.phone)
		.enter().append("path")
			.attr("transform", function(d, i) { return "translate(" + x(Math.floor(d[1]*100)) + ", " + shift(i) + ")"; })
			.attr("class", function(d) { return "segment " + d[0]; })
			.on('click', function(d, i) { playFromTo(Math.floor(d[1] * 1000), Math.floor(d[2] * 1000)); })
			.attr("d", function(d) { return pathGenerator(data.prosody.slice( Math.floor(d[1]*100), Math.floor(d[2]*100)+1)); });
.segment { fill: #ccc; }
.segment.sp { display: none; }

/** Adapted from Margaret Horrigan for American English **/
.segment.IY { fill: #7AC141; }
.segment.IH { fill: #F9C5DC; }
.segment.UH { fill: #FF00FF; }
.segment.UW { fill: #0153A5; }
.segment.EY { fill: #8B8C90; }
.segment.EH { fill: #E61A25; }
.segment.AX { fill: #DF5435; }
.segment.ER { fill: #805EAA; }
.segment.AO { fill: #E2A856; }
.segment.OY { fill: #2E3094; }
.segment.OW { fill: #FC2B1C; }
.segment.AE { fill: #21201E; }
.segment.AH { fill: #DF5435; }
.segment.AA { fill: #bf181f; }
.segment.AY { fill: #FFFFFF; }
.segment.AW { fill: #7C4540; }
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/d3.min.js"></script>
like image 90
Steve Avatar answered Sep 30 '22 22:09

Steve