I have created a D3 chart step by step. I have made some changes to make the SVG responsive. My target now is to make the bar graph more responsive in order to make it easier to read when the screen size is smaller (width). I have pasted the snippet below and at the bottom of the page, I focus on the part that I am thinking the solution is hidden.
var data = [
{"area": "one ", "value": 18000},
{"area": "Two ", "value": 17000},
{"area": "three ", "value": 80000},
{"area": "four ", "value": 55000},
{"area": "five ", "value": 100000},
{"area": "six", "value": 50000},
{"area": "seven", "value": 50000}
];
var margin = {top: 10, right: 10, bottom: 70, left: 30};
var width = 1900 - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom;
//A fully-responsive chart area
var svg = d3.select("#chart-div")
.append("svg")
.attr("width","100%")
.attr("height","500px")
.attr("viewBox","0 0 "+
(width+margin.left+margin.right)+
" "+
(height+margin.top+margin.bottom) )
.append("g")
.attr("transform","translate("+
margin.left+","+margin.top+")");
var tooltip = d3.select("body").append("div").attr("class", "toolTip");
var x = d3.scaleLinear().range([0, width]);
var y = d3.scaleBand().range([height, 0]);
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
data.sort(function(a, b) { return a.value - b.value; });
x.domain([0, d3.max(data, function(d) { return d.value; })]);
y.domain(data.map(function(d) { return d.area; })).padding(0.1);
g.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).ticks(5).tickFormat(function(d) { return parseInt(d / 1000); }).tickSizeInner([-height]));
g.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(y));
g.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", 0)
.attr("height", y.bandwidth())
.attr("y", function(d) { return y(d.area); })
.attr("width", function(d) { return x(d.value); })
.on("mousemove", function(d){
tooltip
.style("left", d3.event.pageX - 50 + "px")
.style("top", d3.event.pageY - 70 + "px")
.style("display", "inline-block")
.html((d.area) + "<br>" + "£" + (d.value));
})
.on("mouseout", function(d){ tooltip.style("display", "none");});
#import url('https://fonts.googleapis.com/css?family=Roboto');
body {
margin: 15px;
background-color: #F1F3F3;
font-family: 'Roboto'!important;
}
.bar {
fill: #6F257F;
}
.axis path,
.axis line {
fill: none;
stroke: #D4D8DA;
stroke-width: 1px;
shape-rendering: crispEdges;
}
.x path {
display: none;
}
.toolTip {
position: absolute;
display: none;
min-width: 80px;
height: auto;
background: none repeat scroll 0 0 #ffffff;
border: 1px solid #6F257F;
padding: 14px;
text-align: center;
}
.svg-container {
display: inline-block;
position: relative;
width: 100%;
padding-bottom: 100%; /* aspect ratio */
vertical-align: top;
overflow: hidden;
}
.svg-content-responsive {
display: inline-block;
position: absolute;
top: 10px;
left: 0;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="chart-div" style="width:100%;height:100%;"></div>
I changed a part of the code with the following:
var parentwidth = $("#chart-div").parent().width();
var margin = {top: 10, right: 10, bottom: 70, left: 30};
var width = parentwidth - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom;
where I am actually getting the parentwidth using jQuery.
I am actually thinking whether :
a) It is possible to avoid jQuery in this case.
b) Ideally, make the bar to scale differently so that everything will be easily read by the user (small text size is an issue):
I am testing the function below but I am possibly getting errors related to some chrome addons to avoid the cross-origin error. I can update the question if the below is the best solution:
function resize() {
var width = parseInt(d3.select("#chart").style("width")) - margin.left - margin.right,
height = parseInt(d3.select("#chart").style("height")) - margin.top - margin.bottom;
// Update the range of the scale with new width/height
xScale.range([0, width]);
yScale.rangeRoundBands([height, 0], 0.1);
// Update the axis and text with the new scale
svg.select(".x.axis")
.call(xAxis)
.attr("transform", "translate(0," + height + ")")
.select(".label")
.attr("transform", "translate(" + width / 2 + "," + margin.bottom / 1.5 + ")");
svg.select(".y.axis")
.call(yAxis);
// Update the tick marks
xAxis.ticks(Math.max(width/75, 2), " $");
// Force D3 to recalculate and update the line
svg.selectAll(".bar")
.attr("width", function(d) { return xScale(d["total"]); })
.attr("y", function(d) { return yScale(d["Name"]); })
.attr("height", yScale.rangeBand());
};
Your question is basically the same as this one.
The TL;DR is: give your <svg> a viewBox attribute and a preserveAspectRatio (e.g. xMinYMin meet) attribute. Then wrap the <svg> in a <div> that has position: relative.
This is not the only solution, but it's probably the easiest to implement and (I think) the most used.
For an overview and discussion of several other solutions, see this article by Amelia Bellamy-Royds.
Also, for an in-depth explanation of the SVG coordinate system, read this series of articles by Sara Soueidan.
As for your doubts about jQuery and the text being too small on mobile phones:
a) you can totally avoid jQuery
b) you can avoid having a text too small by "counter-scaling" the text, namely when your entire barchart (i.e. bars, axis, labels) scales down, your text scales up. This is sometime called sticky text. You can see an example here.
I have altered my script to make it work. Please see the snippet below:
var margin = {top: 20, right: 20, bottom: 50, left: 100},
width = parseInt(d3.select("#chart").style("width")) - margin.left - margin.right,
height = parseInt(d3.select("#chart").style("height")) - margin.top - margin.bottom;
var yScale = d3.scale.ordinal()
.rangeRoundBands([height, 0], 0.1);
var xScale = d3.scale.linear()
.range([0, width]);
var dollarFormatter = d3.format(",.0f")
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.tickFormat(function(d) { return "$" + dollarFormatter(d);});
var svg = d3.select("#chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
return "<div><span>Name:</span> <span style='color:white'>" + d.Name + "</span></div>" +
"<div><span>Sub-Category:</span> <span style='color:white'>" + d["Sub-Category"] + "</span></div>" +
"<div><span>Total Sales:</span> <span style='color:white'>" + "$"+ dollarFormatter(d.total) + "</span></div>";
})
svg.call(tip);
//Get CSV, JSON from URL
//var url = "http://bl.ocks.org/josiahdavis/raw/7d84b2f1837eab9c24d9/top.csv";
var data = [
{
"metric": "Sales",
"Category": "Furniture",
"Sub-Category": "Bookcases",
"Name": "Tom Stivers",
"total": 1889.8,
"Type": "Customer"
},
{
"metric": "Sales",
"Category": "Furniture",
"Sub-Category": "Bookcases",
"Name": "Keith Herrera",
"total": 2020.161,
"Type": "Customer"
},
{
"metric": "Sales",
"Category": "Furniture",
"Sub-Category": "Bookcases",
"Name": "Jack O'Briant",
"total": 2122.545,
"Type": "Customer"
},
{
"metric": "Sales",
"Category": "Furniture",
"Sub-Category": "Bookcases",
"Name": "Nora Paige",
"total": 2154.9,
"Type": "Customer"
},
{
"metric": "Sales",
"Category": "Furniture",
"Sub-Category": "Bookcases",
"Name": "Anna Gayman",
"total": 2396.2656,
"Type": "Customer"
},
{
"metric": "Sales",
"Category": "Furniture",
"Sub-Category": "Bookcases",
"Name": "Tracy Blumstein",
"total": 3083.43,
"Type": "Customer"
},
{
"metric": "Sales",
"Category": "Furniture",
"Sub-Category": "Bookcases",
"Name": "Maribeth Schnelling",
"total": 3406.664,
"Type": "Customer"
},
{
"metric": "Sales",
"Category": "Furniture",
"Sub-Category": "Bookcases",
"Name": "Greg Tran",
"total": 4007.84,
"Type": "Customer"
},
{
"metric": "Sales",
"Category": "Furniture",
"Sub-Category": "Bookcases",
"Name": "Quincy Jones",
"total": 4404.9,
"Type": "Customer"
},
{
"metric": "Sales",
"Category": "Furniture",
"Sub-Category": "Bookcases",
"Name": "Peter Fuller",
"total": 6232.624,
"Type": "Customer"
}];
//d3.csv(url, format, function(error, data){
//if (error) throw error;
// Filter to select a subset
var subset = data.filter(function(el){
return (el["metric"] === "Sales")
&& (el["Sub-Category"] === "Bookcases")
&& (el["Type"] === "Customer");
});
// Sort the data so bar chart is sorted in decreasing order
subset = subset.sort(function(a, b) { return a["total"] - b["total"]; });
console.log(JSON.stringify(subset, null, 2));
yScale.domain(subset.map(function(d) { return d["Name"]; }));
xScale.domain([0, d3.max(subset, function(d) { return d["total"]; })]);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("g")
.attr("class", "x axis")
.call(xAxis)
.attr("transform", "translate(0," + height + ")")
.append("text")
.attr("class", "label")
.attr("transform", "translate(" + width / 2 + "," + margin.bottom / 1.5 + ")")
.style("text-anchor", "middle")
.text("Sales");
svg.selectAll(".bar")
.data(subset)
.enter().append("rect")
.attr("class", "bar")
.attr("width", function(d) { return xScale(d["total"]); })
.attr("y", function(d) { return yScale(d["Name"]); })
.attr("height", yScale.rangeBand())
.on('mouseover', tip.show)
.on('mouseout', tip.hide);;
//});
// Define responsive behavior
function resize() {
var width = parseInt(d3.select("#chart").style("width")) - margin.left - margin.right,
height = parseInt(d3.select("#chart").style("height")) - margin.top - margin.bottom;
// Update the range of the scale with new width/height
xScale.range([0, width]);
yScale.rangeRoundBands([height, 0], 0.1);
// Update the axis and text with the new scale
svg.select(".x.axis")
.call(xAxis)
.attr("transform", "translate(0," + height + ")")
.select(".label")
.attr("transform", "translate(" + width / 2 + "," + margin.bottom / 1.5 + ")");
svg.select(".y.axis")
.call(yAxis);
// Update the tick marks
xAxis.ticks(Math.max(width/75, 2), " $");
// Force D3 to recalculate and update the line
svg.selectAll(".bar")
.attr("width", function(d) { return xScale(d["total"]); })
.attr("y", function(d) { return yScale(d["Name"]); })
.attr("height", yScale.rangeBand());
};
// Call the resize function whenever a resize event occurs
d3.select(window).on('resize', resize);
// Call the resize function
resize();
// Define the format function
function format(d) {
d.total = +d.total;
return d;
}
#import url('https://fonts.googleapis.com/css?family=Roboto');
body {
margin: 5px;
background-color: #F1F3F3;
font-family: 'Roboto'!important;
}
.bar {
fill: #14405F;
}
.bar:hover {
fill: #33A1EE;
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #D4D8DA;
stroke-width: 1px;
shape-rendering: crispEdges;
}
.x path {
display: none;
}
#chart {
width: 100%;
height: 100%;
position: absolute;
}
.d3-tip {
line-height: 1;
font: 14px sans-serif;
padding: 12px;
background: rgba(0, 0, 0, 0.8);
color: rgb(185, 185, 185);
border-radius: 2px;
}
/*
.toolTip {
position: absolute;
display: none;
min-width: 80px;
height: auto;
background: none repeat scroll 0 0 #ffffff;
border: 1px solid #6F257F;
padding: 14px;
text-align: center;
}
*/
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src="https://labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3.js"></script>
<svg id="chart"></svg>
Related
I have the code as shown in the following snippet for generating bar chart in D3 JS.
var chartItems = [{
"time": "10:05",
"count": "5"
},
{
"time": "10:10",
"count": "6"
},
{
"time": "10:15",
"count": "4"
},
{
"time": "10:20",
"count": "3"
},
{
"time": "10:25",
"count": 0
},
{
"time": "10:30",
"count": "4"
},
{
"time": "10:35",
"count": "1"
},
{
"time": "10:40",
"count": "44"
},
{
"time": "10:45",
"count": "55"
},
{
"time": "10:50",
"count": "78"
},
{
"time": "10:55",
"count": "84"
},
{
"time": "11:00",
"count": "120"
}
];
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
return "<strong>" + d.time + ":</strong> <span style='color:red'>" + d.count + "</span>";
});
var margin = {
top: 50,
right: 20,
bottom: 100,
left: 60
};
width = 360 - margin.left - margin.right;
height = 400 - margin.top - margin.bottom;
x = d3.scale.ordinal().rangeRoundBands([0, width], 0.5);
y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5)
.innerTickSize(-width)
.outerTickSize(0)
.tickPadding(10);
x.domain(chartItems.map(function(item) {
return item.time;
}));
y.domain([0, d3.max(chartItems, function(item) {
return item.count;
})]);
var svg = d3.select('#chart-container')
.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 + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0, " + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "middle")
.attr("dx", "-0.5em")
.attr("dy", "-.55em")
.attr("y", 30)
.attr("transform", "rotate(-30)");
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 5)
.attr("dy", "0.8em")
.attr("text-anchor", "end");
svg.call(tip);
svg.selectAll("bar")
.data(chartItems)
.enter()
.append("rect")
.style("fill", 'red')
.attr("x", function(d) {
return x(d.time);
})
.attr("width", x.rangeBand())
.attr("y", function(d) {
return y(d.count);
})
.attr("height", function(d) {
return height - y(d.count);
})
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
.chart-container {
float: left;
}
.d3-tip {
line-height: 1;
font-weight: bold;
padding: 12px;
background: #555555;
color: #fff;
border-radius: 2px;
}
/* Creates a small triangle extender for the tooltip */
.d3-tip:after {
box-sizing: border-box;
display: inline;
font-size: 10px;
width: 100%;
line-height: 1;
color: #555555;
content: "\25BC";
position: absolute;
text-align: center;
}
/* Style northward tooltips differently */
.d3-tip.n:after {
margin: -1px 0 0 0;
top: 100%;
left: 0;
}
body,
html {
width: 100%;
height: 100%;
margin: 0 auto;
}
.graph {
width: auto;
}
.axis {
font: 10px Georgia, Arial, sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #dadada;
shape-rendering: crispEdges;
}
#chart-container {
margin-top: 100px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.2/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.9.1/d3-tip.js"></script>
<div id="chart-container"></div>
Here the issue I am facing is, the Y axis is only showing upto 80, whereas my maximum value is 120. How can I fix this ?
I have added a fiddle here : https://jsfiddle.net/fz47vnL9/1/
Your item counts are strings, so you need to convert them to numbers with parseFloat in order to find the correct maximum value:
y.domain([0, d3.max(chartItems, function(item) {
return parseFloat(item.count);
})]);
I found a vertical stack bar graph sample on google - http://bl.ocks.org/juan-cb/43f10523858abf6053ae
I want to convert it in horizontal stacked bar graph. I have done the changes but something is wrong. Graph is not correct.I think all the bars are overlapped.
Please help me to resolve this. Code is copied.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
width: 960px;
height: 500px;
position: relative;
}
svg {
width: 100%;
height: 100%;
position: center;
}
text{
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.toolTip {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
position: absolute;
display: none;
width: auto;
height: auto;
background: none repeat scroll 0 0 white;
border: 0 none;
border-radius: 8px 8px 8px 8px;
box-shadow: -3px 3px 15px #888888;
color: black;
font: 12px sans-serif;
padding: 5px;
text-align: center;
}
.legend {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 60%;
}
text {
font: 10px sans-serif;
}
.axis text {
font: 10px sans-serif;
}
.axis path{
fill: none;
stroke: #000;
}
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
</style>
<body>
<div class="barGraph" id='stacked-bar'></div>
<script src="http://d3js.org/d3.v4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/1.7.0/d3-legend.min.js"></script>
<script>
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.1, // ems
y = text.attr("y"),
dy = parseFloat(text.attr("dy")),
tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
}
}
});
}
init();
function init(){
var dataset = [{
"goodRating": 27,
"avgRating": 21,
"badRating": 16,
"rooms": "0.01"
},
{
"goodRating": 26,
"avgRating": 22,
"badRating": 31,
"rooms": "0.02"
},
{
"goodRating": 100,
"avgRating": 0,
"badRating": 0,
"rooms": "1"
}];
var groupSpacing = 6;
var margin = {top: 10, right: 10, bottom: 60, left: 100},
width = 1000 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var y = d3.scaleBand()
.range([height, 0]);
var x = d3.scaleLinear()
.range([0, width], .1,.3);
// var colorRange = d3.scale.category20();
var color = d3.scaleOrdinal(d3.schemeCategory20);
var xAxis = d3.axisBottom(x).tickFormat(dataset.rooms),
yAxis = d3.axisLeft(y);
var svg = d3.select("#stacked-bar").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 + ")");
var divTooltip = d3.select("body").append("div").attr("class", "toolTip");
color.domain(d3.keys(dataset[0]).filter(function(key) { return key !== "rooms"; }));
dataset.forEach(function(d) {
var y0 = 0;
var y1 = 0;
d.values = color.domain().map(function(name) { return {name: name, y0: y0, y1: y0 += +d[name]}; });
d.total = d.values[d.values.length - 1].y1;
});
y.domain(dataset.map(function(d) { return d.rooms; }));
x.domain([0, d3.max(dataset, function(d) { return d.total; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".5em")
.attr("transform", "rotate(-65)");
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 9)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Satisfaction %");
var bar = svg.selectAll(".rooms")
.data(dataset)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + y(d.rooms) + ",0)"; });
svg.selectAll(".x.axis .tick text")
.call(wrap, y.bandwidth());
var bar_enter = bar.selectAll("rect")
.data(function(d) { return d.values; })
.enter();
bar_enter.append("rect")
.attr("height", y.bandwidth())
.attr("x", function(d) { return x(d.y1); })
.attr("width", function(d) { return x(d.y1) - x(d.y0) })
.style("fill", function(d) { return color(d.name); });
bar_enter.append("text")
.text(function(d) { return d3.format(".2s")(d.y1-d.y0)+"%"; })
.attr("x", function(d) { return x(d.y1)+(x(d.y0) - x(d.y1))/2; })
.attr("y", y.bandwidth()/3)
.style("fill", '#ffffff');
bar.on("mousemove", function(d){
divTooltip.style("left", d3.event.pageX+10+"px");
divTooltip.style("top", d3.event.pageY-25+"px");
divTooltip.style("display", "inline-block");
var elements = document.querySelectorAll(':hover');
l = elements.length
l = l-1
element = elements[l].__data__
value = element.y1 - element.y0
divTooltip.html("Room No : "+(d.rooms)+"<br>"+element.name+" : "+value+"%");
});
bar.on("mouseout", function(d){
divTooltip.style("display", "none");
});
}
</script>
</body>
Regards,
Pinki Sharma
A few minor things were missing/incorrect:
The group (<g>) containing the bars was being transformed incorrectly (I'm guessing you missed this while changing from vertical stacked to horizontal) i.e. the following line
.attr("transform", function(d) { return "translate(" + y(d.rooms) + ",0)"; });
translates the bar groups from the left and the y position would be 0 and hence the overlap. I've changed that to this:
.attr("transform", function(d) { return "translate(0, " + y(d.rooms) + ")"; });
The rects' x value is changed from x(d.y1) to x(d.y0) (might be a typo)
bar_enter.append("rect")
.attr("height", y.bandwidth())
.attr("x", function(d) { return x(d.y0); })
Axis padding was missing for the scaleBand(). I've added that (check docs for more info)
var y = d3.scaleBand()
.rangeRound([height, 0]).padding(0.1);
Reset the margins to adapt to the SVG dimensions:
var margin = {top: 10, right: 60, bottom: 60, left: 50},
Combining all of the above, here's a fork of your codepen:
HORIZONTAL STACKED BAR CHART DEMO
Hope this helps.
I am trying to get responsive d3js charts. I have set the width and height as 100% for the chart have a resize function which should reduce the size . In what I have done so far it just manages to reduce the labels on the x-axis but the line chart remains of the same size.Is the way I am trying to make the responsive the correct way or is their a better way with which any d3js chart(bar/pie/line) could be made responsive.
var margin = {
top: 30,
right: 20,
bottom: 30,
left: 50
};
var width = parseInt(d3.select("#chart").style("width")) - margin.left - margin.right,
height = parseInt(d3.select("#chart").style("height")) - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(5).tickFormat(function (d) {
return d.replace('SEASONAL_', '');
});;
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(5);
var valueline = d3.svg.line()
.x(function (d) {
return x(d.name);
})
.y(function (d) {
return y(d.count);
});
var svg = d3.select("body")
.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 + ")");
// Get the data
var all = [{
"name": "Seasonal Pop",
"code": "SEASONAL_POP",
"children": [{
"name": "SEASONAL_LYQ1",
"code": "SEASONAL_LYQ1",
"count": 1200
}, {
"name": "SEASONAL_LYQ2",
"code": "SEASONAL_LYQ2",
"count": 2000
}, {
"name": "SEASONAL_LYQ3",
"code": "SEASONAL_LYQ3",
"count": 1060
}, {
"name": "SEASONAL_LYQ4",
"code": "SEASONAL_LYQ4",
"count": 2300
}, {
"name": "SEASONAL_CYQ1",
"code": "SEASONAL_CYQ1",
"count": 1300
}, {
"name": "SEASONAL_CYQ2",
"code": "SEASONAL_CYQ2",
"count": 3400
}, {
"name": "SEASONAL_CYQ3",
"code": "SEASONAL_CYQ3",
"count": 4500
}, {
"name": "SEASONAL_CYQ4",
"code": "SEASONAL_CYQ4",
"count": 5500
}]
}];
var data = all[0].children;
data.forEach(function (d) {
// d.name = +d.date ;
d.count = +d.count;
});
// Scale the range of the data
x.domain(data.map(function (d) {
return d.name;
}));
y.domain([0, d3.max(data, function (d) {
return d.count;
})]);
svg.append("path") // Add the valueline path.
.attr("d", valueline(data));
svg.append("g") // Add the X Axis
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g") // Add the Y Axis
.attr("class", "y axis")
.call(yAxis);
// Define responsive behavior
function resize() {
var width = parseInt(d3.select("#chart").style("width")) - margin.left - margin.right,
height = parseInt(d3.select("#chart").style("height")) - margin.top - margin.bottom;
// Update the range of the scale with new width/height
x.range([0, width]);
y.range([height, 0]);
// Update the axis and text with the new scale
svg.select('.x.axis')
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.select('.y.axis')
.call(yAxis);
// Force D3 to recalculate and update the line
svg.selectAll('.line')
.attr("d", function(d) { return line(d.count); });
// Update the tick marks
xAxis.ticks(Math.max(width/75, 2));
yAxis.ticks(Math.max(height/50, 2));
};
// Call the resize function whenever a resize event occurs
d3.select(window).on('resize', resize);
// Call the resize function
resize();
body {
font: 12px Arial;
}
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
.axis path, .axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
#chart {
width: 100%;
height: 100%;
position: absolute;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<body><svg id="chart"></svg></body>
Well your code kinda works. Save that you're dealing with two SVGs. The on the page with id="chart" and this one thats appended.
var svg = d3.select("body")
.append("svg")
But its not really a D3 issue its more to do with responsive SVGs. Responsive charts have problems scaling. The X & Y axis can become misrepresented.
One approach is to scale the entire Chart/SVG. Forget about listening to window resize events. Just set a viewBox property on your main svg. And set a static width hight thats too the correct ratio of your chat.
var svg = d3.select("#chart")
.attr("viewBox", "0 0 " + viewWidth + " " + viewHeight)
example
var margin = {
top: 30,
right: 20,
bottom: 30,
left: 50
};
var width = 500 - margin.left - margin.right,
height = 240 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(5).tickFormat(function (d) {
return d.replace('SEASONAL_', '');
});;
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(5);
var valueline = d3.svg.line()
.x(function (d) {
return x(d.name);
})
.y(function (d) {
return y(d.count);
});
var viewWidth = width - margin.left - margin.right;
var viewHeight = height + margin.top + margin.bottom;
var svg = d3.select("#chart")
.attr("viewBox", "0 0 " + viewWidth + " " + viewHeight)
// Get the data
var all = [{
"name": "Seasonal Pop",
"code": "SEASONAL_POP",
"children": [{
"name": "SEASONAL_LYQ1",
"code": "SEASONAL_LYQ1",
"count": 1200
}, {
"name": "SEASONAL_LYQ2",
"code": "SEASONAL_LYQ2",
"count": 2000
}, {
"name": "SEASONAL_LYQ3",
"code": "SEASONAL_LYQ3",
"count": 1060
}, {
"name": "SEASONAL_LYQ4",
"code": "SEASONAL_LYQ4",
"count": 2300
}, {
"name": "SEASONAL_CYQ1",
"code": "SEASONAL_CYQ1",
"count": 1300
}, {
"name": "SEASONAL_CYQ2",
"code": "SEASONAL_CYQ2",
"count": 3400
}, {
"name": "SEASONAL_CYQ3",
"code": "SEASONAL_CYQ3",
"count": 4500
}, {
"name": "SEASONAL_CYQ4",
"code": "SEASONAL_CYQ4",
"count": 5500
}]
}];
var data = all[0].children;
data.forEach(function (d) {
// d.name = +d.date ;
d.count = +d.count;
});
// Scale the range of the data
x.domain(data.map(function (d) {
return d.name;
}));
y.domain([0, d3.max(data, function (d) {
return d.count;
})]);
svg.append("path") // Add the valueline path.
.attr("d", valueline(data));
svg.append("g") // Add the X Axis
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g") // Add the Y Axis
.attr("class", "y axis")
.call(yAxis);
body {
font: 12px Arial;
}
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
.axis path, .axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
#chart {
width: 100%;
height: 100%;
position: absolute;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<body><svg id="chart"></svg></body>
I'm currently working with D3.JS attempting to add to an existing graph every 24 hours. Using JSON data like this:
[{"name": "bill", "val": 28}, {"name": "kevin", "val": 46}, {"name": "ryan", "val": 23},{"name": "ville", "val": 56}]
I have a frequency value on my Y axis, and a username on my xaxis. I think I have my placement function correct, but when adding a new column (although the offset should stay the same) all columns are shifted out of place. The goal is to be able to add columns (new data) and keep the columns (rects) above the proper username.
Here is my current working code:
<html>
<head>
<script type="text/javascript" src="d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.2/jquery.min.js"></script>
<script type="data/json" src="data.json"></script>
<style>
#chart rect{
fill: #4aaeea;
}
#chart text{
fill: white;
font: 10px sans-serif;
text-anchor: end;
}
.axis text{
font: 10px sans-serif;
}
.axis path, .axis line{
fill: none;
stroke : #fff;
shape-rendering: crispEdges;
}
body{
background: #1a1a1a;
color : #eaeaea;
padding : 5px;
}
</style>
</head>
<body>
<div id="chart"</div>
<script>
var margin ={top:20, right:30, bottom:30, left:40},
width=960-margin.left - margin.right,
height=500-margin.top-margin.bottom;
// scale to ordinal because x axis is not numerical
var x = d3.scale.ordinal().rangeRoundBands([0, width], .1);
//scale to numerical value by height
var y = d3.scale.linear().range([height, 0]);
var chart = d3.select("#chart")
.append("svg") //append svg element inside #chart
.attr("width", width+(2*margin.left)+margin.right) //set width
.attr("height", height+margin.top+margin.bottom); //set height
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom"); //orient bottom because x-axis will appear below the bars
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var data;
d3.json("http://localhost:8000/data.json", function(error, data){
if (error) return console.warn(error);
x.domain(data.map(function(d){ return d.name}));
y.domain([0, d3.max(data, function(d){return d.val})]);
var bar = chart.selectAll("g")
.data(data)
.enter()
.append("g")
.attr("transform", function(d, i){
return "translate("+x(d.name)+", 0)";
});
console.log(margin.left);
bar.append("rect")
.attr("y", function(d) {
return y(d.val);
})
.attr("x", function(d,i){
return x(margin.left + 2);
})
.attr("height", function(d) {
return height - y(d.val);
})
.attr("width", Math.min.apply(null, [x.rangeBand()-2, 100]));
//.attr("width", x.rangeBand()); //set width base on range on ordinal data
bar.append("text")
.attr("x", (margin.left * 2.2))
.attr("y", function(d) { return y(d.val) })
.attr("dy", ".75em")
.text(function(d) { return d.val; });
chart.append("g")
.attr("class", "x axis")
.attr("transform", "translate("+margin.left+","+ height+")")
.call(xAxis);
chart.append("g")
.attr("class", "y axis")
.attr("transform", "translate("+margin.left+",0)")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Frequency");
});
function type(d) {
d.name = +d.name; // coerce to number
return d;
}
</script>
I have done a few adjustments to the positional attributes of g elements containing bar rect elements and dx and dy attributes of text elements.
Hope this working code snippet helps.
var margin = {
top: 20,
right: 30,
bottom: 30,
left: 40
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// scale to ordinal because x axis is not numerical
var x = d3.scale.ordinal().rangeRoundBands([0, width], .1);
//scale to numerical value by height
var y = d3.scale.linear().range([height, 0]);
var chart = d3.select("#chart")
.append("svg") //append svg element inside #chart
.attr("width", width + margin.left + margin.right) //set width
.attr("height", height + margin.top + margin.bottom); //set height
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom"); //orient bottom because x-axis will appear below the bars
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var data = [{
"name": "bill",
"val": 28
}, {
"name": "kevin",
"val": 46
}, {
"name": "ryan",
"val": 23
}, {
"name": "ville",
"val": 56
}];
x.domain(data.map(function(d) {
return d.name
}));
y.domain([0, d3.max(data, function(d) {
return d.val
})]);
var barWidth = Math.min.apply(null, [x.rangeBand() - 2, 100]);
var bar = chart.selectAll("g")
.data(data)
.enter()
.append("g")
.attr("transform", function(d, i) {
return "translate(" + x(d.name) + ", " + y(d.val) + ")";
});
bar.append("rect")
.attr("y", 0)
.attr("x", barWidth - 10)
.attr("height", function(d) {
return height - y(d.val);
})
.attr("width", barWidth);
bar.append("text")
.attr("x", barWidth - 10)
.attr("y", 0)
.attr("dx", barWidth / 2)
.attr("dy", ".75em")
.text(function(d) {
return d.val;
});
chart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(" + margin.left + "," + height + ")")
.call(xAxis);
chart.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + margin.left + ",0)")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Frequency");
function type(d) {
d.name = +d.name; // coerce to number
return d;
}
#chart rect {
fill: #4aaeea;
}
#chart text {
fill: white;
font: 10px sans-serif;
text-anchor: end;
}
.axis text {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #fff;
shape-rendering: crispEdges;
}
body {
background: #1a1a1a;
color: #eaeaea;
padding: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="chart" </div>
I want to draw an xy multiseries linechart with d3.js. Thats ok.
But afterwards I want to scale the x-axes ordinal.
so thats the code of my xy linechart:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 12px Arial;
}
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
div.tooltip {
position: absolute;
text-align: center;
padding: 5px;
font: 14px sans-serif;
background: black;
color: white;
border: 0px;
border-radius: 8px;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1px;
}
</style>
<body>
<script src="d3.js"></script>
<script src="jquery-2.1.4.js" charset="utf-8"></script>
<script>
var margin = {
top: 20,
right: 20,
bottom: 20,
left: 50
},
width = 1180 - margin.left - margin.right,
height = 580 - margin.top - margin.bottom;
var x = d3.scale.linear().rangeRound([0, width]);
var y = d3.scale.linear().rangeRound([height, 0]);
var linearScale = d3.scale.linear();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var svg = d3.select("body").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 + ")");
/* var line = d3.svg.line()
.x(function(d) { console.log('x'+x(d.arbeitsgang));return x(d.arbeitsgang); })
.y(function(d) { console.log('y'+y(d.koordinaten));return y(d.koordinaten); });
*/
var line = d3.svg.line()
.x(function (d) {
return x(d.x);
})
.y(function (d) {
return y(d.y);
});
// Define 'div' for tooltips
var div = d3.select("body")
.append("div") // declare the tooltip div
.attr("class", "tooltip") // apply the 'tooltip' class
.style("opacity", 0); // set the opacity to nil
//d3.json("Arbeitsgang.json", function(error, data) {
var data = [
{
"key": "Paket 1",
"values": [
{
"x": 0,
"y": 40,
"arbeitsgang": "A1"
},
{
"x": 6,
"y": 30,
"arbeitsgang": "A2"
},
{
"x": 17,
"y": 20,
"arbeitsgang": "A3"
}
]
},
{
"key": "Paket 3",
"values": [
{
"x": 0,
"y": 85,
"arbeitsgang": "A1"
},
{
"x": 8,
"y": 50,
"arbeitsgang": "A2"
},
{
"x": 17,
"y": 89,
"arbeitsgang": "A3"
}
]
},
{
"key": "Paket 2",
"values": [
{
"x": 0,
"y": 45,
"arbeitsgang": "A1"
},
{
"x": 6,
"y": 145,
"arbeitsgang": "A1"
},
{
"x": 17,
"y": 53,
"arbeitsgang": "A1"
}
]
}
];
linearScale.domain(d3.keys(data[0]).filter(function (key) {
return key;
}));
x.domain([
d3.min(data, function (c) {
return d3.min(c.values, function (v) {
return v.x;
});
}),
d3.max(data, function (c) {
return d3.max(c.values, function (v) {
return v.x;
});
})
]);
y.domain([
d3.min(data, function (c) {
return d3.min(c.values, function (v) {
return v.y;
});
}),
d3.max(data, function (c) {
return d3.max(c.values, function (v) {
return v.y;
});
})
]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
var graphen = svg.selectAll(".graphen")
.data(data)
.enter().append("g")
.attr("class", "graphen");
var graph = graphen.append("path")
.attr("class", "line")
.attr("d", function (d) {
return line(d.values);
});
graph.on("mouseover", function (d) {
d3.select(this).style("stroke-width", 7);
div.transition()
.duration(200)
.style("opacity", .9);
div.html(d.key)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
div.style("visibility", "visible");
var selectthegraphs = $('.line').not(this); //select all the rest of the lines, except the one you are hovering on and drop their opacity
d3.selectAll(selectthegraphs)
.style("opacity", 0.2);
var selectcircles = $('.circle');
d3.selectAll(selectcircles)
.style("opacity", 0.2);
})
.on("mouseout", function () {
d3.select(this).style("stroke-width", 1);
div.transition()
.duration(500)
.style("opacity", 0.01);
div.style("visibility", "hidden");
var selectthegraphs = $('.line'); //select all the rest of the lines, except the one you are hovering on and drop their opacity
d3.selectAll(selectthegraphs)
.style("opacity",1);
var selectcircles = $('.circle');
d3.selectAll(selectcircles)
.style("opacity", 1);
});
graphen.each(function (p, j) {
d3.select(this).selectAll("circle")
.data(p.values)
.enter().append("circle")
.style("stroke", "black")
.style("fill", "white")
.attr("class","circle")
.attr("r", 5)
.attr("cx", function (d) {
return x(d.x);
})
.attr("cy", function (d) {
return y(d.y);
})
.on("mouseover", function (d) {
d3.select(this).transition().duration(500)
.attr("r", 10);
div.transition()
.duration(500)
.style("opacity", 0.9);
div.style("visibility", "visible");
div.html("X: " + d.x + "<br/>" + "Y: " + d.y + "<br/>" + "Arbeitsgang: " + d.arbeitsgang)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function (d) {
d3.select(this).transition().duration(500)
.attr("r", 5);
div.transition()
.duration(500)
.style("opacity", 0);
div.style("visibility", "hidden");
})
});
ok. but now I want the same graph with an ordinal scale.
The ticks shall be "data.values.arbeitsgang"
"arbeitsgang": "A1" for example.
please help me
Ordinal scales are not so tough.
var x = d3.scale.ordinal()
.domain(["A1","A2","A3"])
.rangeRoundPoints([0, width]);
The scale should now divide the range going from 0 to your width into 3 equally large parts. I choose for rangeRoundPoints because the values of the ticks are rounded to integers then and I like integers.
I believe you can customize the text of the x axis (of course using your ordinal scale to scale the axis), showing "Arbeitsgang:A1" for example, but I wouldn't know how to do that right on the top of my head.
For more information about ordinal scales, check out this link. Unless I totally misunderstood the question, I think this is the only thing you need.
I realize that I have hardcoded the values of the domain, but an array containing the values will do as well.