Skip to content

Instantly share code, notes, and snippets.

@FraserChapman
Last active August 31, 2015 23:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save FraserChapman/3487b048b07a74c3e20a to your computer and use it in GitHub Desktop.
Save FraserChapman/3487b048b07a74c3e20a to your computer and use it in GitHub Desktop.
linear gradient with draggable stops.

Linear gradient with stops offset proportionally to the input domain value they represent.

For example given the domain: [0, 5, 10, 50, 100] And the range: ["#190729", "#191996", "#2F972F", "#FFFF66", "#FF1919"] The stops would be offset by: 0%, 5%, 10%, 50%, 100%

The colour at the percentage offset is represented by the hex value at the same index position in the scheme, colours between stops are interpolated in a linear fashion.

When a scheme is selected a random domain is generated and the gradient is redrawn using it. Information on the current domain and selected range is given along with the stop percentages and the domain values that they correspond to.

Aditionally the axis ticks can be dragged horizontally to alter the gradient.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
<select id="schemeSelect"></select>
<svg id="colourGradient"></svg>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src="script.js"></script>
</body>
</html>
(function(){
var schemeSelect = d3.select("#schemeSelect"),
margin = 50,
width = document.body.clientWidth-margin*2,
height = document.body.clientHeight-margin*2,
domain = randDomain(),
range = {
Default: ["#190729", "#191996", "#2F972F", "#FFFF66", "#FF1919"],
Warm: ['#FFFFB2', '#FECC5C', '#FD8D3C', '#F03B20', '#BD0026'],
Cool: ['#F1EEF6', '#5CFECC', '#74A9CF', '#2B8CBE', '#045A8D'],
DkLg: ['#000000', '#444444', '#898989', '#CCCCCC', '#FFFFFF'],
LgDk: ['#FFFFFF', '#CCCCCC', '#898989', '#444444', '#000000'],
Spectral: ["#D7191C", "#FDAE61", "#FFFFBF", "#ABDDA4", "#2B83BA"],
Earth: ['#030371', '#80CDC1', '#F5F5F5', '#DFC27D', '#037103'],
Parula: ["#352A87", "#1481D6", "#25B5A9", "#E9B94E", "#F9FB0E"],
Jet: ["#00008F", "#0050FF", "#70FF8F", "#FF6000", "#750505"],
HSV: ["#FF0000", "#FFD700", "#00FF58", "#0028FF", "#FF00D7"],
Accent: ["#7FC97F", "#BEAED4", "#FDC086", "#FFFF99", "#386CB0"],
Paired: ["#A6CEE3", "#1F78B4", "#B2DF8A", "#33A02C", "#FB9A99"],
Set: ["#E41A1C", "#377EB8", "#4DAF4A", "#984EA3", "#FF7F00"]
},
selectedRange = range.Default,
svg = d3.select("#colourGradient")
.append("g")
.attr("transform", "translate(" + margin + "," + margin + ")"),
defs = svg.append('defs'),
linearGradinet = defs.append('linearGradient')
.attr("id", "gradient")
.attr("x2", "100%");
max = d3.max(domain),
cscale = d3.scale.linear()
.domain(domain)
.range(selectedRange)
.interpolate(d3.interpolateRgb),
xscale = d3.scale.linear(),
xAxis = d3.svg.axis()
.scale(xscale)
.orient("bottom")
.tickValues(domain)
.tickSize(20)
.tickFormat(function(d) {
var percent = Math.round((d / max) * 100);
return d + " [" + percent + "%]"
})
drag = d3.behavior.drag()
.on("dragstart", function(){
d3.event.sourceEvent.stopPropagation();
})
.on("drag", function(d, i){
// ignore first and last ticks
if(i === 0 || i === domain.length-1)
{
return;
}
// keep within horizontal bounds
if(d3.event.x < 0 || d3.event.x > width)
{
return;
}
// stop tick reordering
var value = Math.round(xscale.invert(d3.event.x));
// -1 and +1 are ok indecies as we ingnored fist and last.
if(value <= domain[i-1] || value >= domain[i+1])
{
return;
}
domain[i] = value;
d3.select(this).attr("transform", "translate(" + d3.event.x + ",0)");
paint(); // redraw on drag...
})
.on("dragend", function(d){});
// returns a random number between min and max inclusive
function randRange(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
// creates a random domain
function randDomain() {
return [0, randRange(5, 10), randRange(12, 20), randRange(22, 35), randRange(38, 50)];
}
// init
(function() {
// build the scheme select options
for (var scheme in range) {
schemeSelect.append("option").text(scheme);
}
// set up the scheme select change event handler
schemeSelect.on("change", function () {
// get the selected scheme
var index = schemeSelect.property("selectedIndex"),
option = schemeSelect.node()[index];
selectedRange = range[option.text];
domain = randDomain();
max = d3.max(domain);
paint(); // redaw on selection...
});
// set up the window resize event handler
d3.select(window).on('resize', function(){
width = document.body.clientWidth-margin*2;
height = document.body.clientHeight-margin*2;
paint(); // redraw on resize...
});
// apend the various elements
linearGradinet.selectAll("stop")
.data(selectedRange)
.enter()
.append("stop");
svg.append("rect")
.attr("id", "gradientFill")
.attr("fill", "url(#gradient)");
svg.append("rect")
.attr("id", "panel")
.style("fill", "white");
svg.append("text")
.attr("id", "range-label");
svg.append("text")
.attr("id", "domain-label");
svg.append("g")
.attr("class", "x axis");
paint(); // inital draw...
})();
// draw
function paint() {
xscale.domain(d3.extent(domain)).range([0, width]);
xAxis.scale(xscale).tickValues(domain);
svg.select(".x.axis").call(xAxis)
.attr("transform", "translate(0," + (height-60) + ")");
svg.selectAll(".tick").call(drag);
svg.select("#domain-label")
.attr("dy", height)
.text("domain: " + domain.toString());
svg.select("#range-label")
.attr("dy", height+margin/2)
.text("range: " + selectedRange.toString());
svg.select("#panel")
.attr("y", height-60)
.attr("width", width)
.attr("height", 60);
svg.select("#gradientFill")
.style("width", width)
.style("height", height);
svg.selectAll(".tick")
.append("rect")
.attr("x", -5)
.attr("width", 10)
.attr("height", 10)
.style("fill", function(d, i){
return selectedRange[i];
});
linearGradinet.selectAll("stop")
.data(selectedRange)
.attr("offset", function (d, i) {
var percent = (domain[i] / max) * 100;
return percent + "%";
})
.style("stop-color", function (d) {
return d;
});
}
})();
html, body {
height: 100%;
margin: 0;
}
body {
font-family: arial, sans-serif;
font-size: 12px;
overflow: hidden;
}
#schemeSelect {
position: absolute;
top: 20px;
left: 50px;
}
#colourGradient,
.colourRange {
width: 100%;
height: 100%;
}
.axis .tick {
cursor: pointer;
}
.axis path,
.axis line {
fill: none;
stroke: #222222;
shape-rendering: crispEdges;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment