I am trying to create a bar char which animates the bars to load one after the other. I have got this far(shown in JSFiddle), but the transitions are not smooth. Please let me know what changes are to be done to make the animations more smooth.
CSS:
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.bar {
fill: #41a2d3;
}
.x.axis path {
display: none;
}
.tipsy-inner {
text-align: left;
}
.tipsy-inner a {
color: white;
}
.grid .tick {
stroke: lightgrey;
opacity: 0.7;
}
.grid path {
stroke-width: 0;
}
SCRIPT:
var jsonData = [{"dist":"NE_BLR1","bscore":93.707634,"cscore":88.206},{"dist":"NE_BLR2","bscore":2.9553592,"cscore":28.037703},{"dist":"NE_BLR3","bscore":6.6249013,"cscore":60.771496},{"dist":"NE_BLR4","bscore":90.87391,"cscore":86.42939},{"dist":"NE_BLR5","bscore":81.874275,"cscore":1.5155494},{"dist":"NE_BLR6","bscore":11.794424,"cscore":11.316788},{"dist":"NE_BLR7","bscore":42.103214,"cscore":42.328335},{"dist":"NE_BLR8","bscore":96.59683,"cscore":2.5692165},{"dist":"NE_BLR9","bscore":80.03519,"cscore":34.86772},{"dist":"NE_BLR10","bscore":67.834175,"cscore":39.31076},{"dist":"NE_BLR11","bscore":26.525862,"cscore":18.048836},{"dist":"NE_BLR12","bscore":14.0657425,"cscore":29.272057},{"dist":"NE_BLR13","bscore":20.054913,"cscore":53.572613},{"dist":"NE_BLR14","bscore":11.77302,"cscore":66.45609},{"dist":"NE_BLR15","bscore":81.25657,"cscore":77.80638},{"dist":"NE_BLR16","bscore":47.63029,"cscore":1.1937499},{"dist":"NE_BLR17","bscore":79.54459,"cscore":81.614},{"dist":"NE_BLR18","bscore":94.08074,"cscore":80.3455},{"dist":"NE_BLR19","bscore":55.68032,"cscore":89.06907},{"dist":"NE_BLR20","bscore":45.28445,"cscore":45.069145},{"dist":"NE_BLR21","bscore":82.41166,"cscore":59.125107},{"dist":"NE_BLR22","bscore":33.733047,"cscore":93.37988},{"dist":"NE_BLR23","bscore":98.53198,"cscore":18.078703},{"dist":"NE_BLR24","bscore":83.15471,"cscore":40.400578},{"dist":"NE_BLR25","bscore":3.6420703,"cscore":45.9239},{"dist":"NE_BLR26","bscore":56.563927,"cscore":83.02267},{"dist":"NE_BLR27","bscore":10.62563,"cscore":76.39983},{"dist":"NE_BLR28","bscore":83.05605,"cscore":91.5188},{"dist":"NE_BLR29","bscore":99.7658,"cscore":32.68316},{"dist":"NE_BLR30","bscore":79.63283,"cscore":78.877335},{"dist":"NE_BLR31","bscore":27.242237,"cscore":51.338135},{"dist":"NE_BLR32","bscore":69.210144,"cscore":11.239213},{"dist":"NE_BLR33","bscore":6.4760566,"cscore":53.43964},{"dist":"NE_BLR34","bscore":60.054676,"cscore":18.344189},{"dist":"NE_BLR35","bscore":93.7649,"cscore":99.91859},{"dist":"NE_BLR36","bscore":30.083233,"cscore":91.4337},{"dist":"NE_BLR37","bscore":80.51691,"cscore":28.400452},{"dist":"NE_BLR38","bscore":19.416237,"cscore":44.272415},{"dist":"NE_BLR39","bscore":79.10841,"cscore":60.43038},{"dist":"NE_BLR40","bscore":1.789844,"cscore":89.061325},{"dist":"NE_BLR41","bscore":14.223904,"cscore":4.383576},{"dist":"NE_BLR42","bscore":88.20337,"cscore":97.80883},{"dist":"NE_BLR43","bscore":18.071491,"cscore":62.987053},{"dist":"NE_BLR44","bscore":30.62138,"cscore":87.54846},{"dist":"NE_BLR45","bscore":17.996502,"cscore":1.733619},{"dist":"NE_BLR46","bscore":88.58935,"cscore":67.55461},{"dist":"NE_BLR47","bscore":85.947365,"cscore":1.1164486},{"dist":"NE_BLR48","bscore":3.997153,"cscore":2.8819382},{"dist":"NE_BLR49","bscore":48.500816,"cscore":21.182537},{"dist":"NE_BLR50","bscore":88.485214,"cscore":92.17681}];
var results,
data = [],
chart,
bars,
tip,
margin = 100,
w = 15,
h = 500,
count= 0,
x, y,
xAxis, yAxis;
results = d3.map( jsonData );
results.forEach( function( key, val ) {
var result = {};
count = count+1;
result.count = parseInt(count);
result.bscore = parseFloat( val.bscore );
result.cscore = parseFloat( val.cscore );
result.dist = val.dist;
data.push( result );
} );
chart = d3.select("body").append( 'svg:svg' )
.attr( 'class', 'chart' )
.attr( 'width', 1300 )
.attr( 'height', h )
.append('g');
d3.select('svg g')
.attr('transform', 'translate(50, 50)');
x = d3.scale.linear()
.domain( [0, data.length] )
.range( [0, 960] );
y = d3.scale.linear()
.domain( [0, d3.max( data, function( d ) { return d.bscore; } )] )
.rangeRound( [h - margin, 0] );
chart.append("g")
.attr("class", "grid")
.call(d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10)
.tickSize(-1000, 0, 0)
.tickFormat("")
)
.attr("stroke-width", "0.2px");
// Bars
bars = chart.append('g')
.attr('class', 'bars');
// Axis
xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
yAxis = d3.svg.axis()
.scale(y)
.orient("left");
chart.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0, ' + (h) + ')')
.call(xAxis);
chart.append('g')
.attr('class', 'y axis')
.attr('transform', 'translate(' + x.range()[0] + ')')
.call(yAxis);
bars.selectAll( 'rect' )
.data( data )
.enter()
.append( 'svg:rect' )
.attr('class', 'bar')
.attr( 'x', function( d ) { return x( d.count );} )
.attr( 'width', w )
.transition()
.delay(function (d,i){ return i * 300;})
.duration(250)
.attr("y", function(d) { return y( d.bscore ); })
.attr( 'height', function( d ) { return (h - margin) - y( d.bscore ); } );
JSFiddle: http://jsfiddle.net/pg77k/8/
TYIA
You need to initialise the values of y and height to what you want them to be at the start of the transition:
.attr( 'height', 0 )
.attr("y", h - margin)
Complete demo here.
Related
This question already has an answer here:
How do I return y coordinate of a path in d3.js?
(1 answer)
Closed 3 years ago.
I am drawing a simple line using the curveMonotoneX interpolation :
const line = d3
.line()
.x((_, i) => xScale(i))
.y(d => yScale(d))
.curve(d3.curveMonotoneX);
Besides that, I wanted to add points on the line where there is actual data. Because of the interpolation, the points I drawn were not exactly on the line so I switched to d3.curveLinear and my issues were gone.
However, I was wondering is there a ready-to-use method to access the y value of a line using the x value ?
This way, one could draw the points on the line regardless of the interpolation method.
Here's a quick example, wrapping the code here into a reusable function. It places a bunch of points on a fitted curve.
<!DOCTYPE html>
<meta charset="utf-8">
<style type="text/css">
.line {
fill: none;
stroke: orange;
stroke-width: 2;
}
.overlay {
fill: none;
pointer-events: all;
}
.dot {
fill: steelblue;
stroke: #fff;
}
</style>
<body>
<!-- Load in the d3 library -->
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
var margin = {
top: 50,
right: 50,
bottom: 50,
left: 50
},
width = window.innerWidth - margin.left - margin.right,
height = window.innerHeight - margin.top - margin.bottom;
var xScale = d3.scaleLinear()
.domain([0, 9])
.range([0, width]);
var yScale = d3.scaleLinear()
.domain([0, 10])
.range([height, 0]);
var line = d3.line()
.x(function(d, i) {
return xScale(i);
})
.y(function(d) {
return yScale(d);
})
.curve(d3.curveBasis);
var dataset = d3.range(10).map(function(d) {
return d3.randomUniform(1)() * 10;
})
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 + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale));
svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yScale));
var path = svg.append("path")
.datum(dataset)
.attr("class", "line")
.attr("d", line);
svg.selectAll(".dot")
.data(d3.range(0, 9.5, 0.5))
.enter()
.append("circle")
.attr("cx", (d) => xScale(d))
.attr("cy", (d) => yValueForX(d))
.attr("r", 5)
.attr("class", "dot")
function yValueForX(xCor){
var x = xScale(xCor),
pathEl = path.node(),
pathLength = pathEl.getTotalLength();
var beginning = x, end = pathLength, target;
while (true) {
target = Math.floor((beginning + end) / 2);
pos = pathEl.getPointAtLength(target);
if ((target === end || target === beginning) && pos.x !== x) {
break;
}
if (pos.x > x) end = target;
else if (pos.x < x) beginning = target;
else break; //position found
}
return pos.y;
}
</script>
</body>
As a side note, let me remark how thankful I am that there's an active D3.js community here. I've learned a lot in the few times I've posted and hope to only get better.
Let's consider the same data set that I've been using:
test_data.csv:
date,col_1,col_2
11/1/2012,1977652,1802851
12/1/2012,1128739,948687
1/1/2013,1201944,1514667
2/1/2013,1863148,1834006
3/1/2013,1314851,1906060
4/1/2013,1283943,1978702
5/1/2013,1127964,1195606
6/1/2013,1773254,977214
7/1/2013,1929574,1127450
8/1/2013,1980411,1808161
9/1/2013,1405691,1182788
10/1/2013,1336790,937890
11/1/2013,1851053,1358400
12/1/2013,1472623,1214610
1/1/2014,1155116,1757052
2/1/2014,1571611,1935038
3/1/2014,1898348,1320348
4/1/2014,1444838,1934789
5/1/2014,1235087,950194
6/1/2014,1272040,1580656
7/1/2014,980781,1680164
8/1/2014,1391291,1115999
9/1/2014,1211125,1542148
10/1/2014,1020824,1782795
11/1/2014,1685081,926612
12/1/2014,1469254,1767071
1/1/2015,1168523,935897
2/1/2015,1602610,1450541
3/1/2015,1830278,1354876
4/1/2015,1275158,1412555
5/1/2015,1560961,1839718
6/1/2015,949948,1587130
7/1/2015,1413765,1494446
8/1/2015,1166141,1305105
9/1/2015,958975,1202219
10/1/2015,902696,1023987
11/1/2015,961441,1865628
12/1/2015,1363145,1954046
1/1/2016,1862878,1470741
2/1/2016,1723891,1042760
3/1/2016,1906747,1169012
4/1/2016,1963364,1927063
5/1/2016,1899735,1936915
6/1/2016,1300369,1430697
7/1/2016,1777108,1401210
8/1/2016,1597045,1566763
9/1/2016,1558287,1140057
10/1/2016,1965665,1953595
11/1/2016,1800438,937551
12/1/2016,1689152,1221895
1/1/2017,1607824,1963282
2/1/2017,1878431,1415658
3/1/2017,1730296,1947106
4/1/2017,1956756,1696780
5/1/2017,1746673,1662892
6/1/2017,989702,1537646
7/1/2017,1098812,1592064
8/1/2017,1861973,1892987
9/1/2017,1129596,1406514
10/1/2017,1528632,1725020
11/1/2017,925850,1795575
and the .html file:
page.html:
<!DOCTYPE html>
<!-- https://bl.ocks.org/mbostock/3886208 -->
<style>
#tooltip {
position: absolute;
width: 200px;
height: auto;
padding: 10px;
background-color: white;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
-webkit-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
-moz-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
pointer-events: none;
}
#tooltip.hidden {
display: none;
}
#tooltip p {
margin: 0;
font-family: sans-serif;
font-size: 16px;
line-height: 20px;
}
rect:hover {
fill:orange;
}
</style>
<script src="https://d3js.org/d3.v4.js"></script>
<body>
<div id="tooltip" class="hidden">
<p><strong>Month: </strong><span id="month"></span><p>
<p><strong>Value: </strong><span id="value"></span></p>
</div>
<script>
var margin = {top: 20, right: 20, bottom: 50, left: 40},
width = 1300 - margin.left - margin.right,
height = 700 - margin.top - margin.bottom;
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// parse the date / time
// look at the .csv in Notepad! DO NOT LOOK AT EXCEL!
var parseDate = d3.timeParse("%m/%d/%Y");
var x = d3.scaleTime()
.range([0, width - margin.left - margin.right]);
var y = d3.scaleLinear().range([height, 0]);
var z = d3.scaleOrdinal()
.range(["#CE1126", "#00B6D0"]); // red and blue
var xAxis = d3.axisBottom(x)
.ticks(d3.timeMonth.every(1))
.tickFormat(d3.timeFormat("%b")); // label every month
var xYearAxis = d3.axisBottom(x)
.ticks(d3.timeYear.every(1))
.tickFormat(d3.timeFormat("%Y")); // label every year
var formatNum = d3.format(",")
// load .csv file
d3.csv("test_data.csv", function(d, i, columns) {
for (i = 1, t = 0; i < columns.length; ++i) t += d[columns[i]] = +d[columns[i]];
d.total = t;
return d;
}, function(error, data){
if (error) throw error;
data.forEach(function(d) {
//console.log(parseDate(d.date));
d.date = parseDate(d.date);
});
var keys = data.columns.slice(1);
var barWidth = (width - margin.right- margin.left)/(data.length+1);
data.sort(function(a, b) { return b.date - a.date; });
x.domain(d3.extent( data, function(d){ return d.date }) );
var max = x.domain()[1];
var min = x.domain()[0];
var datePlusOneMonth = d3.timeDay.offset(d3.timeMonth.offset(max, 1), -1); // last day of current month: move up one month, back one day
x.domain([min,datePlusOneMonth]);
y.domain([0, d3.max(data, function(d) { return d.total; })]).nice();
z.domain(keys);
// the bars
g.append("g")
.selectAll("g")
.data(d3.stack().keys(keys)(data))
.enter().append("g")
.attr("fill", function(d) { return z(d.key); })
.selectAll("rect")
.data(function(d) { return d; })
.enter()
.append("rect")
.attr("x", function(d) { return x(d.data.date); })
.attr("y", function(d) { return y(d[1]); })
.attr("height", function(d) { return y(d[0]) - y(d[1]); })
.attr("width", barWidth)
.on("mouseover", function(d) {
//Get this bar's x/y values, then augment for the tooltip
var xPosition = parseFloat(d3.select(this).attr("x")) + barWidth / 2;
var yPosition = parseFloat(d3.select(this).attr("y")) / 2 + height / 2;
if (d[0] == 0) var value = d[1]; else var value = d[1]-d[0]; // data set values between col_1 and col_2
//Update the tooltip position and value
d3.select("#tooltip")
.style("left", xPosition + "px")
.style("top", yPosition + "px")
.select("#value")
.text(formatNum(value)); // return the value
d3.select("#tooltip")
.style("left", xPosition + "px")
.style("top", yPosition + "px")
.select("#month")
.text(d3.timeFormat("%B %Y")(d.data.date)); // return the value
//Show the tooltip
d3.select("#tooltip").classed("hidden", false);
})
.on("mouseout", function() {
//Hide the tooltip
d3.select("#tooltip").classed("hidden", true);
});
// x-axis
var axis = g.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
const firstDataYear = x.domain()[0];
xYearAxis.tickValues([firstDataYear].concat(x.ticks()));
var yearAxis = g.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (height + 25) + ")")
.call(xYearAxis);
axis.selectAll("g").select("text")
.attr("transform","translate(" + barWidth/2 + ",0)");
});
</script>
</body>
This does give me the desired behavior, in that I obtain the associated value with a hover:
but it was done rather messily. Namely:
if (d[0] == 0) var value = d[1]; else var value = d[1]-d[0]; // data set values between col_1 and col_2
Basically, the code says this: if the base of the bar is on the x-axis, use the height of the bar (in d[1]) in the tooltip. Otherwise, use d[1]-d[0] (for the bars in blue).
Now that I'm thinking about this, I could have just written
var value = d[1]-d[0];
but is there a way to write this so that I can read the code and specifically notice that it's referencing the col_1 and col_2 values, rather than the difference of these two d components? (Or, if this is the preferred method... I'll just stick with it.)
Here's one approach of obtaining that value:
Add class to the group that holds the bars
.attr('class', function(d) { return d.key; })
Use the class added to fetch the respective column and get the corresponding value in the mouseover function using the data object (i.e. it'll basically end up getting the value from either d.data['col_1'] or d.data['col_2']:
var value = d.data[d3.select(this.parentNode).attr('class')];
var dataAsCsv = `date,col_1,col_2
11/1/2012,1977652,1802851
12/1/2012,1128739,948687
1/1/2013,1201944,1514667
2/1/2013,1863148,1834006
3/1/2013,1314851,1906060
4/1/2013,1283943,1978702
5/1/2013,1127964,1195606
6/1/2013,1773254,977214
7/1/2013,1929574,1127450
8/1/2013,1980411,1808161
9/1/2013,1405691,1182788
10/1/2013,1336790,937890
11/1/2013,1851053,1358400
12/1/2013,1472623,1214610
1/1/2014,1155116,1757052
2/1/2014,1571611,1935038
3/1/2014,1898348,1320348
4/1/2014,1444838,1934789
5/1/2014,1235087,950194
6/1/2014,1272040,1580656
7/1/2014,980781,1680164
8/1/2014,1391291,1115999
9/1/2014,1211125,1542148
10/1/2014,1020824,1782795
11/1/2014,1685081,926612
12/1/2014,1469254,1767071
1/1/2015,1168523,935897
2/1/2015,1602610,1450541
3/1/2015,1830278,1354876
4/1/2015,1275158,1412555
5/1/2015,1560961,1839718
6/1/2015,949948,1587130
7/1/2015,1413765,1494446
8/1/2015,1166141,1305105
9/1/2015,958975,1202219
10/1/2015,902696,1023987
11/1/2015,961441,1865628
12/1/2015,1363145,1954046
1/1/2016,1862878,1470741
2/1/2016,1723891,1042760
3/1/2016,1906747,1169012
4/1/2016,1963364,1927063
5/1/2016,1899735,1936915
6/1/2016,1300369,1430697
7/1/2016,1777108,1401210
8/1/2016,1597045,1566763
9/1/2016,1558287,1140057
10/1/2016,1965665,1953595
11/1/2016,1800438,937551
12/1/2016,1689152,1221895
1/1/2017,1607824,1963282
2/1/2017,1878431,1415658
3/1/2017,1730296,1947106
4/1/2017,1956756,1696780
5/1/2017,1746673,1662892
6/1/2017,989702,1537646
7/1/2017,1098812,1592064
8/1/2017,1861973,1892987
9/1/2017,1129596,1406514
10/1/2017,1528632,1725020
11/1/2017,925850,1795575`;
var margin = {top: 20, right: 20, bottom: 50, left: 40},
width = 1300 - margin.left - margin.right,
height = 700 - margin.top - margin.bottom;
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// parse the date / time
// look at the .csv in Notepad! DO NOT LOOK AT EXCEL!
var parseDate = d3.timeParse("%m/%d/%Y");
var x = d3.scaleTime()
.range([0, width - margin.left - margin.right]);
var y = d3.scaleLinear().range([height, 0]);
var z = d3.scaleOrdinal()
.range(["#CE1126", "#00B6D0"]); // red and blue
var xAxis = d3.axisBottom(x)
.ticks(d3.timeMonth.every(1))
.tickFormat(d3.timeFormat("%b")); // label every month
var xYearAxis = d3.axisBottom(x)
.ticks(d3.timeYear.every(1))
.tickFormat(d3.timeFormat("%Y")); // label every year
var formatNum = d3.format(",")
var data = d3.csvParse(dataAsCsv, function(d, i, columns) {
for (i = 1, t = 0; i < columns.length; ++i) t += d[columns[i]] = +d[columns[i]];
d.total = t;
return d;
})
data.forEach(function(d) {
//console.log(parseDate(d.date));
d.date = parseDate(d.date);
});
var keys = data.columns.slice(1);
var barWidth = (width - margin.right- margin.left)/(data.length+1);
data.sort(function(a, b) { return b.date - a.date; });
x.domain(d3.extent( data, function(d){ return d.date }) );
var max = x.domain()[1];
var min = x.domain()[0];
var datePlusOneMonth = d3.timeDay.offset(d3.timeMonth.offset(max, 1), -1); // last day of current month: move up one month, back one day
x.domain([min,datePlusOneMonth]);
y.domain([0, d3.max(data, function(d) { return d.total; })]).nice();
z.domain(keys);
// the bars
g.append("g")
.selectAll("g")
.data(d3.stack().keys(keys)(data))
.enter().append("g").attr('class', function(d) { return d.key; })
.attr("fill", function(d) { return z(d.key); })
.selectAll("rect")
.data(function(d) { return d; })
.enter()
.append("rect")
.attr("x", function(d) { return x(d.data.date); })
.attr("y", function(d) { return y(d[1]); })
.attr("height", function(d) { return y(d[0]) - y(d[1]); })
.attr("width", barWidth)
.on("mouseover", function(d) {
//Get this bar's x/y values, then augment for the tooltip
var xPosition = parseFloat(d3.select(this).attr("x")) + barWidth / 2;
var yPosition = parseFloat(d3.select(this).attr("y")) / 2 + height / 2;
var value = d.data[d3.select(this.parentNode).attr('class')];
//Update the tooltip position and value
d3.select("#tooltip")
.style("left", xPosition + "px")
.style("top", yPosition + "px")
.select("#value")
.text(formatNum(value)); // return the value
d3.select("#tooltip")
.style("left", xPosition + "px")
.style("top", yPosition + "px")
.select("#month")
.text(d3.timeFormat("%B %Y")(d.data.date)); // return the value
//Show the tooltip
d3.select("#tooltip").classed("hidden", false);
})
.on("mouseout", function() {
//Hide the tooltip
d3.select("#tooltip").classed("hidden", true);
});
// x-axis
var axis = g.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
const firstDataYear = x.domain()[0]
xYearAxis.tickValues([firstDataYear].concat(x.ticks()));
var yearAxis = g.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (height + 25) + ")")
.call(xYearAxis);
axis.selectAll("g").select("text")
.attr("transform","translate(" + barWidth/2 + ",0)");
#tooltip {
position: absolute;
width: 200px;
height: auto;
padding: 10px;
background-color: white;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
-webkit-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
-moz-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
pointer-events: none;
}
#tooltip.hidden {
display: none;
}
#tooltip p {
margin: 0;
font-family: sans-serif;
font-size: 16px;
line-height: 20px;
}
rect:hover {
fill:orange;
}
<script src="https://d3js.org/d3.v4.js"></script>
<div id="tooltip" class="hidden">
<p><strong>Month: </strong><span id="month"></span><p>
<p><strong>Value: </strong><span id="value"></span></p>
</div>
I would like to be able to click on a circle (coordinate points); bring the marker to the position of the circle and pause at the position of the circle and then resume again along the path.
In addition I would like to activate a circle when marker is paused on them - they are clicked (or their Voronoi cell is clicked). My intention is to have an on click function to an href for the circle coordinates eventually.
I think I need to pass the index of the path coordinates into the translateAlong function instead of the time variables but can't work out how to do this.
I’m not sure if the Voronoi cells are necessary - I tried to add this thinking I could pause my transition and activate my circles with the Voronoi cells. In any case I can’t activate the circle with the Voronoi cell.
I was helped considerably recently on Stackoverflow d3 on click on circle pause and resume transition of marker along line
and I am hoping for assistance again
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>basic_animateBetweenCircles</title>
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
path {
stroke: #848484;
fill: none;
}
circle {
fill: steelblue;
stroke: steelblue;
stroke-width: 3px;
}
.line {
fill: none;
stroke: #FE642E;
stroke-width: 4;
stroke-dasharray: 4px, 8px;
}
.point{
fill:#DF013A;
}
</style>
</head>
<body>
<script>
var width = 960,
height = 500;
var data = [
[480, 200],
[580, 400],
[680, 100],
[780, 300],
[180, 300],
[280, 100],
[380, 400]
];
//check index of path data
for (var i = 0; i < data.length; i++) {
var coordindex = i + " " + data[i];
console.log("Coordindex: " + coordindex);
//return coordindex;
};
var duration = 20000;
var line = d3.line()
.x(function(d) {return (d)[0];})
.y(function(d) {return (d)[1];});
var voronoi = d3.voronoi()
.extent([[0, 0], [width, height]]);
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
//path to animate - marker transitions along this path
var path = svg.append("path")
.data([data])
.attr("d", line)
.attr('class', 'line')
.attr("d", function(d) {
return line(d)
});
//voronoi
var voronoiPath = svg.append("g")
.selectAll("path")
.data(voronoi.polygons(data))
.enter().append("path")
.attr("d", polygon)
.on("touchmove mousemove", function() {
d3.select(this)
.style("fill", "purple");
});
//Want to activate circles when marker paused on them / in voronoi cell - intention is to have on click to href
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("class", "point")
.attr("r", 10)
.attr("transform", function(d) { return "translate(" + d + ")"; })
.on('click', function(d, i) {
d3.select(this)
.style("fill", "green");
if (d3.active(this)) {
marker.transition();
setTimeout(function() {
pauseValues.lastTime = pauseValues.currentTime;
//console.log(pauseValues);
}, 100);
} else {
transition();
}
});
var pauseValues = {
lastTime: 0,
currentTime: 0
};
//marker to transition along path
var marker = svg.append("circle")
.attr("r", 19)
.attr("transform", "translate(" + (data[0]) + ")")
.on('click', function(d, i) {
if (d3.active(this)) {
marker.transition();
setTimeout(function() {
pauseValues.lastTime = pauseValues.currentTime;
//console.log(pauseValues);
}, 100);
} else {
transition();
}
});
function transition() {
marker.transition()
.duration(duration - (duration * pauseValues.lastTime))
.attrTween("transform", translateAlong(path.node()))
.on("end", function() {
pauseValues = {
lastTime: 0,
currentTime: 0
};
transition()
});
}
function translateAlong(path) {
var l = path.getTotalLength();
return function(d, i, a) {
return function(t) {
t += pauseValues.lastTime;
var p = path.getPointAtLength(t * l);
pauseValues.currentTime = t;
return "translate(" + p.x + "," + p.y + ")";
};
};
}
function polygon(d) {
return "M" + d.join("L") + "Z";
}
</script>
</body>
If you want to pause at points, I would not run one transition across the entire path. Instead, I would break it up into N transitions, moving from point to point. Before starting the circle on it's next leg, you can pause it for a time. To do this, I would just transition along each line segment with a little algebra:
// copy our data
transData = data.slice();
function transition() {
marker.transition()
.ease(d3.easeLinear)
.duration(duration)
.attrTween("transform", function(){
// get our two points
// slope between them
// and intercetp
var p0 = transData.shift(),
p1 = transData[0];
m = (p0[1] - p1[1]) / (p0[0] - p1[0]),
b = p0[1] - (m * p0[0]),
i = d3.interpolateNumber(p0[0], p1[0]);
// move the point along the line
return function(t){
var x = i(t),
y = m*x + b;
return "translate(" + x + "," + y + ")";
}
})
// one line segment is complete
.on("end", function(){
// if no more movements, stop
if (transData.length <= 1) return;
iter++;
// determine if this is a "pause"
setTimeout(transition, pausePoints.indexOf(iter) !== -1 ? pauseTime : 0);
});
Running code, click a dot to start you can pause a multiple points:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>basic_animateBetweenCircles</title>
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
path {
stroke: #848484;
fill: none;
}
circle {
fill: steelblue;
stroke: steelblue;
stroke-width: 3px;
}
.line {
fill: none;
stroke: #FE642E;
stroke-width: 4;
stroke-dasharray: 4px, 8px;
}
.point {
fill: #DF013A;
}
</style>
</head>
<body>
<script>
var width = 960,
height = 500;
var data = [
[480, 200],
[580, 400],
[680, 100],
[780, 300],
[180, 300],
[280, 100],
[380, 400]
];
var duration = 20000/data.length,
pauseTime = 2000;
var line = d3.line()
.x(function(d) {
return (d)[0];
})
.y(function(d) {
return (d)[1];
});
var voronoi = d3.voronoi()
.extent([
[0, 0],
[width, height]
]);
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
//path to animate - marker transitions along this path
var path = svg.append("path")
.data([data])
.attr("d", line)
.attr('class', 'line')
.attr("d", function(d) {
return line(d)
});
//voronoi
var voronoiPath = svg.append("g")
.selectAll("path")
.data(voronoi.polygons(data))
.enter().append("path")
.attr("d", polygon);
//Want to activate circles when marker paused on them / in voronoi cell - intention is to have on click to href
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("class", "point")
.attr("r", 10)
.attr("transform", function(d) {
return "translate(" + d + ")";
})
.on('click', function(d, i) {
d3.select(this)
.style("fill", "green");
pausePoints.push(i);
if (pausePoints.length === 1)
transition();
});
//marker to transition along path
var marker = svg.append("circle")
.attr("r", 19)
.attr("transform", "translate(" + (data[0]) + ")");
var pausePoints = [],
iter = 0,
transData = data.slice();
function transition() {
marker.transition()
.ease(d3.easeLinear)
.duration(duration)
.attrTween("transform", function(){
var p0 = transData.shift(),
p1 = transData[0];
m = (p0[1] - p1[1]) / (p0[0] - p1[0]),
b = p0[1] - (m * p0[0]),
i = d3.interpolateNumber(p0[0], p1[0]);
return function(t){
var x = i(t),
y = m*x + b;
return "translate(" + x + "," + y + ")";
}
})
.on("end", function(){
if (transData.length <= 1) return;
iter++;
setTimeout(transition, pausePoints.indexOf(iter) !== -1 ? pauseTime : 0);
});
}
function polygon(d) {
return "M" + d.join("L") + "Z";
}
</script>
</body>
I'm having an issue where I'm plotting circles on a multi-line chart which has a different color for each line (circles match the colors). The catch is the way I have the function I'm writing circles on top of circles - which is an issue when I try to hide specific ones.
I want to plot circles based on then name but I'm unsure how to limit the above D3 functions to one name only - currently it plots all circles for each line.
Is there a way to use d.name to limit the plotting to one name each time?
thanks
I think d3.nest is what you want:
var names = d3.nest()
.key(function(d){ return d.name; })
.entries(data);
var data = [
{"name":"1.0E-6MHz","value":"20.0","date":"2017-08-25 21:00:00"},{"name":"1.0E-6MHz","value":"93.8","date":"2017-08-25 22:00:00"},{"name":"1.0E-6MHz","value":"86.2","date":"2017-08-25 23:00:00"},{"name":"1.0E-6MHz","value":"79.2","date":"2017-08-26 00:00:00"},{"name":"1.0E-6MHz","value":"81.7","date":"2017-08-26 01:00:00"},{"name":"1.0E-6MHz","value":"76.2","date":"2017-08-26 02:00:00"},{"name":"1.0E-6MHz","value":"86.2","date":"2017-08-26 03:00:00"},{"name":"1.0E-6MHz","value":"89.2","date":"2017-08-26 04:00:00"},{"name":"1.0E-6MHz","value":"91.9","date":"2017-08-26 05:00:00"},{"name":"1.0E-6MHz","value":"78.2","date":"2017-08-26 06:00:00"},{"name":"1.0E-6MHz","value":"86.2","date":"2017-08-26 07:00:00"},{"name":"1.0E-6MHz","value":"82.2","date":"2017-08-26 08:00:00"},{"name":"1.0E-6MHz","value":"96.2","date":"2017-08-26 09:00:00"},{"name":"1.0E-6MHz","value":"88.7","date":"2017-08-26 10:00:00"},{"name":"1.0E-6MHz","value":"92.3","date":"2017-08-26 11:00:00"},{"name":"1.0E-6MHz","value":"80.2","date":"2017-08-26 12:00:00"},{"name":"1.0E-6MHz","value":"76.2","date":"2017-08-26 13:00:00"},{"name":"1.0E-6MHz","value":"93.2","date":"2017-08-26 14:00:00"},{"name":"1.0E-6MHz","value":"89.2","date":"2017-08-26 15:00:00"},{"name":"1.0E-6MHz","value":"79.2","date":"2017-08-26 16:00:00"},{"name":"1.0E-6MHz","value":"90.2","date":"2017-08-26 17:00:00"},{"name":"1.0E-6MHz","value":"85.2","date":"2017-08-26 18:00:00"},{"name":"1.0E-6MHz","value":"86.5","date":"2017-08-26 19:00:00"},{"name":"1.0E-6MHz","value":"76.2","date":"2017-08-26 20:00:00"},{"name":"1.0E-6MHz","value":"94.5","date":"2017-08-26 21:00:00"},
{"name":"2.0E-6MHz","value":"26.2","date":"2017-08-25 21:00:00"},{"name":"2.0E-6MHz","value":"33.8","date":"2017-08-25 22:00:00"},{"name":"2.0E-6MHz","value":"26.2","date":"2017-08-25 23:00:00"},{"name":"2.0E-6MHz","value":"66.2","date":"2017-08-26 00:00:00"},{"name":"2.0E-6MHz","value":"3.7","date":"2017-08-26 01:00:00"},{"name":"2.0E-6MHz","value":"76.2","date":"2017-08-26 02:00:00"},{"name":"2.0E-6MHz","value":"36.2","date":"2017-08-26 03:00:00"},{"name":"2.0E-6MHz","value":"22.2","date":"2017-08-26 04:00:00"},{"name":"2.0E-6MHz","value":"31.6","date":"2017-08-26 05:00:00"},{"name":"2.0E-6MHz","value":"44.2","date":"2017-08-26 06:00:00"},{"name":"2.0E-6MHz","value":"7.2","date":"2017-08-26 07:00:00"},{"name":"2.0E-6MHz","value":"46.2","date":"2017-08-26 08:00:00"},{"name":"2.0E-6MHz","value":"46.2","date":"2017-08-26 09:00:00"},{"name":"2.0E-6MHz","value":"21.7","date":"2017-08-26 10:00:00"},{"name":"2.0E-6MHz","value":"22.3","date":"2017-08-26 11:00:00"},{"name":"2.0E-6MHz","value":"46.2","date":"2017-08-26 12:00:00"},{"name":"2.0E-6MHz","value":"46.2","date":"2017-08-26 13:00:00"},{"name":"2.0E-6MHz","value":"46.2","date":"2017-08-26 14:00:00"},{"name":"2.0E-6MHz","value":"46.2","date":"2017-08-26 15:00:00"},{"name":"2.0E-6MHz","value":"46.2","date":"2017-08-26 16:00:00"},{"name":"2.0E-6MHz","value":"96.2","date":"2017-08-26 17:00:00"},{"name":"2.0E-6MHz","value":"46.2","date":"2017-08-26 18:00:00"},{"name":"2.0E-6MHz","value":"33.5","date":"2017-08-26 19:00:00"},{"name":"2.0E-6MHz","value":"46.2","date":"2017-08-26 20:00:00"},{"name":"2.0E-6MHz","value":"44.5","date":"2017-08-26 21:00:00"},
{"name":"3.0E-6MHz","value":"38.2","date":"2017-08-25 21:00:00"},{"name":"3.0E-6MHz","value":"43.8","date":"2017-08-25 22:00:00"},{"name":"3.0E-6MHz","value":"56.2","date":"2017-08-25 23:00:00"},{"name":"3.0E-6MHz","value":"46.2","date":"2017-08-26 00:00:00"},{"name":"3.0E-6MHz","value":"53.7","date":"2017-08-26 01:00:00"},{"name":"3.0E-6MHz","value":"3.2","date":"2017-08-26 02:00:00"},{"name":"3.0E-6MHz","value":"46.2","date":"2017-08-26 03:00:00"},{"name":"3.0E-6MHz","value":"66.2","date":"2017-08-26 04:00:00"},{"name":"3.0E-6MHz","value":"37.9","date":"2017-08-26 05:00:00"},{"name":"3.0E-6MHz","value":"42.2","date":"2017-08-26 06:00:00"},{"name":"3.0E-6MHz","value":"4.2","date":"2017-08-26 07:00:00"},{"name":"3.0E-6MHz","value":"46.2","date":"2017-08-26 08:00:00"},{"name":"3.0E-6MHz","value":"46.2","date":"2017-08-26 09:00:00"},{"name":"3.0E-6MHz","value":"21.7","date":"2017-08-26 10:00:00"},{"name":"3.0E-6MHz","value":"22.3","date":"2017-08-26 11:00:00"},{"name":"3.0E-6MHz","value":"46.2","date":"2017-08-26 12:00:00"},{"name":"3.0E-6MHz","value":"46.2","date":"2017-08-26 13:00:00"},{"name":"3.0E-6MHz","value":"46.2","date":"2017-08-26 14:00:00"},{"name":"3.0E-6MHz","value":"46.2","date":"2017-08-26 15:00:00"},{"name":"3.0E-6MHz","value":"46.2","date":"2017-08-26 16:00:00"},{"name":"3.0E-6MHz","value":"96.2","date":"2017-08-26 17:00:00"},{"name":"3.0E-6MHz","value":"46.2","date":"2017-08-26 18:00:00"},{"name":"3.0E-6MHz","value":"33.5","date":"2017-08-26 19:00:00"},{"name":"3.0E-6MHz","value":"46.2","date":"2017-08-26 20:00:00"},{"name":"3.0E-6MHz","value":"34.5","date":"2017-08-26 21:00:00"},
{"name":"4.0E-6MHz","value":"46.2","date":"2017-08-25 21:00:00"},{"name":"4.0E-6MHz","value":"53.8","date":"2017-08-25 22:00:00"},{"name":"4.0E-6MHz","value":"86.2","date":"2017-08-25 23:00:00"},{"name":"4.0E-6MHz","value":"56.2","date":"2017-08-26 00:00:00"},{"name":"4.0E-6MHz","value":"23.7","date":"2017-08-26 01:00:00"},{"name":"4.0E-6MHz","value":"16.2","date":"2017-08-26 02:00:00"},{"name":"4.0E-6MHz","value":"76.2","date":"2017-08-26 03:00:00"},{"name":"4.0E-6MHz","value":"82.2","date":"2017-08-26 04:00:00"},{"name":"4.0E-6MHz","value":"39.9","date":"2017-08-26 05:00:00"},{"name":"4.0E-6MHz","value":"6.2","date":"2017-08-26 06:00:00"},{"name":"4.0E-6MHz","value":"22.2","date":"2017-08-26 07:00:00"},{"name":"4.0E-6MHz","value":"46.2","date":"2017-08-26 08:00:00"},{"name":"4.0E-6MHz","value":"46.2","date":"2017-08-26 09:00:00"},{"name":"4.0E-6MHz","value":"21.7","date":"2017-08-26 10:00:00"},{"name":"4.0E-6MHz","value":"22.3","date":"2017-08-26 11:00:00"},{"name":"4.0E-6MHz","value":"46.2","date":"2017-08-26 12:00:00"},{"name":"4.0E-6MHz","value":"46.2","date":"2017-08-26 13:00:00"},{"name":"4.0E-6MHz","value":"46.2","date":"2017-08-26 14:00:00"},{"name":"4.0E-6MHz","value":"46.2","date":"2017-08-26 15:00:00"},{"name":"4.0E-6MHz","value":"46.2","date":"2017-08-26 16:00:00"},{"name":"4.0E-6MHz","value":"96.2","date":"2017-08-26 17:00:00"},{"name":"4.0E-6MHz","value":"46.2","date":"2017-08-26 18:00:00"},{"name":"4.0E-6MHz","value":"33.5","date":"2017-08-26 19:00:00"},{"name":"4.0E-6MHz","value":"46.2","date":"2017-08-26 20:00:00"},{"name":"4.0E-6MHz","value":"24.5","date":"2017-08-26 21:00:00"},
{"name":"5.0E-6MHz","value":"66.2","date":"2017-08-25 21:00:00"},{"name":"5.0E-6MHz","value":"63.8","date":"2017-08-25 22:00:00"},{"name":"5.0E-6MHz","value":"16.2","date":"2017-08-25 23:00:00"},{"name":"5.0E-6MHz","value":"86.2","date":"2017-08-26 00:00:00"},{"name":"5.0E-6MHz","value":"13.7","date":"2017-08-26 01:00:00"},{"name":"5.0E-6MHz","value":"36.2","date":"2017-08-26 02:00:00"},{"name":"5.0E-6MHz","value":"6.2","date":"2017-08-26 03:00:00"},{"name":"5.0E-6MHz","value":"21.2","date":"2017-08-26 04:00:00"},{"name":"5.0E-6MHz","value":"41.1","date":"2017-08-26 05:00:00"},{"name":"5.0E-6MHz","value":"86.2","date":"2017-08-26 06:00:00"},{"name":"5.0E-6MHz","value":"69.2","date":"2017-08-26 07:00:00"},{"name":"5.0E-6MHz","value":"46.2","date":"2017-08-26 08:00:00"},{"name":"5.0E-6MHz","value":"46.2","date":"2017-08-26 09:00:00"},{"name":"5.0E-6MHz","value":"21.7","date":"2017-08-26 10:00:00"},{"name":"5.0E-6MHz","value":"22.3","date":"2017-08-26 11:00:00"},{"name":"5.0E-6MHz","value":"46.2","date":"2017-08-26 12:00:00"},{"name":"5.0E-6MHz","value":"46.2","date":"2017-08-26 13:00:00"},{"name":"5.0E-6MHz","value":"46.2","date":"2017-08-26 14:00:00"},{"name":"5.0E-6MHz","value":"46.2","date":"2017-08-26 15:00:00"},{"name":"5.0E-6MHz","value":"46.2","date":"2017-08-26 16:00:00"},{"name":"5.0E-6MHz","value":"96.2","date":"2017-08-26 17:00:00"},{"name":"5.0E-6MHz","value":"46.2","date":"2017-08-26 18:00:00"},{"name":"5.0E-6MHz","value":"33.5","date":"2017-08-26 19:00:00"},{"name":"5.0E-6MHz","value":"46.2","date":"2017-08-26 20:00:00"},{"name":"5.0E-6MHz","value":"4.5","date":"2017-08-26 21:00:00"}
];
// parsing
data.forEach(function(d){
d.value = +d.value;
d.date = new Date(d.date);
})
// after this you will have 5 name keys of its values(in your case)
var names = d3.nest()
.key(function(d){ return d.name; })
.entries(data);
// console.log(names)
var chart = d3.select("#chart");
d3.select("#names")
.selectAll("button")
.data(names.map(function(d){ return d.key; }))
.enter()
.append("button")
.text(function(d){ return d; })
.on("click", redraw);
var svgWidth = 500,
svgHeight = 400,
radius = 5,
margin = { top: 30, right: 30, bottom: 30, left: 30 },
width = svgWidth - margin.right - margin.left,
height = svgHeight - margin.top - margin.bottom;
var xScale = d3.time.scale().range([0, width]),
yScale = d3.scale.linear().range([0, height]),
xAxis = d3.svg.axis().orient("bottom").scale(xScale),
yAxis = d3.svg.axis().orient("left");
var svg = chart.append("svg").attr({ width: svgWidth, height: svgHeight });
var gMain = svg.append("g").attr({
class: "gMain",
transform: "translate(" + [margin.left, margin.top] + ")"
}),
gYAxis = gMain.append("g").attr("class", "axis yaxis"),
gXAxis = gMain.append("g").attr({
class: "axis xaxis",
transform: "translate(0," + height + ")"
})
gPlot = gMain.append("g").attr({
class: "plot",
transform: "translate(0," + height + ")"
});
redraw("1.0E-6MHz", 0);
function redraw(name, index){
var points = names[index].values;
xScale.domain(d3.extent(points, function(d){ return d.date; }));
yScale.domain(d3.extent(points, function(d){ return d.value; }));
gXAxis.transition().call(xAxis);
gYAxis.transition().call(yAxis.scale(yScale.copy().range([height, 0])));
var update = gPlot.selectAll("circle").data(points),
enter = update.enter()
.append("circle")
.attr({
class: "circle",
r: radius,
fill: "steelblue",
cx: function(d){ return xScale(d.date); },
cy: function(d){ return -yScale(d.value); }
});
update.exit().remove();
update.transition()
.duration(700)
.attr({
cx: function(d){ return xScale(d.date); },
cy: function(d){ return -yScale(d.value); }
});
}
.axis path{
fill: none;
stroke: black;
stroke-width: 1;
}
.axis line{
stroke: black;
stroke-width: 1;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="names"></div>
<div id="chart"></div>
Please look at http://bl.ocks.org/HoffmannP/95392bf4a37344793786 and help me find an explenation why it just doesn't work in FF but works like a charm in Chrome.
because you're using .style for width, height and x when you need to use .attr.
Having these as .styles is part of SVG 2 and not SVG 1.1 and SVG 2 is unfinished. Firefox does not yet implement this part of SVG 2, although it does implement other parts that Chrome does not.
var margin = {top: 50, right: 20, bottom: 60, left: 70};
var width = 800 - margin.left - margin.right;
var height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.domain([0, 4])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, 60])
.range([height, 0]);
var yVal = d3.scale.linear()
.domain([60, 0])
.range([height, 0]);
var yAxisMinor = d3.svg.axis()
.scale(y)
.ticks(13)
.tickSize(width, 0)
.orient('right');
var yAxisMajor = d3.svg.axis()
.scale(y)
.ticks(7)
.tickSize(width, 0)
.tickPadding(-(width + 5))
.tickFormat(d3.format('d'))
.orient('right');
var svg = d3.select('body').append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var xLabel = svg.append('g')
.attr('class', 'x label')
.attr('transform', 'translate(0, ' + height/2 + ') rotate(-90)')
.append('text')
.attr('text-anchor', 'middle')
.attr('dy', '-40')
.text('Prozent');
var gx = svg
.append('g').attr('class', 'x axis');
gx.append('g')
.attr('transform', 'translate(7, -15)')
.append('line')
.attr('x2', '0')
.attr('y2', height + 15);
gx.append('g')
.attr('transform', 'translate(0, -26) scale(0.15, 0.15)')
.append('path')
.attr('d', 'M0,86.6L50,0L100,86.6C50,75 50,75 0,86.6z');
var gyMinor = svg.append('g')
.attr('class', 'y axis minor')
.call(yAxisMinor);
gyMinor.selectAll('text').remove();
var gyMajor = svg.append('g')
.attr('class', 'y axis major')
.call(yAxisMajor);
gyMajor.selectAll('text')
.style('text-anchor', 'end')
.attr('dy', '7px');
var drawArea = svg.append('g')
.attr('class', 'block')
.attr('transform', 'translate(' + 20 + ', ' + height + ') scale(1, -1)');
var backBlocks = drawArea
.selectAll('rect.back')
.data([64, 64, 64, 64])
.enter()
.append('rect')
.attr('class', 'back')
.attr('width', width/5)
.attr('height', yVal)
.attr('x', function (d, i) { return x(i); });
var frontBlocks = drawArea
.selectAll('rect.front')
.data([0,0,0,0])
.enter()
.append('rect')
.attr('class', 'front')
.attr('width', width/5)
.attr('height', yVal)
.attr('x', function (d, i) { return x(i); });
var newHeight = function (d, i) {
var y = d3.event.clientY;
d3.select(frontBlocks[0][i % 4]).style('height', height + margin.bottom - y);
};
var currentActiveBlock = false;
drawArea.selectAll('rect')
.on('mouseover', function (d, i) {
d3.select(backBlocks[0][i % 4]).style('opacity', '0.5');
})
.on('mouseout', function () {
backBlocks.style('opacity', '0');
})
.on('mousedown', function (d, i) {
d3.select(backBlocks[0][i % 4]).style('opacity', '0.5');
newHeight.call(this, d, i);
currentActiveBlock = i % 4;
})
.on('mousemove', function (d, i) {
if (currentActiveBlock === false) {
return;
}
newHeight.call(this, d, currentActiveBlock);
})
.on('mouseup', function (d, i) {
d3.select(frontBlocks[0][currentActiveBlock]).style('opacity', '1');
newHeight.call(this, d, currentActiveBlock);
currentActiveBlock = false;
});
body {
font: 18px sans-serif;
}
svg {
}
.label text {
font-weight: bold;
}
.y.axis path {
display: none;
}
.x.axis path {
fill: #333;
}
.axis line {
shape-rendering: crispEdges;
stroke: #333;
stroke-width: 2px;
}
.axis.minor line {
stroke-width: 1px;
}
.axis text {
text-anchor: end;
}
.block rect {
cursor: ns-resize;
}
.block rect.back {
opacity: 0.0;
fill: #ddd;
}
}
.block rect.front {
fill: #222;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>