Public
Edited
Jul 5, 2024
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
Insert cell
Insert cell
newColorData = d3.csv(getCsvUrl(url_new_color), (e, i)=>{
if(e['Hex'].trim().charAt(0)==='#'){
return {
Source: e['Source'],
Serise: e['Serise'],
Hex: e['Hex'],
}
}else{
return {
Source: e['Source'],
Serise: e['Serise'],
Hex: '#'+e['Hex'],
}
}
})
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
color_swatchs = {
const swatchsLayout = {
width,
height: 1180,
margin:{
top: 40,
bottom: 40,
left: 40,
right: 40
}
}
const svg = d3.create('svg').attr('viewBox', [0, 0, swatchsLayout.width, swatchsLayout.height])
const g = svg.append('g')
.attr('transform', `translate(${swatchsLayout.margin.left},${swatchsLayout.margin.top})`)


const themeColorData = newColorData
.filter(e=>e['Source']===colorScheme)
.map((e, i)=> {
return {
...e,
index: i+1
}
})
const swatchColorData = []

for (let i = 0; i < themeColorData.length; ++i) {
for (let j = i+1; j < themeColorData.length; ++j) {
swatchColorData.push([themeColorData[i], themeColorData[j]])
}
}

const swatchPairs_g = g.selectAll('g')
.data(swatchColorData)
.join('g')
.attr('transform',d=>{
const i = d[1]['index'] - 2
const j = d[0]['index'] - 1
return `translate(${i * 80},${j * 90})`
})

const distanceThreshhold_1 = .085
const distanceThreshhold_2 = .125
swatchPairs_g
.append('g')
.attr('class', 'color-distance-g')
.attr('transform', `translate(13, 46)`)
.raise()
.append('text')
.datum(d=>{
d['distance'] = getColorDistance(d[0]['Hex'], d[1]['Hex'])
return d
})
.text(d=>{
return d3.format('.2f')(d['distance'])
})
.style("font", "12px sans-serif")
.style("font-weight", d=>{
if(d['distance'] <= distanceThreshhold_1){
return 'bold'
}else if(d['distance'] <= distanceThreshhold_2){
return '600'
}else{
return '500'
}
})
.style("text-anchor","middle")
.attr('fill', d=>{
if(d['distance'] <= distanceThreshhold_1){
return '#D7394F'
}else if(d['distance'] <= distanceThreshhold_2){
return '#B87900'
}else{
return '#8F959E'
}
})
swatchPairs_g.append('g')
.selectAll('g')
.data(d=>d)
.join('g')
.attr('transform', (d,i)=>`translate(${ i * 26},${0})`)
.lower()
.call(g=>{
g.append('circle')
.attr('r', 20)
.attr('fill', d=>d['Hex'])
})
.call(g=>{
g.append('text')
.text(d=>d['index'])
.attr('transform',`translate(0, 32)`)
.style("font", "10px sans-serif")
.style("font-weight", '500')
.style("text-anchor","middle")
.attr('fill', '#8F959E')
})
return svg.node()
}
Insert cell
function getColorDistance(hex_1, hex_2){
const { lightness: lightness_1, chroma: chroma_1, hue: hue_1} = chroma_okhcl(hex_1)
const { lightness: lightness_2, chroma: chroma_2, hue: hue_2} = chroma_okhcl(hex_2)
return Math.sqrt(
Math.pow((lightness_1 - lightness_2), 2) * color_attrs.lightness +
Math.pow((chroma_1 - chroma_2), 2) * color_attrs.chroma +
Math.pow((hue_1 - hue_2) * 1/360, 2) * color_attrs.hue
)
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
donuts_sample_data
Insert cell
Insert cell
function draw_grouped_bars(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.groupedBars.width}`)
.attr('height', `${chart_layout_specific.groupedBars.height}px`)


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


chart_g.append('g')
.attr('class', 'x-axis')
.attr('transform', `translate(0,${chart_layout_specific.groupedBars.innerHeight})`)
.call(xAxis_groupBars)
.call(g=>{
g.selectAll('.domain, .tick line')
.attr('stroke', dashboardStyle.axisLine)
g.selectAll('text')
.attr('fill', dashboardStyle.axisText)
})

chart_g.append('g')
.attr('class', 'y-axis')
.call(yAxis_groupBars)
.call(g=>{
g.selectAll('.tick line').clone()
.attr("x2", chart_layout_specific.groupedBars.innerWidth)
.attr("stroke-opacity", 0.15)
}).call(g=>{
g.selectAll('.domain, .tick line')
.attr('stroke', dashboardStyle.axisLine)
g.selectAll('text')
.attr('fill', dashboardStyle.axisText)
})

chart_g.append('g')
.selectAll('g')
.data(grouped_bars_data.slice(0, chartFirstN.barChart_outer))
.join('g')
.attr('transform', d=> `translate(${outerXScale_groupedBars(d[0])},0)`)
.selectAll('rect')
.data(d=>d[1].slice(0, chartFirstN.barChart_inner))
.join('rect')
.attr('x', d=> innerXScale_groupedBars(d[0]))
.attr('y', d=> yScale_groupedBars(d[1]))
.attr('width', innerXScale_groupedBars.bandwidth())
.attr('height', d=>chart_layout_specific.groupedBars.innerHeight - yScale_groupedBars(d[1]))
.attr('fill', d=>colorScale_groupBars(d[0]))
}
Insert cell
Insert cell
Insert cell
Insert cell
function draw_lines(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.lines.width}`)
.attr('height', `${chart_layout_specific.lines.height}px`)

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

const lines = chart_g.append('g')
.selectAll('g')
.data(lines_data.slice(0, chartFirstN.barChart_inner))
.join('g')
.append('path')
.attr("fill", "none")
.attr('stroke-width', grouped_bars_attrs.line_width)
.attr('stroke-linejoin', 'round')
.attr('stroke-linecap', 'round')
.attr("stroke", d => colorScale_lines(d[0]))
.attr('d',d=>{
// console.log(d)
return line(d[1].slice(0, chartFirstN.barChart_outer))
})
chart_g.append('g')
.attr('class', 'x-axis')
.attr('transform', `translate(0,${chart_layout_specific.lines.innerHeight})`)
.call(xAxis_lines)
.call(g=>{
if(grouped_bars_attrs.groupOrder=='name - key'){
g.selectAll('text')
.attr('text-anchor', 'start')
.attr('transform', `translate(10,0) rotate(${50})`)
}
})
.call(g=>{
g.selectAll('.domain, .tick line')
.attr('stroke', dashboardStyle.axisLine)
g.selectAll('text')
.attr('fill', dashboardStyle.axisText)
})

chart_g.append('g')
.attr('class', 'y-axis')
.call(yAxis_lines)
.call(g=>{
g.selectAll('.tick line').clone()
.attr("x2", chart_layout_specific.lines.innerWidth)
.attr("stroke-opacity", 0.1)
})
.call(g=>{
g.selectAll('.domain, .tick line')
.attr('stroke', dashboardStyle.axisLine)
g.selectAll('text')
.attr('fill', dashboardStyle.axisText)
})
}
Insert cell
Insert cell
Insert cell
keys = 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)
Insert cell
stack = d3.stack()
.keys(keys) //这里的 key 会和数据一一对应匹配
.value((d, key)=>{
// console.log(d)
// console.log(key)
// console.log(d[1])
if(grouped_bars_attrs.groupOrder=='name - key'){
return d[1].find(e=>e['key']===key)['value']
}else{
return d[1].find(e=>e['name']===key)['value']
}
})
Insert cell
grouped_data = d3.groups(donuts_sample_data, d=>{
if(grouped_bars_attrs.groupOrder=='name - key'){
return d['name']
}else{
return d['key']
}
})
//这里要按照‘name’去聚合;stack函数按照 key 聚合,这样交叉找出需要的数值
Insert cell
stacked_data = stack(grouped_data)
Insert cell
function draw_stacked_bars(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.stackedBars.width}`)
.attr('height', `${chart_layout_specific.stackedBars.height}px`)

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

chart_g.append('g')
.attr('class', 'x-axis')
.attr('transform',`translate(0,${chart_layout_specific.stackedBars.innerHeight})`)
.call(xAxis_stacks)
.call(g=>{
if(grouped_bars_attrs.groupOrder=='name - key'){
g.selectAll('text')
.attr('text-anchor', 'start')
.attr('transform', `translate(10,0) rotate(${50})`)
}
})
.call(g=>{
g.selectAll('.domain, .tick line')
.attr('stroke', dashboardStyle.axisLine)
g.selectAll('text')
.attr('fill', dashboardStyle.axisText)
})

chart_g.append('g')
.attr('class', 'y-axis')
.call(yAxis_stacks)
.call(g=>{
g.selectAll('.tick line').clone()
.attr("x2", chart_layout_specific.stackedBars.innerWidth)
.attr("stroke-opacity", 0.1)
})
.call(g=>{
g.selectAll('.domain, .tick line')
.attr('stroke', dashboardStyle.axisLine)
g.selectAll('text')
.attr('fill', dashboardStyle.axisText)
})
const stackBars = chart_g.append('g')
.selectAll('g')
.data(stacked_data)
.join('g')
.attr('class', 'stack-layer-g')
.attr('fill', d=>{
// console.log(d)
return colorScale_stacks(d['key'])
})
.selectAll('rect')
.data(d=>d.slice(0, chartFirstN.barChart_outer))
.join('rect')
.attr('x', d=>{
// console.log(d)
// console.log(typeof d)
return xScale_stacks(d['data'][0])
})
.attr('y', ([y1, y2]) => {
return Math.min(yScale_stacks(y1), yScale_stacks(y2))
})
.attr('width', d=>{
return xScale_stacks.bandwidth()
})
.attr('height', ([y1, y2]) => Math.abs(yScale_stacks(y1) - yScale_stacks(y2)))
// .attr('id', (d,k)=>{
// console.log(d)
// console.log(k)
// })
}
Insert cell
Insert cell
Insert cell
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('g')
.data(newColorData.filter(e=>e['Source']===colorScheme).slice(0, firstN))
.join('g')
.attr('transform', d=>{
return `rotate(${scaleCircle(chroma_okhcl(d['Hex'])['hue'])})`
})
.append('g')
.attr('transform', d=>{
return `translate(${scaleR(chroma_okhcl(d['Hex'])[feilds.radar_rFeild])},${0})`
})
.call(g=>{
g.append('circle')
.attr('r', r)
.attr('fill', d=>d['Hex'])
})
.call(g=>{
g.append('text')
.text((d, i)=>{
return i + 1
})
.attr('transform', d=>{
return `rotate(${ - scaleCircle(chroma_okhcl(d['Hex'])['hue'])}) translate(0, ${2 * r + 6})`
})
.style("font", "10px sans-serif")
.style("font-weight", '500')
.style("text-anchor","middle")
.attr('fill', '#8F959E')
})
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 circle')
.attr('stroke', dashboardStyle.axisLine)
g.selectAll('text')
.attr('fill', dashboardStyle.axisText)
})
}
Insert cell
Insert cell
Insert cell
Insert cell
chart_layout = {
let svgMargin = 20
let svgGutter = 20
return {
svgHeight: 260,
svgMargin,
svgGutter,
ratio:[[1, 3], [1, 1] , [1]],
chartMargin:{
top: 60,
bottom: 60,
left: 60,
right: 60,
},
get frameWidth(){
let frameWidth = width - svgMargin - svgMargin
// - this.svgGutter * (this.ratio.length-1)
return this.ratio.map(e=>{
if(typeof e === 'number'){
return e/d3.sum(this.ratio) * frameWidth
}else if(typeof e === 'object'){
return e.map(d=>{
return d/d3.sum(e) * (frameWidth - this.svgGutter * (e.length-1))
})
}
})
},
}
}
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
}
},
lines:{
width: chart_layout.frameWidth[1][0],
height: chart_layout.svgHeight,
margin:{
top: 12,
bottom: 56,
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
}
},
stackedBars:{
width: chart_layout.frameWidth[1][1],
height: chart_layout.svgHeight,
margin:{
top: 12,
bottom: 56,
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
}
},
composite:{
width: chart_layout.frameWidth[2][0],
height: chart_layout.svgHeight,
margin:{
top: 12,
bottom: 56,
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
}
}
})
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
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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 = d3.scaleOrdinal()
.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]))
.range(newColorData.filter(e=>e['Source']===colorScheme).map(e=>e['Hex']))
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
Insert cell
xScale_lines = d3.scalePoint()
.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.lines.innerWidth])
.padding(.8)
Insert cell
yScale_lines = d3.scaleLinear()
.domain([0, d3.max(lines_data.slice(0, chartFirstN.barChart_inner), d=> d3.max(d[1].slice(0, chartFirstN.barChart_outer), d=>d[1]))])
.range([chart_layout_specific.lines.innerHeight, 0])
Insert cell
colorScale_lines = d3.scaleOrdinal()
.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]))
.range(newColorData.filter(e=>e['Source']===colorScheme).map(e=>e['Hex']))
Insert cell
Insert cell
xAxis_lines = d3.axisBottom().scale(xScale_lines)
Insert cell
yAxis_lines = d3.axisLeft().scale(yScale_lines).ticks(4)
Insert cell
Insert cell
Insert cell
xScale_stacks = 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.stackedBars.innerWidth])
.paddingInner(.3)
.paddingOuter(.4)
Insert cell
stacked_data.flat(2)
Insert cell
d3.max(stacked_data.flat(2))
Insert cell
yScale_stacks = d3.scaleLinear()
.domain([0, d3.max(stacked_data.flat(2))])
.range([chart_layout_specific.stackedBars.innerHeight, 0])
Insert cell
colorScale_stacks = d3.scaleOrdinal()
.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]))
.range(newColorData.filter(e=>e['Source']===colorScheme).map(e=>e['Hex']))
Insert cell
Insert cell
xAxis_stacks = d3.axisBottom()
.scale(xScale_stacks)
Insert cell
yAxis_stacks = d3.axisLeft()
.scale(yScale_stacks)
.ticks(4)
Insert cell
Insert cell
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
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