d3 brush and clipPath: Main chart outside bounds - javascript

I am trying to do a bar chart with a brush component (based on https://bl.ocks.org/SevenChan07/495cd567e0ede0deeb14bb3599dce685) and using the enter-merge-exit pattern but I cant make the brush working properly. When I move the brush the chart goes beyond its bounds. I has probably to do with clipping but I dont know how to fix it.
var defs = focus.append('defs');
// use clipPath
defs.append('clipPath')
.attr('id', 'my-clip-path')
.append('rect')
.attr('width', width)
.attr('height', height);
Any help would be greatly appreciated.
Here is the fiddle and below is a snippet:
let barData = []
for(let i = 0;i < 100; i++){
barData.push({
Prob: Math.random()*10,
labels: 'test' + i
})
}
barchart(barData)
function barchart(data) {
var ordinals = data.map(function (d) {
return d.labels;
});
var svg = d3.select("#myPlot").select("svg");
var margin = {
top: 20,
right: 20,
bottom: 0.3 * svg.attr("height"),
left: 40
},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
margin2 = {
top: 20 + margin.top + height,
right: 20,
bottom: 30,
left: 40
},
height2 = height / 5;
// the scale
var scale = {
x: d3.scaleLinear().range([0, width]).nice(),
x2: d3.scaleLinear().range([0, width]).nice(),
y: d3.scaleLinear().range([height, 0]).nice(),
y2: d3.scaleLinear().range([height2, 0]).nice()
};
let xBand = d3.scaleBand().domain(d3.range(-1, ordinals.length)).range([0, width])
var axis = {
x: d3.axisBottom(scale.x).tickFormat((d, e) => ordinals[d]),
y: d3.axisLeft(scale.y)
};
var brush = d3.brushX()
.extent([[0, 0], [width, height2]])
.on("brush", brushed)
var focus = svg.select('.focus')
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
focus.select(".axis").attr("transform", "translate(0," + height +")");
var context = svg.select('.context')
.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")");
var defs = focus.append('defs');
// use clipPath
defs.append('clipPath')
.attr('id', 'my-clip-path')
.append('rect')
.attr('width', width)
.attr('height', height);
function updateScales(data) {
scale.x.domain([-1, ordinals.length])
scale.y.domain([0, d3.max(data, d => d.Prob)])
scale.x2.domain(scale.x.domain())
scale.y2.domain([0, d3.max(data, d => d.Prob)])
}
svg.call(renderPlot, data)
function renderPlot(selection, data) {
updateScales(data);
selection.select(".context")
.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")")
.select('.brush')
.call(brush)
.call(brush.move, scale.x.range())
selection.select(".axis2")
.attr("transform", "translate(0," + height2 +")");
selection.select(".focus").select(".axis").call(axis.x);
selection.select(".focus").select(".axis.axis--y").call(axis.y);
selection
.call(renderPoints, data);
}
function renderPoints(selection, data) {
var points = selection.select('.focus')
.selectAll('.bar').data(data);
var newPoints = points.enter().append('rect')
.attr('class', 'bar')
.attr('x', (d, i) => {
return scale.x(i) - xBand.bandwidth() * 0.9 / 2
})
.attr('y', (d, i) => {
return scale.y(d.Prob)
})
.attr('width', xBand.bandwidth() * 0.9)
.attr('height', d => {
return height - scale.y(d.Prob)
});
points.merge(newPoints)
.transition().duration(1000)
.attr('x', (d, i) => {
return scale.x(i) - xBand.bandwidth() * 0.9 / 2
})
.attr('y', (d, i) => {
return scale.y(d.Prob)
})
.attr('width', xBand.bandwidth() * 0.9)
.attr('height', d => {
return height - scale.y(d.Prob)
});
points.exit()
.transition().duration(1000)
.remove();
var sPoints = selection.select('.context').selectAll('.bar').data(data);
var newsPoints = sPoints.enter().append('rect')
.attr('class', 'bar')
.attr('x', (d, i) => {
return scale.x2(i) - xBand.bandwidth() * 0.9 / 2
})
.attr('y', (d, i) => scale.y2(d.Prob))
.attr('width', xBand.bandwidth() * 0.9)
.attr('height', d => {
return height2 - scale.y2(d.Prob)
});
sPoints.merge(newsPoints)
.transition().duration(1000)
.attr('x', (d, i) => {
return scale.x2(i) - xBand.bandwidth() * 0.9 / 2
})
.attr('y', (d, i) => scale.y2(d.Prob))
.attr('width', xBand.bandwidth() * 0.9)
.attr('height', d => {
return height2 - scale.y2(d.Prob)
});
sPoints.exit()
.transition().duration(1000)
.remove();
}
function brushed() {
var s = d3.event.selection || scale.x2.range()
scale.x.domain(s.map(scale.x2.invert, scale.x2))
focus.select('.axis').call(axis.x)
focus.selectAll('.bar')
.attr('x', (d, i) => {
return scale.x(i) - xBand.bandwidth() * 0.9 / 2
})
}
}
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<style type="text/css">
.bar { fill: steelblue; }
</style>
<script src='https://d3js.org/d3.v4.min.js' type='text/javascript'></script>
</head>
<body>
<div class='chart span4' id='myPlot'>
<svg width="700" height="500">
<g class="focus">
<g class="axis"></g>
<g class="axis axis--y"></g>
</g>
<g class="context">
<g class="axis2"></g>
<g class="brush"></g>
</g>
</svg>
</div>
</body>
</html>

You've almost got it, you just need to apply the clip path to something. We can easily do this with your bars (you could use a g containing only the bars too):
var newPoints = points.enter().append('rect')
.attr('class', 'bar')
..... // other attributes
.attr("clip-path","url(#my-clip-path)");
We only need to do it on enter, as the clip path doesn't need to be updated (we aren't changing it). Here's a snippet below:
let barData = []
for(let i = 0;i < 100; i++){
barData.push({
Prob: Math.random()*10,
labels: 'test' + i
})
}
barchart(barData)
function barchart(data) {
var ordinals = data.map(function (d) {
return d.labels;
});
var svg = d3.select("#myPlot").select("svg");
var margin = {
top: 20,
right: 20,
bottom: 0.3 * svg.attr("height"),
left: 40
},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
margin2 = {
top: 20 + margin.top + height,
right: 20,
bottom: 30,
left: 40
},
height2 = height / 5;
// the scale
var scale = {
x: d3.scaleLinear().range([0, width]).nice(),
x2: d3.scaleLinear().range([0, width]).nice(),
y: d3.scaleLinear().range([height, 0]).nice(),
y2: d3.scaleLinear().range([height2, 0]).nice()
};
let xBand = d3.scaleBand().domain(d3.range(-1, ordinals.length)).range([0, width])
var axis = {
x: d3.axisBottom(scale.x).tickFormat((d, e) => ordinals[d]),
y: d3.axisLeft(scale.y)
};
var brush = d3.brushX()
.extent([[0, 0], [width, height2]])
.on("brush", brushed)
var focus = svg.select('.focus')
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
focus.select(".axis").attr("transform", "translate(0," + height +")");
var context = svg.select('.context')
.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")");
var defs = focus.append('defs');
// use clipPath
defs.append('clipPath')
.attr('id', 'my-clip-path')
.append('rect')
.attr('width', width)
.attr('height', height);
function updateScales(data) {
scale.x.domain([-1, ordinals.length])
scale.y.domain([0, d3.max(data, d => d.Prob)])
scale.x2.domain(scale.x.domain())
scale.y2.domain([0, d3.max(data, d => d.Prob)])
}
svg.call(renderPlot, data)
function renderPlot(selection, data) {
updateScales(data);
selection.select(".context")
.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")")
.select('.brush')
.call(brush)
.call(brush.move, scale.x.range())
selection.select(".axis2")
.attr("transform", "translate(0," + height2 +")");
selection.select(".focus").select(".axis").call(axis.x);
selection.select(".focus").select(".axis.axis--y").call(axis.y);
selection
.call(renderPoints, data);
}
function renderPoints(selection, data) {
var points = selection.select('.focus')
.selectAll('.bar').data(data);
var newPoints = points.enter().append('rect')
.attr('class', 'bar')
.attr('x', (d, i) => {
return scale.x(i) - xBand.bandwidth() * 0.9 / 2
})
.attr('y', (d, i) => {
return scale.y(d.Prob)
})
.attr('width', xBand.bandwidth() * 0.9)
.attr('height', d => {
return height - scale.y(d.Prob)
})
.attr("clip-path","url(#my-clip-path)");
points.merge(newPoints)
.transition().duration(1000)
.attr('x', (d, i) => {
return scale.x(i) - xBand.bandwidth() * 0.9 / 2
})
.attr('y', (d, i) => {
return scale.y(d.Prob)
})
.attr('width', xBand.bandwidth() * 0.9)
.attr('height', d => {
return height - scale.y(d.Prob)
})
points.exit()
.transition().duration(1000)
.remove();
var sPoints = selection.select('.context').selectAll('.bar').data(data);
var newsPoints = sPoints.enter().append('rect')
.attr('class', 'bar')
.attr('x', (d, i) => {
return scale.x2(i) - xBand.bandwidth() * 0.9 / 2
})
.attr('y', (d, i) => scale.y2(d.Prob))
.attr('width', xBand.bandwidth() * 0.9)
.attr('height', d => {
return height2 - scale.y2(d.Prob)
});
sPoints.merge(newsPoints)
.transition().duration(1000)
.attr('x', (d, i) => {
return scale.x2(i) - xBand.bandwidth() * 0.9 / 2
})
.attr('y', (d, i) => scale.y2(d.Prob))
.attr('width', xBand.bandwidth() * 0.9)
.attr('height', d => {
return height2 - scale.y2(d.Prob)
});
sPoints.exit()
.transition().duration(1000)
.remove();
}
function brushed() {
var s = d3.event.selection || scale.x2.range()
scale.x.domain(s.map(scale.x2.invert, scale.x2))
focus.select('.axis').call(axis.x)
focus.selectAll('.bar')
.attr('x', (d, i) => {
return scale.x(i) - xBand.bandwidth() * 0.9 / 2
})
}
}
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<style type="text/css">
.bar { fill: steelblue; }
</style>
<script src='https://d3js.org/d3.v4.min.js' type='text/javascript'></script>
</head>
<body>
<div class='chart span4' id='myPlot'>
<svg width="700" height="500">
<g class="focus">
<g class="axis"></g>
<g class="axis axis--y"></g>
</g>
<g class="context">
<g class="axis2"></g>
<g class="brush"></g>
</g>
</svg>
</div>
</body>
</html>

Related

D3 change SVG dimensions on resize window

I have a project where I am using D3 js to create a few charts. I am trying to make these charts responsive when the window size changes. To do this I already used viewbox to define the svg:
var svg = d3
.select(this.$refs["chart"])
.classed("svg-container", true)
.append("svg")
.attr("class", "chart")
.attr(
"viewBox",
`0 0 ${width + margin.left + margin.right} ${height +
margin.top +
margin.bottom}`
)
.attr("preserveAspectRatio", "xMinYMin meet")
.classed("svg-content-responsive", true)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
I also use to set the width and height the same as the div where the SVG is inside. So that this chart uses the same size as the div it is inside:
width = this.$refs["chart"].clientWidth - margin.left - margin.right,
height = this.$refs["chart"].clientHeight - margin.top - margin.bottom;
The width and height of this div is set to 100% of it's parent div. So when I am resizing the window the div where the svg is in can change size and aspect ratio.
So this is what the chart looks initially when the page is loaded. So it's getting its height and width from the div it is in:
But when i resize the chart shrinks to still fit inside the new width of the parent div. But the height changes with it. So I assume that the aspect ratio stays the same:
I have tried to update the svg viewport when the window resizes. But the vieuwport isn't being updated when I inspect the SVG element in DOM of the developer tools in Chrome. I have added console logs to check if the width and height of the parent also change and they seem to change. But the updated viewport doesn't gets applied to the svg:
d3.select(window).on("resize", () => {
svg.attr(
"viewBox",
`0 0 ${this.$refs["chart"].clientWidth} ${this.$refs["chart"].clientHeight}`
);
});
<!DOCTYPE html>
<html>
<head>
<title></title>
<script src="https://unpkg.com/vue"></script>
<script src="https://d3js.org/d3.v6.js"></script>
<style>
.area {
fill: url(#area-gradient);
stroke-width: 0px;
}
body{
width: 100%;
height: 100%;
}
.app{
width: 100%;
height: 100%;
}
#page{
width: 100%;
height: 100%;
}
.my_dataviz{
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="app">
<div class="page">
<div id="my_dataviz" ref="chart"></div>
</div>
</div>
<script>
new Vue({
el: '#app',
data: {
type: Array,
required: true,
},
mounted() {
const minScale = 0,
maxScale = 35;
var data = [{
key: 'One',
value: 33,
},
{
key: 'Two',
value: 30,
},
{
key: 'Three',
value: 37,
},
{
key: 'Four',
value: 28,
},
{
key: 'Five',
value: 25,
},
{
key: 'Six',
value: 15,
},
];
console.log(this.$refs["chart"].clientHeight)
// set the dimensions and margins of the graph
var margin = {
top: 20,
right: 0,
bottom: 30,
left: 40
},
width =
this.$refs["chart"].clientWidth - margin.left - margin.right,
height =
this.$refs["chart"].clientHeight - margin.top - margin.bottom;
// set the ranges
var x = d3.scaleBand().range([0, width]).padding(0.3);
var y = d3.scaleLinear().range([height, 0]);
// append the svg object to the body of the page
// append a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3
.select(this.$refs['chart'])
.classed('svg-container', true)
.append('svg')
.attr('class', 'chart')
.attr(
'viewBox',
`0 0 ${width + margin.left + margin.right} ${
height + margin.top + margin.bottom
}`
)
.attr('preserveAspectRatio', 'xMinYMin meet')
.classed('svg-content-responsive', true)
.append('g')
.attr(
'transform',
'translate(' + margin.left + ',' + margin.top + ')'
);
// format the data
data.forEach(function(d) {
d.value = +d.value;
});
// Scale the range of the data in the domains
x.domain(
data.map(function(d) {
return d.key;
})
);
y.domain([minScale, maxScale]);
//Add horizontal lines
let oneFourth = (maxScale - minScale) / 4;
svg
.append('svg:line')
.attr('x1', 0)
.attr('x2', width)
.attr('y1', y(oneFourth))
.attr('y2', y(oneFourth))
.style('stroke', 'gray');
svg
.append('svg:line')
.attr('x1', 0)
.attr('x2', width)
.attr('y1', y(oneFourth * 2))
.attr('y2', y(oneFourth * 2))
.style('stroke', 'gray');
svg
.append('svg:line')
.attr('x1', 0)
.attr('x2', width)
.attr('y1', y(oneFourth * 3))
.attr('y2', y(oneFourth * 3))
.style('stroke', 'gray');
//Defenining the tooltip div
let tooltip = d3
.select('body')
.append('div')
.attr('class', 'tooltip')
.style('position', 'absolute')
.style('top', 0)
.style('left', 0)
.style('opacity', 0);
// append the rectangles for the bar chart
svg
.selectAll('.bar')
.data(data)
.enter()
.append('rect')
.attr('class', 'bar')
.attr('x', function(d) {
return x(d.key);
})
.attr('width', x.bandwidth())
.attr('y', function(d) {
return y(d.value);
})
.attr('height', function(d) {
console.log(height, y(d.value))
return height - y(d.value);
})
.attr('fill', '#206BF3')
.attr('rx', 5)
.attr('ry', 5)
.on('mouseover', (e, i) => {
d3.select(e.currentTarget).style('fill', 'white');
tooltip.transition().duration(500).style('opacity', 0.9);
tooltip
.html(
`<div><h1>${i.key} ${
this.year
}</h1><p>${converter.addPointsToEveryThousand(
i.value
)} kWh</p></div>`
)
.style('left', e.pageX + 'px')
.style('top', e.pageY - 28 + 'px');
})
.on('mouseout', (e) => {
d3.select(e.currentTarget).style('fill', '#206BF3');
tooltip.transition().duration(500).style('opacity', 0);
});
// Add the X Axis and styling it
let xAxis = svg
.append('g')
.attr('transform', 'translate(0,' + height + ')')
.call(d3.axisBottom(x));
xAxis
.select('.domain')
.attr('stroke', 'gray')
.attr('stroke-width', '3px');
xAxis.selectAll('.tick text').attr('color', 'gray');
xAxis.selectAll('.tick line').attr('stroke', 'gray');
// add the y Axis and styling it also only show 0 and max tick
let yAxis = svg.append('g').call(
d3
.axisLeft(y)
.tickValues([this.minScale, this.maxScale])
.tickFormat((d) => {
if (d > 1000) {
d = Math.round(d / 1000);
d = d + 'K';
}
return d;
})
);
yAxis
.select('.domain')
.attr('stroke', 'gray')
.attr('stroke-width', '3px');
yAxis.selectAll('.tick text').attr('color', 'gray');
yAxis.selectAll('.tick line').attr('stroke', 'gray');
d3.select(window).on('resize', () => {
svg.attr(
'viewBox',
`0 0 ${this.$refs['chart'].clientWidth} ${this.$refs['chart'].clientHeight}`
);
});
},
});
</script>
</body>
</html>
There are different approaches to "responsivity" with SVG and in D3 in particular. Using viewBox is one way to handle it, listening for resize events and redrawing the svg is another. If you're going to listen for resize events and re-render, you'll want to make sure you're using the D3 general update pattern.
1. Behavior you're seeing is expected when using viewBox and preserveAspectRatio.
2. In your example Vue and D3 seem to be in conflict over who is in control of the DOM.
Here are some examples to dynamic resizing using different approaches. Run them in full-size windows and use the console to log out the viewport dimensions.
Sara Soueidan's article Understanding SVG Coordinate Systems is really good. Curran Kelleher's example here uses the general update pattern for something that's more idiomatic.
Really hope this helps and good luck with the project! If you find that this answers your question, please mark it as the accepted answer. 👍
Forcing D3 to recalculate the size of the rects and axes on resize events ("sticky" to size of container):
const margin = {top: 20, right: 20, bottom: 50, left: 20}
const width = document.body.clientWidth
const height = document.body.clientHeight
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
const minScale = 0,
maxScale = 35;
const xScale = d3.scaleBand()
.range([0, width])
.padding(0.3);;
const yScale = d3.scaleLinear()
.range([0, height]);
const xAxis = d3.axisBottom(xScale)
const yAxis = d3.axisLeft(yScale)
const 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 + ")");
const data = [
{
key: 'One',
value: 33,
},
{
key: 'Two',
value: 30,
},
{
key: 'Three',
value: 37,
},
{
key: 'Four',
value: 28,
},
{
key: 'Five',
value: 25,
},
{
key: 'Six',
value: 15,
},
];
// format the data
data.forEach((d) => {
d.value = +d.value;
});
// Scale the range of the data in the domains
xScale.domain(data.map((d) => d.key));
yScale.domain([minScale, maxScale]);
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("X Axis");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("width", xScale.bandwidth())
.attr('x', (d) => xScale(d.key))
.attr("y", d => yScale(d.value))
.attr('height', function (d) {
return height - yScale(d.value);
})
.attr('fill', '#206BF3')
.attr('rx', 5)
.attr('ry', 5);
// 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.rangeRound([0, width], 0.1);
yScale.range([height, 0]);
// 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);
// Force D3 to recalculate and update the line
svg.selectAll(".bar")
.attr("width", xScale.bandwidth())
.attr('x', (d) => xScale(d.key))
.attr("y", d => yScale(d.value))
.attr('height', (d) => height - yScale(d.value));
};
// Call the resize function whenever a resize event occurs
d3.select(window).on('resize', resize);
// Call the resize function
resize();
.bar {
fill: #206BF3;
}
.bar:hover {
fill: red;
cursor: pointer;
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
#chart {
width: 100%;
height: 100%;
position: absolute;
}
<!DOCTYPE html>
<meta charset="utf-8">
<head>
<link rel="stylesheet" type="text/css" href="./style.css" />
</head>
<body>
<svg id="chart"></svg>
<script src="https://d3js.org/d3.v6.js"></script>
<script src="./chart.js"></script>
</body>
Using general update pattern (with transition to illustrate changes):
let data = [
{letter: 'A', frequency: 20},
{letter: 'B', frequency: 60},
{letter: 'C', frequency: 30},
{letter: 'D', frequency: 20},
];
chart(data);
function chart(data) {
var svg = d3.select("#chart"),
margin = {top: 55, bottom: 0, left: 85, right: 0},
width = parseInt(svg.style("width")) - margin.left - margin.right,
height = parseInt(svg.style("height")) - margin.top - margin.bottom;
// const barWidth = width / data.length
const xScale = d3.scaleBand()
.domain(data.map(d => d.letter))
.range([margin.left, width - margin.right])
.padding(0.5)
const yScale = d3.scaleLinear()
.domain([0, d3.max(data, d => d.frequency)])
.range([0, height])
const xAxis = svg.append("g")
.attr("class", "x-axis")
const yAxis = svg.append("g")
.attr("class", "y-axis")
redraw(width, height);
function redraw(width, height) {
yScale.range([margin.top, height - margin.bottom])
svg.selectAll(".y-axis")
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(yScale)
.ticks(data, d => d.frequency)
.tickFormat(function(d, i) {
return data[i].frequency;
}));
xScale.rangeRound([margin.left, width - margin.right]);
svg.selectAll(".x-axis").transition().duration(0)
.attr("transform", `translate(0,${height})`)
.call(d3.axisBottom(xScale));
var bar = svg.selectAll(".bar")
.data(data)
bar.exit().remove();
bar.enter().append("rect")
.attr("class", "bar")
.style("fill", "steelblue")
.merge(bar)
// origin of each rect is at top left corner, so width goes to right
// and height goes to bottom :)
.style('transform', 'scale(1, -1)')
.transition().duration(1000)
.attr("width", xScale.bandwidth())
.attr("height", d => yScale(d.frequency))
.attr("y", -height)
.attr("x", d => xScale(d.letter))
.attr("transform", (d, i) => `translate(${0},${0})`)
}
d3.select(window).on('resize', function() {
width = parseInt(svg.style("width")) - margin.left - margin.right,
height = parseInt(svg.style("height")) - margin.top - margin.bottom;
redraw(width, height);
});
}
<!DOCTYPE html>
<html>
<head>
<title>Bar Chart - redraw on window resize</title>
<style>
#chart {
outline: 1px solid red;
position: absolute;
width: 95%;
height: 95%;
overflow: visible;
}
</style>
</head>
<body>
<script type="text/javascript">
var windowWidth = window.innerWidth;
var windowHeight = window.innerHeight;
console.log('viewport width is: '+ windowWidth + ' and viewport height is: ' + windowHeight + '. Resize the browser window to fire the resize event.');
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.5.0/d3.min.js"></script>
<svg id="chart"></svg>
<script src="./responsiveBarWindowWidth.js"></script>
</body>
</html>
And here is your graph, only instead of a hard-coded value of 500px for the #my_dataviz parent, assign it a value of 100vh, which allows the svg to respond to the parent container's height and adjust the width accordingly.
Plunker: https://plnkr.co/edit/sBa6VmRH27xcgNiB?preview
Assigning height of 100vh to parent container
<!DOCTYPE html>
<html>
<head>
<title></title>
<script src="https://unpkg.com/vue"></script>
<script src="https://d3js.org/d3.v6.js"></script>
<style>
.area {
fill: url(#area-gradient);
stroke-width: 0px;
}
// changed from 500px:
#my_dataviz {
height: 100vh
}
</style>
</head>
<body>
<div id="app">
<div class="page">
<div class="">
<div id="my_dataviz" ref="chart"></div>
</div>
</div>
</div>
<script>
new Vue({
el: '#app',
data: {
type: Array,
required: true,
},
mounted() {
const minScale = 0,
maxScale = 35;
var data = [
{
key: 'One',
value: 33,
},
{
key: 'Two',
value: 30,
},
{
key: 'Three',
value: 37,
},
{
key: 'Four',
value: 28,
},
{
key: 'Five',
value: 25,
},
{
key: 'Six',
value: 15,
},
];
console.log(this.$refs["chart"].clientHeight)
// set the dimensions and margins of the graph
var margin = { top: 20, right: 0, bottom: 30, left: 40 },
width =
this.$refs["chart"].clientWidth - margin.left - margin.right,
height =
this.$refs["chart"].clientHeight - margin.top - margin.bottom;
// set the ranges
var x = d3.scaleBand().range([0, width]).padding(0.3);
var y = d3.scaleLinear().range([height, 0]);
// append the svg object to the body of the page
// append a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3
.select(this.$refs['chart'])
.classed('svg-container', true)
.append('svg')
.attr('class', 'chart')
.attr(
'viewBox',
`0 0 ${width + margin.left + margin.right} ${
height + margin.top + margin.bottom
}`
)
.attr('preserveAspectRatio', 'xMinYMin meet')
.classed('svg-content-responsive', true)
.append('g')
.attr(
'transform',
'translate(' + margin.left + ',' + margin.top + ')'
);
// format the data
data.forEach(function (d) {
d.value = +d.value;
});
// Scale the range of the data in the domains
x.domain(
data.map(function (d) {
return d.key;
})
);
y.domain([minScale, maxScale]);
//Add horizontal lines
let oneFourth = (maxScale - minScale) / 4;
svg
.append('svg:line')
.attr('x1', 0)
.attr('x2', width)
.attr('y1', y(oneFourth))
.attr('y2', y(oneFourth))
.style('stroke', 'gray');
svg
.append('svg:line')
.attr('x1', 0)
.attr('x2', width)
.attr('y1', y(oneFourth * 2))
.attr('y2', y(oneFourth * 2))
.style('stroke', 'gray');
svg
.append('svg:line')
.attr('x1', 0)
.attr('x2', width)
.attr('y1', y(oneFourth * 3))
.attr('y2', y(oneFourth * 3))
.style('stroke', 'gray');
//Defenining the tooltip div
let tooltip = d3
.select('body')
.append('div')
.attr('class', 'tooltip')
.style('position', 'absolute')
.style('top', 0)
.style('left', 0)
.style('opacity', 0);
// append the rectangles for the bar chart
svg
.selectAll('.bar')
.data(data)
.enter()
.append('rect')
.attr('class', 'bar')
.attr('x', function (d) {
return x(d.key);
})
.attr('width', x.bandwidth())
.attr('y', function (d) {
return y(d.value);
})
.attr('height', function (d) {
console.log(height, y(d.value))
return height - y(d.value);
})
.attr('fill', '#206BF3')
.attr('rx', 5)
.attr('ry', 5)
.on('mouseover', (e, i) => {
d3.select(e.currentTarget).style('fill', 'white');
tooltip.transition().duration(500).style('opacity', 0.9);
tooltip
.html(
`<div><h1>${i.key} ${
this.year
}</h1><p>${converter.addPointsToEveryThousand(
i.value
)} kWh</p></div>`
)
.style('left', e.pageX + 'px')
.style('top', e.pageY - 28 + 'px');
})
.on('mouseout', (e) => {
d3.select(e.currentTarget).style('fill', '#206BF3');
tooltip.transition().duration(500).style('opacity', 0);
});
// Add the X Axis and styling it
let xAxis = svg
.append('g')
.attr('transform', 'translate(0,' + height + ')')
.call(d3.axisBottom(x));
xAxis
.select('.domain')
.attr('stroke', 'gray')
.attr('stroke-width', '3px');
xAxis.selectAll('.tick text').attr('color', 'gray');
xAxis.selectAll('.tick line').attr('stroke', 'gray');
// add the y Axis and styling it also only show 0 and max tick
let yAxis = svg.append('g').call(
d3
.axisLeft(y)
.tickValues([this.minScale, this.maxScale])
.tickFormat((d) => {
if (d > 1000) {
d = Math.round(d / 1000);
d = d + 'K';
}
return d;
})
);
yAxis
.select('.domain')
.attr('stroke', 'gray')
.attr('stroke-width', '3px');
yAxis.selectAll('.tick text').attr('color', 'gray');
yAxis.selectAll('.tick line').attr('stroke', 'gray');
d3.select(window).on('resize', () => {
svg.attr(
'viewBox',
`0 0 ${this.$refs['chart'].clientWidth} ${this.$refs['chart'].clientHeight}`
);
});
},
});
</script>
</body>
</html>

d3.js brushable choropleth

I'm following this tutorial to build a brushable scatterplot and choropleth map.
I also want to show the states name by adding tooltips on choropleth.
The problem might be how to get the states name in the json file.
I tried features.properties.name but got the error : Uncaught TypeError: Cannot read property 'name' of undefined.
Could anyone please help?
Thanks!
<!DOCTYPE html>
<meta charset="utf-8">
<style>
div { float: left; }
</style>
<body>
<!-- <svg width="760" height="400"></svg> -->
<div id="choropleth"></div>
<div id="scatterplot"></div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>
<script>
d3.queue()
.defer(d3.csv, 'https://raw.githubusercontent.com/irrie/myprojects/master/usage0.csv', function (d) {
return {
name: d.State,
penetration: +d.Facebook_Penetration,
young_proportion: +d.YoungProportion
}
})
.defer(d3.json, 'https://raw.githubusercontent.com/python-visualization/folium/master/tests/us-states.json')
.awaitAll(initialize)
var color = d3.scaleThreshold()
.domain([0.3, 0.44, 0.6])
.range(['#fbb4b9', '#f768a1', '#c51b8a', '#7a0177'])
function initialize(error, results) {
if (error) { throw error }
var data = results[0]
var features = results[1].features
var components = [
choropleth(features),
scatterplot(onBrush)
]
function update() {
components.forEach(function (component) { component(data) })
}
function onBrush(x0, x1, y0, y1) {
var clear = x0 === x1 || y0 === y1
data.forEach(function (d) {
d.filtered = clear ? false
: d.young_proportion < x0 || d.young_proportion > x1 || d.penetration < y0 || d.penetration > y1
})
update()
}
update()
}
function scatterplot(onBrush) {
var margin = { top: 10, right: 15, bottom: 40, left: 75 }
var width = 360 - margin.left - margin.right
var height = 300 - margin.top - margin.bottom
var x = d3.scaleLinear()
.range([0, width])
var y = d3.scaleLinear()
.range([height, 0])
var xAxis = d3.axisBottom()
.scale(x)
// .tickFormat(d3.format('.2s'))
var yAxis = d3.axisLeft()
.scale(y)
.tickFormat(d3.format('.0%'))
var brush = d3.brush()
.extent([[0, 0], [width, height]])
.on('start brush', function () {
var selection = d3.event.selection
var x0 = x.invert(selection[0][0])
var x1 = x.invert(selection[1][0])
var y0 = y.invert(selection[1][1])
var y1 = y.invert(selection[0][1])
onBrush(x0, x1, y0, y1)
})
var svg = d3.select('#scatterplot')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
//.attr("transform", "translate(-370,390)")
.append('g')
.attr("transform", "translate(50,10)")
// .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
var bg = svg.append('g')
var gx = svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
var gy = svg.append('g')
.attr('class', 'y axis')
gx.append('text')
.attr('x', width)
.attr('y', 35)
.style('text-anchor', 'end')
.style('fill', '#000')
.style('font-weight', 'bold')
.text('Young Proportion')
gy.append('text')
.attr('transform', 'rotate(-90)')
.attr('x', 0)
.attr('y', -40)
.style('text-anchor', 'end')
.style('fill', '#000')
.style('font-weight', 'bold')
.text('Facebook Penetration')
svg.append('g')
.attr('class', 'brush')
.call(brush)
return function update(data) {
x.domain(d3.extent(data, function (d) { return d.young_proportion })).nice()
y.domain(d3.extent(data, function (d) { return d.penetration })).nice()
gx.call(xAxis)
gy.call(yAxis)
var bgRect = bg.selectAll('rect')
.data(d3.pairs(d3.merge([[y.domain()[0]], color.domain(), [y.domain()[1]]])))
bgRect.exit().remove()
bgRect.enter().append('rect')
.attr('x', 0)
.attr('width', width)
.merge(bgRect)
.attr('y', function (d) { return y(d[1]) })
.attr('height', function (d) { return y(d[0]) - y(d[1]) })
.style('fill', function (d) { return color(d[0]) })
var circle = svg.selectAll('circle')
.data(data, function (d) { return d.name })
circle.exit().remove()
circle.enter().append('circle')
.attr('r', 4)
.style('stroke', '#fff')
.merge(circle)
.attr('cx', function (d) { return x(d.young_proportion) })
.attr('cy', function (d) { return y(d.penetration) })
.style('fill', function (d) { return color(d.penetration) })
.style('opacity', function (d) { return d.filtered ? 0.5 : 1 })
.style('stroke-width', function (d) { return d.filtered ? 1 : 2 })
}
}
function choropleth(features) {
var width = 450
var height = 320
var projection = d3.geoAlbersUsa()
.scale([width * 1.25])
.translate([width / 2, height / 2])
var path = d3.geoPath().projection(projection)
var svg = d3.select('#choropleth')
.append('svg')
.attr('width', width)
.attr('height', height)
svg.selectAll('path')
.data(features)
.enter()
.append('path')
.attr('d', path)
.style('stroke', '#fff')
.style('stroke-width', 1)
.on("mouseenter", function(d) {
d3.select(this)
.style("stroke-width", 1.5)
.style("stroke-dasharray", 0)
d3.select("#choropleth")
.transition()
.style("opacity", 1)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY) + "px")
.text(features.properties.name)}) //here
.on("mouseleave", function(d) {
d3.select(this)
.style("stroke-width", .25)
.style("stroke-dasharray", 1)
d3.select("#Text")
.transition()
.style("opacity", 0.9);
})
return function update(data) {
svg.selectAll('path')
.data(data, function (d) { return d.name || d.properties.name })
.style('fill', function (d) { return d.filtered ? '#ddd' : color(d.penetration) })
}
}
</script>
</body>
(see also this fiddle: http://jsfiddle.net/swo8r7zk/)
You can use d.name in that scope. d also has penetration and young_proportion properties for each state. These are defined at the beginning of the file where you load the CSV data.

How to print content of tooltip on different lines in D3

Below is the code for horizontal stacked Bar Chart. When I hover on any bar, it shows me content of the element all in single line. I want to add line break to separate each parameter of the data. I tried looking for this type of question and found something with 'tspan' but that is not working in my code. Please have a look.
<%# taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%# taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%# taglib prefix="lit" uri="/WEB-INF/......" %>
<!DOCTYPE html>
<meta charset="utf-8">
<head>
<link rel="stylesheet" media="all" type="text/css" href="<c:url value="/styl........."/>" />
</head>
<body>
<script src="//d3js.org/d3.v3.js"></script>
<script>
var i = 0;
var margins = {
top: 12,
left: 48,
right: 24,
bottom: 24
},
legendPanel = {
width: 180
},
width = 1500 - margins.left - margins.right - legendPanel.width,
height = 900 - margins.top - margins.bottom,
dataset = ${JSONData},
series = dataset.map(function (d) {
return d.name;
}),
dataset = dataset.map(function (d) {
return d.data.map(function (o) {
return {
name: d.name,
y: +o.count,
x: o.time
};
});
}),
stack = d3.layout.stack();
stack(dataset);
var dataset = dataset.map(function (group) {
return group.map(function (d) {
return {
name: d.name,
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("Count: " + d.x + "Time: " + d.y + "String1"+
"\nString2");
//.text(d.x+","+d.y+","+d.name);
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>
<div id="tooltip" class="hidden">
<p><span id="value">100</span>
</p>
</div>
</body>
</html>
Your "tooltip" is just an HTML span; so all you need to do is use .html and a <br> for the line break:
d3.select('#tooltip')
.style('left', xPos + 'px')
.style('top', yPos + 'px')
.select('#value')
.html("Count: " + d.x + "Time: " + d.y + " String1" + "<br>String2");
If you had been creating an svg text element, then you'd need to append multiple tspan elements to it.
Full code:
<html>
<head>
</head>
<body>
<script src="//d3js.org/d3.v3.js"></script>
<script>
var i = 0;
var margins = {
top: 12,
left: 48,
right: 24,
bottom: 24
},
legendPanel = {
width: 180
},
width = 1500 - margins.left - margins.right - legendPanel.width,
height = 900 - margins.top - margins.bottom,
dataset = [{data:[{
count: Math.random(),
time: "One"
},{
count: Math.random(),
time: "Two"
},{
count: Math.random(),
time: "Three"
}]}];
series = dataset.map(function(d) {
return d.name;
}),
dataset = dataset.map(function(d) {
return d.data.map(function(o) {
return {
name: d.name,
y: +o.count,
x: o.time
};
});
}),
stack = d3.layout.stack();
stack(dataset);
var dataset = dataset.map(function(group) {
return group.map(function(d) {
return {
name: d.name,
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')
.html("Count: " + d.x + "Time: " + d.y + " String1" + "<br>String2");
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>
<div id="tooltip" class="hidden">
<p>
<span id="value">100</span>
</p>
</div>
</body>
</html>

D3 axis and bar transition not working

Lost on this especially since I've done it successfully before. The code below is within a function called on a click. This first example works just fine:
y.domain([1, get_max(data)]);
svg.select('.y.axis')
.call(yAxis);
svg.selectAll('.bars')
.attr('y', function (d) { return y(d); })
.attr('height', function (d) { return height - y(d); });
This second example doesn't do anything:
y.domain([1, get_max(data)]);
svg.select('.y.axis')
.transition()
.duration(80)
.call(yAxis);
svg.selectAll('.bars')
.transition()
.duration(80)
.attr('y', function (d) { return y(d); })
.attr('height', function (d) { return height - y(d); });
No javascript errors are produced. It simply doesn't do anything.
Any help would be greatly appreciated. Thank you!
Note: get_max(data) is a special function to get the max of some very oddly formatted data. When I replace it with a hard coded value of 10,000 the problem persists. Again it works fine until I add the transition.
EDIT:
function render(parent, data, brands){
var time_format = parent.attr('data-chart-size') > 3 ? '%b %e' : '%e',
margin = { top: 30, right: 20, bottom: 21, left: 45 },
width = parent.width() - margin.left - margin.right - 110; // -110 for the legends on the right side
height = 205 - margin.top - margin.bottom;
var x = d3.time.scale().domain([ML._dates.start(), ML._dates.end()]).range([0, width]);
y = d3.scale.log().clamp(true).range([height, 1]);
var xAxis = d3.svg.axis().scale(x).orient('bottom')
.tickFormat(d3.time.format(time_format)).tickSize(0).tickPadding(8);
yAxis = d3.svg.axis().scale(y).orient('left').ticks(5, 's').tickSize(0);
svg = d3.select(parent.get(0)).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 brands_length = brands.length,
values_length = data.posts[brands[0]].values.length,
bar_width_adjustment = 60 / values_length,
bar_width = ((width / values_length) / brands_length) - bar_width_adjustment;
range_band = width / values_length;
y.domain([1, get_max(data)]);
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(' + (bar_width * 2) + ',' + height + ')')
.call(xAxis);
svg.append('g')
.attr('class', 'y axis')
.attr('transform', 'translate(0, 0)')
.call(yAxis);
// BUILD THE BARS AND TOOLTIP HIGHLIGHT AREA
for(var brands_loop = 0; brands_loop < brands_length; ++brands_loop){
svg.selectAll('.chart-hover')
.data(data.posts[brands[brands_loop]].values)
.enter().append('rect')
.attr('width', width / values_length)
.attr('x', function (d, i) { return i * range_band - ((range_band - bar_width * 3) / 2); })
.attr('y', 1)
.attr('height', height - y(y.domain()[1]))
.attr('transform', 'translate(' + ((bar_width * (brands_loop + 1) - (bar_width / 2)) + ', 0)'))
.attr('data-index', function (d, i) { return i; })
.attr('class', 'chart-hover')
.style('opacity', 0.01);
svg.selectAll('.bar-' + brands_loop)
.data(data.posts[brands[brands_loop]].values)
.enter().append('rect')
.attr('data-legend-listener-brand', brands[brands_loop])
.attr('data-legend-listener-metric', 'posts')
.attr('data-hover-dispatcher-index', function (d, i) { return i; })
.attr('width', bar_width)
.attr('x', function (d, i) { return i * range_band; })
.attr('y', function (d) { return y(d); })
.attr('height', function (d) { return height - y(d); })
.attr('transform', 'translate(' + ((bar_width * (brands_loop + 1) - (bar_width / 2)) + ', 0)'))
.attr('class', 'posts bars bar-' + brands_loop);
// POPULATE LEGEND TEXTS FOR POSTS, EXPOSURE AND ENGAGEMENT
$('.brand-' + (brands_loop + 1))
.text(data.posts[brands[brands_loop]].label)
.attr('data-legend-brand', brands[brands_loop])
.attr('data-legend-listener-brand', brands[brands_loop])
.prev('i')
.attr('data-legend-brand', brands[brands_loop])
.attr('data-legend-listener-brand', brands[brands_loop]);
}
etc. etc.

Display Pie chart, Grouped and Stacked bar chart in one page

I want to display two charts in one page.The Pie chart gets displayed but the Grouped and Stacked bar chart is not displaying . I tried to change the Id name in but no luck :( . Will appreciate if anyone helps me with the code correction .
/* Display Matrix Chart ,Pie chart, Grouped and Stacked bar chart in one page */
<apex:page showHeader="false">
<apex:includeScript value="{!URLFOR($Resource.jquery1)}"/>
<apex:includeScript value="{!URLFOR($Resource.D3)}"/>
<apex:includeScript value="{!URLFOR($Resource.nvD3)}"/>
<div id="body" height="50%" width="100px" ></div> // Id for Pie chart
<div id="body1" height="30%" width="90px"></div> // Id for Stacked and Grouped bar chart
<script>
// Matrix Chart starts here
var drawChart = function(divId,matrixReportId) {
$.ajax('/services/data/v29.0/analytics/reports/'+matrixReportId,
{
beforeSend: function(xhr) {
xhr.setRequestHeader('Authorization', 'Bearer {!$Api.Session_ID}');
},
success: function(response) {
console.log(response);
var chart = nv.models.multiBarChart();
var chartData = [];
document.getElementById(divId).innerHTML = '';
$.each(response.groupingsDown.groupings, function(di, de) {
var values = [];
chartData.push({"key":de.label, "values": values});
$.each(response.groupingsAcross.groupings, function(ai, ae) {
values.push({"x": ae.label, "y": response.factMap[de.key+"!"+ae.key].aggregates[0].value});
});
});
d3.select('#'+divId).datum(chartData).transition().duration(100).call(chart);
window.setTimeout(function(){
drawChart(divId,matrixReportId);
}, 5000);
}
}
);
};
$(document).ready(function(){
drawChart('chart','00O90000005SSHv');
});
// Pie Chart Starts here
var width =250 ,
height = 450,
radius = Math.min(width, height) / 2;
var data = [{"age":"<5","population":2704659},{"age":"14-17","population":2159981},
{"age":"14-17","population":2159981},{"age":"18-24","population":3853788},
{"age":"25-44","population":14106543},{"age":"45-64","population":8819342},
{"age":">65","population":612463}];
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var arc = d3.svg.arc().outerRadius(radius - 10).innerRadius(0);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.population; });
var svg = d3.select("#body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function(d) { return color(d.data.age); });
g.append("text")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function(d) { return d.data.age; });
// Grouped and Stacked Bar Chart starts here
var n = 2, // number of layers
m = 10, // number of samples per layer
stack = d3.layout.stack(),
layers = stack(d3.range(n).map(function() { return bumpLayer(m, .1); })),
yGroupMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y; }); }),
yStackMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y0 + d.y; }); });
var margin = {top: 40, right: 10, bottom: 20, left: 10},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.domain(d3.range(m))
.rangeRoundBands([0, width], .08);
var y = d3.scale.linear()
.domain([0, yStackMax])
.range([height, 0]);
var color = d3.scale.linear()
.domain([0, n - 1])
.range(["#aad", "#556"]);
var xAxis = d3.svg.axis()
.scale(x)
.tickSize(0)
.tickPadding(6)
.orient("bottom");
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 layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) { return color(i); });
var rect = layer.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("x", function(d) { return x(d.x); })
.attr("y", height)
.attr("width", x.rangeBand())
.attr("height", 0);
rect.transition()
.delay(function(d, i) { return i * 10; })
.attr("y", function(d) { return y(d.y0 + d.y); })
.attr("height", function(d) { return y(d.y0) - y(d.y0 + d.y); });
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
d3.selectAll("input").on("change", change);
var timeout = setTimeout(function() {
d3.select("input[value=\"grouped\"]").property("checked", true).each(change);
}, 2000);
function change() {
clearTimeout(timeout);
if (this.value === "grouped") transitionGrouped();
else transitionStacked();
}
function transitionGrouped() {
y.domain([0, yGroupMax]);
rect.transition()
.duration(500)
.delay(function(d, i) { return i * 10; })
.attr("x", function(d, i, j) { return x(d.x) + x.rangeBand() / n * j; })
.attr("width", x.rangeBand() / n)
.transition()
.attr("y", function(d) { return y(d.y); })
.attr("height", function(d) { return height - y(d.y); });
}
function transitionStacked() {
y.domain([0, yStackMax]);
rect.transition()
.duration(500)
.delay(function(d, i) { return i * 10; })
.attr("y", function(d) { return y(d.y0 + d.y); })
.attr("height", function(d) { return y(d.y0) - y(d.y0 + d.y); })
.transition()
.attr("x", function(d) { return x(d.x); })
.attr("width", x.rangeBand());
}
// Inspired by Lee Byron's test data generator.
function bumpLayer(n, o) {
function bump(a) {
var x = 1/(.1 + Math.random()),
y = 2*Math.random()-.5,
z = 10/(.1 + Math.random());
for (var i = 0; i < n; i++) {
var w = (i/n- y) * z;
a[i] += x * Math.exp(-w * w);
}
}
var a=[],i;
for (i = 0; i < n; ++i) a[i] = o + o * Math.random();
for (i = 0; i < 5; ++i) bump(a);
return a.map(function(d, i) { return {x: i, y: Math.max(0, d)}; });
}
</script>
<svg id="chart" height="50%" width="500px" ></svg> // Id for Matrix chart
</apex:page>
Thanks in advance
first, as we discussed here (d3 Donut chart does not render), you should position your
<div id="body1" height="30%" width="90px"></div>
above the script.
second, you are missing the # in your second svg-declaration to correctly select the div by its id, it has to be
var svg = d3.select("#body1").append("svg")
you could also think about naming the second svg differently (eg svg2) so you don't override your first svg-variable (in case you want to do something with it later).

Categories

Resources