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>
Related
Im trying to create a Heatmap. But what i can do its just to show one rectangle. whats wrong in my code? Ive tried many Things, but still its not working. Can Anyone help me with this?
There arent any error in my code, buts still its wrong. der rectangel should be filled with different colours.
Here is my current Code:
<!DOCTYPE html>
<meta charset="utf-8" />
<title>test</title>
<style>
body {
margin: 50px;
font-family: Arial;
}
</style>
<body>
<p>First Tutorial</p>
<script src="https://d3js.org/d3.v6.js"></script>
<div id="container"> </id>
<script>
/* JavaScript */
var data = [
[2.56, 8.52, 4.92, 2.58, 8.44, 2.29],
[7.94, 8.42, 7.71, 7.0, 8.13, 5.63],
[1.38, 3.29, 2.38, 2.85, 1.38, 1.77],
[1.31, 2.48, 1.04, 1.21, 1.83, 1.48],
[1.58, 8.19, 4.75, 3.38, 4.83, 1.46],
[4.48, 4.08, 4.13, 1.73, 1.37, 2.58], ];
var rowLabels = [
"rowOne",
"rowTwo",
"rowThree",
"rowFour",
"rowFive",
"rowSix",
];
var columnLabels = [
"columnOne",
"columnTwo",
"columnThree",
"columnFour",
"columnFive",
"columnSix",
];
const mapData = data.reduce((res, item, index) => {
const group = rowLabels[index];
item.forEach((value, colIndex) => {
res.min = Math.min(value, res.min);
res.max = Math.max(value, res.max);
res.data.push({group, variable: columnLabels[colIndex], value});
});
return res;
}, {data: [], min: Number.POSITIVE_INFINITY, max: Number.NEGATIVE_INFINITY});
/* Layout constants */
var margin = {top: 0, right:0, bottom: 90, left: 80},
width = 960- margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
/* Initialization of SVG graphics */
var svg = d3.select("#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 + ")");
//X Scale and Axis:
var x= d3.scaleBand()
.range([ 0, width ])
.domain(columnLabels)
.padding(0.01);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.selectAll("text")
.attr("transform", "translate(-10,10) rotate(-45)")
.style("text-anchor", "end")
.style("font-size", 10)
// Y scales and Axis:
var y= d3.scaleBand()
.range([ height, 0 ])
.domain(rowLabels)
.padding(0.01);
svg.append("g")
.call(d3.axisLeft(y))
var myColor = d3.scaleLinear()
.range(["#0000ff", "#00ff00"])
.domain([mapData.min,mapData.max])
svg.selectAll()
.data(mapData.data, function(d) {return d.group+':'+d.variable;})
.enter()
.append("rect")
.attr("x", function(d) { return x(d.group) })
.attr("y", function(d) { return y(d.variable) })
.attr("width", x.bandwidth() )
.attr("height", y.bandwidth() )
.style("fill", function(d) { return myColor(d.value)} )
</script>
</body>
I've been trying to do a Area graph with zoom, which works great unless i give a negative value to Y domain. And then it looks great if i don't try to zoom along the y axis
I've tried using the min value of of y for y0 and while that fixes the rendering (it looks god awful and it renders X where it has no value).
// set the dimensions and margins of the graph
let margin = {
top: 0,
right: 0,
bottom: 30,
left: 30
},
width = 440 - margin.left - margin.right,
height = 240 - margin.top - margin.bottom;
// append the svg object to the body of the page
let svg = d3.select("#my_dataviz")
.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 + ")");
//Read the data
d3.csv("https://raw.githubusercontent.com/dimitriiBirsan/RandomNumbersYear1970-2031/main/testing%20negative%20numbers.csv",
// When reading the csv, I must format variables:
function(d) {
return {
date: d3.timeParse("%m/%d/%Y")(d.date),
value: d.value
}
},
// Now I can use this dataset:
function(data) {
// Add X axis --> it is a date format
let x = d3.scaleTime()
.domain(d3.extent(data, function(d) {
return +d.date;
}))
.range([0, width]);
let xAxis = svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).ticks(4));
// Add Y axis
let y = d3.scaleLinear()
.domain(d3.extent(data, function(d) {
return +d.value;
}))
.range([height, 0]);
let yAxis = svg.append("g")
.call(d3.axisLeft(y));
function make_x_gridlines(x) {
return d3.axisBottom(x)
}
function make_y_gridlines(y) {
return d3.axisLeft(y)
}
// Add a clipPath : everything out of this area won't be drawn
let clip = svg.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("width", width)
.attr("height", height)
.attr("x", 0)
.attr("y", 0);
// Add the area
let line = svg.append('g')
.attr("clip-path", "url(#clip)")
line.append("path")
.datum(data)
.attr("class", "polyArea")
.attr("fill", "blue")
.attr("opacity", 0.7)
.attr("stroke", "none")
.attr("stroke-width", 1.5)
.attr("d", d3.area()
.x(function(d) {
return x(d.date)
})
.y0(y(0))
.y1(function(d) {
return y(d.value)
})
.curve(d3.curveStepAfter)
.defined((d, i) => (i != null))
);
let zoom = d3.zoom()
.scaleExtent([1, 50]) // This control how much you can unzoom (x0.5) and zoom (x20)
.translateExtent([
[0, 0],
[width, height]
])
.extent([
[0, 0],
[width, height]
])
.on("zoom", updateChart);
// This adds an invisible rect on top of the chart area. This rect can recover pointer events: necessary to understand when the user zoom
svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all")
.attr('transform', 'translate(' + margin.top + ')')
.call(zoom);
function updateChart() {
// recover the new scale
let newX = d3.event.transform.rescaleX(x);
let newY = d3.event.transform.rescaleY(y);
// update axes with these new boundaries
xAxis.call(d3.axisBottom(newX).ticks(5))
yAxis.call(d3.axisLeft(newY))
// update location
svg
.select('.polyArea')
.attr("d", d3.area()
.x(function(d) {
return newX(d.date)
})
.y0(y(0))
.y1(function(d) {
return newY(d.value)
})
.curve(d3.curveStepAfter)
.defined((d, i) => (i != null)))
}
})
.grid line {
stroke: grey;
stroke-opacity: 0.7;
shape-rendering: crispEdges;
}
.grid path {
stroke-width 0;
}
<main>
<div id="my_dataviz">
</div>
</main>
<script src="https://d3js.org/d3.v4.js"></script>
You forgot to use newY(0) instead of y(0) so now when you zoom, the zero line is where it should be, not where it was when you started
// set the dimensions and margins of the graph
let margin = {
top: 0,
right: 0,
bottom: 30,
left: 30
},
width = 440 - margin.left - margin.right,
height = 240 - margin.top - margin.bottom;
// append the svg object to the body of the page
let svg = d3.select("#my_dataviz")
.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 + ")");
//Read the data
d3.csv("https://raw.githubusercontent.com/dimitriiBirsan/RandomNumbersYear1970-2031/main/testing%20negative%20numbers.csv",
// When reading the csv, I must format variables:
function(d) {
return {
date: d3.timeParse("%m/%d/%Y")(d.date),
value: d.value
}
},
// Now I can use this dataset:
function(data) {
// Add X axis --> it is a date format
let x = d3.scaleTime()
.domain(d3.extent(data, function(d) {
return +d.date;
}))
.range([0, width]);
let xAxis = svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).ticks(4));
// Add Y axis
let y = d3.scaleLinear()
.domain(d3.extent(data, function(d) {
return +d.value;
}))
.range([height, 0]);
let yAxis = svg.append("g")
.call(d3.axisLeft(y));
function make_x_gridlines(x) {
return d3.axisBottom(x)
}
function make_y_gridlines(y) {
return d3.axisLeft(y)
}
// Add a clipPath : everything out of this area won't be drawn
let clip = svg.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("width", width)
.attr("height", height)
.attr("x", 0)
.attr("y", 0);
// Add the area
let line = svg.append('g')
.attr("clip-path", "url(#clip)")
line.append("path")
.datum(data)
.attr("class", "polyArea")
.attr("fill", "blue")
.attr("opacity", 0.7)
.attr("stroke", "none")
.attr("stroke-width", 1.5)
.attr("d", d3.area()
.x(function(d) {
return x(d.date)
})
.y0(y(0))
.y1(function(d) {
return y(d.value)
})
.curve(d3.curveStepAfter)
.defined((d, i) => (i != null))
);
let zoom = d3.zoom()
.scaleExtent([1, 50]) // This control how much you can unzoom (x0.5) and zoom (x20)
.translateExtent([
[0, 0],
[width, height]
])
.extent([
[0, 0],
[width, height]
])
.on("zoom", updateChart);
// This adds an invisible rect on top of the chart area. This rect can recover pointer events: necessary to understand when the user zoom
svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all")
.attr('transform', 'translate(' + margin.top + ')')
.call(zoom);
function updateChart() {
// recover the new scale
let newX = d3.event.transform.rescaleX(x);
let newY = d3.event.transform.rescaleY(y);
// update axes with these new boundaries
xAxis.call(d3.axisBottom(newX).ticks(5))
yAxis.call(d3.axisLeft(newY))
// update location
svg
.select('.polyArea')
.attr("d", d3.area()
.x(function(d) {
return newX(d.date)
})
.y0(newY(0))
.y1(function(d) {
return newY(d.value)
})
.curve(d3.curveStepAfter)
.defined((d, i) => (i != null)))
}
})
.grid line {
stroke: grey;
stroke-opacity: 0.7;
shape-rendering: crispEdges;
}
.grid path {
stroke-width 0;
}
<main>
<div id="my_dataviz">
</div>
</main>
<script src="https://d3js.org/d3.v4.js"></script>
I am trying to implement the following problem while learning d3.js for visualization.
Using the following titanic dataset:
Plot in scatterplot :
a)the male passengers using an SVG square (width 5, x and y - 2.5 )
b)the female passengers using a circle of radius 2.8
c) Have the survived column used as opacity such that the dead have opacity 0.25 and alive have opacity: 1;
fill-opacity:.1;
stroke: black;
Make the scatterplot axes, make the y axis to log scale, and add the passengers name on their mark (using the SVG title element).
I am implementing the following code to achieve my goals but, I have am not successful in displaying my graph.
Can anyone please help me.
The titanic dataset - here
And my code here:
// set the dimensions and margins of the graph
var margin = {
top: 20,
right: 30,
bottom: 30,
left: 40
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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 + ")");
//Read the data
d3.csv("https://gist.githubusercontent.com/michhar/2dfd2de0d4f8727f873422c5d959fff5/raw/fa71405126017e6a37bea592440b4bee94bf7b9e/titanic.csv", function(rawData) {
const data = rawData.map(function(d) {
return {
age: Number(d.age),
fare: Number(d.fare),
sex: d.sex,
survived: d.survived === "1",
name: d.name
};
});
// Add X axis
var x = d3.scaleLinear()
.domain([0, 80])
.range([0, width]);
svg.append("g")
.attr("transform", "translate(0," + height + ")");
// Add Y axis
var y = d3.scaleLog()
.domain([1e+0, 1e+3])
.range([height, 0]);
svg.append("g");
// Add dots
svg.append('g')
.selectAll("dot").select("female")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
return x(d.age);
})
.attr("cy", function(d) {
return y(d.fare);
})
//.attr("r", 2.8)
.style("opacity", function(d) {
return d.survived ? "1" : "0.25";
})
.style("stroke", "black")
.style("fill-opacity", 0.1)
svg.append('g')
.selectAll("dot").select("male")
.data(data)
.enter()
.append("rect")
.attr("cx", function(d) {
return x(d.age);
})
.attr("cy", function(d) {
return y(d.fare);
})
//.attr("width", 5)
.style("opacity", function(d) {
return d.survived ? "1" : "0.25";
})
.style("stroke", "black")
.style("fill-opacity", 0.1)
.append("svg:title")
.text(function(d) {
return d.name
});
})
<script src="https://d3js.org/d3.v4.js"></script>
<div id="my_dataviz"></div>
can anyone please highlight where i am making mistake and help me please
You really, really need to read the manual, especially the SVG one. rect nodes don't have cx and cy, they have x and y, width, and height. And circle needs a radius r in order to be visible.
And you gave all the properties you read a lowercase starting letter. They need capitals. Look up a manual on debugging.
// set the dimensions and margins of the graph
var margin = {
top: 20,
right: 30,
bottom: 30,
left: 40
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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 + ")");
//Read the data
d3.csv("https://gist.githubusercontent.com/michhar/2dfd2de0d4f8727f873422c5d959fff5/raw/fa71405126017e6a37bea592440b4bee94bf7b9e/titanic.csv", function(rawData) {
const data = rawData.map(function(d) {
return {
age: Number(d.Age),
fare: Number(d.Fare),
sex: d.Sex,
survived: d.Survived === "1",
name: d.Name
};
});
// Add X axis
var x = d3.scaleLinear()
.domain([0, 80])
.range([0, width]);
svg.append("g")
.attr("transform", "translate(0," + height + ")");
// Add Y axis
var y = d3.scaleLog()
.domain([1e+0, 1e+3])
.range([height, 0]);
svg.append("g");
// Add dots
svg.append('g')
.selectAll("dot").select("female")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
return x(d.age);
})
.attr("cy", function(d) {
return y(d.fare);
})
.attr("r", 2.8)
.style("opacity", function(d) {
return d.survived ? "1" : "0.25";
})
.style("stroke", "black")
.style("fill-opacity", 0.1)
svg.append('g')
.selectAll("dot").select("male")
.data(data)
.enter()
.append("rect")
.attr("x", function(d) {
return x(d.age);
})
.attr("y", function(d) {
return y(d.fare);
})
.attr("width", 5)
.attr("height", 5)
.style("opacity", function(d) {
return d.survived ? "1" : "0.25";
})
.style("stroke", "black")
.style("fill-opacity", 0.1)
.append("svg:title")
.text(function(d) {
return d.name
});
})
<script src="https://d3js.org/d3.v4.js"></script>
<div id="my_dataviz"></div>
I've a D3 bar chart with Chinese and English text on x-axis. Whenever the Chinese text comes, the labels are overlapping. I'm unable to wrap the text into multiple lines. If it's only of English text, I'm able to wrap it. Is there a way to wrap the text if it has Chinese text too?
Snippet
var margin = {
top: 10,
right: 0,
bottom: 58,
left: 40
};
var width = 400 - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom;
var barWidth = 40;
var graph;
var xScale;
var yScale;
var dataSet;
dataSet = [{
desc: '即使句子没有空格',
val: 20
},
{
desc: 'Sample text.即使句子没有空格',
val: 40
},
{
desc: 'test3',
val: 60
},
{
desc: 'test4',
val: 80
},
{
desc: 'some dummy text here',
val: 120
}
];
xScale = d3.scaleBand()
.domain(dataSet.map(function(d) {
return d.desc;
}))
.range([0, width]);
yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, 1.15 * d3.max(dataSet, function(d) {
return d.val;
})]);
graph = d3.select("#graph")
.append("svg")
.attr("class", "bar-chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
graph.append("g")
.attr("class", "x-scale")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale))
.selectAll(".tick text")
.call(wrap, xScale.bandwidth());
graph.append("g")
.attr("class", "y-scale")
.call(d3.axisLeft(yScale).tickPadding(10));
graph
.append("g")
.attr('class', 'graph-placeholder')
.selectAll("rect")
.data(dataSet)
.enter()
.append("rect")
.attr("class", "bar1")
.attr("height", height)
.attr("width", barWidth)
.attr('x', d => xScale(d.desc) + (xScale.bandwidth() - barWidth) / 2);
graph
.append("g")
.attr('class', 'graph-main')
.selectAll("bar1")
.data(dataSet)
.enter()
.append("rect")
.attr("class", "bar2")
.attr('x', d => xScale(d.desc) + (xScale.bandwidth() - barWidth) / 2)
.attr("y", function(d) {
return yScale(d.val);
})
.attr("height", function(d) {
return height - yScale(d.val);
})
.attr("width", barWidth);
graph
.append("g")
.attr('class', 'bar-label')
.selectAll("text")
.data(dataSet)
.enter()
.append("text")
.text(d => d.val + '%')
.attr("y", function(d) {
return yScale(d.val) - 5;
}).attr('x', function(d) {
return xScale(d.desc) + ((xScale.bandwidth() - barWidth) / 2);
});
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1,
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);
}
}
});
}
.bar-chart {
background-color: #ccc;
}
.bar2 {
fill: steelblue;
}
.bar1 {
fill: #f2f2f2;
}
text {
font-size: 12px;
/* text-anchor: middle; */
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="container">
<div id="graph"></div>
</div>
Fiddle for the same snippet.
Your wrap function currently splits on white spaces (/\s+/) and wraps these parts in their own <tspan> elements.
It needs to be smarter to be able to wrap-words.
The easy fix is to wrap each character in its own <tspan>.
var margin = {
top: 10,
right: 0,
bottom: 58,
left: 40
};
var width = 400 - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom;
var barWidth = 40;
var graph;
var xScale;
var yScale;
var dataSet;
dataSet = [{
desc: '即使句子没有空格',
val: 20
},
{
desc: 'Sample text.即使句子没有空格',
val: 40
},
{
desc: 'test3',
val: 60
},
{
desc: 'test4',
val: 80
},
{
desc: 'some dummy text here',
val: 120
}
];
xScale = d3.scaleBand()
.domain(dataSet.map(function(d) {
return d.desc;
}))
.range([0, width]);
yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, 1.15 * d3.max(dataSet, function(d) {
return d.val;
})]);
graph = d3.select("#graph")
.append("svg")
.attr("class", "bar-chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
graph.append("g")
.attr("class", "x-scale")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale))
.selectAll(".tick text")
.call(wrap, xScale.bandwidth());
graph.append("g")
.attr("class", "y-scale")
.call(d3.axisLeft(yScale).tickPadding(10));
graph
.append("g")
.attr('class', 'graph-placeholder')
.selectAll("rect")
.data(dataSet)
.enter()
.append("rect")
.attr("class", "bar1")
.attr("height", height)
.attr("width", barWidth)
.attr('x', d => xScale(d.desc) + (xScale.bandwidth() - barWidth) / 2);
graph
.append("g")
.attr('class', 'graph-main')
.selectAll("bar1")
.data(dataSet)
.enter()
.append("rect")
.attr("class", "bar2")
.attr('x', d => xScale(d.desc) + (xScale.bandwidth() - barWidth) / 2)
.attr("y", function(d) {
return yScale(d.val);
})
.attr("height", function(d) {
return height - yScale(d.val);
})
.attr("width", barWidth);
graph
.append("g")
.attr('class', 'bar-label')
.selectAll("text")
.data(dataSet)
.enter()
.append("text")
.text(d => d.val + '%')
.attr("y", function(d) {
return yScale(d.val) - 5;
}).attr('x', function(d) {
return xScale(d.desc) + ((xScale.bandwidth() - barWidth) / 2);
});
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
// split on each character
words = text.text().split('').reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1,
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);
// join with empty string
tspan.text(line.join(""));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
// join with empty string
tspan.text(line.join(""));
line = [word];
tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
}
}
});
}
.bar-chart {
background-color: #ccc;
}
.bar2 {
fill: steelblue;
}
.bar1 {
fill: #f2f2f2;
}
text {
font-size: 12px;
/* text-anchor: middle; */
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="container">
<div id="graph"></div>
</div>
But doing so, we have the same behavior as word-break: break-all, that is, it will break even non CJK texts.
What we want is the word-break: normal behavior. And for this, the best is to use HTML and CSS.
By generating a dummy HTML element, we can check where every character should be rendered, thanks to Range.getBoundingClientRect method:
var margin = {
top: 10,
right: 0,
bottom: 58,
left: 40
};
var width = 400 - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom;
var barWidth = 40;
var graph;
var xScale;
var yScale;
var dataSet;
dataSet = [{
desc: '即使句子没有空格',
val: 20
},
{
desc: 'Sample text.即使句子没有空格',
val: 40
},
{
desc: 'test3',
val: 60
},
{
desc: 'test4',
val: 80
},
{
desc: 'some dummy text here',
val: 120
}
];
xScale = d3.scaleBand()
.domain(dataSet.map(function(d) {
return d.desc;
}))
.range([0, width]);
yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, 1.15 * d3.max(dataSet, function(d) {
return d.val;
})]);
graph = d3.select("#graph")
.append("svg")
.attr("class", "bar-chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
graph.append("g")
.attr("class", "x-scale")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale))
.selectAll(".tick text")
.call(wrap, xScale.bandwidth());
graph.append("g")
.attr("class", "y-scale")
.call(d3.axisLeft(yScale).tickPadding(10));
graph
.append("g")
.attr('class', 'graph-placeholder')
.selectAll("rect")
.data(dataSet)
.enter()
.append("rect")
.attr("class", "bar1")
.attr("height", height)
.attr("width", barWidth)
.attr('x', d => xScale(d.desc) + (xScale.bandwidth() - barWidth) / 2);
graph
.append("g")
.attr('class', 'graph-main')
.selectAll("bar1")
.data(dataSet)
.enter()
.append("rect")
.attr("class", "bar2")
.attr('x', d => xScale(d.desc) + (xScale.bandwidth() - barWidth) / 2)
.attr("y", function(d) {
return yScale(d.val);
})
.attr("height", function(d) {
return height - yScale(d.val);
})
.attr("width", barWidth);
graph
.append("g")
.attr('class', 'bar-label')
.selectAll("text")
.data(dataSet)
.enter()
.append("text")
.text(d => d.val + '%')
.attr("y", function(d) {
return yScale(d.val) - 5;
}).attr('x', function(d) {
return xScale(d.desc) + ((xScale.bandwidth() - barWidth) / 2);
});
function getLines(text, width) {
// create a dummy element
var dummy = d3.select('body')
.append('p')
.classed('dummy-text-wrapper', true)
// set its size to the one we want
.style('width', width + 'px')
.text(text);
var textNode = dummy.node().childNodes[0];
var lines = [''];
var range = document.createRange();
var current = 0;
// get default top value
range.setStart(textNode, 0);
var prevTop = range.getBoundingClientRect().top;
var nextTop = prevTop;
// iterate through all characters
while (current < text.length) {
// move the cursor
range.setStart(textNode, current+1);
// check top position
nextTop = range.getBoundingClientRect().top;
if(nextTop !== prevTop) {
// new line
lines.push("");
}
// add the current character to the last line
lines[lines.length - 1] += text[current++];
prevTop = nextTop;
}
// clean up the DOM
dummy.remove();
return lines;
}
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
words = text.text(),
lines = getLines(words, width),
line = [],
lineHeight = 1,
y = text.attr("y"),
dy = parseFloat(text.attr("dy"));
text.text('');
lines.forEach(function(words, lineNumber) {
text.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", lineNumber * lineHeight + dy + "em")
.text(words);
});
});
}
.bar-chart {
background-color: #ccc;
}
.bar2 {
fill: steelblue;
}
.bar1 {
fill: #f2f2f2;
}
text {
font-size: 12px;
/* text-anchor: middle; */
}
.dummy-text-wrapper {
word-break: normal;
display: inline-block;
opacity: 0;
position: absolute;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="container">
<div id="graph"></div>
</div>
I have a bunch of charts which get drawn in the same div by the user clicking on a link (each click removes the previous svg and then draws the new one). All charts are positioned in the center of the div as expected except donut charts. Any reasons why? I've created a JS Fiddle to help illustrate this.
JS Fiddle
Basically, I have three functions. The generic drawChart() function which takes in the index of the button which has been clicked and contains a switch statement which picks what chart to draw. Then there is chartTwo() which is just two lines to illustrate how that chart is positioned in the center. chartOne() is a donut chart which is being positioned outside of top left corner.
Thanks for any help.
Generic chart builder func
function drawChart(int){
var $chartarea = $('#chartarea'),
ca_w = $chartarea.innerWidth(),
ca_h = $chartarea.innerHeight();
if ($chartarea.find('svg').length > 0) {
$chartarea.find('svg').remove();
}
var margin = {top: 20, right: 20, bottom: 20, left: 20};
var width = ca_w - margin.left - margin.right,
height = ca_h - margin.top - margin.bottom;
var g = d3.select('#chartarea').append('svg')
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append('g')
.style('position', 'relative')
.style('left', '0')
.attr('height', height)
.attr('width', width)
.attr('transform', 'translate('+margin.left+', '+margin.top+')');
switch (int) {
case 0:
chartOne(g, width, height);
break;
case 1:
chartTwo(g, width, height);
break;
default:
chartOne(g, width, height);
}
}
Donut chart func
function chartOne(g, width, height) {
var data = [
{name: "USA", value: 40},
{name: "UK", value: 20},
{name: "Canada", value: 30},
{name: "Maxico", value: 10},
];
var text = "";
var thickness = 40;
var radius = Math.min(width, height) / 2;
var color = d3.scaleOrdinal(d3.schemeCategory10);
var arc = d3.arc()
.innerRadius(radius - thickness)
.outerRadius(radius);
var pie = d3.pie()
.value(function(d) { return d.value; })
.sort(null);
g.selectAll('path')
.data(pie(data))
.enter()
.append("g")
.on("mouseover", function(d) {
let g = d3.select(this)
.style("cursor", "pointer")
.style("fill", "black")
.append("g")
.attr("class", "text-group");
g.append("text")
.attr("class", "name-text")
.text(d.data.name)
.attr('text-anchor', 'middle')
.attr('dy', '-1.2em');
g.append("text")
.attr("class", "value-text")
.text(d.data.value)
.attr('text-anchor', 'middle')
.attr('dy', '.6em');
})
.on("mouseout", function() {
d3.select(this)
.style("cursor", "none")
.style("fill", color(this._current))
.select(".text-group").remove();
})
.append('path')
.attr('d', arc)
.attr('fill', (d,i) => color(i))
.on("mouseover", function() {
d3.select(this)
.style("cursor", "pointer")
.style("fill", "black");
})
.on("mouseout", function() {
d3.select(this)
.style("cursor", "none")
.style("fill", color(this._current));
})
.each(function(d, i) { this._current = i; });
g.append('text')
.attr('text-anchor', 'middle')
.attr('dy', '.35em')
.text(text);
}
Adding a translate to adjust for the width and height can be added to chartOne() function:
g.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
Now you can add the margins and finish up I guess. See demo below:
$(function() {
// on load
$('li').eq(0).addClass('active');
drawChart(0);
$('li').on('click', function() {
var index = $(this).index();
$('li').removeClass('active');
$(this).addClass('active');
drawChart(index);
});
});
function drawChart(int) {
var $chartarea = $('#chartarea'),
ca_w = $chartarea.innerWidth(),
ca_h = $chartarea.innerHeight();
if ($chartarea.find('svg').length > 0) {
$chartarea.find('svg').remove();
}
var margin = {
top: 20,
right: 20,
bottom: 20,
left: 20
};
var width = ca_w - margin.left - margin.right,
height = ca_h - margin.top - margin.bottom;
var g = d3.select('#chartarea').append('svg')
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append('g')
.style('position', 'relative')
.style('left', '0')
.attr('height', height)
.attr('width', width)
.attr('transform', 'translate(' + margin.left + ', ' + margin.top + ')');
switch (int) {
case 0:
chartOne(g, width, height, margin);// edited
break;
case 1:
chartTwo(g, width, height);
break;
default:
chartOne(g, width, height, margin);// edited
}
}
function chartTwo(g, width, height) {
g.append('line')
.attr('x1', 0)
.attr('y1', 0)
.attr('x2', width)
.attr('y2', height)
.attr('stroke', 'grey')
.attr('stroke-width', '10px');
g.append('line')
.attr('x1', width)
.attr('y1', 0)
.attr('x2', 0)
.attr('y2', height)
.attr('stroke', 'grey')
.attr('stroke-width', '10px');
}
function chartOne(g, width, height, margin) { // edited
// ADDED THIS
g.attr("transform", "translate(" + (width / 2 + margin.left) + "," + (height / 2 + margin.top) + ")");
var data = [{
name: "USA",
value: 40
},
{
name: "UK",
value: 20
},
{
name: "Canada",
value: 30
},
{
name: "Maxico",
value: 10
},
];
var text = "";
var thickness = 40;
var radius = Math.min(width, height) / 2;
var color = d3.scaleOrdinal(d3.schemeCategory10);
var arc = d3.arc()
.innerRadius(radius - thickness)
.outerRadius(radius);
var pie = d3.pie()
.value(function(d) {
return d.value;
})
.sort(null);
g.selectAll('path')
.data(pie(data))
.enter()
.append("g")
.on("mouseover", function(d) {
let g = d3.select(this)
.style("cursor", "pointer")
.style("fill", "black")
.append("g")
.attr("class", "text-group");
g.append("text")
.attr("class", "name-text")
.text(d.data.name)
.attr('text-anchor', 'middle')
.attr('dy', '-1.2em');
g.append("text")
.attr("class", "value-text")
.text(d.data.value)
.attr('text-anchor', 'middle')
.attr('dy', '.6em');
})
.on("mouseout", function() {
d3.select(this)
.style("cursor", "none")
.style("fill", color(this._current))
.select(".text-group").remove();
})
.append('path')
.attr('d', arc)
.attr('fill', (d, i) => color(i))
.on("mouseover", function() {
d3.select(this)
.style("cursor", "pointer")
.style("fill", "black");
})
.on("mouseout", function() {
d3.select(this)
.style("cursor", "none")
.style("fill", color(this._current));
})
.each(function(d, i) {
this._current = i;
});
g.append('text')
.attr('text-anchor', 'middle')
.attr('dy', '.35em')
.text(text);
}
* {
margin: 0;
padding: 0;
}
#chartarea {
margin: 20px;
border: solid 1px black;
height: 300px;
width: 500px;
}
ul {
display: flex;
width: 500px;
margin: 20px;
list-style: none;
text-align: center;
}
li {
margin: 0 20px;
padding: 5px;
border-radius: 10px;
flex: 1;
background: grey;
cursor: pointer;
}
li.active {
background: #60cafe
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.9.1/d3.min.js"></script>
<div id="chartarea" class="charts--item"></div>
<ul>
<li>Chart One</li>
<li>Chart Two</li>
</ul>
Your pie/donut chart is positioned with a center at [0,0] while your x is comprised of lines with endpoints like this one:
.attr('x1', 0)
.attr('y1', 0)
.attr('x2', width)
.attr('y2', height)
Your lines start and end at the corner of the visualization, where as your pie/donut chart is centered on the corner.
The easiest way to fix this is to create a g to hold the pie chart that has a different transform than the g to hold the rest of the charts. This new g will have a translate of [width/2,height/2] and will place the center of the pie chart in the center of the visualization. See this fiddle.