Below is the code for horizontal stacked Bar Chart. When I hover on any bar, it shows me content of the element all in single line. I want to add line break to separate each parameter of the data. I tried looking for this type of question and found something with 'tspan' but that is not working in my code. Please have a look.
<%# taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%# taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%# taglib prefix="lit" uri="/WEB-INF/......" %>
<!DOCTYPE html>
<meta charset="utf-8">
<head>
<link rel="stylesheet" media="all" type="text/css" href="<c:url value="/styl........."/>" />
</head>
<body>
<script src="//d3js.org/d3.v3.js"></script>
<script>
var i = 0;
var margins = {
top: 12,
left: 48,
right: 24,
bottom: 24
},
legendPanel = {
width: 180
},
width = 1500 - margins.left - margins.right - legendPanel.width,
height = 900 - margins.top - margins.bottom,
dataset = ${JSONData},
series = dataset.map(function (d) {
return d.name;
}),
dataset = dataset.map(function (d) {
return d.data.map(function (o) {
return {
name: d.name,
y: +o.count,
x: o.time
};
});
}),
stack = d3.layout.stack();
stack(dataset);
var dataset = dataset.map(function (group) {
return group.map(function (d) {
return {
name: d.name,
x: d.y,
y: d.x,
x0: d.y0
};
});
}),
svg = d3.select('body')
.append('svg')
.attr('width', width + margins.left + margins.right + legendPanel.width)
.attr('height', height + margins.top + margins.bottom)
.append('g')
.attr('transform', 'translate(' + margins.left + ',' + margins.top + ')'),
xMax = d3.max(dataset, function (group) {
return d3.max(group, function (d) {
return d.x + d.x0;
});
}),
xScale = d3.scale.linear()
.domain([0, xMax])
.range([0, width]),
months = dataset[0].map(function (d) {
return d.y;
}),
_ = console.log(months),
yScale = d3.scale.ordinal()
.domain(months)
.rangeRoundBands([0, height], .1),
xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom'),
yAxis = d3.svg.axis()
.scale(yScale)
.orient('left'),
colours = d3.scale.category10(),
groups = svg.selectAll('g')
.data(dataset)
.enter()
.append('g')
.style('fill', function (d, i) {
return colours(i);
}),
rects = groups.selectAll('rect')
.data(function (d) {
return d;
})
.enter()
.append('rect')
.attr('x', function (d) {
return xScale(d.x0);
})
.attr('y', function (d, i) {
return yScale(d.y);
})
.attr('height', function (d) {
return yScale.rangeBand();
})
.attr('width', function (d) {
return xScale(d.x);
})
.on('mouseover', function (d) {
var xPos = parseFloat(d3.select(this).attr('x')) / 2 + width/2;
var yPos = parseFloat(d3.select(this).attr('y')) + yScale.rangeBand() / 2;
d3.select('#tooltip')
.style('left', xPos + 'px')
.style('top', yPos + 'px')
.select('#value')
.text("Count: " + d.x + "Time: " + d.y + "String1"+
"\nString2");
//.text(d.x+","+d.y+","+d.name);
d3.select('#tooltip').classed('hidden', false);
})
.on('mouseout', function () {
d3.select('#tooltip').classed('hidden', true);
})
svg.append('g')
.attr('class', 'axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
svg.append('g')
.attr('class', 'axis')
.call(yAxis);
svg.append('rect')
.attr('fill', 'yellow')
.attr('width', 160)
.attr('height', 30 * dataset.length)
.attr('x', width + margins.left)
.attr('y', 0);
series.forEach(function (s, i) {
svg.append('text')
.attr('fill', 'black')
.attr('x', width + margins.left + 8)
.attr('y', i * 24 + 24)
.text(s);
svg.append('rect')
.attr('fill', colours(i))
.attr('width', 60)
.attr('height', 20)
.attr('x', width + margins.left + 90)
.attr('y', i * 24 + 6);
});
</script>
<div id="tooltip" class="hidden">
<p><span id="value">100</span>
</p>
</div>
</body>
</html>
Your "tooltip" is just an HTML span; so all you need to do is use .html and a <br> for the line break:
d3.select('#tooltip')
.style('left', xPos + 'px')
.style('top', yPos + 'px')
.select('#value')
.html("Count: " + d.x + "Time: " + d.y + " String1" + "<br>String2");
If you had been creating an svg text element, then you'd need to append multiple tspan elements to it.
Full code:
<html>
<head>
</head>
<body>
<script src="//d3js.org/d3.v3.js"></script>
<script>
var i = 0;
var margins = {
top: 12,
left: 48,
right: 24,
bottom: 24
},
legendPanel = {
width: 180
},
width = 1500 - margins.left - margins.right - legendPanel.width,
height = 900 - margins.top - margins.bottom,
dataset = [{data:[{
count: Math.random(),
time: "One"
},{
count: Math.random(),
time: "Two"
},{
count: Math.random(),
time: "Three"
}]}];
series = dataset.map(function(d) {
return d.name;
}),
dataset = dataset.map(function(d) {
return d.data.map(function(o) {
return {
name: d.name,
y: +o.count,
x: o.time
};
});
}),
stack = d3.layout.stack();
stack(dataset);
var dataset = dataset.map(function(group) {
return group.map(function(d) {
return {
name: d.name,
x: d.y,
y: d.x,
x0: d.y0
};
});
}),
svg = d3.select('body')
.append('svg')
.attr('width', width + margins.left + margins.right + legendPanel.width)
.attr('height', height + margins.top + margins.bottom)
.append('g')
.attr('transform', 'translate(' + margins.left + ',' + margins.top + ')'),
xMax = d3.max(dataset, function(group) {
return d3.max(group, function(d) {
return d.x + d.x0;
});
}),
xScale = d3.scale.linear()
.domain([0, xMax])
.range([0, width]),
months = dataset[0].map(function(d) {
return d.y;
}),
_ = console.log(months),
yScale = d3.scale.ordinal()
.domain(months)
.rangeRoundBands([0, height], .1),
xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom'),
yAxis = d3.svg.axis()
.scale(yScale)
.orient('left'),
colours = d3.scale.category10(),
groups = svg.selectAll('g')
.data(dataset)
.enter()
.append('g')
.style('fill', function(d, i) {
return colours(i);
}),
rects = groups.selectAll('rect')
.data(function(d) {
return d;
})
.enter()
.append('rect')
.attr('x', function(d) {
return xScale(d.x0);
})
.attr('y', function(d, i) {
return yScale(d.y);
})
.attr('height', function(d) {
return yScale.rangeBand();
})
.attr('width', function(d) {
return xScale(d.x);
})
.on('mouseover', function(d) {
var xPos = parseFloat(d3.select(this).attr('x')) / 2 + width / 2;
var yPos = parseFloat(d3.select(this).attr('y')) + yScale.rangeBand() / 2;
d3.select('#tooltip')
.style('left', xPos + 'px')
.style('top', yPos + 'px')
.select('#value')
.html("Count: " + d.x + "Time: " + d.y + " String1" + "<br>String2");
d3.select('#tooltip').classed('hidden', false);
})
.on('mouseout', function() {
d3.select('#tooltip').classed('hidden', true);
})
svg.append('g')
.attr('class', 'axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
svg.append('g')
.attr('class', 'axis')
.call(yAxis);
svg.append('rect')
.attr('fill', 'yellow')
.attr('width', 160)
.attr('height', 30 * dataset.length)
.attr('x', width + margins.left)
.attr('y', 0);
series.forEach(function(s, i) {
svg.append('text')
.attr('fill', 'black')
.attr('x', width + margins.left + 8)
.attr('y', i * 24 + 24)
.text(s);
svg.append('rect')
.attr('fill', colours(i))
.attr('width', 60)
.attr('height', 20)
.attr('x', width + margins.left + 90)
.attr('y', i * 24 + 6);
});
</script>
<div id="tooltip" class="hidden">
<p>
<span id="value">100</span>
</p>
</div>
</body>
</html>
Related
I'm following this tutorial to build a brushable scatterplot and choropleth map.
I also want to show the states name by adding tooltips on choropleth.
The problem might be how to get the states name in the json file.
I tried features.properties.name but got the error : Uncaught TypeError: Cannot read property 'name' of undefined.
Could anyone please help?
Thanks!
<!DOCTYPE html>
<meta charset="utf-8">
<style>
div { float: left; }
</style>
<body>
<!-- <svg width="760" height="400"></svg> -->
<div id="choropleth"></div>
<div id="scatterplot"></div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>
<script>
d3.queue()
.defer(d3.csv, 'https://raw.githubusercontent.com/irrie/myprojects/master/usage0.csv', function (d) {
return {
name: d.State,
penetration: +d.Facebook_Penetration,
young_proportion: +d.YoungProportion
}
})
.defer(d3.json, 'https://raw.githubusercontent.com/python-visualization/folium/master/tests/us-states.json')
.awaitAll(initialize)
var color = d3.scaleThreshold()
.domain([0.3, 0.44, 0.6])
.range(['#fbb4b9', '#f768a1', '#c51b8a', '#7a0177'])
function initialize(error, results) {
if (error) { throw error }
var data = results[0]
var features = results[1].features
var components = [
choropleth(features),
scatterplot(onBrush)
]
function update() {
components.forEach(function (component) { component(data) })
}
function onBrush(x0, x1, y0, y1) {
var clear = x0 === x1 || y0 === y1
data.forEach(function (d) {
d.filtered = clear ? false
: d.young_proportion < x0 || d.young_proportion > x1 || d.penetration < y0 || d.penetration > y1
})
update()
}
update()
}
function scatterplot(onBrush) {
var margin = { top: 10, right: 15, bottom: 40, left: 75 }
var width = 360 - margin.left - margin.right
var height = 300 - margin.top - margin.bottom
var x = d3.scaleLinear()
.range([0, width])
var y = d3.scaleLinear()
.range([height, 0])
var xAxis = d3.axisBottom()
.scale(x)
// .tickFormat(d3.format('.2s'))
var yAxis = d3.axisLeft()
.scale(y)
.tickFormat(d3.format('.0%'))
var brush = d3.brush()
.extent([[0, 0], [width, height]])
.on('start brush', function () {
var selection = d3.event.selection
var x0 = x.invert(selection[0][0])
var x1 = x.invert(selection[1][0])
var y0 = y.invert(selection[1][1])
var y1 = y.invert(selection[0][1])
onBrush(x0, x1, y0, y1)
})
var svg = d3.select('#scatterplot')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
//.attr("transform", "translate(-370,390)")
.append('g')
.attr("transform", "translate(50,10)")
// .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
var bg = svg.append('g')
var gx = svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
var gy = svg.append('g')
.attr('class', 'y axis')
gx.append('text')
.attr('x', width)
.attr('y', 35)
.style('text-anchor', 'end')
.style('fill', '#000')
.style('font-weight', 'bold')
.text('Young Proportion')
gy.append('text')
.attr('transform', 'rotate(-90)')
.attr('x', 0)
.attr('y', -40)
.style('text-anchor', 'end')
.style('fill', '#000')
.style('font-weight', 'bold')
.text('Facebook Penetration')
svg.append('g')
.attr('class', 'brush')
.call(brush)
return function update(data) {
x.domain(d3.extent(data, function (d) { return d.young_proportion })).nice()
y.domain(d3.extent(data, function (d) { return d.penetration })).nice()
gx.call(xAxis)
gy.call(yAxis)
var bgRect = bg.selectAll('rect')
.data(d3.pairs(d3.merge([[y.domain()[0]], color.domain(), [y.domain()[1]]])))
bgRect.exit().remove()
bgRect.enter().append('rect')
.attr('x', 0)
.attr('width', width)
.merge(bgRect)
.attr('y', function (d) { return y(d[1]) })
.attr('height', function (d) { return y(d[0]) - y(d[1]) })
.style('fill', function (d) { return color(d[0]) })
var circle = svg.selectAll('circle')
.data(data, function (d) { return d.name })
circle.exit().remove()
circle.enter().append('circle')
.attr('r', 4)
.style('stroke', '#fff')
.merge(circle)
.attr('cx', function (d) { return x(d.young_proportion) })
.attr('cy', function (d) { return y(d.penetration) })
.style('fill', function (d) { return color(d.penetration) })
.style('opacity', function (d) { return d.filtered ? 0.5 : 1 })
.style('stroke-width', function (d) { return d.filtered ? 1 : 2 })
}
}
function choropleth(features) {
var width = 450
var height = 320
var projection = d3.geoAlbersUsa()
.scale([width * 1.25])
.translate([width / 2, height / 2])
var path = d3.geoPath().projection(projection)
var svg = d3.select('#choropleth')
.append('svg')
.attr('width', width)
.attr('height', height)
svg.selectAll('path')
.data(features)
.enter()
.append('path')
.attr('d', path)
.style('stroke', '#fff')
.style('stroke-width', 1)
.on("mouseenter", function(d) {
d3.select(this)
.style("stroke-width", 1.5)
.style("stroke-dasharray", 0)
d3.select("#choropleth")
.transition()
.style("opacity", 1)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY) + "px")
.text(features.properties.name)}) //here
.on("mouseleave", function(d) {
d3.select(this)
.style("stroke-width", .25)
.style("stroke-dasharray", 1)
d3.select("#Text")
.transition()
.style("opacity", 0.9);
})
return function update(data) {
svg.selectAll('path')
.data(data, function (d) { return d.name || d.properties.name })
.style('fill', function (d) { return d.filtered ? '#ddd' : color(d.penetration) })
}
}
</script>
</body>
(see also this fiddle: http://jsfiddle.net/swo8r7zk/)
You can use d.name in that scope. d also has penetration and young_proportion properties for each state. These are defined at the beginning of the file where you load the CSV data.
I am trying to do a bar chart with a brush component (based on https://bl.ocks.org/SevenChan07/495cd567e0ede0deeb14bb3599dce685) and using the enter-merge-exit pattern but I cant make the brush working properly. When I move the brush the chart goes beyond its bounds. I has probably to do with clipping but I dont know how to fix it.
var defs = focus.append('defs');
// use clipPath
defs.append('clipPath')
.attr('id', 'my-clip-path')
.append('rect')
.attr('width', width)
.attr('height', height);
Any help would be greatly appreciated.
Here is the fiddle and below is a snippet:
let barData = []
for(let i = 0;i < 100; i++){
barData.push({
Prob: Math.random()*10,
labels: 'test' + i
})
}
barchart(barData)
function barchart(data) {
var ordinals = data.map(function (d) {
return d.labels;
});
var svg = d3.select("#myPlot").select("svg");
var margin = {
top: 20,
right: 20,
bottom: 0.3 * svg.attr("height"),
left: 40
},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
margin2 = {
top: 20 + margin.top + height,
right: 20,
bottom: 30,
left: 40
},
height2 = height / 5;
// the scale
var scale = {
x: d3.scaleLinear().range([0, width]).nice(),
x2: d3.scaleLinear().range([0, width]).nice(),
y: d3.scaleLinear().range([height, 0]).nice(),
y2: d3.scaleLinear().range([height2, 0]).nice()
};
let xBand = d3.scaleBand().domain(d3.range(-1, ordinals.length)).range([0, width])
var axis = {
x: d3.axisBottom(scale.x).tickFormat((d, e) => ordinals[d]),
y: d3.axisLeft(scale.y)
};
var brush = d3.brushX()
.extent([[0, 0], [width, height2]])
.on("brush", brushed)
var focus = svg.select('.focus')
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
focus.select(".axis").attr("transform", "translate(0," + height +")");
var context = svg.select('.context')
.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")");
var defs = focus.append('defs');
// use clipPath
defs.append('clipPath')
.attr('id', 'my-clip-path')
.append('rect')
.attr('width', width)
.attr('height', height);
function updateScales(data) {
scale.x.domain([-1, ordinals.length])
scale.y.domain([0, d3.max(data, d => d.Prob)])
scale.x2.domain(scale.x.domain())
scale.y2.domain([0, d3.max(data, d => d.Prob)])
}
svg.call(renderPlot, data)
function renderPlot(selection, data) {
updateScales(data);
selection.select(".context")
.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")")
.select('.brush')
.call(brush)
.call(brush.move, scale.x.range())
selection.select(".axis2")
.attr("transform", "translate(0," + height2 +")");
selection.select(".focus").select(".axis").call(axis.x);
selection.select(".focus").select(".axis.axis--y").call(axis.y);
selection
.call(renderPoints, data);
}
function renderPoints(selection, data) {
var points = selection.select('.focus')
.selectAll('.bar').data(data);
var newPoints = points.enter().append('rect')
.attr('class', 'bar')
.attr('x', (d, i) => {
return scale.x(i) - xBand.bandwidth() * 0.9 / 2
})
.attr('y', (d, i) => {
return scale.y(d.Prob)
})
.attr('width', xBand.bandwidth() * 0.9)
.attr('height', d => {
return height - scale.y(d.Prob)
});
points.merge(newPoints)
.transition().duration(1000)
.attr('x', (d, i) => {
return scale.x(i) - xBand.bandwidth() * 0.9 / 2
})
.attr('y', (d, i) => {
return scale.y(d.Prob)
})
.attr('width', xBand.bandwidth() * 0.9)
.attr('height', d => {
return height - scale.y(d.Prob)
});
points.exit()
.transition().duration(1000)
.remove();
var sPoints = selection.select('.context').selectAll('.bar').data(data);
var newsPoints = sPoints.enter().append('rect')
.attr('class', 'bar')
.attr('x', (d, i) => {
return scale.x2(i) - xBand.bandwidth() * 0.9 / 2
})
.attr('y', (d, i) => scale.y2(d.Prob))
.attr('width', xBand.bandwidth() * 0.9)
.attr('height', d => {
return height2 - scale.y2(d.Prob)
});
sPoints.merge(newsPoints)
.transition().duration(1000)
.attr('x', (d, i) => {
return scale.x2(i) - xBand.bandwidth() * 0.9 / 2
})
.attr('y', (d, i) => scale.y2(d.Prob))
.attr('width', xBand.bandwidth() * 0.9)
.attr('height', d => {
return height2 - scale.y2(d.Prob)
});
sPoints.exit()
.transition().duration(1000)
.remove();
}
function brushed() {
var s = d3.event.selection || scale.x2.range()
scale.x.domain(s.map(scale.x2.invert, scale.x2))
focus.select('.axis').call(axis.x)
focus.selectAll('.bar')
.attr('x', (d, i) => {
return scale.x(i) - xBand.bandwidth() * 0.9 / 2
})
}
}
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<style type="text/css">
.bar { fill: steelblue; }
</style>
<script src='https://d3js.org/d3.v4.min.js' type='text/javascript'></script>
</head>
<body>
<div class='chart span4' id='myPlot'>
<svg width="700" height="500">
<g class="focus">
<g class="axis"></g>
<g class="axis axis--y"></g>
</g>
<g class="context">
<g class="axis2"></g>
<g class="brush"></g>
</g>
</svg>
</div>
</body>
</html>
You've almost got it, you just need to apply the clip path to something. We can easily do this with your bars (you could use a g containing only the bars too):
var newPoints = points.enter().append('rect')
.attr('class', 'bar')
..... // other attributes
.attr("clip-path","url(#my-clip-path)");
We only need to do it on enter, as the clip path doesn't need to be updated (we aren't changing it). Here's a snippet below:
let barData = []
for(let i = 0;i < 100; i++){
barData.push({
Prob: Math.random()*10,
labels: 'test' + i
})
}
barchart(barData)
function barchart(data) {
var ordinals = data.map(function (d) {
return d.labels;
});
var svg = d3.select("#myPlot").select("svg");
var margin = {
top: 20,
right: 20,
bottom: 0.3 * svg.attr("height"),
left: 40
},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
margin2 = {
top: 20 + margin.top + height,
right: 20,
bottom: 30,
left: 40
},
height2 = height / 5;
// the scale
var scale = {
x: d3.scaleLinear().range([0, width]).nice(),
x2: d3.scaleLinear().range([0, width]).nice(),
y: d3.scaleLinear().range([height, 0]).nice(),
y2: d3.scaleLinear().range([height2, 0]).nice()
};
let xBand = d3.scaleBand().domain(d3.range(-1, ordinals.length)).range([0, width])
var axis = {
x: d3.axisBottom(scale.x).tickFormat((d, e) => ordinals[d]),
y: d3.axisLeft(scale.y)
};
var brush = d3.brushX()
.extent([[0, 0], [width, height2]])
.on("brush", brushed)
var focus = svg.select('.focus')
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
focus.select(".axis").attr("transform", "translate(0," + height +")");
var context = svg.select('.context')
.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")");
var defs = focus.append('defs');
// use clipPath
defs.append('clipPath')
.attr('id', 'my-clip-path')
.append('rect')
.attr('width', width)
.attr('height', height);
function updateScales(data) {
scale.x.domain([-1, ordinals.length])
scale.y.domain([0, d3.max(data, d => d.Prob)])
scale.x2.domain(scale.x.domain())
scale.y2.domain([0, d3.max(data, d => d.Prob)])
}
svg.call(renderPlot, data)
function renderPlot(selection, data) {
updateScales(data);
selection.select(".context")
.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")")
.select('.brush')
.call(brush)
.call(brush.move, scale.x.range())
selection.select(".axis2")
.attr("transform", "translate(0," + height2 +")");
selection.select(".focus").select(".axis").call(axis.x);
selection.select(".focus").select(".axis.axis--y").call(axis.y);
selection
.call(renderPoints, data);
}
function renderPoints(selection, data) {
var points = selection.select('.focus')
.selectAll('.bar').data(data);
var newPoints = points.enter().append('rect')
.attr('class', 'bar')
.attr('x', (d, i) => {
return scale.x(i) - xBand.bandwidth() * 0.9 / 2
})
.attr('y', (d, i) => {
return scale.y(d.Prob)
})
.attr('width', xBand.bandwidth() * 0.9)
.attr('height', d => {
return height - scale.y(d.Prob)
})
.attr("clip-path","url(#my-clip-path)");
points.merge(newPoints)
.transition().duration(1000)
.attr('x', (d, i) => {
return scale.x(i) - xBand.bandwidth() * 0.9 / 2
})
.attr('y', (d, i) => {
return scale.y(d.Prob)
})
.attr('width', xBand.bandwidth() * 0.9)
.attr('height', d => {
return height - scale.y(d.Prob)
})
points.exit()
.transition().duration(1000)
.remove();
var sPoints = selection.select('.context').selectAll('.bar').data(data);
var newsPoints = sPoints.enter().append('rect')
.attr('class', 'bar')
.attr('x', (d, i) => {
return scale.x2(i) - xBand.bandwidth() * 0.9 / 2
})
.attr('y', (d, i) => scale.y2(d.Prob))
.attr('width', xBand.bandwidth() * 0.9)
.attr('height', d => {
return height2 - scale.y2(d.Prob)
});
sPoints.merge(newsPoints)
.transition().duration(1000)
.attr('x', (d, i) => {
return scale.x2(i) - xBand.bandwidth() * 0.9 / 2
})
.attr('y', (d, i) => scale.y2(d.Prob))
.attr('width', xBand.bandwidth() * 0.9)
.attr('height', d => {
return height2 - scale.y2(d.Prob)
});
sPoints.exit()
.transition().duration(1000)
.remove();
}
function brushed() {
var s = d3.event.selection || scale.x2.range()
scale.x.domain(s.map(scale.x2.invert, scale.x2))
focus.select('.axis').call(axis.x)
focus.selectAll('.bar')
.attr('x', (d, i) => {
return scale.x(i) - xBand.bandwidth() * 0.9 / 2
})
}
}
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<style type="text/css">
.bar { fill: steelblue; }
</style>
<script src='https://d3js.org/d3.v4.min.js' type='text/javascript'></script>
</head>
<body>
<div class='chart span4' id='myPlot'>
<svg width="700" height="500">
<g class="focus">
<g class="axis"></g>
<g class="axis axis--y"></g>
</g>
<g class="context">
<g class="axis2"></g>
<g class="brush"></g>
</g>
</svg>
</div>
</body>
</html>
I am a beginner in JS and D3. Doing reseach for my project I found this library which gives the type of plot needed for our project written in d3js. The input is cvs, but the setup requires using json. To migrate the code I have used d3.json, but the app only loads one data element. Any ideas what's going wrong?
input json (snippet)
[
{"date":"2017-09-03 19:49:51","volume":"3070.33","price":"0.0314009","average":"0.0314009"},
{"date":"2017-09-03 19:59:47","volume":"3061.02","price":"0.0313057","average":"0.0313057"},
{"date":"2017-09-03 20:09:46","volume":"3062.63","price":"0.0313221","average":"0.0313221"},
{"date":"2017-09-03 20:19:54","volume":"3049.41","price":"0.0311875","average":"0.0311875"}
]
Code
/* global d3, _ */
(function() {
var margin = {top: 30, right: 20, bottom: 100, left: 50},
margin2 = {top: 210, right: 20, bottom: 20, left: 50},
width = 764 - margin.left - margin.right,
height = 283 - margin.top - margin.bottom,
height2 = 283 - margin2.top - margin2.bottom;
var parseDate = d3.time.format('%d/%m/%Y %H:%M').parse,
bisectDate = d3.bisector(function(d) { return d.date; }).left,
legendFormat = d3.time.format('%b %d, %Y %H:%M');
var x = d3.time.scale().range([0, width]),
x2 = d3.time.scale().range([0, width]),
y = d3.scale.linear().range([height, 0]),
y1 = d3.scale.linear().range([height, 0]),
y2 = d3.scale.linear().range([height2, 0]),
y3 = d3.scale.linear().range([60, 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 priceLine = d3.svg.line()
.interpolate('monotone')
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.price); });
var avgLine = d3.svg.line()
.interpolate('monotone')
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.average); });
var area2 = d3.svg.area()
.interpolate('monotone')
.x(function(d) { return x2(d.date); })
.y0(height2)
.y1(function(d) { return y2(d.price); });
var svg = d3.select('body').append('svg')
.attr('class', 'chart')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom + 60);
svg.append('defs').append('clipPath')
.attr('id', 'clip')
.append('rect')
.attr('width', width)
.attr('height', height);
var make_y_axis = function () {
return d3.svg.axis()
.scale(y)
.orient('left')
.ticks(3);
};
var focus = svg.append('g')
.attr('class', 'focus')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var barsGroup = svg.append('g')
.attr('class', 'volume')
.attr('clip-path', 'url(#clip)')
.attr('transform', 'translate(' + margin.left + ',' + (margin.top + 60 + 20) + ')');
var context = svg.append('g')
.attr('class', 'context')
.attr('transform', 'translate(' + margin2.left + ',' + (margin2.top + 60) + ')');
var legend = svg.append('g')
.attr('class', 'chart__legend')
.attr('width', width)
.attr('height', 30)
.attr('transform', 'translate(' + margin2.left + ', 10)');
legend.append('text')
.attr('class', 'chart__symbol')
.text('Renos : RNS')
var rangeSelection = legend
.append('g')
.attr('class', 'chart__range-selection')
.attr('transform', 'translate(110, 0)');
d3.json('http://rns.ud.vg/datastockstyle.php', function(err, data) {
data.forEach( function(d) {
date = parseDate(d.date);
price = +d.price;
average = +d.average;
volume = +d.volume;
});
var brush = d3.svg.brush()
.x(x2)
.on('brush', brushed);
var xRange = d3.extent(data.map(function(d) { return d.date; }));
x.domain(xRange);
y.domain(d3.extent(data.map(function(d) { return d.price; })));
y3.domain(d3.extent(data.map(function(d) { return d.price; })));
x2.domain(x.domain());
y2.domain(y.domain());
var min = d3.min(data.map(function(d) { return d.price; }));
var max = d3.max(data.map(function(d) { return d.price; }));
var range = legend.append('text')
.text(legendFormat(new Date(xRange[0])) + ' - ' + legendFormat(new Date(xRange[1])))
.style('text-anchor', 'end')
.attr('transform', 'translate(' + width + ', 0)');
focus.append('g')
.attr('class', 'y chart__grid')
.call(make_y_axis()
.tickSize(-width, 0, 0)
.tickFormat(''));
var averageChart = focus.append('path')
.datum(data)
.attr('class', 'chart__line chart__average--focus line')
.attr('d', avgLine);
var priceChart = focus.append('path')
.datum(data)
.attr('class', 'chart__line chart__price--focus line')
.attr('d', priceLine);
focus.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0 ,' + height + ')')
.call(xAxis);
focus.append('g')
.attr('class', 'y axis')
.attr('transform', 'translate(12, 0)')
.call(yAxis);
var focusGraph = barsGroup.selectAll('rect')
.data(data)
.enter().append('rect')
.attr('class', 'chart__bars')
.attr('x', function(d, i) { return x(d.date); })
.attr('y', function(d) { return 155 - y3(d.price); })
.attr('width', 1)
.attr('height', function(d) { return y3(d.price); });
var helper = focus.append('g')
.attr('class', 'chart__helper')
.style('text-anchor', 'end')
.attr('transform', 'translate(' + width + ', 0)');
var helperText = helper.append('text')
var priceTooltip = focus.append('g')
.attr('class', 'chart__tooltip--price')
.append('circle')
.style('display', 'none')
.attr('r', 2.5);
var averageTooltip = focus.append('g')
.attr('class', 'chart__tooltip--average')
.append('circle')
.style('display', 'none')
.attr('r', 2.5);
var mouseArea = svg.append('g')
.attr('class', 'chart__mouse')
.append('rect')
.attr('class', 'chart__overlay')
.attr('width', width)
.attr('height', height)
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.on('mouseover', function() {
helper.style('display', null);
priceTooltip.style('display', null);
averageTooltip.style('display', null);
})
.on('mouseout', function() {
helper.style('display', 'none');
priceTooltip.style('display', 'none');
averageTooltip.style('display', 'none');
})
.on('mousemove', mousemove);
context.append('path')
.datum(data)
.attr('class', 'chart__area area')
.attr('d', area2);
context.append('g')
.attr('class', 'x axis chart__axis--context')
.attr('y', 0)
.attr('transform', 'translate(0,' + (height2 - 22) + ')')
.call(xAxis2);
context.append('g')
.attr('class', 'x brush')
.call(brush)
.selectAll('rect')
.attr('y', -6)
.attr('height', height2 + 7);
function mousemove() {
var x0 = x.invert(d3.mouse(this)[0]);
var i = bisectDate(data, x0, 1);
var d0 = data[i - 1];
var d1 = data[i];
var d = x0 - d0.date > d1.date - x0 ? d1 : d0;
helperText.text(legendFormat(new Date(d.date)) + ' - Price: ' + d.price + ' Avg: ' + d.average);
priceTooltip.attr('transform', 'translate(' + x(d.date) + ',' + y(d.price) + ')');
averageTooltip.attr('transform', 'translate(' + x(d.date) + ',' + y(d.average) + ')');
}
function brushed() {
var ext = brush.extent();
if (!brush.empty()) {
x.domain(brush.empty() ? x2.domain() : brush.extent());
y.domain([
d3.min(data.map(function(d) { return (d.date >= ext[0] && d.date <= ext[1]) ? d.price : max; })),
d3.max(data.map(function(d) { return (d.date >= ext[0] && d.date <= ext[1]) ? d.price : min; }))
]);
range.text(legendFormat(new Date(ext[0])) + ' - ' + legendFormat(new Date(ext[1])))
focusGraph.attr('x', function(d, i) { return x(d.date); });
var days = Math.ceil((ext[1] - ext[0]) / (24 * 3600 * 1000))
focusGraph.attr('width', (40 > days) ? (40 - days) * 5 / 6 : 5)
}
priceChart.attr('d', priceLine);
averageChart.attr('d', avgLine);
focus.select('.x.axis').call(xAxis);
focus.select('.y.axis').call(yAxis);
}
var dateRange = ['1h', '1d', '1w', '1m', '3m', '6m', '1y']
for (var i = 0, l = dateRange.length; i < l; i ++) {
var v = dateRange[i];
rangeSelection
.append('text')
.attr('class', 'chart__range-selection')
.text(v)
.attr('transform', 'translate(' + (18 * i) + ', 0)')
.on('click', function(d) { focusOnRange(this.textContent); });
}
function focusOnRange(range) {
var today = new Date(data[data.length - 1].date)
var ext = new Date(data[data.length - 1].date)
if (range === '1m')
ext.setMonth(ext.getMonth() - 1)
if (range === '1w')
ext.setDate(ext.getDate() - 7)
if (range === '1d')
ext.setDate(ext.getDate() - 1)
if (range === '1h')
ext.setTime(ext.getTime() - 60*60*1000)
if (range === '3m')
ext.setMonth(ext.getMonth() - 3)
if (range === '6m')
ext.setMonth(ext.getMonth() - 6)
if (range === '1y')
ext.setFullYear(ext.getFullYear() - 1)
brush.extent([ext, today])
brushed()
document.getElementById("demo").innerHTML =ext;
context.select('g.x.brush').call(brush.extent([ext, today]))
}
})// end Data
}());
The data contains hyphens not forward slashes.
Your date parsing should not be
var parseDate = d3.time.format('%d/%m/%Y %H:%M').parse,
it should be,
var parseDate = d3.time.format('%d-%m-%Y %H:%M').parse,
Also, what is y0? I don't see y0 in your scales.
.y0(height2)
I don't know what's wrong with your code, but others will be able to help easier with this,
jsFiddle
I'm trying to add a zoom ability to a multi series line chart I've built with d3 and Angular 2.
ngOnChanges(data):void {
this.setup(data);
this.initData(data);
this.buildSVG();
this.scaleAxis(data);
this.populate();
this.drawXAxis();
this.drawYAxis();
this.zoomEventHandler();
this.addVerticalLineTooltip();
}
private setup(data):void {
this.margin = {top: 50, right: 100, bottom: 100, left: 100};
this.width = 600;
this.height = 400;
this.xScale = d3.scaleTime().range([0, this.width]);
this.yScale = d3.scaleLinear().range([this.height, 0]);
this.zScale = d3.scaleOrdinal(d3.schemeCategory10);
this.zScale.domain(d3.keys(data[0]).filter(function (key) {
return key !== "date";
}));
}
private initData(data) {
// format the data
data.forEach((d)=> {
d.date = this.parseDate(d.date);
});
this.ds = this.zScale.domain().map((name)=> {
return {
name: name,
values: data.map((d) => {
return {x: d.date, y: d[name], name: name};
})
};
});
}
/**
* build SVG element using the configurations
**/
private buildSVG():void {
this.host.html('');
this.svg = this.host.append('svg')
.attr('width', this.width + this.margin.left + this.margin.right)
.attr('height', this.height + this.margin.top + this.margin.bottom)
.append('g')
.attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')');
this.svg.append('rect')
.attr('class', 'view')
.attr('x', 0.5)
.attr('y', 0.5)
.attr('width', this.width)
.attr('height', this.height)
.style('fill', '#EEEEEE')
.style('stroke-width', '0px');
}
private scaleAxis(data) {
this.xScale.domain(d3.extent(data, (d) => {
return d.date;
}));
this.yScale.domain([
d3.min(this.ds, (c) => {
return d3.min(c.values, function (d) {
return d.y;
});
}),
d3.max(this.ds, (c) => {
return d3.max(c.values,
(d) => {
return d.y;
});
})
]);
this.zScale.domain(this.ds.map((c) => {
return c.name;
}));
this.make_x_axis = ()=> {
return d3.axisBottom()
.scale(this.xScale)
.tickPadding(15);
};
this.make_y_axis = ()=> {
return d3.axisLeft()
.scale(this.yScale)
.tickPadding(10);
};
}
/**
* Create x axis
**/
private drawXAxis():void {
this.xAxis = d3.axisBottom()
.scale(this.xScale)
.tickPadding(15);
this.svg.append('g')
.attr('class', 'x-axis')
.attr('transform', 'translate(0,' + this.height + ')')
.call(this.xAxis);
this.svg.append('text')
.attr('text-anchor', 'middle')
.attr('transform', 'translate(' + (this.width / 2) + "," + (this.height + this.margin.bottom / 2) + ")")
.text('Date');
}
/**
*create y axis
**/
private drawYAxis():void {
this.yAxis = d3.axisLeft()
.scale(this.yScale)
.tickPadding(10);
this.svg.append('g')
.attr('class', 'y-axis')
.call(this.yAxis)
.append('text')
.attr('transform', 'rotate(-90)');
this.svg.append('text')
.attr('text-anchor', 'middle')
.attr("transform", "translate(-" + (this.margin.left / 2) + "," + (this.height / 2) + ")rotate(-90)")
.text('Sales / Searches');
this.svg.append("g")
.attr("class", "y grid")
.call(this.make_y_axis()
.tickSize(-this.width, 0, 0)
.tickFormat(""))
.style("opacity", "0.1");
}
/**
* Populate the graphs
**/
private populate():void {
// define the line
this.line = d3.line()
.x((d) => {
return this.xScale(d.x);
})
.y((d) => {
return this.yScale(d.y);
});
let chartdata = this.svg.selectAll(".chartdata")
.data(this.ds)
.enter().append("g")
.attr("class", "chartdata");
chartdata.append("path")
.attr("class", "line")
.attr("d", (d) => {
return this.line(d.values);
})
.style("fill", "none")
.style("stroke-width", "2px")
.style("stroke", (d) => {
return this.zScale(d.name);
});
chartdata.append("text")
.datum((d)=> {
return {
name: d.name, value: d.values[d.values.length - 1]
};
})
.attr("transform", (d) => {
return "translate(" +
this.xScale(d.value.x) + "," + this.yScale(d.value.y) + ")";
})
.attr('class', 'lineLabel')
.attr("x", 3)
.attr("dy", "0.35em")
.style("font", "14px open-sans")
.text((d) => {
return d.name;
});
// add the dots
chartdata.selectAll(".circle")
.data((d) => {
return d.values;
})
.enter().append("circle")
.attr("class", "circle")
.attr("r", 4)
.attr("cx", (d) => {
return this.xScale(d.x);
})
.attr("cy", (d) => {
return this.yScale(d.y);
})
.style("fill", (d) => { // Add the colours dynamically
return this.zScale(d.name);
});
}
private zoomEventHandler() {
let zoom = d3.zoom()
.scaleExtent([1, 2])
.translateExtent([[0, -100], this.width + 90, this.height + 100]).on("zoom", () => this.zoomed());
this.svg.call(zoom);
}
private zoomed() {
d3.select('.x-axis').call(this.xAxis.scale(d3.event.transform.rescaleX(this.xScale)));
d3.selectAll('.line').attr('transform', 'translate(' + d3.event.transform.x + ',' + d3.event.transform.y + ') scale(' + d3.event.transform.k + ')');
d3.selectAll('.circle').attr('transform', 'translate(' + d3.event.transform.x + ',' + d3.event.transform.y + ') scale(' + d3.event.transform.k + ')');
}
My chart is responding to the mouse scroll event as below.
Any suggestions on what I have done wrong with my code?
Thank you !
Lost on this especially since I've done it successfully before. The code below is within a function called on a click. This first example works just fine:
y.domain([1, get_max(data)]);
svg.select('.y.axis')
.call(yAxis);
svg.selectAll('.bars')
.attr('y', function (d) { return y(d); })
.attr('height', function (d) { return height - y(d); });
This second example doesn't do anything:
y.domain([1, get_max(data)]);
svg.select('.y.axis')
.transition()
.duration(80)
.call(yAxis);
svg.selectAll('.bars')
.transition()
.duration(80)
.attr('y', function (d) { return y(d); })
.attr('height', function (d) { return height - y(d); });
No javascript errors are produced. It simply doesn't do anything.
Any help would be greatly appreciated. Thank you!
Note: get_max(data) is a special function to get the max of some very oddly formatted data. When I replace it with a hard coded value of 10,000 the problem persists. Again it works fine until I add the transition.
EDIT:
function render(parent, data, brands){
var time_format = parent.attr('data-chart-size') > 3 ? '%b %e' : '%e',
margin = { top: 30, right: 20, bottom: 21, left: 45 },
width = parent.width() - margin.left - margin.right - 110; // -110 for the legends on the right side
height = 205 - margin.top - margin.bottom;
var x = d3.time.scale().domain([ML._dates.start(), ML._dates.end()]).range([0, width]);
y = d3.scale.log().clamp(true).range([height, 1]);
var xAxis = d3.svg.axis().scale(x).orient('bottom')
.tickFormat(d3.time.format(time_format)).tickSize(0).tickPadding(8);
yAxis = d3.svg.axis().scale(y).orient('left').ticks(5, 's').tickSize(0);
svg = d3.select(parent.get(0)).append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var brands_length = brands.length,
values_length = data.posts[brands[0]].values.length,
bar_width_adjustment = 60 / values_length,
bar_width = ((width / values_length) / brands_length) - bar_width_adjustment;
range_band = width / values_length;
y.domain([1, get_max(data)]);
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(' + (bar_width * 2) + ',' + height + ')')
.call(xAxis);
svg.append('g')
.attr('class', 'y axis')
.attr('transform', 'translate(0, 0)')
.call(yAxis);
// BUILD THE BARS AND TOOLTIP HIGHLIGHT AREA
for(var brands_loop = 0; brands_loop < brands_length; ++brands_loop){
svg.selectAll('.chart-hover')
.data(data.posts[brands[brands_loop]].values)
.enter().append('rect')
.attr('width', width / values_length)
.attr('x', function (d, i) { return i * range_band - ((range_band - bar_width * 3) / 2); })
.attr('y', 1)
.attr('height', height - y(y.domain()[1]))
.attr('transform', 'translate(' + ((bar_width * (brands_loop + 1) - (bar_width / 2)) + ', 0)'))
.attr('data-index', function (d, i) { return i; })
.attr('class', 'chart-hover')
.style('opacity', 0.01);
svg.selectAll('.bar-' + brands_loop)
.data(data.posts[brands[brands_loop]].values)
.enter().append('rect')
.attr('data-legend-listener-brand', brands[brands_loop])
.attr('data-legend-listener-metric', 'posts')
.attr('data-hover-dispatcher-index', function (d, i) { return i; })
.attr('width', bar_width)
.attr('x', function (d, i) { return i * range_band; })
.attr('y', function (d) { return y(d); })
.attr('height', function (d) { return height - y(d); })
.attr('transform', 'translate(' + ((bar_width * (brands_loop + 1) - (bar_width / 2)) + ', 0)'))
.attr('class', 'posts bars bar-' + brands_loop);
// POPULATE LEGEND TEXTS FOR POSTS, EXPOSURE AND ENGAGEMENT
$('.brand-' + (brands_loop + 1))
.text(data.posts[brands[brands_loop]].label)
.attr('data-legend-brand', brands[brands_loop])
.attr('data-legend-listener-brand', brands[brands_loop])
.prev('i')
.attr('data-legend-brand', brands[brands_loop])
.attr('data-legend-listener-brand', brands[brands_loop]);
}
etc. etc.