Public
Edited
Oct 11, 2024
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
dashboardStyle = {
//
let style = {}
if(dashboardTheme=='Light'){
style = {
textColor: '#1f2329',
baseBG: '#f2f3f5',
cardBG: '#ffffff',
axisLine: '#1F2329',
axisText: '#1F2329',
borderColor: '#dee0e3'
}
}else if(dashboardTheme=='Dark'){
style = {
textColor: '#f0f0f0',
baseBG: '#0a0a0a',
cardBG: '#1a1a1a',
axisLine: '#EBEBEB',
axisText: '#EBEBEB',
borderColor: '#5f5f5f'
}
}
return style
}
Insert cell
Insert cell
Insert cell
Insert cell
function colorInterpolate(colorArray, {type = 'default'} = {}){
// 构建一个渐变函数对象,用节点作为key
const colorFuncObj = {};
for (let i = 1; i < colorArray.length; ++i) {
colorFuncObj[`${i}`] = [colorArray[i-1], colorArray[i]]
}

// 返回一个函数
return function(number) {
if(number<0 || number>1){
return '#fff'
}

let mappedNumber = number * (colorArray.length - 1)

//根据 前景色 或者 背景色 映射
if(type == 'default'){
}else if(type == 'front'){
mappedNumber = mappedNumber * colorPercentage.frontColor
// console.log('front color')
}else if(type == 'background'){
mappedNumber = mappedNumber * colorPercentage.backgroundColor + (1 - colorPercentage.backgroundColor) * (colorArray.length - 1)
// console.log('background color')
}else{
throw new Error('"type" is illegal')
}

for (const key in colorFuncObj){
if((mappedNumber >= +key-1)&&(mappedNumber < +key)){
const ratio = mappedNumber - Math.trunc(mappedNumber)
const [color1, color2] = colorFuncObj[`${key}`]
return d3.color(d3.interpolate(color1, color2)(ratio)).formatHex()
}else if(mappedNumber == colorArray.length-1){
return colorArray[colorArray.length - 1]
}
}
}
}
Insert cell
function colorSampling(colorArray,n, {type = 'default'} = {}){
if(n == 1){
return [colorInterpolate(colorArray, {type})(0.35)]
}else if(n == 2){
return [colorInterpolate(colorArray, {type})(0.25),
colorInterpolate(colorArray, {type})(0.6)]
}else if(n == 3){
return [colorInterpolate(colorArray, {type})(0.2),
colorInterpolate(colorArray, {type})(0.45),
colorInterpolate(colorArray, {type})(0.8)]
}else if(n == 4){
return [colorInterpolate(colorArray, {type})(0.1),
colorInterpolate(colorArray, {type})(0.35),
colorInterpolate(colorArray, {type})(0.6),
colorInterpolate(colorArray, {type})(0.85)]
}else{
return d3.quantize(colorInterpolate(colorArray, {type}),n)
}
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
colorInterpolate(newColorData.filter(e=>e['Source']===colorScheme).map(e=>e['Hex']))(.6)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function draw_funnel(div) {
const container = div.append('div')
.style('display','flex')
.style('flex-direction', 'column')
const title = container.append('div')
.append('p')
.text('招聘过程漏斗')
.style('margin', '0 0 0 0')
.style('padding', '18px 16px')
.style('font-family', 'sans-serif')
.style('font-size', '14px')
.style('font-weight', 500)
.style('color', dashboardStyle.textColor)

const svg = container.append('div')
.style('display','flex')
.style('flex-direction', 'column')
.append('svg')
.attr('width', `${chart_layout_specific.funnel.width}`)
.attr('height', `${chart_layout_specific.funnel.height}px`)


const chart_g = svg.append('g').attr('transform',`translate(${chart_layout_specific.funnel.margin.left},${chart_layout_specific.funnel.margin.top})`)



let funnelBarNum = funnel_data.slice(0, chartFirstN.funnel).length
if(funnelBarNum < 4){
funnelBarNum = 4
}
const funnelBarUnit = chart_layout_specific.funnel.innerHeight/(funnelBarNum * 3 - 1)


const chart_center_g = chart_g.append('g')
.attr('transform',d=>{
let yOffset = 0
if(chartFirstN.funnel < 4){
yOffset = (chart_layout_specific.funnel.innerHeight - funnelBarUnit*3*chartFirstN.funnel-funnelBarUnit)/2
}
return `translate(${chart_layout_specific.funnel.innerWidth/2}, ${yOffset})`
})

chart_center_g.append('g')
.attr('class', 'rects-g')
.selectAll('g')
.data(funnel_data.slice(0, chartFirstN.funnel))
.join('g')
.attr('transform', (d, i) => `translate(${- widthScale_funnel(d[1])/2}, ${i * funnelBarUnit * 3})`)
.call(g=>{
g.append('rect')
.attr('width', d => widthScale_funnel(d[1]))
.attr('height', funnelBarUnit * 2)
.attr('rx', 4)
.attr('fill', (d, i) => colorScale_funnel(d[0]))
})
.call(g=>{
g.append('text')
.attr('transform', (d, i) => `translate(${widthScale_funnel(d[1])/2}, ${funnelBarUnit + 6})`)
.text(d => d[1])
.style("font", "13px sans-serif")
.style("font-weight", '600')
.style("text-anchor","middle")
.attr('fill', d=>{
const bgColor = d3.color( colorScale_funnel(d[0])).formatHex()
const contrast = colorContrast(bgColor, textColor)
if(contrast < contrastThreshold){
return '#fff'
}else{
return textColor
}
})
})



const trapezoid_data = [];
for (let i = 0; i < funnel_data.slice(0, chartFirstN.funnel).length - 1; i++) {
trapezoid_data.push(funnel_data.slice(0, chartFirstN.funnel).slice(i, i + 2));
}

chart_center_g.append('g')
.attr('class', 'trapezoid-g')
.selectAll('g')
.data(trapezoid_data)
.join('g')
.attr('transform', (d, i) => `translate(${- widthScale_funnel(d[0][1])/2}, ${i * funnelBarUnit * 3 + funnelBarUnit * 2})`)
.call(g=>{
g.append('polygon')
.attr('points', (d, i) => {
const point0 = {
x:0,
y:0
},
point1 = {
x:widthScale_funnel(d[0][1]),
y:0
},
point2 = {
x:(widthScale_funnel(d[0][1])-widthScale_funnel(d[1][1]))/2+widthScale_funnel(d[1][1]),
y:funnelBarUnit
},
point3 = {
x:(widthScale_funnel(d[0][1])-widthScale_funnel(d[1][1]))/2,
y:funnelBarUnit
}
return `${point0.x},${point0.y} ${point1.x},${point1.y} ${point2.x},${point2.y} ${point3.x},${point3.y}`
})
.attr('fill', ()=>{
if(dashboardTheme=='Light'){
return '#F4F4F4'
}else if(dashboardTheme=='Dark'){
return '#3A3A3A'
}
})
})
}
Insert cell
Insert cell
heatmap_data = d3.rollups(heatmap_sample_data, v=>d3.sum(v, d=>d['value']), e => e['name'], e=> e['key'])
Insert cell
function draw_heatmap(div) {
const container = div.append('div')
.style('display','flex')
.style('flex-direction', 'column')
const title = container.append('div')
.append('p')
.text('员工绩效热力图')
.style('margin', '0 0 0 0')
.style('padding', '18px 16px')
.style('font-family', 'sans-serif')
.style('font-size', '14px')
.style('font-weight', 500)
.style('color', dashboardStyle.textColor)

const svg = container.append('div')
.style('display','flex')
.style('flex-direction', 'column')
.append('svg')
.attr('width', `${chart_layout_specific.heatmap.width}`)
.attr('height', `${chart_layout_specific.heatmap.height}`)


const chart_g = svg.append('g')
.attr('transform',`translate(${chart_layout_specific.heatmap.margin.left},${chart_layout_specific.heatmap.margin.top})`)

chart_g.append('g')
.call(yAxis_heatmap)
.call(g=>{
g.selectAll('.domain').remove()
})
.call(g=>{
g.selectAll('.domain, .tick line')
.attr('stroke', dashboardStyle.axisLine)
g.selectAll('text')
.attr('fill', dashboardStyle.axisText)
})
chart_g.append('g')
.attr('transform',`translate(0, ${chart_layout_specific.heatmap.innerHeight})`)
.call(xAxis_heatmap)
.call(g=>{
g.selectAll('.domain').remove()
})
.call(g=>{
g.selectAll('.domain, .tick line')
.attr('stroke', dashboardStyle.axisLine)
g.selectAll('text')
.attr('fill', dashboardStyle.axisText)
})

const chart_rects_g = chart_g
.append('g')
.attr('class', 'heatmap-rects-g')
chart_rects_g.selectAll('g')
.data(heatmap_data)
.join('g')
.attr('class', 'rects-horizontal-g')
.attr('transform',d=> `translate(0, ${yBandScale_heatmap(d[0])})`)
.selectAll('g')
.data(d=> d[1])
.join('g')
.attr('transform',d=> `translate(${xBandScale_heatmap(d[0])}, 0)`)
.call(g=>{
g.append('rect')
.attr('x', heatmap_attrs.gap/2)
.attr('y', heatmap_attrs.gap/2)
.attr('width', xBandScale_heatmap.bandwidth() - heatmap_attrs.gap)
.attr('height', yBandScale_heatmap.bandwidth() - heatmap_attrs.gap)
.attr('rx', 4)
.attr('fill', d=>colorScale_heatmap(d[1]))
})
.call(g=>{
g.append('g')
.call(
g=>{
g.append('text')
.attr('transform',`translate(${xBandScale_heatmap.bandwidth()*0.85}, ${yBandScale_heatmap.bandwidth()*0.44})`)
.text(d=>{
const ratio = d[1]/d3.sum(heatmap_sample_data, d=>d['value'])
return d3.format(".1%")(ratio)
})
.style("font", "13px sans-serif")
.style("font-weight", '600')
.style("text-anchor","end")
.attr('fill', d=>{
const bgColor = d3.color(colorScale_heatmap(d[1])).formatHex()
const contrast = colorContrast(bgColor, textColor)
if(contrast < contrastThreshold){
return '#fff'
}else{
return textColor
}
})
})
.call(
g=>{
g.append('text')
.attr('transform',`translate(${xBandScale_heatmap.bandwidth()*0.85}, ${yBandScale_heatmap.bandwidth()*0.44 + 15})`)
.text(d=>d[1])
.style("font", "13px sans-serif")
.style("font-weight", '600')
.style("text-anchor","end")
.attr('fill', d=>{
const bgColor = d3.color(colorScale_heatmap(d[1])).formatHex()
const contrast = colorContrast(bgColor, textColor)
if(contrast < contrastThreshold){
return '#fff'
}else{
return textColor
}
})
})

})


}
Insert cell
textColor = newColorData.filter(e=>e['Source']===colorScheme)[0]['Hex']
Insert cell
newColorData.filter(e=>e['Source']===colorScheme)
Insert cell
colorContrast(textColor, '#fff')
Insert cell
// contrastThreshold = 4.0
Insert cell
Insert cell
function colorContrast(c1, c2) {
const l1 = lrgb_luminance(rgb_lrgb(d3.rgb(c1)));
const l2 = lrgb_luminance(rgb_lrgb(d3.rgb(c2)));
return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
}
Insert cell
function lrgb_luminance([r, g, b]) {
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
Insert cell
function rgb_lrgb({r, g, b}) {
return [r / 255, g / 255, b / 255].map(rgb_lrgb1);
}
Insert cell
function rgb_lrgb1(v) {
return v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
}
Insert cell
Insert cell
newColorData.filter(e=>e['Source']===colorScheme)
Insert cell
colorSampling(newColorData.filter(e=>e['Source']===colorScheme).map(e=>e['Hex']), 12)
Insert cell
stepColors = colorSampling(newColorData.filter(e=>e['Source']===colorScheme).map(e=>e['Hex']), steps).map(e=>{
return {
Hex: e
}
})
Insert cell
Insert cell
function draw_radar(div){
const svg = div.append('svg').attr('width', '100%').attr('height', '100%')
const svg_radar_g = svg.append('g').attr('id', 'svg-plot-group')
.attr('transform', `translate(${plot_layout.margin.left},${plot_layout.margin.top})`);

const svg_radar_g_center_g = svg_radar_g.append('g').attr('id', 'svg-plot-group-center-g')
.attr('transform', `translate(${plot_layout.innerR},${plot_layout.innerR})`);

svg_radar_g_center_g.append('g')
.selectAll('circle')
.data(stepColors)
.join('circle')
.attr('r', r)
.attr('fill', d=>d['Hex'])
.attr('cx', d=>scaleR(chroma_okhcl(d['Hex'])[feilds.radar_rFeild]))
.attr('transform', d=>`rotate(${scaleCircle(chroma_okhcl(d['Hex'])['hue'])})`)
svg_radar_g_center_g.append('g')
.call(rAxis)
.call(g=>{
g.selectAll('.domain, .tick line')
.attr('stroke', dashboardStyle.axisLine)
g.selectAll('text')
.attr('fill', dashboardStyle.axisText)
})

svg_radar_g_center_g.append('g')
.call(circleAxis)
.call(g=>{
g.selectAll('.domain, .tick line')
.attr('stroke', dashboardStyle.axisLine)
g.selectAll('text')
.attr('fill', dashboardStyle.axisText)
})
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
chart_layout_specific = ({
donuts:{
width: chart_layout.frameWidth[0][0],
height: chart_layout.svgHeight,
},
groupedBars:{
width: chart_layout.frameWidth[0][1],
height: chart_layout.svgHeight,
margin:{
top: 12,
bottom: 36,
left: 64,
right: 36,
},
get innerWidth(){
return this.width - this.margin.left - this.margin.right
},
get innerHeight(){
return this.height - this.margin.top - this.margin.bottom
}
},
funnel:{
width: chart_layout.frameWidth[1][0],
height: chart_layout.svgHeight * 1.2,
margin:{
top: 8,
bottom: 24,
left: 24,
right: 24,
},
get innerWidth(){
return this.width - this.margin.left - this.margin.right
},
get innerHeight(){
return this.height - this.margin.top - this.margin.bottom
}
},
heatmap:{
width: chart_layout.frameWidth[1][1],
height: chart_layout.svgHeight * 1.2,
margin:{
top: 12,
bottom: 56,
left: 56,
right: 36,
},
get innerWidth(){
return this.width - this.margin.left - this.margin.right
},
get innerHeight(){
return this.height - this.margin.top - this.margin.bottom
}
},
})
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
circleAxis = function(node) {
node.append('g')
.attr('class','x-axis')
.call(g=>{
g.selectAll('g')
.data(scaleR.ticks(5))
.join('g')
.append('circle')
.attr('r', d=>scaleR(d))
.attr("fill", "none")
.attr("stroke", dashboardStyle.axisLine)//外圈边界
.attr('opacity', (d,n)=> !n?0:1)
.attr("stroke-width", 0.5)
.attr("stroke-dasharray", "2")
})
.call(g=>{
g.append('circle')
.attr('r', plot_layout.innerR)
.attr("fill", "none")
.attr("stroke", dashboardStyle.axisLine)//外圈边界
.attr("stroke-width", 0.5)
})
.call(g=>{
g.append('circle')
.attr('r', d=>scaleR(0))
.attr("fill", "none")
.attr("stroke", dashboardStyle.axisLine)//内圈边界
.attr("stroke-width", 0.5)
})
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
scaleColor_donuts = {
let samplingNum
if(chartFirstN.donuts< donuts_data.length){
samplingNum = chartFirstN.donuts
}else{
samplingNum = donuts_data.length
}
return d3.scaleOrdinal()
.domain(donuts_data.sort((a,b)=>b[1]-a[1]).map(d=> d[0]))
.range(colorSampling(newColorData.filter(e=>e['Source']===colorScheme).map(e=>e['Hex']), samplingNum, {type: 'front'}))
}
Insert cell
Insert cell
Insert cell
outerXScale_groupedBars =d3.scaleBand()
.domain(d3.groups(donuts_sample_data, d=>{
if(grouped_bars_attrs.groupOrder=='name - key'){
return d['name']
}else{
return d['key']
}
}).map(e=>e[0]).slice(0, chartFirstN.barChart_outer))
.range([0, chart_layout_specific.groupedBars.innerWidth])
.paddingInner(grouped_bars_attrs.paddingInner)
.paddingOuter(grouped_bars_attrs.paddingOuter)
Insert cell
innerXScale_groupedBars =d3.scaleBand()
.domain(d3.groups(donuts_sample_data, d=>{
if(grouped_bars_attrs.groupOrder=='name - key'){
return d['key']
}else{
return d['name']
}
}).map(e=>e[0]).slice(0, chartFirstN.barChart_inner))
.range([0, outerXScale_groupedBars.bandwidth()])
.paddingInner(grouped_bars_attrs.paddingInner_2)
.paddingOuter(grouped_bars_attrs.paddingOuter_2)
Insert cell
yScale_groupedBars = d3.scaleLinear()
.domain([0, d3.max(grouped_bars_data.slice(0, chartFirstN.barChart_outer), d=> d3.max(d[1].slice(0, chartFirstN.barChart_inner), d=>d[1]))])
.range([chart_layout_specific.groupedBars.innerHeight, 0])
Insert cell
d3.max(grouped_bars_data, d=> d3.max(d[1], d=>d[1]))
Insert cell
colorScale_groupBars = {
let keyName, samplingNum
if(grouped_bars_attrs.groupOrder=='name - key'){
keyName = 'key'
if(chartFirstN.barChart_inner < grouped_bars_data[0][1].length){
samplingNum = chartFirstN.barChart_inner
}else{
samplingNum = grouped_bars_data[0][1].length
}
}else{
keyName = 'name'
if(chartFirstN.barChart_inner < grouped_bars_data[0][1].length){
samplingNum = chartFirstN.barChart_inner
}else{
samplingNum = grouped_bars_data[0][1].length
}
}
return d3.scaleOrdinal()
.domain(d3.groups(donuts_sample_data, d=>d[keyName]).map(e=>e[0]))
.range(colorSampling(newColorData.filter(e=>e['Source']===colorScheme).map(e=>e['Hex']), samplingNum, {type: 'front'}))
}
Insert cell
Insert cell
xAxis_groupBars = d3.axisBottom().scale(outerXScale_groupedBars)
Insert cell
yAxis_groupBars = d3.axisLeft().scale(yScale_groupedBars).ticks(4)
Insert cell
Insert cell
widthScale_funnel = d3.scaleLinear()
.domain([0, d3.max(funnel_data, d=>d[1])])
.range([0, chart_layout_specific.funnel.innerWidth])
Insert cell
colorScale_funnel = {
let samplingNum
if(chartFirstN.funnel< funnel_data.length){
samplingNum = chartFirstN.funnel
}else{
samplingNum = funnel_data.length
}

return d3.scaleOrdinal()
.domain(funnel_data.map(d=>d[0]))
.range(colorSampling(newColorData.filter(e=>e['Source']===colorScheme).map(e=>e['Hex']), samplingNum, {type: 'front'}))
}
Insert cell
Insert cell
Insert cell
yBandScale_heatmap = d3.scaleBand()
.domain(d3.groups(heatmap_sample_data, d=>d['name']).map(d=>d[0]))
.range([0, chart_layout_specific.heatmap.innerHeight])
.paddingInner(0)
.paddingOuter(0)
Insert cell
xBandScale_heatmap = d3.scaleBand()
.domain(d3.groups(heatmap_sample_data, d=>d['key']).map(d=>d[0]))
.range([0, chart_layout_specific.heatmap.innerWidth])
.paddingInner(0)
.paddingOuter(0)
Insert cell
colorScale_heatmap = {
if(heatmap_attrs.colorClassed){
}else{
return d3.scaleSequential()
.domain([d3.max(heatmap_data, d=>d3.max(d[1], e=> e[1])), 0])
.interpolator(colorInterpolate(newColorData.filter(e=>e['Source']===colorScheme).map(e=>e['Hex']), {type: 'background'}))
}
}
// d3.scaleOrdinal()
// .domain(funnel_data.map(d=>d[0]))
// .range(colorSampling(newColorData.filter(e=>e['Source']===colorScheme).map(e=>e['Hex']), samplingNum))

Insert cell
Insert cell
yAxis_heatmap = d3.axisLeft().scale(yBandScale_heatmap)
Insert cell
xAxis_heatmap = d3.axisBottom().scale(xBandScale_heatmap)
Insert cell
Insert cell
function ramp(color, n = 512) {
const canvas = DOM.canvas(n, 1);
const context = canvas.getContext("2d");
canvas.style.margin = "0 -14px";
canvas.style.width = "calc(100% + 28px)";
canvas.style.height = "40px";
canvas.style.imageRendering = "-moz-crisp-edges";
canvas.style.imageRendering = "pixelated";
for (let i = 0; i < n; ++i) {
context.fillStyle = color(i / (n - 1));
context.fillRect(i, 0, 1, 1);
}
return canvas;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
sample_data = d3.csv(getCsvUrl(url_sample_data), (e)=>{
return {
name: e['Name'],
key: e['Key'],
value: +e['Value'].replace(',','').replace(' ',''),
chartType: e['Chart']
}
})
Insert cell
donuts_sample_data = sample_data.filter(e=>e['chartType']== 'donuts_groupedbars')
Insert cell
funnel_sample_data = sample_data.filter(e=>e['chartType']== 'funnel')
Insert cell
heatmap_sample_data = sample_data.filter(e=>e['chartType']== 'heatmap')
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more