Public
Edited
Mar 10, 2023
Insert cell
Insert cell
chart = doublebardraw(data,entity_idx,"#twinbarcompartment")
Insert cell
entity_idx = "Agrn"
Insert cell
data = FileAttachment("dummydata.json").json();
Insert cell
nodatag = []
Insert cell
nodatap = []
Insert cell
allg = ['g_Cerebellum','g_Cortex','g_Hippocampus','g_Hypothalamus','g_Medulla','g_Midbrain','g_Olfactorybulb','g_Pons','g_Restbrain','g_Thalamus']
Insert cell
allp = ['p_Cerebellum','p_Cortex','p_Hippocampus','p_Hypothalamus','p_Medulla','p_Midbrain','p_Olfactorybulb','p_Pons','p_Restbrain','p_Thalamus']
Insert cell
function doublebardraw(data,entity_idx,nodata,id){
// SET UP DIMENSIONS
var w = 500,
h = 300;

// margin.middle is distance from center line to each y-axis
var margin = {
top: 50,
right: 320,
bottom: 140,
left: 50,
middle: 50, //28
error: 2
};

// the width of each side of the chart
var regionWidth = w/2 - margin.middle;

// these are the x-coordinates of the y-axes
var pointA = regionWidth,
pointB = w - regionWidth;


// CREATE SVG
const svg = d3.create("svg")
.attr("id",id)
.attr('width', margin.left + w + margin.right)
.attr('height', margin.top + h + margin.bottom)
.attr('padding',50)
.attr('transform', translation(margin.left, margin.top));

const Glycopeptide = svg.append("text")
.attr("fill", "black")
.attr("x", w/4+margin.left)
.attr("y", 15)
.attr("text-anchor", "middle")
.text("Glycopeptide");
const Proteome = svg.append("text")
.attr("fill", "black")
.attr("x", 3*(w/4)+margin.left)
.attr("y", 15)
.attr("text-anchor", "middle")
.text("Proteome");



// find the maximum data value on either side
// since this will be shared by both of the x-axes
var maxValue = Math.max(
d3.max(data, function(d) { return d.value_g; }),
d3.max(data, function(d) { return d.value_p; })
);

// SET UP SCALES

// the xScale goes from 0 to the width of a region
// it will be reversed for the left x-axis
var xScale = d3.scaleLinear()
.domain([0, maxValue])
.range([0, regionWidth])
.nice();

var xScaleLeft = d3.scaleLinear()
.domain([0, maxValue])
.range([regionWidth, 0]);

var xScaleRight = d3.scaleLinear()
.domain([0, maxValue])
.range([0, regionWidth]);


var yScale = d3.scaleBand()
.domain(data.map(function(d) { return d.name; }))
.rangeRound([0,h])
.padding(0.1);

var yScaleDataLeft = d3.scaleBand()
.domain(data.map(function(d) { return d.nodatatag; }))
.rangeRound([0,h])
.padding(0.1);

var yScaleDataRight = d3.scaleBand()
.domain(data.map(function(d) { return d.nodatatap; }))
.rangeRound([0,h])
.padding(0.1);


// SET UP AXES
var yAxisLeft = d3.axisRight(yScale)
.tickSize(4,0)
.tickPadding(margin.middle-4);

var yAxisRight = d3.axisLeft(yScale)
.tickSize(4,0)
.tickFormat('');
var yAxisDataRight = d3.axisRight(yScaleDataRight)
.tickSize(4,0)
.tickPadding(margin.middle-4);
var yAxisDataLeft = d3.axisLeft(yScaleDataLeft)
.tickSize(4,0)
.tickPadding(margin.middle-4);

var xAxisRight = d3.axisBottom(xScale)
.ticks(5);

var xAxisLeft = d3.axisBottom(xScale.copy().range([pointA, 0]))
// REVERSE THE X-AXIS SCALE ON THE LEFT SIDE BY REVERSING THE RANGE
.ticks(5);

// MAKE GROUPS FOR EACH SIDE OF CHART
// scale(-1,1) is used to reverse the left side so the bars grow left instead of right
var leftBarGroup = svg.append('g')
.attr('transform', translation(pointA, 0) + 'scale(-1,1)')
.attr("id","lefti");
var rightBarGroup = svg.append('g')
.attr('transform', translation(pointB, 0));

// DRAW AXES
svg.append('g')
.attr('class', 'axis y left')
.attr('transform', translation(pointA+margin.left, margin.top))
.call(yAxisLeft)
.selectAll('text')
.style('text-anchor', 'middle');

svg.append('g')
.attr('class', 'axis y left')
.attr('transform', translation(pointA+margin.left, margin.top))
.call(yAxisDataLeft)
.selectAll('text')
.style('text-anchor', 'left');
svg.append('g')
.attr('class', 'axis y left')
.attr('transform', translation(pointB+margin.left, margin.top))
.call(yAxisDataRight)
.selectAll('text')
.style('text-anchor', 'right');

svg.append('g')
.attr('class', 'axis y right')
.attr('transform', translation(pointB+margin.left, margin.top))
.call(yAxisRight);

svg.append('g')
.attr('class', 'axis x left')
.attr('transform', translation(margin.left, h+margin.top))
.call(xAxisLeft);

svg.append('g')
.attr('class', 'axis x right')
.attr('transform', translation(pointB+margin.left, h+margin.top))
.call(xAxisRight);

if (allg.every(elem => nodatag.includes(elem))){
// keine bars aber text mittig
const noG = svg.append("text")
.attr("fill", "black")
.attr("x", w/4+margin.left)
.attr("y", h/2+margin.top)
.attr("text-anchor", "middle")
.text("no data available");
} else {
// add bars
leftBarGroup.selectAll('.bar.left')
.data(data)
.enter().append('rect')
.attr('class', 'bar left')
.attr('x', -margin.left)
.attr('y', function(d) { return yScale(d.name)+margin.top; })
.attr('width', function(d) { return xScale(d.value_g); })
.attr('height', yScale.bandwidth())
.append("title").text(function(d) { return ` Gene: ${entity_idx}\n Compartment: ${d.name}\n Value: ${parseFloat(d.value_g).toFixed(2)}`});
for (var i = 0; i < nodatag.length; i++) {
let entity = nodatag[i].split('_')
entity = entity[1]
const noGG = svg.append("text")
.attr("fill", "black")
.attr("x", pointA-margin.left-yScale.bandwidth()/2) // bandwidth als puffer
.attr("y", yScale(entity)+margin.top+yScale.bandwidth()/2)
.attr("text-anchor", "right")
.attr("alignment-baseline", "central")
.style("font-size", "10px")
.text("no data available ");
}
// error bars
let gl = leftBarGroup.append('g');
var lines = gl.selectAll('.line.error')
.data(data);
lines.enter()
.append('line')
.attr('class', 'error')
.attr('x1', function(d) { return xScale(parseFloat(d.value_g)+parseFloat(d.moe_g))-margin.left; })
.attr('x2', function(d) { return xScale(parseFloat(d.value_g)-parseFloat(d.moe_g))-margin.left; })
.attr('y1', function(d) { return yScale(d.name)+(yScale.bandwidth()/2)+margin.top; })
.attr('y2', function(d) { return yScale(d.name)+(yScale.bandwidth()/2)+margin.top; });
let glTl = leftBarGroup.append('g');
var lines = glTl.selectAll('.line.errorT')
.data(data);
lines.enter()
.append('line')
.attr('class', 'errorT')
.attr('x1', function(d) { return xScale(parseFloat(d.value_g)+parseFloat(d.moe_g))-margin.left; })
.attr('x2', function(d) { return xScale(parseFloat(d.value_g)+parseFloat(d.moe_g))-margin.left; })
.attr('y1', function(d) { return yScale(d.name)+(yScale.bandwidth()/2)+margin.top+margin.error; })
.attr('y2', function(d) { return yScale(d.name)+(yScale.bandwidth()/2)+margin.top-margin.error; });

}
if (allp.every(elem => nodatap.includes(elem))){
// keine bars aber text mittig
const noP = svg.append("text")
.attr("fill", "black")
.attr("x", 3*(w/4)+margin.left)
.attr("y", h/2+margin.top)
.attr("text-anchor", "middle")
.text("no data available");
} else {
//add bars
rightBarGroup.selectAll('.bar.right')
.data(data)
.enter().append('rect')
.attr('class', 'bar right')
.attr('x', margin.left)
.attr('y', function(d) { return yScale(d.name)+margin.top; })
.attr('width', function(d) { return xScale(d.value_p); })
.attr('height', yScale.bandwidth())
.append("title").text(function(d) { return ' Gene:'+entity_idx+`\n Comparment: ${d.name}\n Value: ${parseFloat(d.value_p).toFixed(2)}`});
for (var i = 0; i < nodatap.length; i++) {
let entity = nodatap[i].split('_')
entity = entity[1]
const noPP = svg.append("text")
.attr("fill", "black")
.attr("x", pointB+margin.left+yScale.bandwidth()/2) // bandwidth als puffer
.attr("y", yScale(entity)+margin.top+yScale.bandwidth()/2)
.attr("text-anchor", "left")
.attr("alignment-baseline", "central")
.style("font-size", "10px")
.text("no data available ");
}
//error bars
let glTr = leftBarGroup.append('g');
var lines = glTr.selectAll('.line.errorT')
.data(data);
lines.enter()
.append('line')
.attr('class', 'errorT')
.attr('x1', function(d) { return xScale(parseFloat(d.value_g)-parseFloat(d.moe_g))-margin.left; })
.attr('x2', function(d) { return xScale(parseFloat(d.value_g)-parseFloat(d.moe_g))-margin.left; })
.attr('y1', function(d) { return yScale(d.name)+(yScale.bandwidth()/2)+margin.top+margin.error; })
.attr('y2', function(d) { return yScale(d.name)+(yScale.bandwidth()/2)+margin.top-margin.error; });
let gr = rightBarGroup.append('g');
var lines = gr.selectAll('.line.error')
.data(data);
lines.enter()
.append('line')
.attr('class', 'error')
.attr('x1', function(d) { return xScale(parseFloat(d.value_p)+parseFloat(d.moe_p))+margin.left; })
.attr('x2', function(d) { return xScale(parseFloat(d.value_p)-parseFloat(d.moe_p))+margin.left; })
.attr('y1', function(d) { return yScale(d.name)+(yScale.bandwidth()/2)+margin.top; })
.attr('y2', function(d) { return yScale(d.name)+(yScale.bandwidth()/2)+margin.top; });
let grTl = rightBarGroup.append('g');
var lines = grTl.selectAll('.line.errorT')
.data(data);
lines.enter()
.append('line')
.attr('class', 'errorT')
.attr('x1', function(d) { return xScale(parseFloat(d.value_p)+parseFloat(d.moe_p))+margin.left; })
.attr('x2', function(d) { return xScale(parseFloat(d.value_p)+parseFloat(d.moe_p))+margin.left; })
.attr('y1', function(d) { return yScale(d.name)+(yScale.bandwidth()/2)+margin.top+margin.error; })
.attr('y2', function(d) { return yScale(d.name)+(yScale.bandwidth()/2)+margin.top-margin.error; });
let grTr = rightBarGroup.append('g');
var lines = grTr.selectAll('.line.errorT')
.data(data);
lines.enter()
.append('line')
.attr('class', 'errorT')
.attr('x1', function(d) { return xScale(parseFloat(d.value_p)-parseFloat(d.moe_p))+margin.left; })
.attr('x2', function(d) { return xScale(parseFloat(d.value_p)-parseFloat(d.moe_p))+margin.left; })
.attr('y1', function(d) { return yScale(d.name)+(yScale.bandwidth()/2)+margin.top+margin.error; })
.attr('y2', function(d) { return yScale(d.name)+(yScale.bandwidth()/2)+margin.top-margin.error; });
}
// string concatenation for translations
function translation(x,y) {
return 'translate(' + x + ',' + y + ')';
}
return svg.node()
}
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