Related
I am trying to understand why d3.line behaves differently around null values when combined with scale.
To elaborate, my dataset looks like this and it will always contain some null
const data = [
{ "x": 50, "y": 97.04013083865155 },
{ "x": 100, "y": null },
{ "x": 150, "y": 98.62594214598816 },
{ "x": 200, "y": 76.49419950954189 },
{ "x": 250, "y": 29.30639006661442 },
{ "x": 300, "y": 29.366842697148176 },
{ "x": 350, "y": 51.587600132998325 },
{ "x": 400, "y": null },
{ "x": 450, "y": null },
{ "x": 500, "y": 26.90860254816283 },
{ "x": 550, "y": null },
{ "x": 600, "y": 99.1622268038577 }
]
No Scale +null
If I want to generate a line, and not use scale, I get the following
////////////////////////////////////////////////////////////
//////////////////////// 1 DATA ///////////////////////////
////////////////////////////////////////////////////////////
const data = [
{ "x": 50, "y": 97.04013083865155 },
{ "x": 100, "y": null },
{ "x": 150, "y": 98.62594214598816 },
{ "x": 200, "y": 76.49419950954189 },
{ "x": 250, "y": 29.30639006661442 },
{ "x": 300, "y": 29.366842697148176 },
{ "x": 350, "y": 51.587600132998325 },
{ "x": 400, "y": null },
{ "x": 450, "y": null },
{ "x": 500, "y": 26.90860254816283 },
{ "x": 550, "y": null },
{ "x": 600, "y": 99.1622268038577 }
]
height = 400,
width = 720;
padding = {
top: 70,
bottom: 50,
left: 70,
right: 70
}
const boundHeight = height - padding.top - padding.bottom;
const boundWidth = width - padding.right - padding.left;
////////////////////////////////////////////////////////////
//////////////////////// 2 CREATE SCALE ////////////////////
////////////////////////////////////////////////////////////
const scaleX = d3.scaleLinear()
.range([0, boundWidth])
.domain(d3.extent(data, d => d.x))
const scaleY = d3.scaleLinear()
.range([boundHeight, 0])
.domain(d3.extent(data, d => d.y))
////////////////////////////////////////////////////////////
//////////////////////// 3 SVG// ///////////////////////////
////////////////////////////////////////////////////////////
const svgns = 'http://www.w3.org/2000/svg'
const svg = d3.select('svg')
svg
.attr('xmlns', svgns)
.attr('viewBox', `0 0 ${width} ${height}`)
svg.append('rect')
.attr('class', 'vBoxRect')
.style("overflow", "visible")
.attr('width', `${width}`)
.attr('height', `${height}`)
.attr('stroke', 'red')
.attr('fill', 'none')
//create BOUND rect -- to be deleted later
svg.append('rect')
.attr('class', 'boundRect')
.attr('x', `${padding.left}`)
.attr('y', `${padding.top}`)
.attr('width', `${boundWidth}`)
.attr('height', `${boundHeight}`)
.attr('fill', 'none')
.attr('stroke', 'black')
//create bound element
bound = svg.append('g')
.attr('class', 'bound')
.style('transform', `translate(${padding.left}px,${padding.top}px)`)
//constrcuct line generators
////////////////////////////////////////////////////////////
////////////////////////NO SCALE// /////////////////////////
////////////////////////////////////////////////////////////
noScale = d3.line()
.x(d => d.x)
.y(d => d.y)
(data)
//no Scale
bound.append('path')
.attr('class', 'Black-noScale')
.attr('d', noScale)
.attr('fill', 'none')
.attr('stroke', 'black')
.attr('stroke-width', '2')
console.log(noScale)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>
<body>
<svg>
</svg>
<div id="container" class="svg-container"></div>
<script src="next.js"></script>
</body>
</html>
With Scale +null
However, if I want to generate a line, and use scale, the generator fails
////////////////////////////////////////////////////////////
//////////////////////// 1 DATA ///////////////////////////
////////////////////////////////////////////////////////////
const data = [
{ "x": 50, "y": 97.04013083865155 },
{ "x": 100, "y": null },
{ "x": 150, "y": 98.62594214598816 },
{ "x": 200, "y": 76.49419950954189 },
{ "x": 250, "y": 29.30639006661442 },
{ "x": 300, "y": 29.366842697148176 },
{ "x": 350, "y": 51.587600132998325 },
{ "x": 400, "y": null },
{ "x": 450, "y": null },
{ "x": 500, "y": 26.90860254816283 },
{ "x": 550, "y": null },
{ "x": 600, "y": 99.1622268038577 }
]
height = 400,
width = 720;
padding = {
top: 70,
bottom: 50,
left: 70,
right: 70
}
const boundHeight = height - padding.top - padding.bottom;
const boundWidth = width - padding.right - padding.left;
////////////////////////////////////////////////////////////
//////////////////////// 2 CREATE SCALE ////////////////////
////////////////////////////////////////////////////////////
const scaleX = d3.scaleLinear()
.range([0, boundWidth])
.domain(d3.extent(data, d => d.x))
const scaleY = d3.scaleLinear()
.range([boundHeight, 0])
.domain(d3.extent(data, d => d.y))
////////////////////////////////////////////////////////////
//////////////////////// 3 SVG// ///////////////////////////
////////////////////////////////////////////////////////////
const svgns = 'http://www.w3.org/2000/svg'
const svg = d3.select('svg')
svg
.attr('xmlns', svgns)
.attr('viewBox', `0 0 ${width} ${height}`)
svg.append('rect')
.attr('class', 'vBoxRect')
.style("overflow", "visible")
.attr('width', `${width}`)
.attr('height', `${height}`)
.attr('stroke', 'red')
.attr('fill', 'none')
//create BOUND rect -- to be deleted later
svg.append('rect')
.attr('class', 'boundRect')
.attr('x', `${padding.left}`)
.attr('y', `${padding.top}`)
.attr('width', `${boundWidth}`)
.attr('height', `${boundHeight}`)
.attr('fill', 'none')
.attr('stroke', 'black')
//create bound element
bound = svg.append('g')
.attr('class', 'bound')
.style('transform', `translate(${padding.left}px,${padding.top}px)`)
//constrcuct line generators
////////////////////////////////////////////////////////////
////////////////////////WITH SCALE// ////////////////////////
////////////////////////////////////////////////////////////
withScale = d3.line()
.x(d => scaleX(d.x))
.y(d => scaleY(d.y))
(data)
bound.append('path')
.attr('class', 'Orange-withScale')
.attr('d', withScale)
.attr('fill', 'none')
.attr('stroke', 'orange')
.attr('stroke-width', '2')
console.log(withScale);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>
<body>
<svg>
</svg>
<div id="container" class="svg-container"></div>
<script src="next.js"></script>
</body>
</html>
On the other hand, the genrator works well with defined whether scale used or not
////////////////////////////////////////////////////////////
//////////////////////// 1 DATA ///////////////////////////
////////////////////////////////////////////////////////////
const data = [
{ "x": 50, "y": 97.04013083865155 },
{ "x": 100, "y": null },
{ "x": 150, "y": 98.62594214598816 },
{ "x": 200, "y": 76.49419950954189 },
{ "x": 250, "y": 29.30639006661442 },
{ "x": 300, "y": 29.366842697148176 },
{ "x": 350, "y": 51.587600132998325 },
{ "x": 400, "y": null },
{ "x": 450, "y": null },
{ "x": 500, "y": 26.90860254816283 },
{ "x": 550, "y": null },
{ "x": 600, "y": 99.1622268038577 }
]
height = 400,
width = 720;
padding = {
top: 70,
bottom: 50,
left: 70,
right: 70
}
const boundHeight = height - padding.top - padding.bottom;
const boundWidth = width - padding.right - padding.left;
////////////////////////////////////////////////////////////
//////////////////////// 2 CREATE SCALE ////////////////////
////////////////////////////////////////////////////////////
const scaleX = d3.scaleLinear()
.range([0, boundWidth])
.domain(d3.extent(data, d => d.x))
const scaleY = d3.scaleLinear()
.range([boundHeight, 0])
.domain(d3.extent(data, d => d.y))
////////////////////////////////////////////////////////////
//////////////////////// 3 SVG// ///////////////////////////
////////////////////////////////////////////////////////////
const svgns = 'http://www.w3.org/2000/svg'
const svg = d3.select('svg')
svg
.attr('xmlns', svgns)
.attr('viewBox', `0 0 ${width} ${height}`)
svg.append('rect')
.attr('class', 'vBoxRect')
.style("overflow", "visible")
.attr('width', `${width}`)
.attr('height', `${height}`)
.attr('stroke', 'red')
.attr('fill', 'none')
//create BOUND rect -- to be deleted later
svg.append('rect')
.attr('class', 'boundRect')
.attr('x', `${padding.left}`)
.attr('y', `${padding.top}`)
.attr('width', `${boundWidth}`)
.attr('height', `${boundHeight}`)
.attr('fill', 'none')
.attr('stroke', 'black')
//create bound element
bound = svg.append('g')
.attr('class', 'bound')
.style('transform', `translate(${padding.left}px,${padding.top}px)`)
//constrcuct line generators
////////////////////////////////////////////////////////////
////////////////////////NO SCALE// /////////////////////////
////////////////////////////////////////////////////////////
noScaleWithDefinedFilter = d3.line()
.x(d => d.x)
.y(d => d.y)
.defined(d => d.y)
(data.filter((a) => a.y !== null))
noScaleWithDefined = d3.line()
.x(d => d.x)
.y(d => d.y)
.defined(d => d.y)
(data)
//shows complete line
bound.append('path')
.attr('class', 'Violet-noScale+defined+filter')
.attr('d', noScaleWithDefinedFilter)
.attr('fill', 'none')
.attr('stroke', 'violet')
.attr('stroke-width', '2')
.style('transform', 'translateY(50px)')
//does not show null Y
bound.append('path')
.attr('class', 'Red-noScale+defined')
.attr('d', noScaleWithDefined)
.attr('fill', 'none')
.attr('stroke', 'red')
.attr('stroke-width', '2')
.style('transform', 'translateY(75px)')
////////////////////////////////////////////////////////////
////////////////////////WITH SCALE// /////////////////////////
////////////////////////////////////////////////////////////
withScaleWithDefined = d3.line()
.x(d => scaleX(d.x))
.y(d => scaleY(d.y))
.defined(d => d.y)
(data)
withScaleWithDefinedFilter = d3.line()
.x(d => scaleX(d.x))
.y(d => scaleY(d.y))
.defined(d => d.y)
(data.filter((a) => a.y !== null))
bound.append('path')
.attr('class', 'Salmon-withScale+Defined+Filter')
.attr('d', withScaleWithDefinedFilter)
.attr('fill', 'none')
.attr('stroke', 'salmon')
.attr('stroke-width', '2')
bound.append('path')
.attr('class', 'Blue-withScale+Defined')
.attr('d', withScaleWithDefined)
.attr('fill', 'none')
.attr('stroke', 'blue')
.attr('stroke-width', '2')
.style('transform', 'translateY(-25px)')
console.log(noScaleWithDefinedFilter);
console.log(noScaleWithDefined);
console.log(withScaleWithDefined);
console.log(withScaleWithDefinedFilter);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>
<body>
<svg>
</svg>
<div id="container" class="svg-container"></div>
<script src="next.js"></script>
</body>
</html>
How can I achieve, what the following does
const noScale = d3.line()
.x(d => d.x)
.y(d => d.y)
(data)
but with scaling
const withScale = d3.line()
.x(d => scaleX(d.x))
.y(d => scaleY(d.y))
(data)
Can this be achieved without destroying the data?
This is by design and documented: if you pass a null value to that linear scale it will return undefined, and obviously you cannot do anything with undefined inside the d attribute.
The solution for the scale itself is using unknown, which sets the unknown returned value to any value you want, for instance zero:
const scaleY = d3.scaleLinear()
.unknown(0)
etc...
If, on the other hand, you want to skip the null values, you have to change the line generator itself with defined, not the scale.
Here's your code with unknown(0):
////////////////////////////////////////////////////////////
//////////////////////// 1 DATA ///////////////////////////
////////////////////////////////////////////////////////////
const data = [
{ "x": 50, "y": 97.04013083865155 },
{ "x": 100, "y": null },
{ "x": 150, "y": 98.62594214598816 },
{ "x": 200, "y": 76.49419950954189 },
{ "x": 250, "y": 29.30639006661442 },
{ "x": 300, "y": 29.366842697148176 },
{ "x": 350, "y": 51.587600132998325 },
{ "x": 400, "y": null },
{ "x": 450, "y": null },
{ "x": 500, "y": 26.90860254816283 },
{ "x": 550, "y": null },
{ "x": 600, "y": 99.1622268038577 }
]
height = 400,
width = 720;
padding = {
top: 70,
bottom: 50,
left: 70,
right: 70
}
const boundHeight = height - padding.top - padding.bottom;
const boundWidth = width - padding.right - padding.left;
////////////////////////////////////////////////////////////
//////////////////////// 2 CREATE SCALE ////////////////////
////////////////////////////////////////////////////////////
const scaleX = d3.scaleLinear()
.range([0, boundWidth])
.domain(d3.extent(data, d => d.x))
const scaleY = d3.scaleLinear()
.unknown(0)
.range([boundHeight, 0])
.domain(d3.extent(data, d => d.y))
////////////////////////////////////////////////////////////
//////////////////////// 3 SVG// ///////////////////////////
////////////////////////////////////////////////////////////
const svgns = 'http://www.w3.org/2000/svg'
const svg = d3.select('svg')
svg
.attr('xmlns', svgns)
.attr('viewBox', `0 0 ${width} ${height}`)
svg.append('rect')
.attr('class', 'vBoxRect')
.style("overflow", "visible")
.attr('width', `${width}`)
.attr('height', `${height}`)
.attr('stroke', 'red')
.attr('fill', 'none')
//create BOUND rect -- to be deleted later
svg.append('rect')
.attr('class', 'boundRect')
.attr('x', `${padding.left}`)
.attr('y', `${padding.top}`)
.attr('width', `${boundWidth}`)
.attr('height', `${boundHeight}`)
.attr('fill', 'none')
.attr('stroke', 'black')
//create bound element
bound = svg.append('g')
.attr('class', 'bound')
.style('transform', `translate(${padding.left}px,${padding.top}px)`)
//constrcuct line generators
////////////////////////////////////////////////////////////
////////////////////////WITH SCALE// ////////////////////////
////////////////////////////////////////////////////////////
withScale = d3.line()
.x(d => scaleX(d.x))
.y(d => scaleY(d.y))
(data)
bound.append('path')
.attr('class', 'Orange-withScale')
.attr('d', withScale)
.attr('fill', 'none')
.attr('stroke', 'orange')
.attr('stroke-width', '2')
console.log(withScale);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>
<body>
<svg>
</svg>
<div id="container" class="svg-container"></div>
<script src="next.js"></script>
</body>
</html>
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>
I am facing a difficult task in combining the Bivariate Area Graph with Line Graph in a single graph d3.js. If I am successful with the bivariate area graph i am not able to draw a line chart in the same graph and vice versa. Either one is working for me. I have provided data for bivariate area which is at x and y axis. whether it is possible to draw the line chart with same data? I have gone through lot of graphs available online and i couldn't find one. Help needed in drawing the line chart with bivariate area chart.
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.area {
fill: steelblue;
}
</style>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
// data
var data = [{
"month": "January",
"high": "59.5",
"low" : "57.0"
}, {
"month": "February",
"high": "59.5",
"low" : "53.4"
}, {
"month": "March",
"high": "59.0",
"low" : "53.4"
}, {
"month": "April",
"high": "62.4",
"low" : "54.7"
}, {
"month": "May",
"high": "58.3",
"low" : "52.7"
}, {
"month": "June",
"high": "62.1",
"low" : "54.5"
}, {
"month": "July",
"high": "60.8",
"low" : "53.4"
}, {
"month": "August",
"high": "61.0",
"low" : "52.5"
}, {
"month": "September",
"high": "62.4",
"low" : "52.9"
}, {
"month": "October",
"high": "65.3",
"low" : "54.0"
}, {
"month": "November",
"high": "70.3",
"low" : "55.0"
}, {
"month": "December",
"high": "82.2",
"low" : "58.6"
}];
// margins
var margin = {top: 20, right: 30, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// parsing data
var parseDate = d3.time.format("%B").parse;
// x-axis encoding
var x = d3.time.scale()
.range([0, width]);
// y-axis encoding
var y = d3.scale.linear()
.range([height, 0]);
// x-axis scaling
var xAxis = d3.svg.axis()
.scale(x)
.tickFormat(d3.time.format("%b"))
.orient("bottom");
// y-axis scaling
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
// area draw
var area = d3.svg.area()
.x(function(d) { return x(d.month); })
.y0(function(d) { return y(d.low); })
.y1(function(d) { return y(d.high); });
// adding the svg element
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//function
function chart(error, data) {
data.forEach(function(d) {
d.month = parseDate(d.month);
d.low = +d.low;
d.high = +d.high;
});
// setting scales
x.domain(d3.extent(data, function(d) { return d.month;})).range([0, 600]);
y.domain([d3.min(data, function(d) { return d.low; }),
d3.max(data, function(d) { return d.high; })]);
// area logic
svg.append("path")
.datum(data)
.attr("class", "area")
.attr("d", area);
// appending x axis data
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// text label for the x axis
svg.append("text")
.attr("x", 0 )
.attr("y", 480 )
.style("text-anchor", "middle")
.text("2015");
// appending y axis data
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Temperature (ºF)");
}
chart([],data);
/*);*/
</script>
Do it this way.
Working code http://jsfiddle.net/cyril123/7a4pp6gx/2/
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.area {
fill: steelblue;
}
.line {
fill: transparent;
stroke:red;
stroke-width:3px;
}
.line1 {
fill: transparent;
stroke:green;
stroke-width:3px;
}
</style>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
// data
var data = [{
"month": "January",
"high": "59.5",
"low" : "57.0"
}, {
"month": "February",
"high": "59.5",
"low" : "53.4"
}, {
"month": "March",
"high": "59.0",
"low" : "53.4"
}, {
"month": "April",
"high": "62.4",
"low" : "54.7"
}, {
"month": "May",
"high": "58.3",
"low" : "52.7"
}, {
"month": "June",
"high": "62.1",
"low" : "54.5"
}, {
"month": "July",
"high": "60.8",
"low" : "53.4"
}, {
"month": "August",
"high": "61.0",
"low" : "52.5"
}, {
"month": "September",
"high": "62.4",
"low" : "52.9"
}, {
"month": "October",
"high": "65.3",
"low" : "54.0"
}, {
"month": "November",
"high": "70.3",
"low" : "55.0"
}, {
"month": "December",
"high": "82.2",
"low" : "58.6"
}];
// margins
var margin = {top: 20, right: 30, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// parsing data
var parseDate = d3.time.format("%B").parse;
// x-axis encoding
var x = d3.time.scale()
.range([0, width]);
// y-axis encoding
var y = d3.scale.linear()
.range([height, 0]);
// x-axis scaling
var xAxis = d3.svg.axis()
.scale(x)
.tickFormat(d3.time.format("%b"))
.orient("bottom");
// y-axis scaling
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
// area draw
var area = d3.svg.area()
.x(function(d) { return x(d.month); })
.y0(function(d) { return y(d.low); })
.y1(function(d) { return y(d.high); });
var line = d3.svg.line()
.x(function(d) { return x(d.month); })
.y(function(d) { return y(d.high); });
var line1 = d3.svg.line()
.x(function(d) { return x(d.month); })
.y(function(d) { return y(d.low); });
// adding the svg element
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//function
function chart(error, data) {
data.forEach(function(d) {
d.month = parseDate(d.month);
d.low = +d.low;
d.high = +d.high;
});
// setting scales
x.domain(d3.extent(data, function(d) { return d.month;})).range([0, 600]);
y.domain([d3.min(data, function(d) { return d.low; }),
d3.max(data, function(d) { return d.high; })]);
// area logic
svg.append("path")
.datum(data)
.attr("class", "area")
.attr("d", area);
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
svg.append("path")
.datum(data)
.attr("class", "line1")
.attr("d", line1);
// appending x axis data
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// text label for the x axis
svg.append("text")
.attr("x", 0 )
.attr("y", 480 )
.style("text-anchor", "middle")
.text("2015");
// appending y axis data
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Temperature (ºF)");
}
chart([],data);
/*);*/
</script>
I'm trying to make a bar chart with d3, and for this I'm following this tutorial d3noob chart, everything looks good but I'm unable to load the data from a JSON inside a variable instead of load from a JSON file, i've managed to load the json to a var but I have no idea of how to iterate the data and create the bars. The code:
<!DOCTYPE html>
<meta charset="utf-8">
<head>
<style>
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
</style>
</head>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var jdata = [
{
"date": "2013-01",
"value": "53"
},
{
"date": "2013-02",
"value": "165"
},
{
"date": "2013-03",
"value": "269"
},
{
"date": "2013-04",
"value": "344"
},
{
"date": "2013-05",
"value": "376"
},
{
"date": "2013-06",
"value": "410"
},
{
"date": "2013-07",
"value": "421"
},
{
"date": "2013-08",
"value": "405"
},
{
"date": "2013-09",
"value": "376"
},
{
"date": "2013-10",
"value": "359"
},
{
"date": "2013-11",
"value": "392"
},
{
"date": "2013-12",
"value": "433"
},
{
"date": "2014-01",
"value": "455"
},
{
"date": "2014-02",
"value": "478"
}
];
// console.log(jdata);
root = JSON.parse( jdata );
var margin = {top: 20, right: 20, bottom: 70, left: 40},
width = 600 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
// Parse the date / time
var parseDate = d3.time.format("%Y-%m").parse;
var x = d3.scale.ordinal().rangeRoundBands([0, width], .05);
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(d3.time.format("%Y-%m"));
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10);
var svg = d3.select("body").append("svg")
.data(root)
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform","translate(" + margin.left + "," + margin.top + ")");
//d3.json
//d3.csv("bar-data.csv", function(error, data) {
//d3.selectAll('g').data(function(data){
data.forEach(function(d) {
d.date = parseDate(d.date);
d.value = +d.value;
});
x.domain(data.map(function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.value; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", "-.55em")
.attr("transform", "rotate(-90)" );
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Value ($)");
svg.selectAll("bar")
.data(data)
.enter().append("rect")
.style("fill", "steelblue")
.attr("x", function(d) { return x(d.date); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); });
});
</script>
</body>
It's here too: jsfiddle.
See my comments in the code...
var jdata = [
{
"date": "2013-01",
"value": "53"
},
{
"date": "2013-02",
"value": "165"
},
{
"date": "2013-03",
"value": "269"
},
{
"date": "2013-04",
"value": "344"
},
{
"date": "2013-05",
"value": "376"
},
{
"date": "2013-06",
"value": "410"
},
{
"date": "2013-07",
"value": "421"
},
{
"date": "2013-08",
"value": "405"
},
{
"date": "2013-09",
"value": "376"
},
{
"date": "2013-10",
"value": "359"
},
{
"date": "2013-11",
"value": "392"
},
{
"date": "2013-12",
"value": "433"
},
{
"date": "2014-01",
"value": "455"
},
{
"date": "2014-02",
"value": "478"
}
],
//root = JSON.parse(jdata),
//***EDIT*** jdata is not a string as required by JSON.parse
//it's already well formed so just use it
data = jdata,
margin = {top: 20, right: 20, bottom: 70, left: 40},
width = 600 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom,
// Parse the date / time
//***a better comment would be "for parsing the date and time"...
parseDate = d3.time.format("%Y-%m").parse,
x = d3.scale.ordinal().rangeRoundBands([0, width], .05),
y = d3.scale.linear().range([height, 0]),
xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(d3.time.format("%Y-%m")),
yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10),
svg = d3.select("body").append("svg")
//***EDIT***
//the data method returns the enter collection and your not ready for it yet...
//.data(root)
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform","translate(" + margin.left + "," + margin.top + ")");
//d3.json
//d3.csv("bar-data.csv", function(error, data) {
//d3.selectAll('g').data(function(data){
data.forEach(function (d) {
//NOW is parsing the date and time
d.date = parseDate(d.date);
d.value = +d.value;
});
x.domain(data.map(function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.value; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", "-.55em")
.attr("transform", "rotate(-90)" );
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Value ($)");
svg.selectAll("bar")
.data(data)
.enter().append("rect")
.style("fill", "steelblue")
.attr("x", function(d) { return x(d.date); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); });
//You forgot to comment this out...
// });
</script>
I have been at it for a couple of weeks and cant seem to figure out how to draw the below chart with multiple paths.
Focus+Context via Brushing chart
I have tried to create a jsfiddle but was not able to replicate the screen that i do get. At this point what i have is similar to the original chart just with one path instead of area and the brushing works. Basically trying to combine the Focus chart and Multi-Series Line Chart Multiseries chart .
However when I try to add another path nothing works. Please suggest any ideas or changes that I need to make for it to work. Also are there any other similar charts (or chart exaples) that I can look at. The data could be rearranged in any way or form for this to work.
Jsfiddle
<div id='dashboardChart'>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
id="svg">
</div>
path {
fill:none;
stroke:white;
stroke-width:2px;
}
.axis path, .axis line {
fill: none;
stroke: #CCC;
shape-rendering: crispEdges;
}
.brush .extent {
stroke: #fff;
fill-opacity: .125;
shape-rendering: crispEdges;
}
.path_green {
stroke:green;
}
.path_red {
stroke:red;
}
.path_yellow {
stroke:yellow;
}
function drawChart() {
var margin = {
top: 5,
right: 10,
bottom: 100,
left: 50
},
margin2 = {
top: 200,
right: 10,
bottom: 20,
left: 50
},
width = 1075 - margin.left - margin.right,
height = 280 - margin.top - margin.bottom,
height2 = 280 - margin2.top - margin2.bottom;
var parseDate = d3.time.format("%Y-%m-%d").parse;
var x = d3.time.scale().range([0, width]),
x2 = d3.time.scale().range([0, width]),
y = d3.scale.linear().range([height, 0]),
y2 = d3.scale.linear().range([height2, 0]);
var xAxis = d3.svg.axis().scale(x).orient("bottom"),
xAxis2 = d3.svg.axis().scale(x2).orient("bottom"),
yAxis = d3.svg.axis().scale(y).orient("left");
var brush = d3.svg.brush()
.x(x2)
.on("brush", brush);
var area = d3.svg.area()
.interpolate("monotone")
.x(function (d) {
return x(d.date);
})
.y0(height)
.y1(function (d) {
return y(d.red);
});
var area2 = d3.svg.area()
.interpolate("monotone")
.x(function (d) {
return x2(d.date);
})
.y0(height2)
.y1(function (d) {
return y2(d.red);
});
var svg = d3.select("#dashboardChart #svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
var focus = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var context = svg.append("g")
.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")");
var data = [{
"date": "2013-02-08T05:00:00.000Z",
"data": null,
"red": 0,
"yellow": 0,
"green": 1
}, {
"date": "2013-02-07T05:00:00.000Z",
"data": null,
"red": 485,
"yellow": 0,
"green": 491
}, {
"date": "2013-02-06T05:00:00.000Z",
"data": null,
"red": 2884,
"yellow": 0,
"green": 2881
}, {
"date": "2013-02-05T05:00:00.000Z",
"data": null,
"red": 3191,
"yellow": 0,
"green": 3188
}, {
"date": "2013-02-04T05:00:00.000Z",
"data": null,
"red": 180,
"yellow": 0,
"green": 184
}, {
"date": "2013-02-03T05:00:00.000Z",
"data": null,
"red": 0,
"yellow": 0,
"green": 0
}, {
"date": "2013-02-02T05:00:00.000Z",
"data": null,
"red": 0,
"yellow": 0,
"green": 0
}, {
"date": "2013-02-01T05:00:00.000Z",
"data": null,
"red": 0,
"yellow": 0,
"green": 0
}, {
"date": "2013-01-31T05:00:00.000Z",
"data": null,
"red": 0,
"yellow": 0,
"green": 0
}, {
"date": "2013-01-30T05:00:00.000Z",
"data": null,
"red": 1,
"yellow": 0,
"green": 0
}, {
"date": "2013-01-29T05:00:00.000Z",
"data": null,
"red": 0,
"yellow": 0,
"green": 2
}, {
"date": "2013-01-28T05:00:00.000Z",
"data": null,
"red": 0,
"yellow": 0,
"green": 0
}, {
"date": "2013-01-27T05:00:00.000Z",
"data": null,
"red": 1,
"yellow": 1,
"green": 1
}, {
"date": "2013-01-26T05:00:00.000Z",
"data": null,
"red": 0,
"yellow": 0,
"green": 1
}, {
"date": "2013-01-25T05:00:00.000Z",
"data": null,
"red": 0,
"yellow": 0,
"green": 0
}, {
"date": "2013-01-24T05:00:00.000Z",
"data": null,
"red": 0,
"yellow": 0,
"green": 0
}, {
"date": "2013-01-23T05:00:00.000Z",
"data": null,
"red": 49,
"yellow": 0,
"green": 45
}, {
"date": "2013-01-22T05:00:00.000Z",
"data": null,
"red": 59,
"yellow": 0,
"green": 64
}, {
"date": "2013-01-21T05:00:00.000Z",
"data": null,
"red": 119,
"yellow": 1,
"green": 125
}, {
"date": "2013-01-20T05:00:00.000Z",
"data": null,
"red": 0,
"yellow": 1,
"green": 0
}, {
"date": "2013-01-19T05:00:00.000Z",
"data": null,
"red": 0,
"yellow": 0,
"green": 0
}, {
"date": "2013-01-18T05:00:00.000Z",
"data": null,
"red": 84,
"yellow": 0,
"green": 81
}, {
"date": "2013-01-17T05:00:00.000Z",
"data": null,
"red": 76,
"yellow": 1,
"green": 77
}, {
"date": "2013-01-16T05:00:00.000Z",
"data": null,
"red": 0,
"yellow": 1,
"green": 0
}, {
"date": "2013-01-15T05:00:00.000Z",
"data": null,
"red": 0,
"yellow": 0,
"green": 0
}, {
"date": "2013-01-14T05:00:00.000Z",
"data": null,
"red": 0,
"yellow": 0,
"green": 0
}, {
"date": "2013-01-13T05:00:00.000Z",
"data": null,
"red": 0,
"yellow": 0,
"green": 0
}, {
"date": "2013-01-12T05:00:00.000Z",
"data": null,
"red": 0,
"yellow": 0,
"green": 0
}, {
"date": "2013-01-11T05:00:00.000Z",
"data": null,
"red": 0,
"yellow": 0,
"green": 0
}, {
"date": "2013-01-10T05:00:00.000Z",
"data": null,
"red": 0,
"yellow": 0,
"green": 0
}];
x.domain(d3.extent(data.map(function (d) {
return d.date;
})));
y.domain([0, d3.max(data.map(function (d) {
return d.red;
}))]);
x2.domain(x.domain());
y2.domain(y.domain());
focus.append("path")
.datum(data)
.attr("clip-path", "url(#clip)")
.attr("d", area)
.attr("class", "path_red");
focus.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
focus.append("g")
.attr("class", "y axis")
.call(yAxis);
context.append("path")
.datum(data)
.attr("d", area2)
.attr("class", "path_red");
context.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height2 + ")")
.call(xAxis2);
context.append("g")
.attr("class", "x brush")
.call(brush)
.selectAll("rect")
.attr("y", -6)
.attr("height", height2 + 7);
function brush() {
x.domain(brush.empty() ? x2.domain() : brush.extent());
focus.select("path").attr("d", area);
focus.select(".x.axis").call(xAxis);
}
}
drawChart();
As per your comment, you were able to plot the three areas but had difficulty in brushing them. I have an working example here: http://jsfiddle.net/BVzyq/1/ wherein, I have added three <path> elements corresponding to the three colors in the data: ['red', 'yellow', 'green'].
I abstracted out the functions which could take in a color and return the appropriate d value:
var area = function (color) {
return d3.svg.area()
.interpolate("monotone")
.x(function (d) {
return x(d.date);
})
.y0(height)
.y1(function (d) {
return y(d[color]);
});
};
var area2 = function (color) {
return d3.svg.area()
.interpolate("monotone")
.x(function (d) {
return x2(d.date);
})
.y0(height2)
.y1(function (d) {
return y2(d[color]);
});
};
They can be abstracted further, but these are closest to the code you have written.
These functions are used while creating the paths:
focus.selectAll('path')
.data(['red', 'yellow', 'green'])
.enter()
.append('path')
.attr('clip-path', 'url(#clip)')
.attr('d', function (col) {
return area(col)(data);
})
.attr('class', function (col) {
return "path_" + col + " data";
});
// ...
context.selectAll('path')
.data(['red', 'yellow', 'green'])
.enter()
.append('path')
.attr('d', function (col) {
return area2(col)(data);
})
.attr('class', function (col) {
return "path_" + col;
});
The CSS classes seemed to suggest this form of data-join. I also added another class data to the paths which correspond to the time-series plots. This makes it easy to distinguish these <path>s from those meant for the axis.
Finally, in the brush function, recalculate the d attribute for all path.data elements:
function brush() {
x.domain(brush.empty() ? x2.domain() : brush.extent());
focus.selectAll("path.data").attr("d", function (col) {
return area(col)(data);
});
focus.select(".x.axis").call(xAxis);
}
Note that I changed some of the values in data to make all the three colours visible.
Great solution musically_ut, for others having a similar problem. I was able to load additional csv's, like the example found in this link - Focus+Context via Brushing but was really hung up on how to brush all of the lines together, even though they were both rendering properly in area and area2.
Turns out, looking at your code, all I had to do was change focus.select to focus.selectAll like you have. Thanks!
And for anyone working with the original tutorial code, you can add alternate csv's just by copying the following code and pointing to your new csv:
d3.csv("sp501.csv", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.price = +d.price;
});
x.domain(d3.extent(data.map(function(d) { return d.date; })));
y.domain([0, d3.max(data.map(function(d) { return d.price; }))]);
x2.domain(x.domain());
y2.domain(y.domain());
focus.append("path")
.datum(data)
.attr("clip-path", "url(#clip)")
.attr("d", area)
.attr("class", "timeLine2");
context.append("path")
.datum(data)
.attr("class", "timeLine2")
.attr("d", area2);
context.append("g")
.attr("class", "x axis2")
.attr("transform", "translate(0," + height2 + ")")
.call(xAxis2);
context.append("g")
.attr("class", "x brush")
.call(brush)
.selectAll("rect")
.attr("y", -6)
.attr("height", height2 + 7);
});