Skip to content

Commit

Permalink
trivariate progress
Browse files Browse the repository at this point in the history
  • Loading branch information
joewdavies committed Jan 14, 2025
1 parent a15b80e commit 862d5e9
Show file tree
Hide file tree
Showing 3 changed files with 205 additions and 54 deletions.
130 changes: 103 additions & 27 deletions build/eurostatmap.js

Large diffs are not rendered by default.

36 changes: 24 additions & 12 deletions src/core/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,40 @@
export function updateCSSRule(selector, property, value) {
// Validate the selector
if (!selector.startsWith('.') && !selector.startsWith('#')) {
throw new Error('Invalid selector: Must start with "." for classes or "#" for IDs.');
throw new Error('Invalid selector: Must start with "." for classes or "#" for IDs.')
}

// Check if the rule already exists in any stylesheet
const styleSheets = Array.from(document.styleSheets);
const styleSheets = Array.from(document.styleSheets)
for (const styleSheet of styleSheets) {
try {
const rules = styleSheet.cssRules || styleSheet.rules;
const rules = styleSheet.cssRules || styleSheet.rules
for (const rule of rules) {
if (rule.selectorText === selector) {
// Update the property if the rule exists
rule.style[property] = value;
return;
rule.style[property] = value
return
}
}
} catch (e) {
// Some stylesheets (e.g., cross-origin) may not be accessible
console.warn(`Could not access rules in stylesheet:`, e);
console.warn(`Could not access rules in stylesheet:`, e)
}
}

// If the rule doesn't exist, create a new stylesheet and add it
let customSheet = document.getElementById('custom-styles');
let customSheet = document.getElementById('custom-styles')
if (!customSheet) {
customSheet = document.createElement('style');
customSheet.id = 'custom-styles';
document.head.appendChild(customSheet);
customSheet = document.createElement('style')
customSheet.id = 'custom-styles'
document.head.appendChild(customSheet)
}

// Add the new rule to the custom stylesheet
try {
customSheet.sheet.insertRule(`${selector} { ${property}: ${value}; }`, customSheet.sheet.cssRules.length);
customSheet.sheet.insertRule(`${selector} { ${property}: ${value}; }`, customSheet.sheet.cssRules.length)
} catch (e) {
console.error(`Failed to insert rule: ${selector} { ${property}: ${value}; }`, e);
console.error(`Failed to insert rule: ${selector} { ${property}: ${value}; }`, e)
}
}

Expand Down Expand Up @@ -372,3 +372,15 @@ export function getParameterByName(name) {
results = regex.exec(location.search)
return !results ? null : decodeURIComponent(results[1].replace(/\+/g, ' '))
}

export const hexToRgb = (hex) => {
hex = hex.replace('#', '')
if (hex.length === 3) {
hex = hex
.split('')
.map((h) => h + h)
.join('')
}
const int = parseInt(hex, 16)
return [(int >> 16) & 255, (int >> 8) & 255, int & 255]
}
93 changes: 78 additions & 15 deletions src/legend/legend-choropleth-trivariate.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as Legend from './legend'
import { select, selectAll } from 'd3-selection'
import { executeForAllInsets, getFontSizeFromClass } from '../core/utils'
import { executeForAllInsets, getFontSizeFromClass, hexToRgb } from '../core/utils'

/**
* A legend for choropleth-trivariate maps
Expand Down Expand Up @@ -81,10 +81,10 @@ function drawTrivariateVennDiagram(container, colors, labels) {
const circleRad = 30

//draw Circle 1
const circle1 = container
.append('circle')
.attr('r', circleRad)
.attr('transform', 'translate(' + xCenter1 + ',' + yCenter1 + ')')
// const circle1 = container
// .append('circle')
// .attr('r', circleRad)
// .attr('transform', 'translate(' + xCenter1 + ',' + yCenter1 + ')')

//add'l specs for Circle 2
const offsetFactor = 1.2
Expand All @@ -93,20 +93,20 @@ function drawTrivariateVennDiagram(container, colors, labels) {
const yCenter2 = yCenter1 //creating new var for clarity

//draw Circle 2
const circle2 = container
.append('circle')
.attr('r', circleRad)
.attr('transform', 'translate(' + xCenter2 + ',' + yCenter2 + ')')
// const circle2 = container
// .append('circle')
// .attr('r', circleRad)
// .attr('transform', 'translate(' + xCenter2 + ',' + yCenter2 + ')')

//add'l specs for Circle 3
const xCenter3 = xCenter1 + offset / 2
const yCenter3 = yCenter1 + (Math.sqrt(3) * offset) / 2

//draw Circle 3
const circle3 = container
.append('circle')
.attr('r', circleRad)
.attr('transform', 'translate(' + xCenter3 + ',' + yCenter3 + ')')
// const circle3 = container
// .append('circle')
// .attr('r', circleRad)
// .attr('transform', 'translate(' + xCenter3 + ',' + yCenter3 + ')')

//compute first points of intersection
const triHeight = Math.sqrt(circleRad ** 2 - (offset / 2) ** 2)
Expand Down Expand Up @@ -190,15 +190,25 @@ function drawTrivariateVennDiagram(container, colors, labels) {
const ptCycle = points.map((i) => xPoints[i - 1]).concat(points.map((i) => yPoints[i - 1]))
const shape = makeIronShapes(ptCycle)

container.append('path').attr('d', shape).attr('class', 'segment').attr('fill', '#cc6666').attr('opacity', 1)
let color
if (index == 0) {
color = multiplyBlendMultipleHex([colors[2], colors[0]]) // pink + cyan
} else if (index == 1) {
color = multiplyBlendMultipleHex([colors[1], colors[0]]) // cyan + yellow
} else if (index == 2) {
color = multiplyBlendMultipleHex([colors[1], colors[2]]) // pink + yellow
}

container.append('path').attr('d', shape).attr('class', 'segment').attr('fill', color).attr('opacity', 1)
})

// nucleus (combination of all 3 colors)
roundedTriPoints.forEach((points, index) => {
const ptCycle = points.map((i) => xPoints[i - 1]).concat(points.map((i) => yPoints[i - 1]))
const shape = makeRoundedTri(ptCycle)
const color = multiplyBlendMultipleHex(colors)

container.append('path').attr('d', shape).attr('class', 'segment').attr('fill', '#66cc66').attr('opacity', 1)
container.append('path').attr('d', shape).attr('class', 'segment').attr('fill', color).attr('opacity', 1)
})

container
Expand All @@ -211,6 +221,26 @@ function drawTrivariateVennDiagram(container, colors, labels) {
})

// label intersects
const yOffset = container
.append('text')
.text(labels[0])
.attr('x', xCenter1 - circleRad - 3)
.attr('y', xCenter1)
.attr('class', 'venn-label')
.attr('text-anchor', 'end')
container
.append('text')
.text(labels[1])
.attr('x', xCenter2 + circleRad + 3)
.attr('y', yCenter2)
.attr('class', 'venn-label')
container
.append('text')
.text(labels[2])
.attr('x', xCenter3)
.attr('y', yCenter3 + circleRad + 15)
.attr('class', 'venn-label')
.attr('text-anchor', 'middle')
// container.append('text').text('1').attr('x', xIsect1).attr('y', yIsect1)
// container.append('text').text('2').attr('x', xIsect2).attr('y', yIsect2)
// container.append('text').text('3').attr('x', xIsect3).attr('y', yIsect3)
Expand All @@ -219,6 +249,39 @@ function drawTrivariateVennDiagram(container, colors, labels) {
// container.append('text').text('6').attr('x', xIsect6).attr('y', yIsect6)
}

//blends two colors using 'multiply' blending mode. Returns the blended color as an RGB string
function multiplyBlendMultipleHex(colors) {
// Convert hex color to RGB
const hexToRgb = (hex) => {
hex = hex.replace('#', '')
if (hex.length === 3) {
hex = hex
.split('')
.map((h) => h + h)
.join('')
}
const int = parseInt(hex, 16)
return [(int >> 16) & 255, (int >> 8) & 255, int & 255]
}

// Convert RGB to hex
const rgbToHex = ([r, g, b]) => `#${[r, g, b].map((c) => c.toString(16).padStart(2, '0')).join('')}`

// Convert all hex colors to RGB arrays
const rgbColors = colors.map(hexToRgb)

// Initialize the result with the first color
let blended = [...rgbColors[0]]

// Sequentially multiply each color with the result
for (let i = 1; i < rgbColors.length; i++) {
blended = blended.map((v, idx) => Math.round((v / 255) * (rgbColors[i][idx] / 255) * 255))
}

// Return the blended color as a hex code
return rgbToHex(blended)
}

/**
* Draws a trivariate legend as a Venn Diagram
*/
Expand Down

0 comments on commit 862d5e9

Please sign in to comment.