Adding connectors on mouse over similar to draw.io - d3.js

I am trying to simulate an arrow feature similar to draw.io where on mouse hover on an object, i would like to show the blue dots on the center of each side and arrows on all 4 sides. Any idea how to implement the same.
Appreciate your response.. Thank you.

I've made an example using a square. Hovering spawns crosses and arrows along each side. I recommend reading this documentation about paths in SVG to see how I drew these shapes.
const size = 200,
margin = 100,
offset = 10,
crossesPerSide = 4;
const arrowPath = 'M-5,0 h10 v30 h10 l-15,15 l-15,-15 h10 Z';
const crossPath = 'M-6,-5 l12,10 m0,-10 l-12,10';
const g = d3.select('svg')
.append('g')
.on('mouseover', () => {
g.selectAll('.arrow')
.data('nesw'.split(''))
.enter()
.append('path')
.classed('arrow', true)
.attr('d', arrowPath)
.attr('transform', (d, i) => `
translate(${margin} ${margin})
translate(${size / 2} ${size + offset})
rotate(${i * 90} 0 ${-offset - size / 2})
`)
g.selectAll('.cross')
.data(d3.range(crossesPerSide * 4))
.enter()
.append('path')
.classed('cross', true)
.attr('d', crossPath)
.attr('transform', (d, i) => {
const sideIdx = Math.floor(i / crossesPerSide);
const crossIdx = i % crossesPerSide;
const spacePerCross = (size / crossesPerSide);
return `
translate(${margin} ${margin})
rotate(${sideIdx * 90} ${size / 2} ${size / 2})
translate(${spacePerCross * (crossIdx + 0.5)}, 0)
`;
})
})
.on('mouseleave', () => {
g.selectAll('.arrow').remove();
g.selectAll('.cross').remove();
});
g.append('rect')
.attr('x', margin)
.attr('y', margin)
.attr('width', size)
.attr('height', size)
.attr('stroke', 'black')
.attr('fill', 'white')
svg {
border: solid 1px red;
}
.cross {
stroke: darkblue;
stroke-width: 2;
opacity: 0.6;
}
.arrow {
fill: darkblue;
opacity: 0.2;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="400" height="400"></svg>

Related

Tooltip in d3/topojson choropleth map not working

I have a Choropleth map where the tooltip is working for most of it, but the central states are now showing the tooltip...in face, they are not even running the mouseout callback function at all (tested with a console.log command).
At first I was using d3-tip, and that wasn't working, and it was the first time attempting it, so I thought I might be doing something wrong, so I opted to implement a standard div that toggles between display: none and display: block and when it still wasn't working, I threw in a console.log command to see if the callback function was running at all, and it's not. It's mostly an issue with Kansas, but some of the counties in the surrounding states are having problems too. and I know it's not an issue with the data set, because the example given, which pulls from the same data set is working fine.
Here is the css for the tooltip:
#tooltip{
display: none;
background-color: rgba(32,32,32,1);
position: absolute;
border-radius: 10px;
padding: 10px;
width: 200px;
height: 40px;
color: white
}
and the JS code:
$(function(){
//svg setup
const svgPadding = 60;
const svgWidth = 1000;
const svgHeight = 600;
var svg = d3.select('body')
.append('svg')
.attr('width', svgWidth)
.attr('height', svgHeight)
.attr('id', 'map');
function createChart(topData, eduData){
//scales
var colorScale = d3.scaleSequential(d3.interpolateBlues);
var unitScale = d3.scaleLinear()
.domain(d3.extent(eduData.map(e => e.bachelorsOrHigher)))
.range([0,1])
//map
var path = d3.geoPath();
svg.selectAll('.county')
.data(topojson.feature(topData, topData.objects.counties).features)
.enter()
.append('path')
.attr('class', 'county')
.attr('d', path)
.attr('data-fips', d=>d.id)
.attr('eduIndex', d => eduData.map(e => e.fips).indexOf(d.id))
.attr('data-education', function(){
var index = d3.select(this).attr('eduIndex');
if (index == -1)return 0;
return eduData[
d3.select(this).
attr('eduIndex')
]
.bachelorsOrHigher
})
.attr('fill', function(){
var value = d3.select(this).attr('data-education');
return colorScale(unitScale(value));
})
.attr('stroke', function(){
return d3.select(this).attr('fill');
})
.on('mouseover', function(d){
var index = d3.select(this).attr('eduIndex');
var education = d3.select(this).attr('data-education');
var county = index == -1 ? 'unknown' : eduData[index].area_name;
console.log(county)
var tooltip = d3.select('#tooltip')
.style('left', d3.event.pageX + 10 + 'px')
.style('top', d3.event.pageY + 10 + 'px')
.style('display', 'block')
.attr('data-education', education)
.html(`${county}: ${education}`)
})
.on('mouseout', ()=>d3.select('#tooltip').style('display', 'none'));
svg.append('path')
.datum(topojson.mesh(topData, topData.objects.states, (a,b)=>a.id!=b.id))
.attr('d', path)
.attr('fill', 'rgba(0,0,0,0)')
.attr('stroke', 'black')
.attr('stroke-width', 0.4)
//legend scale
const legendWidth = 0.5 * svgWidth;
const legendHeight = 30;
const numCells = 1000;
const cellWidth = legendWidth/numCells;
const legendUnitScale = d3.scaleLinear()
.domain([0, legendWidth])
.range([0,1]);
//legend
var legend = svg.append('svg')
.attr('id', 'legend')
.attr('width', legendWidth)
.attr('height', legendHeight)
.attr('x', 0.5 * svgWidth)
.attr('y', 0)
for (let i = 0; i < numCells; i++){
legend.append('rect')
.attr('x', i * cellWidth)
.attr('width', cellWidth)
.attr('height', legendHeight - 10)
.attr('fill', colorScale(legendUnitScale(i*cellWidth)))
}
}
//json requests
d3.json('https://raw.githubusercontent.com/no-stack-dub-sack/testable-projects-fcc/master/src/data/choropleth_map/counties.json')
.then(function(topData){
d3.json('https://raw.githubusercontent.com/no-stack-dub-sack/testable-projects-fcc/master/src/data/choropleth_map/for_user_education.json')
.then(function(eduData){
createChart(topData, eduData);
});
});
});
The issue is that you are applying a fill to the state mesh. Let's change the fill from rgba(0,0,0,0) to rgba(10,10,10,0.1):
It should be clear now why the mouse interaction doesn't work in certain areas: the mesh is filled over top of it. Regardless of the fact you can't see the mesh due to it having 0 opacity, it still intercepts the mouse events.
The mesh is meant only to represent the borders: it is a collection of geojson lineStrings (see here too). The mesh is not intended to be filled, it only should have a stroke.
If you change the mesh fill to none, or the pointer events of the mesh to none, then the map will work as expected.

D3 transition along segments of path and pause at coordinate values

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>

d3js how to get rotated rect's corner coordinates?

I'm pretty new to d3js and feeling a little overwhelmed here. I'm trying to figure out how to query a rotated rectangle's corner coordinates so i can place a circle on that location (eventually I'm going to use that as a starting coordinate for a line to link to other nodes).
Here is an image showing what I'm trying to do:
Currently I'm getting the circle on the left of the svg boundary below, I'm trying to place it roughly where the x is below.
Here is my code for the circle:
let rx = node.attr("x");
let ry = node.attr("y");
g.append("circle")
.attr("cx",rx)
.attr("cy",ry)
.attr("r",5);
Here is my jsFiddle: jsFiddle and a Stack Overflow snippet
let d3Root = 'd3-cpm';
let w = document.documentElement.clientWidth;
let h = document.documentElement.clientHeight;
//TODO put type any
let eData = {
width: 180,
height: 180,
padding: 80,
fill: '#E0E0E0',
stroke: '#c3c5c5',
strokeWidth: 3,
hoverFill: '#1958b5',
hoverStroke: '#0046ad',
hoverTextColor: '#fff',
rx: 18,
ry: 18,
rotate: 45,
label: 'Decision Node',
textFill: 'black',
textHoverFill: 'white'
};
let cWidth;
let cHeight = h;
d3.select(d3Root)
.append("div")
.attr("id", "d3-root")
.html(function () {
let _txt = "Hello From D3! <br/>Frame Width: ";
let _div = d3.select(this);
let _w = _div.style("width");
cWidth = parseInt(_div.style("width"));
_txt += cWidth + "<br/> ViewPort Width: " + w;
return _txt;
});
let svg = d3.select(d3Root)
.append("svg")
.attr("width", cWidth)
.attr("height", cHeight)
.call(d3.zoom()
//.scaleExtent([1 / 2, 4])
.on("zoom", zoomed));
;
let g = svg.append("g")
.on("mouseover", function (d) {
d3.select(this)
.style("cursor", "pointer");
d3.select(this).select("rect")
.style("fill", eData.hoverFill)
.style("stroke", eData.hoverStroke);
d3.select(this).select("text")
.style("fill", eData.textHoverFill);
})
.on("mouseout", function (d) {
d3.select(this)
.style("cursor", "default");
d3.select(this).select("rect")
.style("fill", eData.fill)
.style("stroke", eData.stroke);
d3.select(this).select("text")
.style("fill", eData.textFill);
});
let node = g.append("rect")
.attr("width", eData.width)
.attr("height", eData.height)
.attr("fill", eData.fill)
.attr("stroke", eData.stroke)
.attr("stroke-width", eData.strokeWidth)
.attr("rx", eData.rx)
.attr("ry", eData.ry)
.attr("y", eData.padding)
.attr('transform', function () {
let _x = calcXLoc();
console.log(_x);
return "translate(" + _x + "," + "0) rotate(45)";
})
.on("click", ()=> {
console.log("rect clicked");
d3.event.stopPropagation();
//this.nodeClicked();
});
let nText = g.append('text')
.text(eData.label)
.style('fill', eData.textFill)
.attr('x', calcXLoc() - 50)
.attr('y', eData.width + 10)
.attr("text-anchor", "middle")
.on("click", ()=> {
console.log("text clicked");
d3.event.stopPropagation();
//this.nodeClicked();
});
let rx = node.attr("x");
let ry = node.attr("y");
g.append("circle")
.attr("cx",rx)
.attr("cy",ry)
.attr("r",5);
function calcXLoc() {
return (cWidth / 2 - eData.width / 2) + eData.width;
}
function zoomed() {
g.attr("transform", d3.event.transform);
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<d3-cpm></d3-cpm>
You're applying a transform to your rect to position and rotate it. It has no x attribute, so that comes back as undefined. This gets you slightly closer:
let rx = parseInt(node.attr("x"), 10) | 0;
let ry = parseInt(node.attr("y"), 10) | 0;
let height = parseInt(node.attr("height"), 10) | 0;
let transform = node.attr("transform");
g.append("circle")
.attr("cx",rx + height)
.attr("cy",ry + height)
.attr("transform", transform)
.attr("r",5);
But note that this is going to get kind of clunky and difficult to deal with - it'd be better if your data was modeled in such a way that the circular points were handled in there as well and could be somehow derived/transformed consistently....
Updated fiddle: https://jsfiddle.net/dcw48tk6/7/
Image:

How to implement a smooth transition when new bands are created in d3?

In the example below, I'm trying to animate new items appearance.
As you can see, they animate from the bottom of the chart to their position.
However, existing items ("second" in this example) jump, instead of smoothly transitioning to their new position.
I thought it is because the new band suddenly appears, without a transition. So, I tried to add a transition:
const band = bandUpdate.enter()
.append('g')
.attr('class', 'band')
.merge(bandUpdate)
.transition(t)
.attr('transform', (_, i) => `translate(0, ${i * bandHeight})`);
But, I'm getting:
Uncaught TypeError: band.selectAll(...).data is not a function
Could you explain the error please, and suggest a way to avoid the undesired jump?
Bonus: How could I animate the y axis labels?
Playground
const width = 300;
const height = 200;
const margin = { top: 30, right: 30, bottom: 30, left: 50 };
let data = {};
const main = d3.select('.chart')
.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})`);
const xScale = d3.scaleLinear().domain([0, 16]).range([0, width]);
const xAxis = d3.axisBottom(xScale);
main.append('g')
.attr('transform', `translate(0, ${height})`)
.call(xAxis);
const yScale = d3.scaleBand().domain([]).range([0, height]);
const yAxis = d3.axisLeft(yScale);
const yAxisG = main.append('g').call(yAxis);
const bandG = main.append('g');
function update() {
const t = d3.transition().duration(500);
const ids = Object.keys(data);
yScale.domain(ids);
yAxisG.call(yAxis);
const bandHeight = yScale.bandwidth();
const bandUpdate = bandG.selectAll('.band').data(ids, id => id);
const band = bandUpdate.enter()
.append('g')
.attr('class', 'band')
.merge(bandUpdate)
// .transition(t) // Throws: Uncaught TypeError: band.selectAll(...).data is not a function
.attr('transform', (_, i) => `translate(0, ${i * bandHeight})`);
bandUpdate.exit().remove();
const itemUpdate = band.selectAll('.item')
.data(id => data[id], item => item.value);
const itemG = itemUpdate.enter().append('g').attr('class', 'item');
const rectHeight = 4;
itemG
.append('rect')
.attr('class', (_, i) => `item-${i}`)
.attr('x', d => xScale(d.value))
.attr('width', d => width - xScale(d.value))
.attr('height', rectHeight)
.attr('y', height)
.transition(t)
.attr('y', bandHeight / 2 - rectHeight / 2);
itemG
.append('circle')
.attr('class', (_, i) => `item-${i}`)
.attr('cx', d => xScale(d.value))
.attr('r', 6)
.attr('cy', height)
.transition(t)
.attr('cy', bandHeight / 2);
itemUpdate
.select('rect')
.attr('x', d => xScale(d.value))
.attr('width', d => width - xScale(d.value))
.transition(t)
.attr('y', bandHeight / 2 - rectHeight / 2);
itemUpdate
.select('circle')
.attr('cx', d => xScale(d.value))
.transition(t)
.attr('cy', bandHeight / 2);
itemUpdate.exit().remove();
}
update();
setTimeout(() => {
data['first'] = [
{
value: 7
},
{
value: 10
}
];
update();
}, 1000);
setTimeout(() => {
data['second'] = [
{
value: 1
}
];
update();
}, 2000);
setTimeout(() => {
data['third'] = [
{
value: 13
}
];
update();
}, 3000);
svg {
margin: 0 30px 30px 30px;
}
.item-0 {
fill: red;
}
.item-1 {
fill: green;
}
<div class="chart"></div>
<script src="https://unpkg.com/d3#4.4.1/build/d3.js"></script>
Just break your band constant:
const band = bandUpdate.enter()
.append('g')
.attr('class', 'band')
.merge(bandUpdate);
band.transition(t)
.attr('transform', (_, i) => `translate(0, ${i * bandHeight})`);
Here is the updated CodePen: http://codepen.io/anon/pen/oBWJdp?editors=0010
Explanation:
According to the documentation, selection.transition([name]):
Returns a new transition on the given selection with the specified name.
So, when you later in the code do this:
const itemUpdate = band.selectAll('.item')
.data(id => data[id], item => item.value);
You're selecting a new transition, and that's giving you the error (you cannot bind data to a transition).
Breaking the band constant makes itemUpdate a selection based in the band selection, not in the following transition.

Why are events from d3 in firefox not handled

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>

Resources