Published
Edited
Apr 24, 2021
1 fork
13 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// Extract colours from the image using the ColorThief package
colorThief_palette = {
const colorThief = new ColorThief();
let colorThief_palette = colorThief.getPalette(image, 20);
// convert array items to 'rgb(...)' strings
colorThief_palette.forEach(function(d,i){
colorThief_palette[i] = "rgb(" + d[0] + "," + d[1] + "," + d[2] + ")";
});
return colorThief_palette;
}
Insert cell
colour_data_hsl = {
// convert colour_data rgbs to HSL
let object_colours_full = [];

colorThief_palette.forEach(function (d) {
var colour_hsl = d3.hsl(d);
if (isNaN(colour_hsl.h)) {
colour_hsl.h = 0;
};
if (isNaN(colour_hsl.s)) {
colour_hsl.s = 0;
};
if (isNaN(colour_hsl.l)) {
colour_hsl.l = 0;
};
object_colours_full.push({
"rgb": d,
"h": colour_hsl.h,
"s": colour_hsl.s,
"l": colour_hsl.l
});
});
// remove low saturation / low lightness colours
const saturationLimit = 0.2,
lightnessLowerLimit = 0.2,
lightnessHigherLimit = 1;
let object_colours_full_lowSaturationRemoved = [];

object_colours_full.forEach(function (d, i) {
if (d.s > saturationLimit && d.l < lightnessHigherLimit && d.l > lightnessLowerLimit) {
object_colours_full_lowSaturationRemoved.push(d);
}
});
return object_colours_full_lowSaturationRemoved;

}
Insert cell
// construct data structure from which to create colour wheel graphic

pie_chart_matrix = {

let pie_chart_matrix = [];
// array representing 12 sectors of circle, from 30-360 at 30degree intervals
const pie_chart_range = d3.range(30, 390, 30);
// fill pie_chart_matrix array with colours and matching circle sector angles (from pie_chart_range) according to each hues' angle on an artist's colour wheel
for (let i = 0; i < colour_data_hsl.length; i++) {
// Use the ColourWheel object (credit Ben Knight https://github.com/benknight/kuler-d3) to shift the angle for the hue to bring it closer to an artist's colour wheel (Red-Yellow-Blue rather than Red-Green-Blue)
const converted_h = ColourWheel.scientificToArtisticSmooth(colour_data_hsl[i].h);
for (let j = 0; j < pie_chart_range.length; j++) {
if (converted_h < pie_chart_range[j]) {
pie_chart_matrix.push([pie_chart_range[j], colour_data_hsl[i]]);
break;
};
};
};
function compare(a, b) {
if (a[1].l > b[1].l) return -1;
if (a[1].l < b[1].l) return 1;
return 0;
}
// order segments from darkest at the outside to lightest in the middle
pie_chart_matrix.sort(compare);

// calculate the total count and order of colors in each sector
// add this information to pie_chart_matrix
let counts = {};
let pie_chart_matrix_counts = [];
pie_chart_matrix.forEach(function (x) {
let match_count = (counts[x[0]] || 0) + 1;
counts[x[0]] = match_count;
let array_entry = x.push(match_count);
pie_chart_matrix_counts.push(array_entry);
});

pie_chart_matrix.forEach(function (d, i) {
let match_total = counts[d[0]];
pie_chart_matrix[i].push(match_total);
});
return pie_chart_matrix;
}
Insert cell
Insert cell
display = {
// create div element where image, colour wheel graphic, and colour palette grid are displayed together
const display = d3.create("div").attr("width", 800);

// create image element
display
.append("img")
.attr("id", "display_image")
.attr("src", image_urls[+selected_image])
.attr("width", 500)
.style("padding-right", "10px");

// Create an svg element for the colour wheel graphic
const vis = display
.append("svg")
.attr("width", 150)
.style("vertical-align", "top")
.style("background", "rgba(212, 211, 211, 1)");

// Create an svg for the colour palette in grid form
const palette_grid = display
.append("svg")
.attr("width", 150)
.style("vertical-align", "top")
.style("padding-left", "10px");

// generate the colour palette as a grid
colour_data_hsl.forEach(function (d, i) {
palette_grid
.append("rect")
.attr("fill", d.rgb)
.attr("width", 20)
.attr("height", 10)
.attr("transform", "translate(0," + i * 10 + ")");
});

//
// GENERATE COLOUR WHEEL GRAPHIC
//
const vis_colour_wheel = vis
.append("g")
.attr("transform", "translate(25,25)");

// white circle background
vis_colour_wheel
.append("g")
.attr("class", "r axis")
.attr("transform", "translate(50,50)")
.append("circle")
.attr("r", outerRadius)
.attr("fill", "white")
.attr("stroke", "#d4d4dd")
.attr("stroke-dasharray", 2);

// for each element of pie_chart_matrix, draw a circle sector of that colour
vis_colour_wheel
.selectAll("path")
.data(pie_chart_matrix)
.enter()
.append("path")
// The arc generator produces a circular or annular sector, as in a pie or donut chart https://github.com/d3/d3-shape#arcs
.attr("d", arc)
.style("fill", function (d) {
return d[1].rgb;
})
.attr("transform", "translate(50,50) rotate(-15)");

// draw lines between sectors
let ga_vis = vis_colour_wheel
.append("g")
.attr("class", "a axis")
.attr("transform", "translate(50,50)")
.selectAll("g")
.data(d3.range(-15, 345, 30))
.enter()
.append("g")
.attr("transform", function (d) {
return "rotate (" + d + ")";
});

ga_vis
.append("line")
.attr("x2", outerRadius)
.attr("stroke", "#d4d4dd")
.attr("stroke-dasharray", 2);

return display;
}
Insert cell
Insert cell
Insert cell
Insert cell
// ColourWheel functions convert between 'artistic' RYB (Red - Yellow - Blue) and 'scientific' RGB (Red - Green - Blue) colour wheels
// from Ben Knight https://github.com/benknight/kuler-d3

ColourWheel = ({
mapRange: function (value, fromLower, fromUpper, toLower, toUpper) {
return (toLower + (value - fromLower) * ((toUpper - toLower) / (fromUpper - fromLower)));
},
scientificToArtisticSmooth: function (hue) {
return (
hue < 35 ? hue * (60 / 35) :
hue < 60 ? this.mapRange(hue, 35, 60, 60, 122) :
hue < 120 ? this.mapRange(hue, 60, 120, 122, 165) :
hue < 180 ? this.mapRange(hue, 120, 180, 165, 218) :
hue < 240 ? this.mapRange(hue, 180, 240, 218, 275) :
hue < 300 ? this.mapRange(hue, 240, 300, 275, 330) :
this.mapRange(hue, 300, 360, 330, 360));
}
})
Insert cell
angle = d3.scaleLinear().domain([0, 360]).range([0, 2 * Math.PI]);
Insert cell
arc = d3.arc()
.innerRadius(function (d) {
if (d[3] > 1) {
return outerRadius * ((d[2] - 1) / d[3]);
} else {
return innerRadius;
}
})
.outerRadius(function (d) {
return (outerRadius * (d[2] / d[3]));
})
.startAngle(function (d) {
return angle(d[0] - 30);
})
.endAngle(function (d) {
return angle(d[0]);
});
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more