D3 v4 chart with Bi directional chart - javascript

How to create this chart with D3? Any help will help full tried with High charts but not help full much
click event is not working on this drill down event is added but it won't work on click on bars or y-axis labels.
const data = [
{ "name": 'IT', "value": 20, "negativeValue": -80 },
{ "name": 'Capital Invest', "value": 30, "negativeValue": -70 },
{ "name": 'Infrastructure', "value": 40, "negativeValue": -60 }
];
Highcharts.setOptions({
lang: {
drillUpText: `◁ Back to {series.description}`,
},
});
Highcharts.chart({
chart: {
type: 'bar',
renderTo: 'alignmentChart',
height: 530,
marginRight: 20,
backgroundColor: 'transparent',
events: {
drilldown(e: any) {
if (e.seriesOptions.fits) {
linesPositive = e.seriesOptions.line;
} else {
lineNegative = e.seriesOptions.line;
}
labels = !!e.seriesOptions && e.seriesOptions.data.map(a => a.name);
},
drillup(e: any) {
if (e.seriesOptions.fits) {
linesPositive = e.seriesOptions.line;
} else {
lineNegative = e.seriesOptions.line;
}
labels = !!e.seriesOptions && e.seriesOptions.data.map(a => a.name);
},
},
},
title: {
text: '',
},
colors: ['#f7a704', '#458dde'],
// tooltip: this.getTooltip(this),
xAxis: {
reversed: false,
tickPositions: Array.from(Array(this.multi.positive.length).keys()),
labels: {
useHTML: true,
formatter() {
return `<span title="${labels[this.value]}">${labels[this.value]}</span>`;
},
style: {
color: '#000000',
},
step: 1,
},
lineWidth: 0,
tickWidth: 0,
},
yAxis: {
title: {
text: null,
},
max: 100,
min: -100,
plotLines: [{
color: '#e5e5e5',
value: 0,
width: 1,
zIndex: 20,
}],
lineWidth: 1,
gridLineWidth: 0,
tickWidth: 1,
// offset: 100,
labels: {
y: 30,
align: 'center',
},
},
plotOptions: {
bar: {
pointWidth: 12,
},
series: {
stacking: 'normal',
dataLabels: {
enabled: true,
color: '#6b6b6b',
style: {
fontSize: '12px',
fontFamily: 'Proxima Nova'
},
formatter() {
return '';
},
inside: false,
},
},
},
series: [{
name: 'Fits Role',
description: 'Subfunctions',
data: this.multi.positive,
type: undefined
}, {
name: 'Not Fit Role',
description: 'Subfunctions',
data: this.multi.negative,
type: undefined
}],
drilldown: {
allowPointDrilldown: false,
activeAxisLabelStyle: {
fontSize: '12px',
fontWeight: 'bold',
color: '#007bc7',
textDecoration: 'none',
},
series: this.multi.drilldowns,
},
credits: {
enabled: false,
},
legend: {
enabled: false,
},
exporting: {
enabled: false,
},
});

I had to make only very few changes compared to the answer I shared. As I said in my comment, I create one g node per item, and draw two rects for every one.
Then I update the rects to have the same datum shape ({ name: string, value: number }), regardless of whether it's positive or negative. That allows me to do exactly the same things for both types.
// Now, the data can also be negative
const data = [{
"name": 'IT',
"value": 20,
"negativeValue": -80
}, {
"name": 'Capital Invest',
"value": 30,
"negativeValue": -70
}, {
"name": 'Infrastructure',
"value": 40,
"negativeValue": -60
}];
const width = 600,
height = 300,
margin = {
top: 20,
left: 100,
right: 40,
bottom: 40
};
// Now, we don't use 0 as a minimum, but get it from the data using d3.extent
const x = d3.scaleLinear()
.domain([-100, 100])
.range([0, width]);
const y = d3.scaleBand()
.domain(data.map(d => d.name))
.range([height, 0])
.padding(0.1);
const svg = d3.select('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom);
const g = svg
.append('g')
.attr('transform', `translate(${margin.left} ${margin.right})`);
// One group per data entry, each holding two bars
const barGroups = g
.selectAll('.barGroup')
.data(data);
barGroups.exit().remove();
const newBarGroups = barGroups.enter()
.append('g')
.attr('class', 'barGroup');
// Append one bar for the positive value, and one for the negative one
newBarGroups
.append('rect')
.attr('class', 'positive')
.attr('fill', 'darkgreen');
newBarGroups
.append('rect')
.attr('class', 'negative')
.attr('fill', 'darkred');
const positiveBars = newBarGroups
.merge(barGroups)
.select('.positive')
.datum(d => ({
name: d.name,
value: d.value
}));
const negativeBars = newBarGroups
.merge(barGroups)
.select('.negative')
.datum(d => ({
name: d.name,
value: d.negativeValue
}));
newBarGroups.selectAll('rect')
// If a bar is positive it starts at x = 0, and has positive width
// If a bar is negative it starts at x < 0 and ends at x = 0
.attr('x', d => d.value > 0 ? x(0) : x(d.value))
.attr('y', d => y(d.name))
// If the bar is positive it ends at x = v, but that means it's x(v) - x(0) wide
// If the bar is negative it ends at x = 0, but that means it's x(0) - x(v) wide
.attr('width', d => d.value > 0 ? x(d.value) - x(0) : x(0) - x(d.value))
.attr('height', y.bandwidth())
// Let's color the bar based on whether the value is positive or negative
g.append('g')
.classed('x-axis', true)
.attr('transform', `translate(0, ${height})`)
.call(d3.axisBottom(x))
g.append('g')
.classed('y-axis', true)
.attr('transform', `translate(${x(0)}, 0)`)
.call(d3.axisLeft(y))
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<svg></svg>
Alternatively, you can do it without merging the selections like so:
// Now, the data can also be negative
const data = [{
"name": 'IT',
"value": 20,
"negativeValue": -80
}, {
"name": 'Capital Invest',
"value": 30,
"negativeValue": -70
}, {
"name": 'Infrastructure',
"value": 40,
"negativeValue": -60
}];
const width = 600,
height = 300,
margin = {
top: 20,
left: 100,
right: 40,
bottom: 40
};
// Now, we don't use 0 as a minimum, but get it from the data using d3.extent
const x = d3.scaleLinear()
.domain([-100, 100])
.range([0, width]);
const y = d3.scaleBand()
.domain(data.map(d => d.name))
.range([height, 0])
.padding(0.1);
const svg = d3.select('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom);
const g = svg
.append('g')
.attr('transform', `translate(${margin.left} ${margin.right})`);
// One group per data entry, each holding two bars
const positiveBars = g
.selectAll('.positive')
.data(data);
positiveBars.exit().remove();
positiveBars.enter()
.append('rect')
.attr('class', 'positive')
.attr('fill', 'darkgreen')
.merge(positiveBars)
.attr('x', x(0))
.attr('y', d => y(d.name))
// The bar is positive. It ends at x = v, but that means it's x(v) - x(0) wide
.attr('width', d => x(d.value) - x(0))
.attr('height', y.bandwidth());
const negativeBars = g
.selectAll('.negative')
.data(data);
negativeBars.exit().remove();
negativeBars.enter()
.append('rect')
.attr('class', 'negative')
.attr('fill', 'darkred')
.merge(negativeBars)
.attr('x', d => x(d.negativeValue))
.attr('y', d => y(d.name))
// The bar is negative. It ends at x = 0, but that means it's x(0) - x(v) wide
.attr('width', d => x(0) - x(d.negativeValue))
.attr('height', y.bandwidth());
g.append('g')
.classed('x-axis', true)
.attr('transform', `translate(0, ${height})`)
.call(d3.axisBottom(x))
g.append('g')
.classed('y-axis', true)
.attr('transform', `translate(${x(0)}, 0)`)
.call(d3.axisLeft(y))
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<svg></svg>

Related

D3.js graph shows up as text

I want to open a graph as a modal after clicking another graph. However, what shows up in the modal is plain text, like such:
After inspecting the element, it seems like the text is the <g> tag that was in the svg, but I'm not sure what that means.
^ The corresponding HTML code is <text fill="currentColor" x="-9" dy="0.32em">−1.5</text>
I'm not sure why this is happening and I can't find any similar problems online for this.
Here's my graph code:
function Wavelet() {
const gWidth = window.innerWidth / 5
const gWidth_modal = window.innerWidth/3
var margin = {top: 20, right: 30, bottom: 30, left: 20},
width = gWidth - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
var margin_modal = {top: 20, right: 30, bottom: 30, left: 20},
width_modal = gWidth_modal - margin_modal.left - margin_modal.right,
height_modal = 400 - margin_modal.top - margin_modal.bottom;
// append the svg object to the body of the page
var svg = d3.select("#contour").selectAll("*").remove();
svg = d3.select("#contour").append("svg")
.attr("width", gWidth + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
var svg_modal = d3.select("#contour-modal").selectAll("*").remove();
svg_modal = d3.select("#contour-modal").append("svg_modal")
.attr("width", width_modal + margin_modal.left + margin_modal.right)
.attr("height", height_modal + margin_modal.top + margin_modal.bottom)
.append("g")
.attr("transform", "translate(" + margin_modal.left + "," + margin_modal.top + ")")
var color = d3.scaleLinear()
.domain([0, 0.01]) // Points per square pixel.
.range(["#c3d7e8", "#23527a"])
const data = [
{ x: 3.4, y: 4.2 },
{ x: -1, y: 4.2 },
{ x: -1, y: 2.8 },
{ x: 3.6, y: 4.3 },
{ x: -0.1, y: 3.7 },
{ x: 4.7, y: 2.5 },
{ x: 0.8, y: 3.6 },
{ x: 4.7, y: 3.7 },
{ x: -0.4, y: 4.2 },
{ x: 0.1, y: 2.2 },
{ x: 0.5, y: 3 },
{ x: 4.3, y: 4.5 },
{ x: 3.4, y: 2.7 },
{ x: 4.4, y: 3.6 },
{ x: 3.3, y: 0.6 },
{ x: 3, y: 3.4 },
{ x: 4.7, y: 0 },
{ x: -0.7, y: 2.7 },
{ x: 2.6, y: 2 },
{ x: 0, y: -1 },
{ x: 3.4, y: 4.5 },
{ x: 3.9, y: 4.6 },
{ x: 0.7, y: 3.9 },
{ x: 3, y: 0.2 }
];
// Add X axis
var x = d3.scaleLinear()
.domain([-2, 6])
.range([ 0, width ]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
var x_modal = d3.scaleLinear()
.domain([-2, 6])
.range([ 0, width_modal ]);
svg_modal.append("g")
.attr("transform", "translate(0," + height_modal + ")")
.call(d3.axisBottom(x_modal));
// Add Y axis
var y = d3.scaleLinear()
.domain([-2, 5])
.range([ height, 0 ]);
svg.append("g")
.call(d3.axisLeft(y));
var y_modal = d3.scaleLinear()
.domain([-2, 5])
.range([ height_modal, 0 ]);
svg_modal.append("g")
.call(d3.axisLeft(y_modal));
// compute the density data
var densityData = d3.contourDensity()
.x(function(d) { return x(d.x); }) // x and y = column name in .csv input data
.y(function(d) { return y(d.y); })
.size([width, height])
.bandwidth(20) // smaller = more precision in lines = more lines
(data)
// Add the contour: several "path"
svg
.selectAll("path")
.data(densityData)
.enter()
.append("path")
.attr("d", d3.geoPath())
.attr("fill", function(d) { return color(d.value); })
.attr("stroke", "#4285f4")
svg_modal
.selectAll("path")
.data(densityData)
.enter()
.append("path")
.attr("d", d3.geoPath())
.attr("fill", function(d) { return color(d.value); })
.attr("stroke", "#4285f4")
}
export default Wavelet
I have 2 svg as the one in the modal doesn't get rendered if I use the same one (for some reason). If that's what's causing me problems, please let me know how to fix it.
useEffect(() => {
(async () => {
await Wavelet()
})();
});
// const showModal = () => {setIsopen((prev) => !prev); console.log("aaaaaaaaaa");}
function showModal (id) {
setIsopen((prev) => !prev);
setModalID(id)
}
function Graph() {
const modal = <div>
<div id="contour" onClick={() => showModal(4)}>
</div>
{isOpen && (modalID == 4) && (
<ModalContent onClose={() => setIsopen(false)}>
<div className="modal_c">
<h3>{modalID}</h3>
<div id="contour-modal" >
</div>
<p> </p>
</div>
</ModalContent>
)}
</div>
return modal;
}
}
My modal class if anybody wants to reference it:
const modal = {
position: "fixed",
zIndex: 1,
left: 0,
top: 0,
width: "100vw",
height: "100vh",
overflow: "auto",
backgroundColor: "rgba(192,192,192,0.5)",
display: "flex",
alignItems: "center"
};
const close = {
position: "absolute",
top: "11vh",
right: "27vw",
color: "#000000",
fontSize: 40,
fontWeight: "bold",
cursor: "pointer"
};
const modalContent = {
display: "flex",
alignItems: "center",
justifyContent: "center",
width: "50%",
height: "80%",
margin: "auto",
backgroundColor: "white"
// border: "2px solid #000000"
};
export const Modal = ({ onOpen, children }) => {
console.log(children)
return <div onClick={onOpen}> {children}</div>;
};
export const ModalContent = ({ onClose, children }) => {
return (
<div style={modal}>
<div style={modalContent}>
<span style={close} onClick={onClose}>
×
</span>
{children}</div>
</div>
);
};
Problem is with your this statement : d3.select("#contour-modal").append("svg_modal").In order to render the elements correctly, you need to append them to svg. d3.select("#contour-modal").append("svg") . svg_modal is an invalid tag for your requirement. Kindly read more about svg rendering. Done a basic test of your code:
const gWidth = window.innerWidth / 5
const gWidth_modal = window.innerWidth / 3
var margin = {
top: 20,
right: 30,
bottom: 30,
left: 20
},
width = gWidth - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
var margin_modal = {
top: 20,
right: 30,
bottom: 30,
left: 20
},
width_modal = gWidth_modal - margin_modal.left - margin_modal.right,
height_modal = 200 - margin_modal.top - margin_modal.bottom;
// append the svg object to the body of the page
var svg = d3.select("#contour").selectAll("*").remove();
svg = d3.select("#contour").append("svg")
.attr("width", gWidth + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
var svg_modal = d3.select("#contour-modal").selectAll("*").remove();
svg_modal = d3.select("#contour-modal").append("svg")
.attr("width", width_modal + margin_modal.left + margin_modal.right)
.attr("height", height_modal + margin_modal.top + margin_modal.bottom)
.append("g")
.attr("transform", "translate(" + margin_modal.left + "," + margin_modal.top + ")")
var color = d3.scaleLinear()
.domain([0, 0.01]) // Points per square pixel.
.range(["#c3d7e8", "#23527a"])
const data = [{
x: 3.4,
y: 4.2
},
{
x: -1,
y: 4.2
},
{
x: -1,
y: 2.8
},
{
x: 3.6,
y: 4.3
},
{
x: -0.1,
y: 3.7
},
{
x: 4.7,
y: 2.5
},
{
x: 0.8,
y: 3.6
},
{
x: 4.7,
y: 3.7
},
{
x: -0.4,
y: 4.2
},
{
x: 0.1,
y: 2.2
},
{
x: 0.5,
y: 3
},
{
x: 4.3,
y: 4.5
},
{
x: 3.4,
y: 2.7
},
{
x: 4.4,
y: 3.6
},
{
x: 3.3,
y: 0.6
},
{
x: 3,
y: 3.4
},
{
x: 4.7,
y: 0
},
{
x: -0.7,
y: 2.7
},
{
x: 2.6,
y: 2
},
{
x: 0,
y: -1
},
{
x: 3.4,
y: 4.5
},
{
x: 3.9,
y: 4.6
},
{
x: 0.7,
y: 3.9
},
{
x: 3,
y: 0.2
}
];
// Add X axis
var x = d3.scaleLinear()
.domain([-2, 6])
.range([0, width]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
var x_modal = d3.scaleLinear()
.domain([-2, 6])
.range([0, width_modal]);
svg_modal.append("g")
.attr("transform", "translate(0," + height_modal + ")")
.call(d3.axisBottom(x_modal));
// Add Y axis
var y = d3.scaleLinear()
.domain([-2, 5])
.range([height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
var y_modal = d3.scaleLinear()
.domain([-2, 5])
.range([height_modal, 0]);
svg_modal.append("g")
.call(d3.axisLeft(y_modal));
// compute the density data
var densityData = d3.contourDensity()
.x(function(d) {
return x(d.x);
}) // x and y = column name in .csv input data
.y(function(d) {
return y(d.y);
})
.size([width, height])
.bandwidth(20) // smaller = more precision in lines = more lines
(data)
// Add the contour: several "path"
svg
.selectAll("path")
.data(densityData)
.enter()
.append("path")
.attr("d", d3.geoPath())
.attr("fill", function(d) {
return color(d.value);
})
.attr("stroke", "#4285f4")
svg_modal
.selectAll("path")
.data(densityData)
.enter()
.append("path")
.attr("d", d3.geoPath())
.attr("fill", function(d) {
return color(d.value);
})
.attr("stroke", "#4285f4")
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.5.0/d3.min.js" integrity="sha512-+rnC6CO1Ofm09H411e0Ux7O9kqwM5/FlEHul4OsPk4QIHIYAiM77uZnQyIqcyWaZ4ddHFCvZGwUVGwuo0DPOnQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<div id="contour">
</div>
<div id="contour-modal">
</div>

Colored intercection area

How can I set the color blue for the area of ​​the graph intercepted between the two lines in d3.js.
I'm using d3.area for the orange area and d3.line for the lines. Is there a d3 function for this?
const svg = select(svgRef.current);
const { width, height } =
dimensions || wrapperRef.current.getBoundingClientRect();
const xScale = scaleLinear()
.domain([min(data, (d) => d.x), max(data, (d) => d.x)])
.range([0, width]);
const yScale = scaleLinear()
.domain([0, max(data, (d) => d.high)])
.range([height, 0]);
const xAxis = axisBottom(xScale);
svg.append("g").attr("transform", `translate(0, ${height})`).call(xAxis);
const yAxis = axisLeft(yScale);
svg.append("g").call(yAxis);
const areaGenerator = area()
.defined((d) => true)
.x((d) => xScale(d.x))
.y0((d) => yScale(d.low))
.y1((d) => yScale(d.high))
.curve(curveMonotoneX);
const areaData1 = areaGenerator(data);
svg.append("path")
.attr("class", "layer")
.style("fill", "orange")
.attr("d", areaData1);
const line = d3.line().x(p => xScale(p.x)).y(p => yScale(p.y));
svg.append("path").attr("class", "line1").attr("stroke", "black").attr("d", line(points[0]));
svg.append("path").attr("class", "line2").attr("stroke", "black").attr("d", line(points[1]));
It is solved!
Thank you so much Mehdi!
Now it works!
Bellow the complete source code using React JS:
import React, { useEffect, useRef, useState } from "react";
import {
select,
axisBottom,
min,
max,
scaleLinear,
axisLeft,
area,
line,
curveMonotoneX,
} from "d3";
import useResizeObserver from "./useResizeObserver";
const data = [
{ x: 0, low: 100, high: 245 },
{ x: 3, low: 97, high: 244 },
{ x: 5, low: 94, high: 243 },
{ x: 8, low: 92, high: 242 },
{ x: 10, low: 90, high: 240 },
{ x: 13, low: 88, high: 237 },
{ x: 16, low: 85, high: 234 },
{ x: 18, low: 83, high: 232 },
{ x: 20, low: 80, high: 230 },
{ x: 22, low: 79, high: 225 },
{ x: 25, low: 69, high: 220 },
{ x: 28, low: 49, high: 215 },
{ x: 30, low: 0, high: 210 },
{ x: 40, low: 0, high: 120 },
{ x: 49, low: 0, high: 0 },
];
const points = [
[
{ x: 0, y: 0 },
{ x: 10, y: 70 },
{ x: 20, y: 150 },
{ x: 25, y: 180 },
{ x: 30, y: 245 },
],
[
{ x: 14, y: 0 },
{ x: 27, y: 100 },
{ x: 30, y: 150 },
{ x: 38, y: 245 },
],
];
function StackedBarChart() {
const svgRef = useRef();
const wrapperRef = useRef();
const dimensions = useResizeObserver(wrapperRef);
const [init, setInit] = useState(false);
useEffect(() => {
if (data.length && points.length && !init) {
setInit(true);
const svg = select(svgRef.current);
const { width, height } =
dimensions || wrapperRef.current.getBoundingClientRect();
const xScale = scaleLinear()
.domain([min(data, (d) => d.x), max(data, (d) => d.x)])
.range([0, width]);
const yScale = scaleLinear()
.domain([0, max(data, (d) => d.high)])
.range([height, 0]);
const xAxis = axisBottom(xScale);
svg.append("g").attr("transform", `translate(0, ${height})`).call(xAxis);
const yAxis = axisLeft(yScale);
svg.append("g").call(yAxis);
const areaGenerator = area()
.defined((d) => true)
.x((d) => xScale(d.x))
.y0((d) => yScale(d.low))
.y1((d) => yScale(d.high))
.curve(curveMonotoneX);
const areaData1 = areaGenerator(data);
const lineGenerator = line()
.x((p) => xScale(p.x))
.y((p) => yScale(p.y));
const polygon1 = points.map((point) =>
point.map((item) => [item.x, item.y])
);
const arrPolygons = [...polygon1[0].reverse(), ...polygon1[1]];
const clip = svg.append("g").append("clipPath").attr("id", "clip");
clip
.selectAll("#pol1")
.data([arrPolygons])
.enter()
.append("polygon")
.attr("id", "pol1")
.attr("points", (d) => {
return d.map((i) => [xScale(i[0]), yScale(i[1])]).join(" ");
});
const areaChart1 = svg
.append("path")
.attr("id", "area1")
.attr("class", "layer1")
.style("fill", "orange")
.attr("d", areaData1);
const areaChart = svg
.append("path")
.attr("id", "area")
.attr("class", "layer")
.attr("clip-path", "url(#clip)")
.style("fill", "blue")
.attr("d", areaData1);
svg
.append("path")
.attr("class", "line1")
.attr("d", lineGenerator(points[0]));
svg
.append("path")
.attr("class", "line2")
.attr("d", lineGenerator(points[1]));
}
}, [data, dimensions, init, points]);
return (
<>
<div ref={wrapperRef} style={{ marginBottom: "2rem" }}>
<svg ref={svgRef} />
</div>
</>
);
}
export default StackedBarChart;

How to make this D3 horizontal bar chart work?

Recently I have started learning D3.js v5 and I have to make different sorts of graph. One of those graphs is a stacked horizontal bar chart. I followed a tutorial in Udemy but I am not able to make this chart to work.
Can anybody help me out?
So, I know d3 uses a stack generator to generate stacked bar chart. I used it and checking the values in console it seems that the data is formatted pretty well and and can start generating stacked bar chart but i can't seem to understand why its not working.
<html>
<script src="https://d3js.org/d3.v5.min.js"></script>
<body></body>
<script type="text/javascript">
var Margin = {top: 30, right: 20, bottom: 40, left: 110};
var InnerWidth = 432 - Margin.left - Margin.right;
var InnerHeight = 432 - Margin.top - Margin.bottom;
var diff_variant=[{Missense_Mutation: 5, Silent: 4, Splice_Site: 0},
{Missense_Mutation: 8, Splice_Site: 0, Silent: 0},
{Splice_Site: 4, Missense_Mutation: 4, Silent: 0},
{Missense_Mutation: 4, Silent: 1, Splice_Site: 0},
{Missense_Mutation: 5, Splice_Site: 0, Silent: 0},
{Missense_Mutation: 5, Splice_Site: 0, Silent: 0},
{Missense_Mutation: 4, Splice_Site: 0, Silent: 0},
{Missense_Mutation: 4, Splice_Site: 0, Silent: 0},
{Silent: 1, Missense_Mutation: 1, Splice_Site: 0},
{Missense_Mutation: 1, Splice_Site: 0, Silent: 0}];
var data1=[{name: "Missense_Mutation", count: 41},
{name: "Silent", count: 6},
{name: "Splice_Site", count: 4}];
var variant_name=["Missense_Mutation", "Splice_Site", "Silent"];
var stack = d3.stack().keys(variant_name);
var x = d3.scaleLinear()
.domain([0, d3.max(data1, function(d){return d.count;})])
.range([0,InnerWidth]);
var x_axis = d3.scaleLinear()
.domain([0, d3.max(data1, function(d){return d.count;})])
.range([0,InnerWidth]);
var y_axis = d3.scaleLinear()
.domain([0, data1.length])
.range([0,InnerWidth]);
var y = d3.scaleBand()
.domain(data1.map(function(d){return d.name;}))
.rangeRound([0,InnerHeight])
.padding(0.1);
var svg5 = d3.select("body").append("svg")
.attr("preserveAspectRatio", "xMinYMin meet")
.attr("viewBox", `0 0 ${InnerWidth + Margin.left + Margin.right} ${InnerHeight + Margin.top + Margin.bottom}`)
.style("background","lightblue")
.append("g")
.attr("transform", "translate(" + Margin.left + "," + Margin.top + ")");
svg5.append("g").style("font", "12px sans").call(d3.axisLeft(y));
svg5.append("g").attr('transform', `translate(0, ${InnerHeight})`).style("font", "12px times").call(d3.axisBottom(x_axis));
var series = svg5.selectAll("g").data(stack(diff_variant))
.enter().append("g")
.style("fill", (d,i)=>d3.schemeSet3[i]);
series.selectAll("rect").data(function(d){return d;})
.enter().append("rect")
.attr("height", 25)
.attr("width", function(d){return x(d[1]) - x(d[0]);})
.attr("x", function(d){return x(d[0]);})
.attr("y", function(d,i){return i*40;});
</script>
</html>
When you do this...
var series = svg5.selectAll("g")//etc...
... you are selecting <g> elements that already exist in the SVG (the two axes) and binding data to them. That's why your enter selection is empty.
You can select null (read more about it here):
var series = svg5.selectAll(null)
Or you can select by a class, that you later set in the enter selection:
var series = svg5.selectAll(".foo")
However, the best solution is just moving the axes' group to the bottom: that not only will fix your issue, but it will also paint the axis above the rectangles.
Here is your code with that change:
<html>
<script src="https://d3js.org/d3.v5.min.js"></script>
<body></body>
<script type="text/javascript">
var Margin = {
top: 30,
right: 20,
bottom: 40,
left: 110
};
var InnerWidth = 432 - Margin.left - Margin.right;
var InnerHeight = 432 - Margin.top - Margin.bottom;
var diff_variant = [{
Missense_Mutation: 5,
Silent: 4,
Splice_Site: 0
},
{
Missense_Mutation: 8,
Splice_Site: 0,
Silent: 0
},
{
Splice_Site: 4,
Missense_Mutation: 4,
Silent: 0
},
{
Missense_Mutation: 4,
Silent: 1,
Splice_Site: 0
},
{
Missense_Mutation: 5,
Splice_Site: 0,
Silent: 0
},
{
Missense_Mutation: 5,
Splice_Site: 0,
Silent: 0
},
{
Missense_Mutation: 4,
Splice_Site: 0,
Silent: 0
},
{
Missense_Mutation: 4,
Splice_Site: 0,
Silent: 0
},
{
Silent: 1,
Missense_Mutation: 1,
Splice_Site: 0
},
{
Missense_Mutation: 1,
Splice_Site: 0,
Silent: 0
}
];
var data1 = [{
name: "Missense_Mutation",
count: 41
},
{
name: "Silent",
count: 6
},
{
name: "Splice_Site",
count: 4
}
];
var variant_name = ["Missense_Mutation", "Splice_Site", "Silent"];
var stack = d3.stack().keys(variant_name);
var x = d3.scaleLinear()
.domain([0, d3.max(data1, function(d) {
return d.count;
})])
.range([0, InnerWidth]);
var x_axis = d3.scaleLinear()
.domain([0, d3.max(data1, function(d) {
return d.count;
})])
.range([0, InnerWidth]);
var y_axis = d3.scaleLinear()
.domain([0, data1.length])
.range([0, InnerWidth]);
var y = d3.scaleBand()
.domain(data1.map(function(d) {
return d.name;
}))
.rangeRound([0, InnerHeight])
.padding(0.1);
var svg5 = d3.select("body").append("svg")
.attr("preserveAspectRatio", "xMinYMin meet")
.attr("viewBox", `0 0 ${InnerWidth + Margin.left + Margin.right} ${InnerHeight + Margin.top + Margin.bottom}`)
.style("background", "lightblue")
.append("g")
.attr("transform", "translate(" + Margin.left + "," + Margin.top + ")");
var series = svg5.selectAll("g").data(stack(diff_variant))
.enter().append("g")
.style("fill", (d, i) => d3.schemeSet3[i]);
series.selectAll("rect").data(function(d) {
return d;
})
.enter().append("rect")
.attr("height", 25)
.attr("width", function(d) {
return x(d[1]) - x(d[0]);
})
.attr("x", function(d) {
return x(d[0]);
})
.attr("y", function(d, i) {
return i * 40;
});
svg5.append("g").style("font", "12px sans").call(d3.axisLeft(y));
svg5.append("g").attr('transform', `translate(0, ${InnerHeight})`).style("font", "12px times").call(d3.axisBottom(x_axis));
</script>
</html>

d3 multiline update path and transition path does not work with nested data

I want to update the circles and the paths in this graph with a transition. However it does not work.
I am not very experienced with D3. How can I make my code better? Changing data structure is no problem. I want bind the data to graph with exit() remove() and enter() without deleting whole graph and add the data every time I update again. I do not know how to use enter() exit() and remove() for nested data. One time for the whole group and the other side updating the circle and paths. The ID should be fixed.
Here I have a little single line example from d3noob.
here is a JS Fiddle
var data = [{
id: 'p1',
points: [{
point: {
x: 10,
y: 10
}
}, {
point: {
x: 100,
y: 30
}
}]
},
{
id: 'p2',
points: [{
point: {
x: 30,
y: 100
}
}, {
point: {
x: 230,
y: 30
}
},
{
point: {
x: 50,
y: 200
}
},
{
point: {
x: 50,
y: 300
}
},
]
}
];
var svg = d3.select("svg");
var line = d3.line()
.x((d) => d.point.x)
.y((d) => d.point.y);
function updateGraph() {
console.log('dataset contains', data.length, 'item(s)')
var allGroup = svg.selectAll(".pathGroup").data(data, function(d) {
return d.id
});
var g = allGroup.enter()
.append("g")
.attr("class", "pathGroup")
allGroup.exit().remove()
g.append("path")
.attr("class", "line")
.attr("stroke", "red")
.attr("stroke-width", "1px")
.transition()
.duration(500)
.attr("d", function(d) {
return line(d.points)
});
g.selectAll("path")
.transition()
.duration(500)
.attr("d", function(d) {
return line(d.points)
});
g.selectAll("circle")
.data(d => d.points)
.enter()
.append("circle")
.attr("r", 4)
.attr("fill", "teal")
.attr("cx", function(d) {
return d.point.x
})
.attr("cy", function(d) {
return d.point.y
})
.exit().remove()
}
updateGraph()
document.getElementById('update').onclick = function(e) {
data = [{
id: 'p1',
points: [{
point: {
x: 10,
y: 10
}
}, {
point: {
x: 100,
y: 30
}
}]
},
{
id: 'p2',
points: [{
point: {
x: 30,
y: 100
}
}, {
point: {
x: 230,
y: 30
}
},
{
point: {
x: 50,
y: 300
}
},
]
}
];
updateGraph()
}
$('#cb1').click(function() {
if ($(this).is(':checked')) {
data = [{
id: 'p1',
points: [{
point: {
x: 10,
y: 10
}
}, {
point: {
x: 100,
y: 30
}
}]
},
{
id: 'p2',
points: [{
point: {
x: 30,
y: 100
}
}, {
point: {
x: 230,
y: 30
}
},
{
point: {
x: 50,
y: 200
}
},
{
point: {
x: 50,
y: 300
}
},
]
}
];
} else {
data = [{
id: 'p1',
points: [{
point: {
x: 10,
y: 10
}
}, {
point: {
x: 100,
y: 30
}
}]
}];
}
updateGraph()
});
Edited to change the data join to be be appropriate.
I think the issue has to do with variable data. So, I added data as an argument to the function and specified separate names for different data sets. When I specify different data names, I'm able to update the chart:
var first_data = [{
id: 'p1',
points: [{
point: {
x: 100,
y: 10
}
}, {
point: {
x: 100,
y: 30
}
}]
},
{
id: 'p2',
points: [{
point: {
x: 30,
y: 100
}
}, {
point: {
x: 230,
y: 30
}
},
{
point: {
x: 50,
y: 200
}
},
{
point: {
x: 50,
y: 300
}
},
]
}
];
var svg = d3.select("svg");
var line = d3.line()
.x((d) => d.point.x)
.y((d) => d.point.y);
function updateGraph(data) {
console.log('dataset contains', data.length, 'item(s)')
var allGroup = svg.selectAll(".pathGroup")
.data(data, function(d) { return d; });
var g = allGroup.enter()
.append("g")
.attr("class", "pathGroup")
allGroup.exit().remove()
g.append("path")
.attr("class", "line")
.attr("stroke", "red")
.attr("stroke-width", "1px")
.transition()
.duration(500)
.attr("d", function(d) {
console.log("update path");
return line(d.points)
});
g.selectAll("path")
.transition()
.duration(500)
.attr("d", function(d) {
return line(d.points)
});
g.selectAll("circle")
.data(d => d.points)
.enter()
.append("circle")
.attr("r", 4)
.attr("fill", "teal")
.attr("cx", function(d) {
return d.point.x
})
.attr("cy", function(d) {
return d.point.y
})
.exit().remove()
}
updateGraph(first_data)
document.getElementById('update').onclick = function(e) {
var update_data = [{
id: 'p1',
points: [{
point: {
x: 20,
y: 10
}
}, {
point: {
x: 100,
y: 30
}
}]
},
{
id: 'p2',
points: [{
point: {
x: 30,
y: 100
}
}, {
point: {
x: 230,
y: 30
}
},
{
point: {
x: 50,
y: 300
}
},
]
}
];
updateGraph(update_data)
}
$('#cb1').click(function() {
if ($(this).is(':checked')) {
var click_data = [{
id: 'p1',
points: [{
point: {
x: 10,
y: 10
}
}, {
point: {
x: 100,
y: 30
}
}]
},
{
id: 'p2',
points: [{
point: {
x: 30,
y: 100
}
}, {
point: {
x: 230,
y: 30
}
},
{
point: {
x: 50,
y: 200
}
},
{
point: {
x: 50,
y: 300
}
},
]
}
];
} else {
var click_data = [{
id: 'p1',
points: [{
point: {
x: 10,
y: 10
}
}, {
point: {
x: 100,
y: 30
}
}]
}];
}
updateGraph(click_data)
});
Fiddle: https://jsfiddle.net/wj266kmx/32/

D3 transition from stacked bar to bar chart only works the first time

I am trying to create two charts, a stacked bar and a bar, each displaying a different dataset. But I would like to transition from the stacked bar chart to the bar chart when I click on a button and vice versa. The code I put together works only the first time, then after that when I want to transition back to the stacked bar chart from the bar chart all the bars stack on top of each other in the form of a single bar. Can someone point me in the right direction of how to transition back from bar to stacked bar? Any help would be appreciated. (I haven't really messed with the axes yet so it is normal that they are not changing).
Here is a link to how it currently looks:https://jhjanicki.github.io/stackbartobar/
Below is my code:
var value = 'stack';
var data = [{
name: "Shihuahuaco",
value: 1067,
china: 772
}, {
name: "Cachimbo",
value: 283,
china: 1
}, {
name: "Estoraque",
value: 204,
china: 150
}, {
name: "Cumala",
value: 154,
china: 0
}, {
name: "Ishpingo",
value: 108,
china: 3
}, {
name: "Huayruro",
value: 108,
china: 1
}, {
name: "Tornillo",
value: 61,
china: 4
}, {
name: "Congona",
value: 54,
china: 0
}, {
name: "Capirona",
value: 37,
china: 5
}, {
name: "Tahuari",
value: 33,
china: 14
}, {
name: "Marupa",
value: 33,
china: 1
}, {
name: "Quinilla",
value: 28,
china: 4
}, {
name: "Azucar huayo",
value: 22,
china: 15
}, {
name: "Protium sp.",
value: 19,
china: 0
}, {
name: "Nogal",
value: 15,
china: 6
}, {
name: "Ana Caspi",
value: 14,
china: 2
}, {
name: "Cedro",
value: 14,
china: 0
}, {
name: "Carapa guianensis",
value: 12,
china: 0
}, {
name: "Leche caspi",
value: 12,
china: 0
}, {
name: "Andiroba",
value: 11,
china: 0
}, {
name: "Copaiba",
value: 7,
china: 4
}, {
name: "Palo baston",
value: 6,
china: 0
}, {
name: "Moena",
value: 5,
china: 0
}, {
name: "Almendro",
value: 5,
china: 0
}, {
name: "Chancaquero",
value: 4,
china: 0
}, {
name: "Caimitillo",
value: 3,
china: 1
}, {
name: "Nogal amarillo",
value: 3,
china: 0
}, {
name: "Couma macrocarpa",
value: 3,
china: 0
}, {
name: "Tulpay",
value: 3,
china: 0
}, {
name: "Carapa",
value: 3,
china: 0
}, {
name: "Dacryodes olivifera",
value: 2,
china: 0
}, {
name: "Capinuri",
value: 2,
china: 2
}, {
name: "Brosimum alicastrum",
value: 2,
china: 0
}, {
name: "Paramachaerium ormosioide",
value: 2,
china: 0
}, {
name: "Brosimum sp.",
value: 2,
china: 0
}, {
name: "Manchinga",
value: 2,
china: 0
}];
// data for stacked bar
var points = [{
'lon': 105.3,
'lat': 33.5,
'name': 'China',
'GTF': 1024,
"ID": "CHN"
},
{
'lon': -70.9,
'lat': 18.8,
'name': 'Dominican Republic',
'GTF': 470,
"ID": "DOM"
},
{
'lon': -101,
'lat': 38,
'name': 'USA',
'GTF': 248,
"ID": "USA"
},
{
'lon': -102.5,
'lat': 22.7,
'name': 'Mexico',
'GTF': 220,
"ID": "MEX"
},
{
'lon': 2.98,
'lat': 46,
'name': 'France',
'GTF': 85,
"ID": "FRA"
}
];
//data for bar
var margin = {
top: 20,
right: 30,
bottom: 150,
left: 60
},
widthB = 700 - margin.left - margin.right,
heightB = 500 - margin.top - margin.bottom;
var dataIntermediate = ['value', 'china'].map(function(key, i) {
return data.map(function(d, j) {
return {
x: d['name'],
y: d[key]
};
})
})
var dataStackLayout = d3.layout.stack()(dataIntermediate);
var svg = d3.select("#chart").append("svg")
.attr("width", widthB + margin.left + margin.right)
.attr("height", heightB + margin.top + margin.bottom)
var gBar = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr('class', 'gBar');
var x = d3.scale.ordinal()
.rangeRoundBands([0, widthB], .2);
var y = d3.scale.linear()
.range([heightB, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(8)
.tickFormat(function(d) {
return y.tickFormat(4, d3.format(",d"))(d)
});
data.forEach(function(d) {
d.value = +d.value; // coerce to number
d.china = +d.china;
});
x.domain(dataStackLayout[0].map(function(d) {
return d.x;
}));
y.domain([0, d3.max(dataStackLayout[dataStackLayout.length - 1],
function(d) {
return d.y0 + d.y;
})]).nice();
var layer;
var bars;
//axes
gBar.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (heightB + 10) + ")")
.call(xAxis)
.selectAll("text")
.style('font-size', '14px')
.style('font-family', 'Alegreya')
.style("text-anchor", "end")
.attr("dx", "-0.40em")
.attr("dy", ".10em")
.attr("transform", function(d) {
return "rotate(-65)"
});
gBar.append("g")
.attr("class", "y axis")
.call(yAxis)
.selectAll("text")
.style('font-size', '16px')
.style('font-family', 'Alegreya');
function draw() {
if (value == 'stack') {
layer = gBar.selectAll(".stack")
.data(dataStackLayout);
layer.exit()
.transition()
.delay(function(d, i) {
return 30 * i;
})
.duration(1500)
.style("fill", "none")
.remove();
layer.enter().append("g")
.attr("class", "stack")
.style("fill", function(d, i) {
return i == 0 ? '#b4d5c3' : '#ecaeb3';
});
bars = layer.selectAll("rect")
.data(function(d) {
return d;
});
// the "EXIT" set:
bars.exit()
.transition()
.delay(function(d, i) {
return 30 * i;
})
.duration(1500)
.attr("y", y(0))
.attr("height", heightB - y(0))
.style('fill-opacity', 1e-6)
.remove();
// the "ENTER" set:
bars.enter().append("rect")
.transition()
.delay(function(d, i) {
return 30 * i;
})
.duration(3000)
.attr("x", function(d) {
return x(d.x);
})
.attr("y", function(d) {
return y(d.y + d.y0);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y + d.y0);
})
.attr("width", x.rangeBand());
// the "UPDATE" set:
bars.transition().delay(function(d, i) {
return 30 * i;
}).duration(1500).attr("x", function(d) {
return x(d.x);
})
.attr("width", x.rangeBand()) // constant, so no callback function(d) here
.attr("y", function(d) {
return y(d.y + d.y0);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y + d.y0);
});
} else { // draw bar
x.domain(points.map(function(d) {
return d.name;
}));
y.domain([0, 1024]).nice();
bars = layer.selectAll("rect")
.data(points);
// the "EXIT" set:
bars.exit()
.transition()
.delay(function(d, i) {
return 30 * i;
})
.duration(1500)
.attr("y", y(0))
.attr("height", heightB - y(0))
.style('fill-opacity', 1e-6)
.remove();
// the "ENTER" set:
bars.enter().append("rect")
.transition()
.delay(function(d, i) {
return 30 * i;
})
.duration(3000)
.attr("x", function(d) {
return x(d.name);
})
.attr("y", function(d) {
return y(d.GTF);
})
.attr("height", function(d) {
return heightB - y(d.GTF);;
})
.attr("width", x.rangeBand());
// the "UPDATE" set:
bars.transition().delay(function(d, i) {
return 30 * i;
}).duration(1500).attr("x", function(d) {
return x(d.name);
})
.attr("width", x.rangeBand()) // constant, so no callback function(d) here
.attr("y", function(d) {
return y(d.GTF);
})
.attr("height", function(d) {
return heightB - y(d.GTF);
});
}
}
window.onload = draw();
$("#click").on('click', function() {
if (value == 'stack') {
value = 'bar';
} else {
value = 'stack';
}
draw();
});
body {
font-family: 'Alegreya', serif;
}
.axis text {
font: 10px sans-serif;
}
.axis path {
fill: none;
stroke: #000;
stroke-width: 0px;
shape-rendering: crispEdges;
}
.axis line {
fill: none;
stroke: #000;
stroke-width: 0.5px;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://d3js.org/d3.v3.js"></script>
<div id="chart"></div>
<p id="click"> click here to change </p>
The problem in your code is that you're changing the scales' domain for the bar chart, but you're not changing them back for the stacked bar chart.
Therefore, you should put this in the draw() section (conditional statement) for the stacked bar:
x.domain(dataStackLayout[0].map(function(d) {
return d.x;
}));
y.domain([0, d3.max(dataStackLayout[dataStackLayout.length - 1],
function(d) {
return d.y0 + d.y;
})]).nice();
Here is your code with that change (I also put a call for the x axis):
var value = 'stack';
var data = [{
name: "Shihuahuaco",
value: 1067,
china: 772
}, {
name: "Cachimbo",
value: 283,
china: 1
}, {
name: "Estoraque",
value: 204,
china: 150
}, {
name: "Cumala",
value: 154,
china: 0
}, {
name: "Ishpingo",
value: 108,
china: 3
}, {
name: "Huayruro",
value: 108,
china: 1
}, {
name: "Tornillo",
value: 61,
china: 4
}, {
name: "Congona",
value: 54,
china: 0
}, {
name: "Capirona",
value: 37,
china: 5
}, {
name: "Tahuari",
value: 33,
china: 14
}, {
name: "Marupa",
value: 33,
china: 1
}, {
name: "Quinilla",
value: 28,
china: 4
}, {
name: "Azucar huayo",
value: 22,
china: 15
}, {
name: "Protium sp.",
value: 19,
china: 0
}, {
name: "Nogal",
value: 15,
china: 6
}, {
name: "Ana Caspi",
value: 14,
china: 2
}, {
name: "Cedro",
value: 14,
china: 0
}, {
name: "Carapa guianensis",
value: 12,
china: 0
}, {
name: "Leche caspi",
value: 12,
china: 0
}, {
name: "Andiroba",
value: 11,
china: 0
}, {
name: "Copaiba",
value: 7,
china: 4
}, {
name: "Palo baston",
value: 6,
china: 0
}, {
name: "Moena",
value: 5,
china: 0
}, {
name: "Almendro",
value: 5,
china: 0
}, {
name: "Chancaquero",
value: 4,
china: 0
}, {
name: "Caimitillo",
value: 3,
china: 1
}, {
name: "Nogal amarillo",
value: 3,
china: 0
}, {
name: "Couma macrocarpa",
value: 3,
china: 0
}, {
name: "Tulpay",
value: 3,
china: 0
}, {
name: "Carapa",
value: 3,
china: 0
}, {
name: "Dacryodes olivifera",
value: 2,
china: 0
}, {
name: "Capinuri",
value: 2,
china: 2
}, {
name: "Brosimum alicastrum",
value: 2,
china: 0
}, {
name: "Paramachaerium ormosioide",
value: 2,
china: 0
}, {
name: "Brosimum sp.",
value: 2,
china: 0
}, {
name: "Manchinga",
value: 2,
china: 0
}];
var points = [{
'lon': 105.3,
'lat': 33.5,
'name': 'China',
'GTF': 1024,
"ID": "CHN"
}, {
'lon': -70.9,
'lat': 18.8,
'name': 'Dominican Republic',
'GTF': 470,
"ID": "DOM"
}, {
'lon': -101,
'lat': 38,
'name': 'USA',
'GTF': 248,
"ID": "USA"
}, {
'lon': -102.5,
'lat': 22.7,
'name': 'Mexico',
'GTF': 220,
"ID": "MEX"
}, {
'lon': 2.98,
'lat': 46,
'name': 'France',
'GTF': 85,
"ID": "FRA"
}];
var margin = {
top: 20,
right: 30,
bottom: 150,
left: 60
},
widthB = 700 - margin.left - margin.right,
heightB = 500 - margin.top - margin.bottom;
var dataIntermediate = ['value', 'china'].map(function(key, i) {
return data.map(function(d, j) {
return {
x: d['name'],
y: d[key]
};
})
})
var dataStackLayout = d3.layout.stack()(dataIntermediate);
var svgBar = d3.select("#chart").append("svg")
.attr("width", widthB + margin.left + margin.right)
.attr("height", heightB + margin.top + margin.bottom)
var gBar = svgBar.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr('class', 'gBar');
var x = d3.scale.ordinal()
.rangeRoundBands([0, widthB], .2);
var y = d3.scale.linear()
.range([heightB, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(8)
.tickFormat(function(d) {
return y.tickFormat(4, d3.format(",d"))(d)
});
data.forEach(function(d) {
d.value = +d.value; // coerce to number
d.china = +d.china;
});
x.domain(dataStackLayout[0].map(function(d) {
return d.x;
}));
y.domain([0, d3.max(dataStackLayout[dataStackLayout.length - 1],
function(d) {
return d.y0 + d.y;
})]).nice();
var layer;
// this part
var bars;
var gX = gBar.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (heightB + 10) + ")");
gBar.append("g")
.attr("class", "y axis")
.call(yAxis)
.selectAll("text")
.style('font-size', '16px')
.style('font-family', 'Alegreya');
function draw() {
if (value == 'stack') {
x.domain(dataStackLayout[0].map(function(d) {
return d.x;
}));
y.domain([0, d3.max(dataStackLayout[dataStackLayout.length - 1],
function(d) {
return d.y0 + d.y;
})]).nice();
layer = gBar.selectAll(".stack")
.data(dataStackLayout);
layer.exit()
.transition()
.delay(function(d, i) {
return 30 * i;
})
.duration(1500)
.style("fill", "none")
.remove();
layer.enter().append("g")
.attr("class", "stack")
.style("fill", function(d, i) {
return i == 0 ? '#b4d5c3' : '#ecaeb3';
});
bars = layer.selectAll("rect")
.data(function(d) {
return d;
});
bars.exit()
.transition()
.delay(function(d, i) {
return 30 * i;
})
.duration(1500)
.attr("y", y(0))
.attr("height", heightB - y(0))
.style('fill-opacity', 1e-6)
.remove();
bars.enter().append("rect")
.transition()
.delay(function(d, i) {
return 30 * i;
})
.duration(3000)
.attr("x", function(d) {
return x(d.x);
})
.attr("y", function(d) {
return y(d.y + d.y0);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y + d.y0);
})
.attr("width", x.rangeBand());
// the "UPDATE" set:
bars.transition().delay(function(d, i) {
return 30 * i;
}).duration(1500).attr("x", function(d) {
return x(d.x);
}) // (d) is one item from the data array, x is the scale object from above
.attr("width", x.rangeBand()) // constant, so no callback function(d) here
.attr("y", function(d) {
return y(d.y + d.y0);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y + d.y0);
})
.style("fill-opacity", 1);
gX.call(xAxis)
.selectAll("text")
.style('font-size', '14px')
.style('font-family', 'Alegreya')
.style("text-anchor", "end")
.attr("dx", "-0.40em")
.attr("dy", ".10em")
.attr("transform", function(d) {
return "rotate(-65)"
});
} else {
x.domain(points.map(function(d) {
return d.name;
}));
y.domain([0, 1024]).nice();
// this part
bars = layer.selectAll("rect")
.data(points);
bars.exit()
.transition()
.delay(function(d, i) {
return 30 * i;
})
.duration(1500)
.attr("y", y(0))
.attr("height", heightB - y(0))
.style('fill-opacity', 1e-6)
.remove();
bars.enter().append("rect")
.transition()
.delay(function(d, i) {
return 30 * i;
})
.duration(3000)
.attr("x", function(d) {
return x(d.name);
})
.attr("y", function(d) {
return y(d.GTF);
})
.attr("height", function(d) {
return heightB - y(d.GTF);;
})
.attr("width", x.rangeBand());
// the "UPDATE" set:
bars.transition().delay(function(d, i) {
return 30 * i;
}).duration(1500).attr("x", function(d) {
return x(d.name);
}) // (d) is one item from the data array, x is the scale object from above
.attr("width", x.rangeBand()) // constant, so no callback function(d) here
.attr("y", function(d) {
return y(d.GTF);
})
.attr("height", function(d) {
return heightB - y(d.GTF);
});
gX.call(xAxis);
}
}
window.onload = draw();
$("#click").on('click', function() {
if (value == 'stack') {
value = 'bar';
} else {
value = 'stack';
}
draw();
});
body {
font-family: 'Alegreya', serif;
}
.axis text {
font: 10px sans-serif;
}
.axis path {
fill: none;
stroke: #000;
stroke-width: 0px;
shape-rendering: crispEdges;
}
.axis line {
fill: none;
stroke: #000;
stroke-width: 0.5px;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://d3js.org/d3.v3.js"></script>
<div id="chart"></div>
<button id="click"> click here to change </button>
PS: Besides that, there is a lot of other minor changes you should do in your code, both for performance and design. As this is (now) a running code, I suggest you post further questions about how to improve it on Code Review, using the d3.js tag.

Categories

Resources