Published unlisted
Edited
Apr 15, 2022
2 stars
Insert cell
Insert cell
diameter = sectorSize* (zoneRings.length-1) * 3
Insert cell
sectorSize = 28
Insert cell
trackSize = (diameter / 2) / (zoneRings.length+1) // (radius)/number_of_track_rings +1 = the number_of_track_rings + empty center track
Insert cell
zoneRings = [ 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 , 2, 2, 2, 2, 3, 3, 3] // based on area in a circle
Insert cell
textRings = { // which rings have text
let last = []
let uniq = [... new Set(zoneRings)]
for (let i = 0; i < uniq.length; ++i) {
last.push(zoneRings.lastIndexOf(uniq[i]))
}
return last
}
Insert cell
zoneName = ['Hold', 'Assess', 'Trial', 'Adopt']
Insert cell
teTracks = {

let tracks = [];
for (let trackNumber = 0; trackNumber < zoneRings.length; trackNumber++) {

// this sector calator has to be a number diviable by 4. That divids the circle into 4 segments.
let s = roundDownToMultiple(calculateCircumference(trackSize * trackNumber + trackSize + (trackSize / 2)) / sectorSize,4)

let sColours = []
let sQuad=[]
for (let sec = 0; sec < s; ++sec) {
let quady = whatQuad(s, sec, trackNumber)
sColours.push(quady[0]) // the colour of the sector
sQuad.push(`quadrant${quady[1]}`) // which of the 4 quadrents is the sector in
}
tracks.push({
track: trackNumber,
zone: zoneRings[trackNumber],
zoneRadiusInner: trackSize * trackNumber + trackSize,
zoneRadiusOuter: trackSize * trackNumber + (trackSize * 2),
sectors: d3.range(s).fill(1),
xy: [],
sectorColour: sColours, // the colour of the sector
sectorsQuadrant: sQuad // what quadrent are the sectors in
})
}


// this might be wrong as i have a gutter track for text
tracks.sectorsInZone = d3.range(4).fill(0) // the number of sectors in each zone of a quadrant
tracks.map( (d)=> {
tracks.sectorsInZone[d.ring]+=(d.sectors.length/4)
})

// fix text rings

for (let txtring = 0; txtring < textRings.length; ++txtring) {
let t = tracks[textRings[txtring]]
t.sectors=[1,1,1,1]

let cg = t.sectorColour.length/4
let nc = [t.sectorColour[0],t.sectorColour[cg],t.sectorColour[cg*2],t.sectorColour[cg*3]]
t.sectorColour = nc
t.sectorsQuadrant = ['quadrant0','quadrant1','quadrant2','quadrant3']
}

return tracks
}


Insert cell
Insert cell
colours = ['#E15554', '#FFD400', '#64F58D', '#8D80AD']
Insert cell
function whatQuad (sectors, sector, zone) {
let QuadColour = d3.scaleQuantize()
.domain([0, sectors])
.range(colours);

let QuadRingColour = d3.scaleLinear()
.domain([0, 4.5])
.range([QuadColour(sector), "black"]); // domain is 4 as we dont want it to be black

return [QuadRingColour(zoneRings[zone]), colours.indexOf(QuadColour(sector))] // returns colour and zone of sector
}
Insert cell
draw_radar = {

let strokeSize = 0.5;

const svgRadar = d3.create("svg")
.attr("viewBox", [0, 0, diameter, diameter])
.style('background', '#fff')
.attr('id', 'radarSVG')
.attr('width',1024)

const pie = d3.pie().value(d => d);

// draw the tracks[]
for (let trackNumber = 0; trackNumber < teTracks.length; trackNumber++) {

const pieGroup = svgRadar.append('g')
.attr('transform', `translate(${diameter / 2},${diameter / 2})`) // center this might be better in parent element
.attr('id', `track_${trackNumber}`);

const arc = d3.arc()
.innerRadius(teTracks[trackNumber].zoneRadiusInner) // inner radius of donut
.outerRadius(teTracks[trackNumber].zoneRadiusOuter) // outer radius of donut
const g = pieGroup.selectAll('.arc')
.data(() => {
let flippy = pie(teTracks[trackNumber].sectors)
if(textRings.includes(trackNumber)) {
let temp = flippy[1].startAngle // should use destructuring here
flippy[1].startAngle = flippy[1].endAngle
flippy[1].endAngle = temp

temp = flippy[2].startAngle
flippy[2].startAngle = flippy[2].endAngle
flippy[2].endAngle = temp
}
return flippy
})

.enter().append('g')
// add class names to define quadrant and zone.
.attr('class', (d, idx) => `${teTracks[trackNumber].sectorsQuadrant[idx]} ${zoneName[teTracks[trackNumber].zone]}`)
.attr('id', (d, idx) => { // add an id to each sector so we can address it.
teTracks[trackNumber].xy.push(arc.centroid(d)) // >>> "hacky mc hack face", way to get the center point of each sector
return `sector${idx}_${trackNumber}`
});
g.append('path')
.attr('d', arc) // svg d attribute defines a path to be drawn.
.attr('class','arc')
.attr('id', (d, idx) => `path${idx}_${trackNumber}`)
.style('fill', (d, sector) => teTracks[trackNumber].sectorColour[sector])
// .style('stroke', (d, sector) => teTracks[trackNumber].sectorColour[sector]) // invisable sectors
.style('stroke', '#2F0080') // visable sectors
.style('stroke-width', strokeSize);

}

// make arcs to add "zoneName[]" text.
// textRings[] array containing the track numbers to add the text too
// sector0_5 the group to append the text too Sector number _ track number
// path0_5 p sector numbr _ track

for (let txtTrack = 0; txtTrack < textRings.length; ++txtTrack) {
for (let q = 0; q < 4; ++q) {

svgRadar.select(`#sector${q}_${textRings[txtTrack]}`)
.append("text")
.attr('text-anchor','middle')
.attr('font-family','Verdana')
.attr('font-size',sectorSize )
.attr('dy', () => (q==1||q==2)? -sectorSize*0.33: sectorSize)
.style("fill", "#FFF")
.style("stroke", "#000")
.append("textPath")
.attr("xlink:href", `#path${q}_${textRings[txtTrack]}`)
.attr('startOffset','25%')
.text(zoneName[q]); // will fix text used later

}
}

return svgRadar.node()
}
Insert cell
Insert cell
function calculateCircumference(radius) { return 2 * Math.PI * radius; }
Insert cell
function roundDownToMultiple(number, multiple) { return number - (number % multiple); }
Insert cell
Insert cell
GoogleSheet = await d3.csv("https://docs.google.com/spreadsheets/d/18A7oDuavlh89rAmqcaXpqle8QLqIvlAkoEUxcObzuUM/gviz/tq?tqx=out:csv&sheet=1985253373")
Insert cell
// GoogleSheet.columns
Insert cell
// InputSanitizer = function () {
// var relaxedOptions = {
// allowedTags: ['b', 'i', 'em', 'strong', 'a', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li', 'ul',
// 'br', 'p', 'u'],
// allowedAttributes: {
// a: ['href']
// }
// }

// var restrictedOptions = {
// allowedTags: [],
// allowedAttributes: {},
// textFilter: function (text) {
// return text.replace(/&amp;/, '&')
// }
// }

// function trimWhiteSpaces (blip) {
// var processedBlip = {}
// _.forOwn(blip, function (value, key) {
// processedBlip[key.trim()] = value.trim()
// })
// return processedBlip
// }

// var self = {}
// self.sanitize = function (rawBlip) {
// var blip = trimWhiteSpaces(rawBlip)
// blip.description = sanitizeHtml(blip.description, relaxedOptions)
// blip.name = sanitizeHtml(blip.name, restrictedOptions)
// blip.isNew = sanitizeHtml(blip.isNew, restrictedOptions)
// blip.ring = sanitizeHtml(blip.ring, restrictedOptions)
// blip.quadrant = sanitizeHtml(blip.quadrant, restrictedOptions)

// return blip
// }

// self.sanitizeForProtectedSheet = function (rawBlip, header) {
// var blip = trimWhiteSpaces(rawBlip)

// const descriptionIndex = header.indexOf('description')
// const nameIndex = header.indexOf('name')
// const isNewIndex = header.indexOf('isNew')
// const quadrantIndex = header.indexOf('quadrant')
// const ringIndex = header.indexOf('ring')

// const description = descriptionIndex === -1 ? '' : blip[descriptionIndex]
// const name = nameIndex === -1 ? '' : blip[nameIndex]
// const isNew = isNewIndex === -1 ? '' : blip[isNewIndex]
// const ring = ringIndex === -1 ? '' : blip[ringIndex]
// const quadrant = quadrantIndex === -1 ? '' : blip[quadrantIndex]

// blip.description = sanitizeHtml(description, relaxedOptions)
// blip.name = sanitizeHtml(name, restrictedOptions)
// blip.isNew = sanitizeHtml(isNew, restrictedOptions)
// blip.ring = sanitizeHtml(ring, restrictedOptions)
// blip.quadrant = sanitizeHtml(quadrant, restrictedOptions)

// return blip
// }

// return self
// }
Insert cell
// blips = _.map(GoogleSheet, new InputSanitizer().sanitize)
Insert cell
// sanitizeHtml = require('https://bundle.run/sanitize-html@2.4.0')
Insert cell
// Inputs.table(blips)
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