I created a scrollable heatmap and I want to make the x-axis fixed when scrolling the y-axis. I checked some posts here and tried to make the solutions work for example with position fixed, but it did not work out.
Edit: I tried to solve it with this example Fix x-axis position in scrollable Heatmap d3
but now my x-axis description disappeared and the x-axis is placed at the bottom.
var width = 500,
height = 600,
margintop = 50,
marginbottom = 50,
marginright = 10,
marginleft = 50
d3.csv("https://raw.githubusercontent.com/Lea1216/d3/main/heatmap.csv", function(data) {
var svg = d3.select("#my_dataviz")
.append("div")
.classed("chart",true)
.append("svg")
.attr("width",width +marginleft + marginright)
.attr("height",height+margintop + marginbottom)
.append("g")
.attr("transform",
"translate(" + marginleft + "," + margintop + ")");
var axis = d3.select("#my_dataviz")
.append("svg")
.attr("width", width + marginleft +marginright)
.attr("height",40)
.append("g")
.attr("transform", "translate(" + marginleft + ", 0)");
var x_axis = d3.scaleBand()
.range([0, width])
.domain(data.map(function(d) {
return d.group;
}))
.padding(0.01);
axis.call(d3.axisTop(x_axis))
.selectAll("text")
.style("text-anchor", "end")
.style("position","fixed")
.attr("dx",15)
.attr("dy",5)
.attr("transform", "rotate(-65)");
var y_axis = d3.scaleBand()
.range([height, 0])
.domain(data.map(function(d) {
return d.activity;
}))
.padding(0.01);
svg.append("g")
.call(d3.axisLeft(y_axis))
.attr("class", "y_axis")
.selectAll("text")
.on("click", function(d) {
window.open(d.url, "_blank")
});
var myColor = d3.scaleLinear()
.range(["white", "#C37B89"])
.domain([1, 100])
svg.selectAll()
.data(data, function(d) {
return d.group + ':' + d.activity;
})
.enter()
.append("rect")
.attr("x", function(d) {
return x_axis(d.group)
})
.attr("y", function(d) {
return y_axis(d.activity)
})
.attr("width", x_axis.bandwidth())
.attr("height", y_axis.bandwidth())
.style("fill", function(d) {
return myColor(d.value)
})
.style("stroke-width", 1)
.style("stroke", "none")
})
.rect {
opacity: 0.8;
}
#my_dataviz {
width: 600px;
height: 500px;
overflow-y: scroll;
padding: 50px;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="my_dataviz"></div>
There are several possible solutions for this. The first one is to draw two SVG elements on the same level and use position: fixed on the X-axis element. You can see that the blocks still show up behind the axis, but you could fix that by drawing a rect the total size of the axis SVG element and giving it fill: white:
var width = 500,
height = 600,
margintop = 50,
marginbottom = 50,
marginright = 10,
marginleft = 50
d3.csv("https://raw.githubusercontent.com/Lea1216/d3/main/heatmap.csv", function(data) {
// Add the axis *before* adding the SVG, because the order matters in HTML
var axis = d3.select("#my_dataviz")
.append("svg")
.attr("width", width + marginleft + marginright)
// Add 2 so you have a little bit of room left for the black bar
// i.e. margin top has to be less than total height!
.attr("height", margintop + 2)
.style("position", "fixed") // this makes the axis fixed
.append("g")
.attr("transform", "translate(" + marginleft + ", " + margintop + ")");
var svg = d3.select("#my_dataviz")
.append("svg")
.attr("width", width + marginleft + marginright)
.attr("height", height + margintop + marginbottom)
.append("g")
.attr("transform", "translate(" + marginleft + ", " + margintop + ")");
var x_axis = d3.scaleBand()
.range([0, width])
.domain(data.map(function(d) {
return d.group;
}))
.padding(0.01);
axis.call(d3.axisTop(x_axis))
.selectAll("text")
.style("text-anchor", "end")
.style("position", "fixed")
.attr("dx", 15)
.attr("dy", 5)
.attr("transform", "rotate(-65)");
var y_axis = d3.scaleBand()
.range([height, 0])
.domain(data.map(function(d) {
return d.activity;
}))
.padding(0.01);
svg.append("g")
.call(d3.axisLeft(y_axis))
.attr("class", "y_axis")
.selectAll("text")
.on("click", function(d) {
window.open(d.url, "_blank")
});
var myColor = d3.scaleLinear()
.range(["white", "#C37B89"])
.domain([1, 100])
svg.selectAll()
.data(data, function(d) {
return d.group + ':' + d.activity;
})
.enter()
.append("rect")
.attr("x", function(d) {
return x_axis(d.group)
})
.attr("y", function(d) {
return y_axis(d.activity)
})
.attr("width", x_axis.bandwidth())
.attr("height", y_axis.bandwidth())
.style("fill", function(d) {
return myColor(d.value)
})
.style("stroke-width", 1)
.style("stroke", "none")
})
.rect {
opacity: 0.8;
}
#my_dataviz {
border: solid 1px red;
width: 600px;
height: 300px;
overflow-y: scroll;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="my_dataviz"></div>
The second option is to put the other SVG in a div and scroll the div instead of the SVG. Then, you do need to do more with CSS and you need to move some CSS rules from the #my_dataviz element to the .chart element:
var width = 500,
height = 600,
margintop = 50,
marginbottom = 50,
marginright = 10,
marginleft = 50
d3.csv("https://raw.githubusercontent.com/Lea1216/d3/main/heatmap.csv", function(data) {
// Add the axis *before* adding the SVG, because the order matters in HTML
var axis = d3.select("#my_dataviz")
.append("svg")
.attr("width", width + marginleft + marginright)
// Add 2 so you have a little bit of room left for the black bar
// i.e. margin top has to be less than total height!
.attr("height", margintop + 1)
.append("g")
.attr("transform", "translate(" + marginleft + ", " + margintop + ")");
var svg = d3.select("#my_dataviz")
.append("div")
// Note the CSS rules for .chart
.attr("class", "chart")
.append("svg")
.attr("width", width + marginleft + marginright)
// No margin-top required here, because the other element already took care of it
.attr("height", height + marginbottom)
.append("g")
// Same, no margin-top
.attr("transform", "translate(" + marginleft + ", 0)");
var x_axis = d3.scaleBand()
.range([0, width])
.domain(data.map(function(d) {
return d.group;
}))
.padding(0.01);
axis.call(d3.axisTop(x_axis))
.selectAll("text")
.style("text-anchor", "end")
.style("position", "fixed")
.attr("dx", 15)
.attr("dy", 5)
.attr("transform", "rotate(-65)");
var y_axis = d3.scaleBand()
.range([height, 0])
.domain(data.map(function(d) {
return d.activity;
}))
.padding(0.01);
svg.append("g")
.call(d3.axisLeft(y_axis))
.attr("class", "y_axis")
.selectAll("text")
.on("click", function(d) {
window.open(d.url, "_blank")
});
var myColor = d3.scaleLinear()
.range(["white", "#C37B89"])
.domain([1, 100])
svg.selectAll()
.data(data, function(d) {
return d.group + ':' + d.activity;
})
.enter()
.append("rect")
.attr("x", function(d) {
return x_axis(d.group)
})
.attr("y", function(d) {
return y_axis(d.activity)
})
.attr("width", x_axis.bandwidth())
.attr("height", y_axis.bandwidth())
.style("fill", function(d) {
return myColor(d.value)
})
.style("stroke-width", 1)
.style("stroke", "none")
})
.rect {
opacity: 0.8;
}
#my_dataviz {
width: 600px;
border: solid 1px red;
}
/* To make sure there is no space between the DIV and the SVG */
#my_dataviz > * {
display: block;
}
.chart {
overflow-y: scroll;
max-height: 300px;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="my_dataviz"></div>
Related
I have made a D3 histogram look responsive but I want it to be 100% responsive on any screen (div) resize. I think the idea is to calculate the diagonal of the parent div and use that to change the axis length accordingly. In this case below, The said idea calculates the horizontal size but I can not get it to recognize a vertical length and that messes up the horizontal resize.
var color = "steelblue";
// Generate a 1000 data points using normal distribution with mean=20, deviation=5
var values = d3.range(1000).map(d3.random.normal(20, 5));
//var values = {{data}}
// A formatter for counts.
var formatCount = d3.format(",.0f");
var margin = {
top: 30,
right: 5,
bottom: 20,
left: 5
},
width = parseInt(d3.select("#histog").style("width")) - margin.left - margin.right
height = 275 - margin.top - margin.bottom;
var max = d3.max(values);
var min = d3.min(values);
var x = d3.scale.linear()
.domain([min, max])
.range([0, width]);
// Generate a histogram using twenty uniformly-spaced bins.
var data = d3.layout.histogram()
.bins(x.ticks(20))
(values);
var yMax = d3.max(data, function(d) {
return d.length
});
var yMin = d3.min(data, function(d) {
return d.length
});
var colorScale = d3.scale.linear()
.domain([yMin, yMax])
.range([d3.rgb(color).brighter(), d3.rgb(color).darker()]);
var y = d3.scale.linear()
.domain([0, yMax])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bOkottom");
var svg = d3.select("#histog").append("svg")
.attr("width", "100%")
.attr("height", "26vh")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var bar = svg.selectAll(".bar")
.data(data)
.enter().append("g")
.attr("class", "bar")
.attr("transform", function(d) {
return "translate(" + x(d.x) + "," + y(d.y) + ")";
});
bar.append("rect")
.attr("x", 1)
.attr("width", (x(data[0].dx) - x(0)) - 1)
.attr("height", function(d) {
return height - y(d.y);
})
.attr("fill", function(d) {
return colorScale(d.y)
});
bar.append("text")
.attr("dy", ".75em")
.attr("y", -12)
.attr("x", (x(data[0].dx) - x(0)) / 2)
.attr("text-anchor", "middle")
.text(function(d) {
return formatCount(d.y);
});
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
/*
* Adding refresh method to reload new data
*/
function refresh(values) {
// var values = d3.range(1000).map(d3.random.normal(20, 5));
var data = d3.layout.histogram()
.bins(x.ticks(20))
(values);
// Reset y domain using new data
var yMax = d3.max(data, function(d) {
return d.length
});
var yMin = d3.min(data, function(d) {
return d.length
});
y.domain([0, yMax]);
var colorScale = d3.scale.linear()
.domain([yMin, yMax])
.range([d3.rgb(color).brighter(), d3.rgb(color).darker()]);
var bar = svg.selectAll(".bar").data(data);
// Remove object with data
bar.exit().remove();
bar.transition()
.duration(1000)
.attr("transform", function(d) {
return "translate(" + x(d.x) + "," + y(d.y) + ")";
});
bar.select("rect")
.transition()
.duration(1000)
.attr("height", function(d) {
return height - y(d.y);
})
.attr("fill", function(d) {
return colorScale(d.y)
});
bar.select("text")
.transition()
.duration(1000)
.text(function(d) {
return formatCount(d.y);
});
}
.histocontainer {
float: right;
width: 55%;
margin-right: auto;
margin-left: auto;
text-align: top;
}
#media (min-width: 576px) {
.histocontainer {
max-width: 540px;
}
}
#media (min-width: 768px) {
.histocontainer {
max-width: 720px;
}
}
#media (min-width: 992px) {
.histocontainer {
max-width: 960px;
}
}
#media (min-width: 1200px) {
.histocontainer {
max-width: 1140px;
}
}
#scalesvg {
width: 20%;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<svg id="scalesvg">
<div id=histog></div>
</svg>
I've simplified the example a bit and used the structure of your refresh function to create a resize function. I use window.addEventListener("resize") to detect resizes and trigger the function.
All the stuff that can be done without knowing the width and height, I do outside the function, and inside I just redraw the bins and the axis.
It's all your code, I just moved it around a little.
var color = "steelblue";
// Generate a 1000 data points using normal distribution with mean=20, deviation=5
var values = d3.range(1000).map(d3.random.normal(20, 5));
// A formatter for counts.
var formatCount = d3.format(",.0f");
var margin = {
top: 30,
right: 5,
bottom: 20,
left: 5
};
var max = d3.max(values);
var min = d3.min(values);
var x = d3.scale.linear()
.domain([min, max]);
var data = d3.layout.histogram()
.bins(x.ticks(20))
(values);
var yMax = d3.max(data, function(d) {
return d.length
});
var yMin = d3.min(data, function(d) {
return d.length
});
var colorScale = d3.scale.linear()
.domain([yMin, yMax])
.range([d3.rgb(color).brighter(), d3.rgb(color).darker()]);
var y = d3.scale.linear()
.domain([0, yMax]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var svg = d3.select("svg")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "x axis");
function resize() {
var rect = d3.select('svg').node().getBoundingClientRect();
var width = rect.width - margin.left - margin.right;
var height = Math.min(rect.height, window.innerHeight) - margin.top - margin.bottom;
x.range([0, width]);
y.range([height, 0]);
// Reposition elements
svg.select('x.axis')
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
var bar = svg.selectAll(".bar").data(data);
// Remove object with data
bar.exit().remove();
var newBars = bar.enter()
.append("g")
.attr("class", "bar");
newBars.append("rect")
.attr("x", 1);
newBars.append("text")
.attr("dy", ".75em")
.attr("y", -12)
.attr("text-anchor", "middle")
bar.transition()
.duration(1000)
.attr("transform", function(d) {
return "translate(" + x(d.x) + "," + y(d.y) + ")";
});
bar.select("rect")
.transition()
.duration(1000)
.attr("width", (x(data[0].dx) - x(0)) - 1)
.attr("height", function(d) {
return height - y(d.y);
})
.attr("fill", function(d) {
return colorScale(d.y);
});
bar.select("text")
.transition()
.duration(1000)
.attr("x", (x(data[0].dx) - x(0)) / 2)
.text(function(d) {
return formatCount(d.y);
});
}
window.addEventListener("resize", function() {
console.log('resize!');
resize();
});
resize();
svg {
float: left;
width: 100%;
height: 300px;
margin-right: auto;
margin-left: auto;
text-align: top;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<svg></svg>
I am having trouble creating a chart with a simple line.
I'll put here my code and an image of how the line is getting off axis Y and X. I really have no idea why this is happening.
Chart:
HTML:
<div id="myChart"></div>
JavaScript:
Function to adjust my json only with the data I need:
var reduceVendedores = vendedores.reduce(function (allSales, sales) {
if (allSales.some(function (e) {
return e.vendnm === sales.vendnm;
})) {
allSales.filter(function (e) {
return e.vendnm === sales.vendnm
})[0].Vendas_Ano += sales.Vendas_Ano;
allSales.filter(function (e) {
return e.vendnm === sales.vendnm
})[0].Vendas_Ant += sales.Vendas_Ant
} else {
allSales.push({
vendnm: sales.vendnm,
Vendas_Ano: sales.Vendas_Ano,
Vendas_Ant: sales.Vendas_Ant
})
}
return allSales;
}, []);
Defining X and Y Axes of the Chart:
var margin = { top: 30, right: 30, bottom: 70, left: 60 },
width = 600 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
function getMax() {
return reduceVendedores.map(d => d.Vendas_Ano)
}
var svg = d3.select("#myChart")
.append("svg")
.attr("width", width)
.attr("height", height);
var xscale = d3.scaleBand()
.range([0, width - 100])
.domain(reduceVendedores.map(function (d) { return d.vendnm; }))
var yscale = d3.scaleLinear()
.domain([0, d3.max(getMax()) + 30000])
.range([height / 2, 0]);
var x_axis = d3.axisBottom().scale(xscale);
var y_axis = d3.axisLeft().scale(yscale);
svg.append("g")
.attr("transform", "translate(50, 10)")
.call(y_axis);
var xAxisTranslate = height / 2 + 10;
svg.append("g")
.attr("transform", "translate(50, " + xAxisTranslate + ")")
.call(d3.axisBottom(xscale))
.selectAll("text")
.attr("transform", "translate(-10,0)rotate(-45)")
.style("text-anchor", "end");
Defining the chart line:
svg.append("path")
.datum(reduceVendedores)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", d3.line()
.x(function (d) { return xscale(d.vendnm); })
.y(function (d) { return yscale(d.Vendas_Ano) })
)
You're not adjusting for the margins you gave to your axis with:
.attr("transform", "translate(50, 10)")
Try:
svg.append('g')
.attr('transform','translate(50,0)')
.append("path")
.datum(reduceVendedores)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", d3.line()
.x(function (d) { return xscale(d.vendnm); })
.y(function (d) { return yscale(d.Vendas_Ano) })
)
Typically you'd set your margin in svg, like:
var svg = d3.select("#myChart")
.append("svg")
.attr("width", width)
.attr("height", height)
.append('g')
.attr('transform','translate(' + margin.left + ',' + margin.top + ')')
But doing so know would ruin the alignment of your axis.
I would like to add a tooltip to the line chart, where each data point displays a text box upon hover, as follows:
-----------------|
x-coordinate: ## |
y-coordinate: ## |
-----------------|
The working snippet for the working graph is posted below. But I will comment out the tooltip block to plot the chart.
Thanks.
var margin = {top: 50, right: 50, bottom: 50, left: 50}
, width = window.innerWidth - margin.left - margin.right
, height = window.innerHeight - margin.top - margin.bottom;
//labels
var labels = ['Mon','Tue','Thur','Frid'];
var yvals = [12,11,0,18];
// X scale
var xScale = d3.scalePoint()
.domain(labels) // input
.range([0, width-1]); // output
// Y scale
var yScale = d3.scaleLinear()
.domain([0, 20])
.range([height,0]);
var line = d3.line()
.x(function(d, i) { return xScale(labels[i]); })
.y(function(d) { return yScale(d.y); })
.curve(d3.curveMonotoneX)
var dataset = d3.range(yvals.length).map(function(d,i) { return {"y": yvals[i]} })
//Tooltip
//var tip = d3.select('body')
//.append('div')
//.attr('class', 'tip')
//.html('number:'+ function(d,i) return {data[data.i]})
// .style('border', '1px solid steelblue')
// .style('padding', '5px')
//.style('position', 'absolute')
// .style('display', 'none')
//.on('mouseover', function(d, i) {
// tip.transition().duration(0);
// })
// .on('mouseout', function(d, i) {
// tip.style('display', 'none');
// });
// SVGs
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 + ")");
svg.append("rect")
.attr("width", "100%")
.attr("height", "100%")
.attr("fill", "white");
svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// x axis call
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
//.call(d3.axisBottom(xScale));
.call(d3.axisBottom(xScale));
// y axis call
svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yScale));
svg.append("path")
.datum(dataset)
.attr("class", "line")
.attr("d", line);
// 12. Appends a circle for each datapoint
svg.selectAll(".dot")
.data(dataset)
.enter().append("circle") // Uses the enter().append() method
.attr("class", "dot") // Assign a class for styling
.attr("cx", function(d, i) { return xScale(labels[i]) })
.attr("cy", function(d,i) { return yScale(yvals[i]) })
.attr("r", 3);
//.on('mouseover', function(d, i) {
// tip.transition().duration(0);
// })
svg.append("text")
.attr("class", "title")
.attr("x", width/2)
.attr("y", 0 - (margin.top / 2))
.attr("text-anchor", "middle")
.text("Testing");
.line {
fill: none;
stroke: orange;
stroke-width: 1;
}
.dot {
fill: brown;
stroke: #fff;
}
<!DOCTYPE html>
<meta charset="utf-8">
<style type="text/css">
</style>
<body>
</body>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
</script>
I have just made a few changes to the mousemove event.
var margin = {
top: 50,
right: 50,
bottom: 50,
left: 50
},
width = window.innerWidth - margin.left - margin.right,
height = window.innerHeight - margin.top - margin.bottom;
//labels
var labels = ['Mon', 'Tue', 'Thur', 'Frid'];
var yvals = [12, 11, 0, 18];
// X scale
var xScale = d3.scalePoint()
.domain(labels) // input
.range([0, width - 1]); // output
// Y scale
var yScale = d3.scaleLinear()
.domain([0, 20])
.range([height, 0]);
var line = d3.line()
.x(function(d, i) {
return xScale(labels[i]);
})
.y(function(d) {
return yScale(d.y);
})
.curve(d3.curveMonotoneX)
var dataset = d3.range(yvals.length).map(function(d, i) {
return {
"y": yvals[i]
}
})
var tip = d3.select('body').append("div")
.attr("class", "tip");
// SVGs
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 + ")");
svg.append("rect")
.attr("width", "100%")
.attr("height", "100%")
.attr("fill", "white");
svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// x axis call
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
//.call(d3.axisBottom(xScale));
.call(d3.axisBottom(xScale));
// y axis call
svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yScale));
svg.append("path")
.datum(dataset)
.attr("class", "line")
.attr("d", line);
// 12. Appends a circle for each datapoint
svg.selectAll(".dot")
.data(dataset)
.enter().append("circle") // Uses the enter().append() method
.attr("class", "dot") // Assign a class for styling
.attr("cx", function(d, i) {
return xScale(labels[i])
})
.attr("cy", function(d, i) {
return yScale(yvals[i])
})
.attr("r", 3)
.on("mouseover", function() {
tip.style("display", null);
})
.on("mouseout", function() {
tip.style("display", "none");
})
.on("mousemove", function(d) {
return tip
.style("left", d3.event.pageX + "px")
.style("top", d3.event.pageY + 10 + "px")
.style("visibility", "visible")
.html(function() {
return '<div style="border:1px solid #ccc;">' +
'<p style="font-weight:bold;">' + d.y + '</p>' +
'</div>';
})
})
svg.append("text")
.attr("class", "title")
.attr("x", width / 2)
.attr("y", 0 - (margin.top / 2))
.attr("text-anchor", "middle")
.text("Testing");
.line {
fill: none;
stroke: orange;
stroke-width: 1;
}
.dot {
fill: brown;
stroke: #fff;
}
.tip {
position: absolute;
border: 1px solid steelblue;
visibility: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.0.0/d3.min.js"></script>
Here is the working jsFiddle
Hope it helps :)
Ive been at this for hours and cant seem to get my grouped bar chart to behave. Specifically trying to obtain a proper width for the 'g' translate property around each bar.
I have tried multiple methods and this seems to be the most elegant although im open to other solutions. The goal is something like this: http://www.cagrimmett.com/til/2016/04/26/responsive-d3-bar-chart.html
var data = [{"category":"Securily Provisions","values":[{"value":50,"rate":"Work Performed"},{"value":40,"rate":"Knowledge, Skills, and Abilities"}]},{"category":"Investigate","values":[{"value":25,"rate":"Work Performed"},{"value":21,"rate":"Knowledge, Skills, and Abilities"}]},{"category":"Operate and Maintain","values":[{"value":3,"rate":"Work Performed"},{"value":22,"rate":"Knowledge, Skills, and Abilities"}]},{"category":"Oversee and Govern","values":[{"value":12,"rate":"Work Performed"},{"value":7,"rate":"Knowledge, Skills, and Abilities"}]},{"category":"Protect and Defend","values":[{"value":6,"rate":"Work Performed"},{"value":15,"rate":"Knowledge, Skills, and Abilities"}]},{"category":"Collect and Operate","values":[{"value":92,"rate":"Work Performed"},{"value":85,"rate":"Knowledge, Skills, and Abilities"}]}]
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 ;
var x0 = d3.scale.ordinal().rangeRoundBands([0, width], .5);
var x1 = d3.scale.ordinal();
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var color = d3.scale.ordinal()
.range(["#02bfe7","#fdb81e"]);
var svg = d3.select('#chart-area').append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("preserveAspectRatio", "xMinYMin meet")
.append("g").attr("class","container")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//d3.json("data.json", function(error, data) {
var categoriesNames = data.map(function(d) {
return d.category;
});
var rateNames = data[0].values.map(function(d) {
return d.rate;
});
x0.domain(categoriesNames);
x1.domain(rateNames).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(data, function(categorie) {
return d3.max(categorie.values, function(d) {
return d.value;
});
})]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.style('opacity','0')
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.style('font-weight','bold')
.text("Value");
svg.select('.y').transition().duration(500).delay(1300).style('opacity','1');
var slice = svg.selectAll(".slice")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform",function(d) { return "translate(" + x0(d.category) + ",0)"; });
slice.selectAll("rect")
.data(function(d) { return d.values; })
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("x", function(d) { return x1(d.rate); })
.style("fill", function(d) { return color(d.rate) })
.attr("y", function(d) { return y(0); })
.attr("height", function(d) { return height - y(0); })
.on("mouseover", function(d) {
d3.select(this).style("fill", d3.rgb(color(d.rate)).darker(2));
})
.on("mouseout", function(d) {
d3.select(this).style("fill", color(d.rate));
});
slice.selectAll("rect")
.transition()
.delay(function (d) {return Math.random()*1000;})
.duration(1000)
.attr("class","bar")
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); });
//Legend
var legend = svg.selectAll(".legend")
.data(data[0].values.map(function(d) { return d.rate; }).reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d,i) { return "translate(0," + i * 20 + ")"; })
.style("opacity","0");
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", function(d) { return color(d); });
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) {return d; });
legend.transition().duration(500).delay(function(d,i){ return 1300 + 100 * i; }).style("opacity","1");
document.addEventListener("DOMContentLoaded", resize);
d3.select(window).on('resize', resize);
function resize() {
console.log('----resize function----');
// update width
width = parseInt(d3.select('#chart-area').style('width'), 10);
width = width - margin.left - margin.right;
height = parseInt(d3.select("#chart-area").style("height"));
height = height - margin.top - margin.bottom;
console.log('----resiz width----'+width);
console.log('----resiz height----'+height);
// resize the chart
x0.range([0, width]);
x0.rangeRoundBands([0, width], .03);
y.range([height, 0]);
yAxis.ticks(Math.max(height/50, 2));
xAxis.ticks(Math.max(width/50, 2));
d3.select(svg.node().parentNode)
.style('width', (width + margin.left + margin.right) + 'px');
svg.selectAll('.g')
//.attr("x", function(d) { return x0(categoriesNames); })
//.attr("x", function(d) { return x1(d.rate); })
// Problem here applying new width within translate
.attr("transform", "translate(10,0)")
.attr("width", x1.rangeBand());
svg.selectAll("text")
// .attr("x", function(d) { return x0(categoriesNames); })
.attr("x", (function(d) { return x0(categoriesNames ) + x0.rangeBand() / 2 ; } ))
.attr("y", function(d) { return y(rateNames) + 1; })
.attr("dy", ".75em");
svg.select('.x.axis').call(xAxis.orient('bottom')).selectAll("text").attr("x",55);
}
//});
.bar{
fill: steelblue;
}
.bar:hover{
fill: brown;
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
#chart-area {width: 100%;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="chart-area"></div>
Here's a solution. Is this the desired output? Try resizing the window.
JS FIDDLE
Resize function:
function resize() {
console.log('----resize function----');
// update width
width = parseInt(d3.select('#chart-area').style('width'), 10);
width = width - margin.left - margin.right;
height = parseInt(d3.select("#chart-area").style("height"));
height = height - margin.top - margin.bottom;
console.log('----resiz width----'+width);
console.log('----resiz height----'+height);
// resize the chart
x0.rangeRoundBands([0, width], .5);
x1.domain(rateNames).rangeRoundBands([0, x0.rangeBand()]);
y.range([height, 0]);
yAxis.ticks(Math.max(height/50, 2));
xAxis.ticks(Math.max(width/50, 2));
d3.select(svg.node().parentNode)
.style('width', (width + margin.left + margin.right) + 'px');
svg.selectAll('.g')
.attr("transform",function(d) {
return "translate(" + x0(d.category) + ",0)";
});
svg.selectAll('.g').selectAll("rect").attr("width", x1.rangeBand())
.attr("x", function(d) { return x1(d.rate); })
svg.selectAll(".legend rect")
.attr("x", width - 18);
svg.selectAll('.legend text')
.attr("x", width - 24)
svg.select('.x.axis').call(xAxis.orient('bottom'));
}
I made a few changes to the resize function. Here's why:
x0 and x1 ranges (both) have to be reset on resize:
x0.rangeRoundBands([0,width],.5);
x1.domain(rateNames).rangeRoundBands([0,x0.rangeBand()]);
y.range([height, 0]);
Translate of (10,0) was being force set in the resize function and you cannot apply width to a (group).
Basically, you just need to call all the code from the original render that includes width and height changes. Take a look at the resize function.
Re-rendering X-axis at the bottom included a static value for the x ticks:
svg.select('.x.axis').call(xAxis.orient('bottom')).selectAll("text").attr("x",55);
Just removed the attr("x", 55)
Hope this helps. :)
I'm trying to make a d3 scatterplot recurring to json data. I know that d3 has d3.json to load json data but my code isn't working. I'm not that good dealing with js (it's my first time with this language), that's why I need help in this.
Basically, I need to plot this data (date in xAxis and IADP_mGycm2 in yAxis):
[
{
"imageID": 1,
"value": 288.3,
"date": "2015-10-22"
},
{
"imageID": 2,
"value": 188.1,
"date": "2015-10-22"
}
]
JS:
var margin = { top: 50, right: 300, bottom: 50, left: 50 },
outerWidth = 1050,
outerHeight = 500,
width = outerWidth - margin.left - margin.right,
height = outerHeight - margin.top - margin.bottom;
var x = d3.scale.linear()
.range([0, width]).nice();
var y = d3.scale.linear()
.range([height, 0]).nice();
var xCat = "date",
yCat = "value";
d3.json("CR.json", function(error, rawData) {
if (error) {
console.error(error);
return;
}
var data = rawData.map(function (d) {
return {
date: d.date,
value: d.value
}
});
var xMax = d3.max(data, function(d) { return d["date"]; }),
xMin = d3.min(data, function(d) { return d["date"]; }),
xMin = xMin > 0 ? 0 : xMin,
yMax = d3.max(data, function(d) { return d["value"]; }) ,
yMin = d3.min(data, function(d) { return d["value"]; }),
yMin = yMin > 0 ? 0 : yMin;
x.domain([xMin, xMax]);
y.domain([yMin, yMax]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickSize(-height);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickSize(-width);
var color = d3.scale.category10();
var tip = d3.tip()
.attr("class", "d3-tip")
.offset([-10, 0])
.html(function(d) {
return xCat + ": " + d["date"] + "<br>" + yCat + ": " + d["value"];
});
var zoomBeh = d3.behavior.zoom()
.x(x)
.y(y)
.scaleExtent([0, 500])
.on("zoom", zoom);
var svg = d3.select("#scatter")
.append("svg")
.attr("width", outerWidth)
.attr("height", outerHeight)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoomBeh);
svg.call(tip);
svg.append("rect")
.attr("width", width)
.attr("height", height);
svg.append("g")
.classed("x axis", true)
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.classed("label", true)
.attr("x", width)
.attr("y", margin.bottom - 10)
.style("text-anchor", "end")
.text(xCat);
svg.append("g")
.classed("y axis", true)
.call(yAxis)
.append("text")
.classed("label", true)
.attr("transform", "rotate(-90)")
.attr("y", -margin.left)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text(yCat);
var objects = svg.append("svg")
.classed("objects", true)
.attr("width", width)
.attr("height", height);
objects.append("svg:line")
.classed("axisLine hAxisLine", true)
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", width)
.attr("y2", 0)
.attr("transform", "translate(0," + height + ")");
objects.append("svg:line")
.classed("axisLine vAxisLine", true)
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", 0)
.attr("y2", height);
objects.selectAll(".dot")
.data(data)
.enter().append("circle")
.classed("dot", true)
.attr("cy", function (d) { return d.value; })
.attr("cx", function (d) { return d.date; })
.attr("transform", transform)
.style("fill", "red")
.on("mouseover", tip.show)
.on("mouseout", tip.hide);
var legend = svg.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.classed("legend", true)
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("circle")
.attr("r", 3.5)
.attr("cx", width + 20)
.attr("fill", color);
legend.append("text")
.attr("x", width + 26)
.attr("dy", ".35em")
.text(function(d) { return d; });
d3.select("input").on("click", change);
function change() {
xCat = "date";
xMax = d3.max(data, function(d) { return d["date"]; });
xMin = d3.min(data, function(d) { return d["date"]; });
zoomBeh.x(x.domain([xMin, xMax])).y(y.domain([yMin, yMax]));
var svg = d3.select("#scatter").transition();
svg.select(".x.axis").duration(750).call(xAxis).select(".label").text("date");
objects.selectAll(".dot").transition().duration(1000).attr("transform", transform);
}
function zoom() {
svg.select(".x.axis").call(xAxis);
svg.select(".y.axis").call(yAxis);
svg.selectAll(".dot")
.attr("transform", transform);
}
function transform(d) {
return "translate(" + x(d["date"]) + "," + y(d["value"]) + ")";
}
});
HTML:
<html>
<head>
<meta charset="utf-8">
<title>Visualization</title>
<link rel="stylesheet" href="scatter.css" charset="utf-8">
</head>
<body>
<div id="scatter"></div>
<input type="button" name="xAxis" value="xAxis">
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="http://labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3.js"></script>
<script src="scatter.js" charset="utf-8"></script>
</body>
</html>
CSS:
rect {
fill: transparent;
shape-rendering: crispEdges;
}
.axis path,
.axis line {
fill: none;
stroke: rgba(0, 0, 0, 0.1);
shape-rendering: crispEdges;
}
.axisLine {
fill: none;
shape-rendering: crispEdges;
stroke: rgba(0, 0, 0, 0.5);
stroke-width: 2px;
}
.dot {
fill-opacity: .5;
}
.d3-tip {
line-height: 1;
font-weight: bold;
padding: 12px;
background: rgba(0, 0, 0, 0.8);
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: rgba(0, 0, 0, 0.8);
content: "\25BC";
position: absolute;
text-align: center;
}
/* Style northward tooltips differently */
.d3-tip.n:after {
margin: -1px 0 0 0;
top: 100%;
left: 0;
}
Thanks in advance!
One issue I see is that you're missing a }) at the end of this code chunk:
var data = rawData.map(function (d) {
return {
date: d.date,
IADP_mGycm2: d.IADP_mGycm2
};
Try changing it to this:
var data = rawData.map(function (d) {
return {
date: d.date,
IADP_mGycm2: d.IADP_mGycm2
}
});
It also helps in debugging if you include the specific error message you're getting.
Well, you define xCat as:
var xCat = "Date"
but your mapping function uses:
date: d.date
so, when you reference d[xCat] what you are getting is d.Date (which is undefined and would cause NaN) instead of d.date. That's all I can see with a quick look through.
You can fix this by using d['date'] or d.date instead of d[xCat].
Ok, after looking a little further at this, I've identified a couple of problems. The main issue is that the chart you are trying to emulate has numeric values on both the x and y axes. However, you are trying to use dates. In order to do that, you have to use d3.time.scale() for the x-axis instead of a linear scale. You also have to transform the date strings from your data to date objects and use your time scale to scale your x-axis values. Here is scatter.js with the changes:
var margin = { top: 50, right: 300, bottom: 50, left: 50 },
outerWidth = 1050,
outerHeight = 500,
width = outerWidth - margin.left - margin.right,
height = outerHeight - margin.top - margin.bottom;
// Use a time scale for the x-axis
var x = d3.time.scale()
.range([0, width]).nice();
var y = d3.scale.linear()
.range([height, 0]).nice();
var xCat = "date",
yCat = "Dose";
d3.json("CR.json", function(error, rawData) {
if (error) {
console.error(error);
return;
}
var data = rawData.map(function(d) {
return {
// Create date objects, not strings
date: new Date(d.date),
IADP_mGycm2: d.IADP_mGycm2
}
});
var xMax = d3.max(data, function(d) { return d["date"]; }),
xMin = d3.min(data, function(d) { return d["date"]; }),
//xMin = xMin > 0 ? 0 : xMin,
yMax = d3.max(data, function(d) { return d["IADP_mGycm2"]; }),
yMin = d3.min(data, function(d) { return d["IADP_mGycm2"]; });
//yMin = yMin > 0 ? 0 : yMin;
x.domain([xMin, xMax]);
y.domain([yMin, yMax]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickSize(-height);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickSize(-width);
var color = d3.scale.category10();
var tip = d3.tip()
.attr("class", "d3-tip")
.offset([-10, 0])
.html(function(d) {
return xCat + ": " + d["date"] + "<br>" + yCat + ": " + d["IADP_mGycm2"];
});
var zoomBeh = d3.behavior.zoom()
.x(x)
.y(y)
.scaleExtent([0, 500])
.on("zoom", zoom);
var svg = d3.select("#scatter")
.append("svg")
.attr("width", outerWidth)
.attr("height", outerHeight)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoomBeh);
svg.call(tip);
svg.append("rect")
.attr("width", width)
.attr("height", height);
svg.append("g")
.classed("x axis", true)
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.classed("label", true)
.attr("x", width)
.attr("y", margin.bottom - 10)
.style("text-anchor", "end")
.text(xCat);
svg.append("g")
.classed("y axis", true)
.call(yAxis)
.append("text")
.classed("label", true)
.attr("transform", "rotate(-90)")
.attr("y", -margin.left)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text(yCat);
var objects = svg.append("svg")
.classed("objects", true)
.attr("width", width)
.attr("height", height);
objects.append("svg:line")
.classed("axisLine hAxisLine", true)
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", width)
.attr("y2", 0)
.attr("transform", "translate(0," + height + ")");
objects.append("svg:line")
.classed("axisLine vAxisLine", true)
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", 0)
.attr("y2", height);
objects.selectAll(".dot")
.data(data)
.enter().append("circle")
.classed("dot", true)
.attr("cy", function(d) { return d.IADP_mGycm2; })
// Use the time scale to scale the values for the x-axis
.attr("cx", function(d) { return x(d.date); })
.attr("transform", transform)
.style("fill", "red")
.on("mouseover", tip.show)
.on("mouseout", tip.hide);
var legend = svg.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.classed("legend", true)
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("circle")
.attr("r", 3.5)
.attr("cx", width + 20)
.attr("fill", color);
legend.append("text")
.attr("x", width + 26)
.attr("dy", ".35em")
.text(function(d) { return d; });
d3.select("input").on("click", change);
function change() {
xCat = "date";
xMax = d3.max(data, function(d) { return d["date"]; });
xMin = d3.min(data, function(d) { return d["date"]; });
zoomBeh.x(x.domain([xMin, xMax])).y(y.domain([yMin, yMax]));
var svg = d3.select("#scatter").transition();
svg.select(".x.axis").duration(750).call(xAxis).select(".label").text("date");
objects.selectAll(".dot").transition().duration(1000).attr("transform", transform);
}
function zoom() {
svg.select(".x.axis").call(xAxis);
svg.select(".y.axis").call(yAxis);
svg.selectAll(".dot")
.attr("transform", transform);
}
function transform(d) {
return "translate(" + x(d["date"]) + "," + y(d["IADP_mGycm2"]) + ")";
}
});
This gets rid of the errors you were seeing, but it still doesn't plot the circles correctly. Sorry, I don't have the time to work all that out. However, this should move you forward and get you closer to figuring it out yourself. To learn more about time scales, see https://github.com/mbostock/d3/wiki/Time-Scales. Also, if you really want to learn D3, I highly recommend the book D3.js in Action by Elijah Meeks. https://www.manning.com/books/d3-js-in-action. One of the better programming books I have read.