Fixed position D3 tip rather than at mouse/s position - javascript

I am finishing off a simple scatterplot graph project using D3. I am using D3 tip to show more information about the subject on the graph upon mouseover.
Currently, the tip shows up position to wherever the subject is on the graph.
However, I want to position my tip in a fixed position in the bottom right corner of the graph whenever a tip is triggered.
I have tried to amend the d3-tip class in CSS but no matter what I try, it seems to get overridden, and the tip's top and left positions are always wherever the mouse happens to be.
Here is a Codepen of my project, where you will see the tip appear wherever the subject is positioned: http://codepen.io/alanbuchanan/pen/RryBVQ
How can I ensure my tip shows up in a fixed position at the bottom right of the graph?
Javascript:
var w = 600;
var h = 700;
var margin = {top: 70, bottom: 90, left: 110, right: 70};
var nameMargin = 2;
var chartWidth = w - margin.left - margin.right;
var chartHeight = h - margin.top - margin.bottom;
var url = 'https://raw.githubusercontent.com/FreeCodeCamp/ProjectReferenceData/master/cyclist-data.json';
var svg = d3.select('body')
.append('svg')
.attr('width', w + margin.right)
.attr('height', h)
.style('background-color', '#eeeeee')
.style('border-radius', '10px')
.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`);
var formatMinutes = d => {
d -= 2210
var mins = Math.floor(d / 60)
var secs = d - mins * 60
return mins + ':' + secs
};
// Scales
var x = d3.scale.linear()
.domain([
2390 + 5,
2210
])
.range([0, chartWidth]);
var y = d3.scale.linear()
.domain([1, 35 + 1])
.range([0, chartHeight]);
// Axes
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom')
.ticks(7)
.tickFormat(formatMinutes);
var yAxis = d3.svg.axis()
.scale(y)
.orient('left')
.ticks(10);
var getFlag = nat => {
switch (nat) {
// Deal with odd cases
case 'GER': nat = "DE"; break;
case 'SUI': nat = "CH"; break;
case 'UKR': nat = "UA"; break;
case 'POR': nat = "PT"; break;
default: break;
}
return nat.substr(0, 2).toLowerCase();
};
$.getJSON(url, data => {
console.log(data)
var tip = d3.tip()
.attr('class', 'd3-tip')
.html(d => {
return `<h4><span class="flag-icon flag-icon-` + getFlag(d.Nationality) + `"></span> ${d.Name}</h4>
<h5>Time: ${d.Time} in ${d.Year}</h5>
<p>${d.Doping || 'No allegations'}</p>`
});
// Add data from JSON
svg.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('cx', d => x(d.Seconds))
.attr('cy', d => y(d.Place))
.attr('r', '2')
.style('width', '4px')
.style('height', '10px');
svg.call(tip);
var createLink = d => {
return `window.location='${d.URL}`
};
var text = svg.selectAll('text')
.data(data)
.enter()
.append('text')
.attr('x', d => x(d.Seconds - nameMargin))
.attr('y', d => y(d.Place))
.attr("dy", ".35em")
.text(d => d.Name)
.attr('class', 'rider-name')
.attr('class', d => d.Doping.length === 0 ? 'noAllegations' : 'hasAllegations')
.on('click', (d) => location.href = d.URL)
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
svg.append('g')
.attr('class', 'axis')
.attr('transform', `translate(0, ${chartHeight})`)
.call(xAxis);
svg.append('g')
.attr('class', 'axis')
.call(yAxis);
// Axis labels
svg.append('text')
.attr('class', 'label')
.attr('text-anchor', 'middle')
.attr('x', chartWidth / 2)
.attr('y', chartHeight + 50)
.text('Mins and secs behind best time');
svg.append('text')
.attr('class', 'label')
.attr('text-anchor', 'middle')
.attr('y', 0 - margin.left)
.attr('x', 0 - (chartHeight / 2))
.attr('dy', '3.9em')
.attr('transform', 'rotate(-90)')
.text('Place');
// Title
var title = svg.append('text')
.attr('class', 'title')
.attr('x', chartWidth / 2)
.attr('y', 0 - (margin.top / 2))
.attr('text-anchor', 'middle')
.text('Doping in Professional Bicycle Racing: 35 Fastest times up Alpe d\'Huez')
.style('font-size', '16px')
.style('font-weight', 'bold');
});
CSS:
#import url(https://fonts.googleapis.com/css?family=Raleway:400,100);
$font: 'Raleway', sans-serif;
$font-light: 100;
$font-med: 400;
$green: #416600;
$red: #b40000;
body {
background-color: #DADADA;
position: relative;
font-family: $font;
}
.axis path,
.axis line {
fill: none;
stroke: gray;
shape-rendering: crispEdges;
}
.d3-tip {
width: 200px;
}
svg {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, 10%);
border: 1px solid $red;
box-shadow: 3px 3px 15px #888888;
}
h4, h5 {
margin: 0;
}
text {
cursor: pointer;
}
.noAllegations {
fill: $green;
}
.hasAllegations {
fill: $red;
}
.title {
cursor: initial;
fill: $red;
}
.noAllegations, .hasAllegations {
text-transform: uppercase;
font-weight: $font-med;
font-size: 12px;
}
circle {
fill: black;
}
.label {
cursor: initial;
}

Related

How can I add a label with value to every point of the chart?

I have a line chart with x for timescale and y for value.
https://jsfiddle.net/gh6t04w2/21/
const margin = {top:50, right:0, bottom:88, left: 50};
const width = window.innerWidth * 0.8;
const height = window.innerHeight * 0.9;
const innerWidth = width - margin.left - margin.right;
const innerHeight = 300;//height - margin.top - margin.bottom;
console.log(height);
const svg = d3.select('#chart').append('svg')
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
d3.timeFormatDefaultLocale({
"dateTime": "%A, %e %B %Y г. %X",
"date": "%d.%m.%Y",
"time": "%H:%M:%S",
"periods": ["AM", "PM"],
"days": ["воскресенье", "понедельник", "вторник", "среда", "четверг", "пятница", "суббота"],
"shortDays": ["вс", "пн", "вт", "ср", "чт", "пт", "сб"],
"months": ["январь", "февраль", "март", "апрель", "май", "июнь", "июль", "август", "сентябрь", "октябрь", "ноябрь", "декабрь"],
"shortMonths": ["янв", "фев", "мар", "апр", "май", "июн", "июл", "авг", "сен", "окт", "ноя", "дек"]
});
const render = data => {
const title = 'Доходность РФ'
const xValue = d=> d.timestamp;
const yValue = d=> d.growth;
const xAxisLabel = '';
const yAxisLabel = 'Доходность, %';
const circleRadius = 4;
const circleOpacityHover = 0.6;
const circleOpacity = 0.8;
const circleRadiusHover = 10;
const duration = 250;
const xScale = d3.scaleTime()
.domain(d3.extent(data, xValue))
.range([0, innerWidth])
.nice();
const yScale = d3.scaleLinear()
.domain(d3.extent(data, yValue))
.range([innerHeight, 0])
.nice();
const g = svg.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
const yAxisTickFormat = number =>
d3.format("")(number * 100);
const xAxis = d3.axisBottom(xScale)
.ticks(8)
.tickSize(-innerHeight)
.tickPadding(15);
const yAxis = d3.axisLeft(yScale)
.tickFormat(yAxisTickFormat)
.tickSize(-innerWidth)
.tickPadding(10);
const yAxisG = g.append('g').call(yAxis);
yAxisG.selectAll('.domain').remove();
const xAxisG = g.append('g').call(xAxis)
.attr('transform', `translate(0, ${innerHeight})`)
yAxisG.append('text')
.attr('class', 'axis-label')
.attr('y', -60 )
.attr('x', -innerHeight/2)
.attr('transform', `rotate(-90)`)
.attr('text-anchor', 'middle')
.attr('fill', "black")
.text(yAxisLabel);
xAxisG.select('.domain').remove();
xAxisG.append('text')
.attr('class', 'axis-label')
.attr('y', 75 )
.attr('x', innerWidth/2)
.attr('fill', "black")
.text(xAxisLabel);
const lineGenerator = d3.line()
.x(d=> xScale(xValue(d)))
.y(d=> yScale(yValue(d)));
g.append('path')
.attr('class', 'line-path')
.attr('d', lineGenerator(data));
g.selectAll('circle').data(data)
.enter().append('circle')
.attr('cy', d=> yScale(yValue(d)))
.attr('cx', d=> xScale(xValue(d)))
.attr('r', circleRadius)
.on("mouseover", function(d) {
d3.select(this)
.transition()
.duration(duration)
.style('opacity', circleOpacityHover)
.attr("r", circleRadiusHover);
})
.on("mouseout", function(d) {
d3.select(this)
.transition()
.style('opacity', circleOpacity)
.duration(duration)
.attr("r", circleRadius);
});
//
//
g.append('text')
.attr('y', -10 )
.text(title);
};
const csvUrl = 'https://gist.githubusercontent.com/waitfornight6/b491f4146e104b78c12d5c65a5151aa3/raw/b629cc4921474bfe347c0ded14943c3e11d32bf2/data.csv';
d3.csv(csvUrl, onCsvLoaded);
function onCsvLoaded(data) {
data.forEach(d => {
const parts = d.timestamp.split('.');
d.growth = +d.growth;
d.timestamp = new Date(parts[2], parts[0]-1, parts[1]);
});
console.log(data);
render(data);
}
//Table
body {
margin: 0px;
overflow: hidden;
}
circle {
fill: steelblue;
}
.line-path {
fill:none;
stroke: steelblue;
stroke-width: 2;
stroke-linejoin: round;
}
text {
font-size: 2em;
font-family: sans-serif;
}
.tick text {
fill: #8E8883;
font-size: 1.7em;
}
.tick line {
stroke: #C0C0BB;
}
.axis-label {
font-size: 2em;
fill: #635F5D;
}
.title {
font-size: 3.7em;
fill: #635F5D;
}
#chart {height:500px;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<div id="chart"></div>
I need to add a label for every point of the chart that wold appear like on a chart here http://bl.ocks.org/bobmonteverde/2070123
I am a beginner with the d3, I tried to append the text labael to the circles, but this didn't work. Can anyone help?
The easiest way is to use d3-tip module. Follow it on GitHub https://github.com/Caged/d3-tip.
You can also find a basic example of usage here: http://bl.ocks.org/davegotz/bd54b56723c154d25eedde6504d30ad7.
I have updated your jsfiddle:
added d3-tip library and initialised d3-tip:
const tool_tip = d3.tip()
.attr("class", "d3-tip")
.html(d => d.growth*100 + ", " + d.timestamp.getDate() + '/' + (d.timestamp.getMonth()+1) + '/' + d.timestamp.getFullYear());
g.call(tool_tip);
merged your mouseevents with tooltip:
g.selectAll('circle').data(data)
.enter().append('circle')
.attr('cy', d=> yScale(yValue(d)))
.attr('cx', d=> xScale(xValue(d)))
.attr('r', circleRadius)
.on("mouseover", mouseover)
.on("mouseout", mouseout);
function mouseover (d,i){
d3.select(this)
.transition()
.duration(duration)
.style('opacity', circleOpacityHover)
.attr("r", circleRadiusHover);
tool_tip.show(d,this)
}
function mouseout (d,i){
d3.select(this)
.transition()
.style('opacity', circleOpacity)
.duration(duration)
.attr("r", circleRadius);
tool_tip.hide(d,this)}
updated CSS
.d3-tip {
line-height: 1;
padding: 6px;
background: rgba(0, 0, 0, 0.8);
color: #fff;
border-radius: 4px;
font-size: 12px;
}
You can see the updated fiddle here: https://jsfiddle.net/liubomyr_gavryliv/p95zjb0d/
Sincerely

In D3 bar graph, y-axis ordinal scale is not aligning properly with bars

I was following Mike's tutorial Let's make a bar graph part II, part III for the bar graph in d3.
I end up with a horizontal bar graph.
The Problem is I'm not able to set the y-axis labels aligned with its respective bar, maybe I'm somewhere wrong with ordinal scale or setting up the y position of the bars.
Also, the graph bars are starting from the top instead of baseline.
var options = {
margin: {
top: 0,
right: 30,
bottom: 0,
left: 50
},
width: 560,
height: 400,
element: 'body',
colors: ["#f44336", "#e91e63", "#673ab7", "#3f51b5", "#2196f3", "#03a9f4", "#00bcd4", "#009688", "#4caf50", "#8bc34a", "#cddc39", "#ffeb3b", "#ffc107", "#ff9800", "#ff5722", "#795548", "#607d8b"]
};
options.mWidth = options.width - options.margin.left - options.margin.right;
options.mHeight = options.height - options.margin.top - options.margin.bottom;
var _colorScale = d3.scale.ordinal()
// .domain([minValue,maxValue])
.range(options.colors);
var items = Math.round((Math.random() * 10) + 3);
var data = [];
for (var i = 0; i < items; i++) {
data.push({
label: 'label' + i,
value: Math.random() * 100
});
}
var _barThickness = 20;
var width = options.mWidth,
height = options.mHeight;
var svg = d3.select(options.element)
.append("svg")
.attr("width", width).attr("height", height)
.attr("viewBox", "0 0 " + options.width + " " + options.height)
//.attr("preserveAspectRatio", "xMinYMin meet")
.append("g");
svg.attr("transform", "translate(" + options.margin.left * 2 + "," + options.margin.top + ")");
var maxValue = 0,
minValue = 0;
for (var i = 0; i < data.length; i++) {
maxValue = Math.max(maxValue, data[i].value);
minValue = Math.min(minValue, data[i].value);
}
var scales = {
x: d3.scale.linear().domain([0, maxValue]).range([0, width / 2]),
y: d3.scale.ordinal().rangeRoundBands([0, height], .1)
}
scales.y.domain(data.map(function(d) {
return d.label;
}));
var bars = svg.append("g")
.attr("class", "bar");
var xAxis = d3.svg.axis()
.scale(scales.x)
.orient("bottom")
.ticks(5);
var yAxis = d3.svg.axis()
.scale(scales.y)
.orient("left");
var xaxis = svg.append("g")
.attr("class", "x axis")
.attr('transform', 'translate(' + 0 + ',' + height + ')')
.call(xAxis)
.append("text")
.attr("x", width / 2)
.attr("dy", "3em")
.style("text-anchor", "middle")
.text("Durations in hours");
var yaxis = svg.append("g")
.attr("class", "y axis")
.attr('transform', 'translate(' + 0 + ',' + 0 + ')')
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", -options.margin.left)
.attr("x", -height / 2)
.attr("dy", ".71em")
.style("text-anchor", "middle")
.text("Labels");
var bar = bars.selectAll('rect.bar').data(data);
var rect = bar.enter()
.append('g')
.attr('transform', function(d, i) {
return 'translate(0,' + parseInt(i * _barThickness + 2) + ')';
});
rect.append('rect')
.attr('class', 'bar')
.attr('fill', function(d) {
return _colorScale(d.value);
})
.attr('width', function(d) {
return scales.x(d.value);
})
.attr('height', _barThickness - 1)
.attr('y', function(d, i) {
return (i * _barThickness);
})
rect.append('text')
.attr('x', function(d) {
return scales.x(d.value) + 2;
})
.attr('y', function(d, i) {
return (i * _barThickness);
})
.attr('dy', '0.35em')
.text(function(d) {
return Math.round(d.value);
});
svg {
width: 100%;
height: 100%;
}
text {
font-size: 0.8em;
line-height: 1em;
}
path.slice {
stroke-width: 2px;
}
polyline {
opacity: .3;
stroke: black;
stroke-width: 2px;
fill: none;
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
Problem 1:
Instead of this:
var _barThickness = 20;
Do this:
var _barThickness = scales.y.rangeBand();
Read here
If you want to regulate the thickness
Instead of this:
y: d3.scale.ordinal().rangeRoundBands([0, height], .1)
Do this:
y: d3.scale.ordinal().rangeRoundBands([0, height], .7)
Problem 2:
Incorrect transform.
Instead of this:
var rect = bar.enter()
.append('g')
.attr('transform', function(d, i) {
return 'translate(0,' + parseInt(i * _barThickness + 2) + ')';
});
Do this:
var rect = bar.enter()
.append('g');
Problem 3:
Incorrect calculation of y for the bars.
.attr('y', function(d, i) {
return (i * _barThickness);
})
Do this:
.attr('y', function(d, i) {
return scales.y(d.label);
})
working code here

Add space between x-axis and line with time scale

I am using an example below.
HTML-
<div id="chart-holder"></div>
CSS-
.chart-holder {
position: relative;
font-family: Helvetica, sans-serif;
}
.chart-tip {
display: none;
position: absolute;
min-width: 70px;
top: 0px;
left: 0px;
padding: 3px 5px;
background: rgba(0, 0, 0, 0.8);
color: #fff;
font-size: .8em;
text-shadow: none;
text-align: center;
z-index: 10500;
border-radius: 3px;
}
.v {
display: block;
font-size: 1.2em;
}
path {
stroke: #0da9c0;
stroke-width: 2;
fill: none;
}
path.domain {
stroke: #aaa;
stroke-width: 1;
}
line {
stroke: #aaa;
}
text {
font-family: Arial;
font-size: 9pt;
fill: #555;
}
circle {
cursor: pointer;
}
Javascript-
var data = [["2013-01-24 06:38:02.235191", 52], ["2013-01-23 06:38:02.235310", 54], ["2013-01-22 06:38:02.235330", 45], ["2013-01-21 06:38:02.235346", 53]],
maxValue = d3.max(data, function (d) { return d[1]; }),
margin = {top: 20, right: 20, bottom: 50, left: 50},
width = 500 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom,
svg, x, y, xAxis, yAxis, line;
$.each(data, function(index, val) {
val[0] = new Date(val[0]);
});
x = d3.time.scale()
.range([0, width])
y = d3.scale.linear()
.domain([0, maxValue])
.range([height, 0]);
xAxis = d3.svg.axis()
.scale(x)
.tickSize(4, 2, 0)
.ticks(d3.time.days, 1)
.tickFormat(d3.time.format("%m/%d"))
.orient("bottom");
yAxis = d3.svg.axis()
.scale(y)
// .ticks(5)
// .tickValues([0, maxValue * 0.25, maxValue * 0.5, maxValue * 0.75, maxValue])
.tickSize(4, 2, 0)
.orient("left");
line = d3.svg.line()
.x(function(d) { return x(d[0]); })
.y(function(d) { return y(d[1]); });
svg = d3.select("#chart-holder").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
x.domain(d3.extent(data, function(d) { return d[0]; }));
y.domain([d3.min(data, function (d) { return d[1]; })-1, maxValue]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.attr("transform", function(d) {
return "rotate(-60)translate(" + -this.getBBox().height * 1.7 + "," +
-this.getBBox().width/4 + ")";
});
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
// .append("text")
// .attr("transform", "rotate(-90)")
// .attr("y", 6)
// .attr("dy", ".71em")
// .style("text-anchor", "end")
// .text("Engagement");
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
svg
.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("fill", "#0b8da0")
.attr("r", 3.5)
.attr("cx", function(d) { return x(d[0]); })
.attr("cy", function(d) { return y(d[1]); })
.on("mouseover", function(d) { showData(this, d);})
.on("mouseout", function() { hideData(this);});
function showData(obj, d) {
var coord = d3.mouse(obj);
var chartTip = d3.select(".chart-tip");
var format = d3.time.format("%b %d, %Y");
// enlarge circle
d3.select(obj)
.attr('r', 5);
// now we just position the chartTip roughly where our mouse is
chartTip.style("left", (coord[0] + 60) + "px" );
chartTip.style("top", (coord[1] + 10) + "px");
$(".chart-tip").html('<span class="v">' + d[1] + '</span>' + format(d[0]));
$(".chart-tip").fadeIn(100);
}
function hideData(obj) {
// enlarge circle
d3.select(obj)
.attr('r', 3.5);
$(".chart-tip").fadeOut(100);
}
$('#chart-holder').append($('<div />', {
'class': 'chart-tip'
}));
Here is the fiddle
http://jsfiddle.net/murli2308/n7Vmr/12/
I can add space between axis and line if I am using ordinal scale or linear scale. But when I am using time scale, I am not able to add space between axis and line.
I tried
x = d3.time.scale()
.range([0, width]);
It is not working.
The x.domain is the user space side of the user space to pixel space mapping. In your code you are setting the domain as:
x.domain(d3.extent(data, function(d) { return d[0] - 1; }));
Which says start my x axis at my min time minus 1 millisecond.
If you want some space before / after the line. Try:
x.domain([
d3.min(data, function(d) { return d[0].getTime() - 1.8e+7; }),
d3.max(data, function(d) { return d[0].getTime() + 1.8e+7; })
]);
Which says start my x axis 5 hours before my min date and stop it 5 hours after my max date.
Updated fiddle.
EDITS
Just used a percentage based scheme, say you want 5% padding (3.6 hours with your current data):
var minDate = d3.min(data, function(d) { return d[0].getTime(); }),
maxDate = d3.max(data, function(d) { return d[0].getTime(); }),
padding = (maxDate - minDate) * .05;
x.domain([minDate - padding, maxDate + padding]);
Updated fiddle.

Enable scroll on the axis of D3 chart

I have a simple stacked bar chart :
The code is here.
I would like to have scroll-bar on the axis but as you can see in the link the scroll appears for the div container with the help of CSS.
But i need something like this chart with scroll!
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<link href='https://fonts.googleapis.com/css?family=Open+Sans:400,300,700' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
<script src="https://ajax.goquery.min.js"></script>
<style>
.axis path,
.axis line {
fill: none;
stroke: #BDBDBD;
}
.axis text {
font-family: 'Open Sans regular', 'Open Sans';
font-size: 13px;
}
.y.axis{
direction: ltr;
}
.grid .tick {
stroke: lightgrey;
opacity: 0.7;
}
.grid path {
stroke-width: 0;
}
.rect {
stroke: lightgrey;
fill-opacity: 0.6;
}
.wrapperDiv {
Width: 984px;
height: 35px;
border: thin solid black;
#margin-top: 36px;
#margin-bottom: 48px;
#margin-right: 20px;
#margin-left: 0px;
}
.divChart {
float:left;
font-size:13px;
color : #424242;
font-family: 'Open Sans regular', 'Open Sans';
#border: thin solid white;
margin-top: -0px;
#margin-bottom: 48px;
#margin-right: 150px;
margin-left: 50px;
#background-color: lightgrey;
width: 984px;
height: 500px;
#padding: 25px;
border: thin solid navy;
#margin: 25px;
#max-height:500px;
overflow-y:scroll;
direction: rtl;
}
</style>
</head>
<body>
<div class="divChart" id="wrapper-chart">
<div id ="chartID" ></div>
</div>
<script src="http://d3js.org/d3.v3.min.js"></script><script>
<script>
var dataset = [{"key":"Completion","values":[{"name":"Module 1","value":0},{"name":"Module 2","value":0},{"name":"Module 3","value":0},{"name":"Module 4","value":0},{"name":"Module 5","value":0},{"name":"Module 6","value":0},{"name":"Module 7","value":0},{"name":"Module 8","value":0.56},{"name":"Module 9","value":13.24},{"name":"Module 10","value":12.66}]},{"key":"NonCompletion","values":[{"name":"Module 1","value":100},{"name":"Module 2","value":100},{"name":"Module 3","value":100},{"name":"Module 4","value":100},{"name":"Module 5","value":100},{"name":"Module 6","value":100},{"name":"Module 7","value":100},{"name":"Module 8","value":99.44},{"name":"Module 9","value":86.76},{"name":"Module 10","value":87.34}]}];
function intChart(chartID, dataset) {
var margins = {top: 20, right: 20, bottom: 30, left: 120};
var width = 880 - margins.left -margins.right;
var height = 5250- margins.top - margins.bottom;
var old_width = width,old_height= height;
var module_fixed = 80;
height = Math.floor((dataset[0].values.length * height)/module_fixed)
var x = d3.scale.ordinal().rangeRoundBands([0, width], .1,.1)
var y = d3.scale.linear().rangeRound([height, 0], .1);
var series = dataset.map(function(d) {
return d.key;
});
dataset = dataset.map(function(d) {
return d.values.map(function(o, i) {
// Structure it so that your numeric
// axis (the stacked amount) is y
return {
y: o.value,
x: o.name
};
});
});
var stack = d3.layout.stack();
stack(dataset);
var dataset = dataset.map(function(
group) {
return group.map(function(d) {
// Invert the x and y values, and y0 becomes x0
return {
x: d.y,
y: d.x,
x0: d.y0
};
});
});
var xMax = d3.max(dataset, function(
group) {
return d3.max(group, function(d) {
return d.x + d.x0;
});
});
var xScale = d3.scale.linear()
.domain([0, xMax])
.range([0, width]);
var moduleName = dataset[0]
.map(function(d) {
return d.y;
});
var yScale = d3.scale.ordinal()
.domain(moduleName)
.rangeRoundBands([height,0]);
var svg = d3.select('#chartID')
.append('svg')
.attr("width", width + margins.left +
margins.right)
.attr("height", height + margins.top +
margins.bottom)
.append('g')
.attr('transform', 'translate(60,' + margins.top +
')');
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom')
.ticks(2)
.tickSize(0)
.tickPadding(20)
.tickFormat(function(d) {
return d + "%";
});
var yAxis = d3.svg.axis()
.scale(yScale)
.orient('left')
.tickSize(0);
var colours = d3.scale.ordinal().range(
["#8bc34a", "#ff8a65"]);
var groups = svg.selectAll('g')
.data(dataset)
.enter()
.append('g').attr('class', 'stacked')
.style('fill', function(d, i) {
return colours(i);
});
var rects = groups.selectAll(
'stackedBar')
.data(function(d, i) {
return d;
})
.enter()
.append('rect')
.attr('class', 'stackedBar')
.attr('x', function(d) {
return xScale(d.x0);
})
.attr('y', function(d, i) {
return yScale(d.y);
})
.attr('height', 48)
.attr('width', 0)
rects.transition()
.delay(function(d, i) {
return i * 50;
})
.attr("x", function(d) {
return xScale(d.x0);
})
.attr("width", function(d) {
return xScale(d.x);
})
.duration(3000);
//Added
x.domain(dataset.map(function(d) {
return d.value;
}));
y.domain([0, d3.max(dataset, function(
d) {
return d.name;
})]);
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' +height + ')')
.call(xAxis)
.append("text")
.attr("transform", "rotate(360)")
.attr("y",10)
.attr("x", 140)
.attr("dy", ".30em")
.text("Percentage of Students");
svg.append('g')
.attr('class', 'y axis')
.call(yAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.5em")
.attr("dy", ".15em")
.attr("y", "-")
.attr("opacity", 1)
.attr("transform", function(d) {
return "rotate(-40)"
})
// Draw Y-axis grid lines
svg.selectAll("line.y")
.data(y.ticks(2))
.enter().append("line")
.attr("class", "y")
.attr("x1", 0)
.attr("x2", 450)
.attr("y1", y)
.attr("y2", y)
.style("stroke", "#ccc");
}
$(document).ready(function(){
intChart("chartID", dataset);
});
</script>
Would appreciate any help.
Thanks in advance.
I don't think this can be done using D3 only. If you want to use CSS, you need to fix the position of the x-axis. You can add a separate DIV and SVG container for the X axis (these are not scrollable), and the rest of the chart in another.
I modified you code to do this see here. Please note that you code needs a lot of cleaning, as there are several non-functional parts that makes it really confusing.
The modifications are as follows:
HTML
Added a new DIV (xaxis)
<div id="wrapper-chart">
<div class="divChart" id="chartID"></div>
<div id="xaxis"></div>
</div>
CSS
Added styling for the new div (same as divChart but without the scrolling)
#xaxis {
float: left;
font-size: 13px;
color: #424242;
font-family: 'Open Sans regular', 'Open Sans';
width: 984px;
direction: rtl;
}
JS
A new SVG container for the x-axis. Notice the height attribute.
var xaxis_svg = d3.select('#xaxis')
.append('svg')
.attr("width", width + margins.left + margins.right)
.attr("height", margins.bottom)
.append('g')
.attr('transform', 'translate(60,0)');
Append the x-axis to the container.
xaxis_svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + 0 + ')')
.call(xAxis)
.append("text")
.attr("y", 10)
.attr("x", 140)
.attr("dy", ".30em")
.text("Percentage of Students");
Hope this helps.

Need help using d3.js to create Stacked Bar Chart

I am currently trying to convert this (mbostock.github.com/d3/ex/stack.html) vertical stacked bar chart using d3.js to a horizontal stacked bar chart, but I have had no luck. if anyone has an example of a horizontally stacked bar chart from d3.js or knows how to modify the following code correctly or point me in the correct direction that would be a great help.
var margin = 20,
width = 960,
height = 500 - .5 - margin,
mx = m,
my = d3.max(data, function(d) {
return d3.max(d, function(d) {
return d.y0 + d.y;
});
}),
mz = d3.max(data, function(d) {
return d3.max(d, function(d) {
return d.y;
});
}),
x = function(d) { return d.x * width / mx; },
y0 = function(d) { return height - d.y0 * height / my; },
y1 = function(d) { return height - (d.y + d.y0) * height / my; },
y2 = function(d) { return d.y * height / mz; }; // or `my` to not rescale
var vis = d3.select("#chart")
.append("svg")
.attr("width", width)
.attr("height", height + margin);
var layers = vis.selectAll("g.layer")
.data(data)
.enter().append("g")
.style("fill", function(d, i) { return color(i / (n - 1)); })
.attr("class", "layer");
var bars = layers.selectAll("g.bar")
.data(function(d) { return d; })
.enter().append("g")
.attr("class", "bar")
.attr("transform", function(d) { return "translate(" + x(d) + ",0)"; });
bars.append("rect")
.attr("width", x({x: .9}))
.attr("x", 0)
.attr("y", height)
.attr("height", 0)
.transition()
.delay(function(d, i) { return i * 10; })
.attr("y", y1)
.attr("height", function(d) { return y0(d) - y1(d); });
The trick is: treat it almost the same as a vertical stacked bar chart, but invert the x and y values before stacking, and then back again once stacked. Note the comments in the code below.
My blog post about this: http://datashaman.github.io/2014/01/26/horizontal-stacked-bar-chart-d3/
Demo of the code below: http://bl.ocks.org/datashaman/8621955
jsFiddle: http://jsfiddle.net/datashaman/rBfy5/2/
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
.bar {
}
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 11px;
}
#tooltip {
position: absolute;
text-align: center;
width: 40px;
height: auto;
padding: 10px;
background-color: white;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
-webkit-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
-moz-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
pointer-events: none;
}
#tooltip.hidden {
display: none;
}
#tooltip p {
margin: 0;
font-family: sans-serif;
font-size: 16px;
line-height: 20px;
}
</style>
</head>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<div id="tooltip" class="hidden">
<p><span id="value">100</span></p>
</div>
<script>
var margins = {
top: 12,
left: 48,
right: 24,
bottom: 24
},
legendPanel = {
width: 240
},
width = 700 - margins.left - margins.right - legendPanel.width,
height = 100 - margins.top - margins.bottom,
dataset = [
{
data: [
{ month: 'Aug', count: 123 },
{ month: 'Sep', count: 234 },
{ month: 'Oct', count: 345 }
],
name: 'Series #1'
},
{
data: [
{ month: 'Aug', count: 235 },
{ month: 'Sep', count: 267 },
{ month: 'Oct', count: 573 }
],
name: 'Series #2'
}
],
series = dataset.map(function(d) { return d.name; }),
dataset = dataset.map(function(d) {
return d.data.map(function(o, i) {
// Structure it so that your numeric
// axis (the stacked amount) is y
return {
y: o.count,
x: o.month
};
});
}),
stack = d3.layout.stack();
stack(dataset);
var dataset = dataset.map(function(group) {
return group.map(function(d) {
// Invert the x and y values, and y0 becomes x0
return {
x: d.y,
y: d.x,
x0: d.y0
};
});
}),
svg = d3.select('body')
.append('svg')
.attr('width', width + margins.left + margins.right + legendPanel.width)
.attr('height', height + margins.top + margins.bottom)
.append('g')
.attr('transform', 'translate(' + margins.left + ',' + margins.top + ')'),
xMax = d3.max(dataset, function(group) {
return d3.max(group, function(d) {
return d.x + d.x0;
});
}),
xScale = d3.scale.linear()
.domain([0, xMax])
.range([0, width]),
months = dataset[0].map(function(d) { return d.y; }),
_ = console.log(months),
yScale = d3.scale.ordinal()
.domain(months)
.rangeRoundBands([0, height], .1),
xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom'),
yAxis = d3.svg.axis()
.scale(yScale)
.orient('left'),
colours = d3.scale.category10(),
groups = svg.selectAll('g')
.data(dataset)
.enter()
.append('g')
.style('fill', function(d, i) {
return colours(i);
}),
rects = groups.selectAll('rect')
.data(function(d) { return d; })
.enter()
.append('rect')
.attr('x', function(d) { return xScale(d.x0); })
.attr('y', function(d, i) { return yScale(d.y); })
.attr('height', function(d) { return yScale.rangeBand(); })
.attr('width', function(d) { return xScale(d.x); })
.on('mouseover', function(d) {
var xPos = parseFloat(d3.select(this).attr('x')) / 2 + width / 2;
var yPos = parseFloat(d3.select(this).attr('y')) + yScale.rangeBand() / 2;
d3.select('#tooltip')
.style('left', xPos + 'px')
.style('top', yPos + 'px')
.select('#value')
.text(d.x);
d3.select('#tooltip').classed('hidden', false);
})
.on('mouseout', function() {
d3.select('#tooltip').classed('hidden', true);
})
svg.append('g')
.attr('class', 'axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
svg.append('g')
.attr('class', 'axis')
.call(yAxis);
svg.append('rect')
.attr('fill', 'yellow')
.attr('width', 160)
.attr('height', 30 * dataset.length)
.attr('x', width + margins.left)
.attr('y', 0);
series.forEach(function(s, i) {
svg.append('text')
.attr('fill', 'black')
.attr('x', width + margins.left + 8)
.attr('y', i * 24 + 24)
.text(s);
svg.append('rect')
.attr('fill', colours(i))
.attr('width', 60)
.attr('height', 20)
.attr('x', width + margins.left + 90)
.attr('y', i * 24 + 6);
});
</script>
</body>
</html>
Try Frank Guerino's examples:
http://bl.ocks.org/2141479 - horizontal bar chart
and
http://bl.ocks.org/2354192 - Multiple D3 Top Down and Bottom Up Stacked Bar Charts (without d3.layout.stack)
Combine the horizontal code with the Crimea example or Multiple D3 Top Down example and you should be on your track. Mainly what you have to look for is how to calculate the coordinates right in the horizontal setup. The rest is the same like any other stack example.
Best regards!

Categories