Source: component/legendThreshold.js

import * as d3 from "d3";

/**
 * Reusable Threshold Legend Component
 *
 * @module
 * @see https://bl.ocks.org/mbostock/4573883
 */
export default function() {

	/* Default Properties */
	let width = 100;
	let height = 150;
	let thresholdScale;
	let opacity = 1;
	let transition = { ease: d3.easeLinear, duration: 0 };

	/**
	 * Constructor
	 *
	 * @constructor
	 * @alias legendThreshold
	 * @param {d3.selection} selection - The chart holder D3 selection.
	 */
	function my(selection) {
		height = (height ? height : this.attr("height"));
		width = (width ? width : this.attr("width"));

		const domainMin = d3.min(thresholdScale.domain());
		const domainMax = d3.max(thresholdScale.domain());
		const domainMargin = (domainMax - domainMin) * 0.1;

		const x = d3.scaleLinear()
			.domain([domainMin - domainMargin, domainMax + domainMargin])
			.range([0, height]);

		// Legend Container
		const legendContainer = selection.selectAll(".legendContainer")
			.data([0]);

		const legendContainerEnter = legendContainer.enter()
			.append("g")
			.classed("legendContainer", true)
			.attr("width", width)
			.attr("height", height)
			.merge(legendContainer);

		// Use D3 Axis to generate scale ticks
		const axis = d3.axisRight(x)
			.tickSize(30)
			.tickValues(thresholdScale.domain());

		legendContainerEnter.transition()
			.ease(transition.ease)
			.duration(transition.duration)
			.call(axis)
			.selectAll(".domain")
			.attr("opacity", 0);

		const colors = legendContainerEnter.selectAll("rect")
			.data(thresholdScale.range().map((color) => {
				const d = thresholdScale.invertExtent(color);
				if (typeof d[0] === "undefined") d[0] = x.domain()[0];
				if (typeof d[1] === "undefined") d[1] = x.domain()[1];
				return d;
			}));

		colors.enter()
			.append("rect")
			.merge(colors)
			.transition()
			.ease(transition.ease)
			.duration(transition.duration)
			.attr("width", 20)
			.attr("y", (d) => x(d[0]))
			.attr("height", (d) => x(d[1]) - x(d[0]))
			.style("fill", (d) => thresholdScale(d[0]))
			.attr("fill-opacity", opacity)
			.attr("stroke", (d) => thresholdScale(d[0]))
			.attr("stroke-width", 1);
	}

	/**
	 * Width Getter / Setter
	 *
	 * @param {number} _v - Width in px.
	 * @returns {*}
	 */
	my.width = function(_v) {
		if (!arguments.length) return width;
		width = _v;
		return my;
	};

	/**
	 * Height Getter / Setter
	 *
	 * @param {number} _v - Height in px.
	 * @returns {*}
	 */
	my.height = function(_v) {
		if (!arguments.length) return height;
		height = _v;
		return my;
	};

	/**
	 * Threshold Scale Getter / Setter
	 *
	 * @param {d3.scale} _v - D3 scale.
	 * @returns {*}
	 */
	my.thresholdScale = function(_v) {
		if (!arguments.length) return thresholdScale;
		thresholdScale = _v;
		return my;
	};

	/**
	 * Transition Getter / Setter XX
	 *
	 * @param {d3.transition} _v - Transition.
	 * @returns {*}
	 */
	my.transition = function(_v) {
		if (!arguments.length) return transition;
		transition = _v;
		return this;
	};

	/**
	 * Opacity Getter / Setter
	 *
	 * @param {number} _v - Opacity 0 -1.
	 * @returns {*}
	 */
	my.opacity = function(_v) {
		if (!arguments.length) return opacity;
		opacity = _v;
		return this;
	};

	/**
	 * Transition Getter / Setter
	 *
	 * @param {d3.transition} _v - Transition.
	 * @returns {*}
	 */
	my.transition = function(_v) {
		if (!arguments.length) return transition;
		transition = _v;
		return this;
	};

	return my;
}