How to move tick labels in horizontal bar chart? - d3.js

I've created a horizontal bar chart in d3 and I'm trying to implement a sort function to order both the bars and their corresponding labels. The chart so far looks like this:
Clicking on the "Sort" button sorts the bars properly, but doesn't move the labels.
The data for chart looks like this
const data = [
{
COL_DIV_CODE: 'Academic Affairs',
avg_base: 67778.9,
},
{
COL_DIV_CODE: 'Finance and Administration',
avg_base: 75000.1,
},
{
COL_DIV_CODE: 'Arts and Humanities, College of',
avg_base: 68109.0,
},
];
Here's a full demo of the code so far: bl.ocks.org
Specifically this part:
d3.select("#byValue").on("click", function() {
data.sort(function(a,b) {
return d3.descending(a.avg_base, b.avg_base);
});
yScale.domain(data.map(function(d) {
return d.avg_base;
}));
// Move bars
svg.selectAll(".bar")
.transition()
.duration(500)
.attr("y", function(d, i) {
console.log("bar: ", d.avg_base, " ", yScale(d.avg_base));
return yScale(d.avg_base);
});
// Move bar labels
svg.selectAll(".bar-label")
.transition()
.duration(500)
.attr("y", function(d) {
var obj = findObjectByCollegeName(d, data);
return yScale(obj.COL_DIV_CODE) + yScale.bandwidth() / 2 - 8;
});
});
Appreciate any help!

Firstly, try not to manually change nodes that you didn't create yourself -- namely axis labels. Usually, the proper approach is to select the container you applied axis to, and just use the call(axis) on it again.
Secondly, there isn't a real reason for you to change the domain field for the scale; you do need to reapply it because the order changed, but you can reuse COL_DIV_CODE. Particularly important, because it seems axis uses the field to identify labels before and after the change (call(axis)).
Some minor things -- use alternatives to attr where available, since it always overwrites everything, when sometimes you might want to only change parts. I'm talking about assigning classes (use classed), and styles (styled).
Here is your bl.ocks code with minimal changes
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
</style>
</head>
<body>
<button id="byValue">Sort by Value</button>
<script>
const data = [
{
COL_DIV_CODE: 'Academic Affairs',
avg_base: 67778.9,
},
{
COL_DIV_CODE: 'Finance and Administration',
avg_base: 75000.1,
},
{
COL_DIV_CODE: 'Arts and Humanities, College of',
avg_base: 68109.0,
},
];
const maxObj = data.reduce(function(max, obj) {
return obj.avg_base > max.avg_base? obj : max;
});
function findObjectByCollegeName(name, data) {
for (var i=0;i<data.length;i++) {
if (data[i].COL_DIV_CODE == name) {
return data[i]
}
}
}
const heightMargin = 120;
const widthMargin = 300;
const width = 1200 - 2 * widthMargin;
const height = 400 - 2 * heightMargin;
var svg = d3.select("body").append("svg")
.attr("width", 960)
.attr("height", 500)
const chart = svg.append('g')
.attr('transform', `translate(${widthMargin}, ${heightMargin})`);
// Draw X axis
const xScale = d3.scaleLinear()
.range([width, 0])
.domain([maxObj.avg_base, 0]);
chart.append('g')
.attr('transform', `translate(0, 0)`)
.call(d3.axisTop(xScale));
// Draw Y axis
const yScale = d3.scaleBand()
.range([0, height])
.domain(data.map((s) => s.COL_DIV_CODE))
.padding(0.2);
var axisLeft = d3.axisLeft(yScale).tickFormat(function(d) {return d; });
chart.append('g')
.attr('transform', `translate(0, 0)`)
.attr('class', 'y-axis')
.call(axisLeft);
d3.selectAll(".y-axis .tick text")
.attr("class", "bar-label"); // Add a class to the bar labels
// Draw gridlines - vertical
chart.append('g')
.attr('class', 'grid')
.call(d3.axisTop()
.scale(xScale)
.tickSize(-height, 0, 0)
.tickFormat(''));
// // Draw bars
chart.selectAll()
.data(data)
.enter()
.append('rect')
.attr("class","bar")
.attr('style', 'fill: steelblue')
.attr('y', (s) => yScale(s.COL_DIV_CODE))
.attr('x', 0)
.attr('width', (s) => xScale(s.avg_base))
.attr('height', yScale.bandwidth());
d3.select("#byValue").on("click", function() {
data.sort(function(a,b) {
return d3.descending(a.avg_base, b.avg_base);
});
yScale.domain(data.map(function(d) {
return d.COL_DIV_CODE;
}));
// Move bars
svg.selectAll(".bar")
.transition()
.duration(500)
.attr("y", function(d, i) {
console.log("bar: ", d.avg_base, " ", yScale(d.avg_base));
return yScale(d.COL_DIV_CODE);
});
// Move bar labels
/*
svg.selectAll(".bar-label")
.transition()
.duration(500)
.attr("y", function(d) {
var obj = findObjectByCollegeName(d, data);
return yScale(obj.COL_DIV_CODE) + yScale.bandwidth() / 2 - 8;
});
*/
chart.select('g.y-axis')
.transition()
.duration(500)
.call(axisLeft);
});
</script>
</body>

Related

How to add updating labels in D3 Vertical Bar Chart in an Ember Application

I have a vertical bar chart in my Ember application and I am struggling to attach text labels to the top of the bars.
The chart is broken up into the following functions:
Drawing the static elements of the chart:
didInsertElement() {
let svg = select(this.$('svg')[0]);
this.set('svg', svg);
let height = 325
let width = 800
let padding = {
top: 10,
bottom: 30,
left: 40,
right: 0
};
this.set('barsContainer', svg.append('g')
.attr('class', 'bars')
.attr('transform', `translate(${padding.left}, ${padding.top})`)
);
let barsHeight = height - padding.top - padding.bottom;
this.set('barsHeight', barsHeight);
let barsWidth = width - padding.left - padding.right;
// Y scale & axes
let yScale = scaleLinear().range([barsHeight, 0]);
this.set('yScale', yScale);
this.set('yAxis', axisLeft(yScale));
this.set('yAxisContainer', svg.append('g')
.attr('class', 'axis axis--y axisWhite')
.attr('transform', `translate(${padding.left}, ${padding.top})`)
);
// X scale & axes
let xScale = scaleBand().range([0, barsWidth]).paddingInner(0.15);
this.set('xScale', xScale);
this.set('xAxis', axisBottom(xScale));
this.set('xAxisContainer', svg.append('g')
.attr('class', 'axis axis--x axisWhite')
.attr('transform', `translate(${padding.left}, ${padding.top + barsHeight})`)
);
// Color scale
this.set('colorScale', scaleLinear().range(COLORS[this.get('color')]));
this.renderChart();
this.set('didRenderChart', true);
},
This re-draws the chart when the model changes:
didUpdateAttrs() {
this.renderChart();
},
This handles the drawing of the chart:
renderChart() {
let data = this.get('data');
let counts = data.map(data => data.count);
// Update the scales
this.get('yScale').domain([0, Math.max(...counts)]);
this.get('colorScale').domain([0, Math.max(...counts)]);
this.get('xScale').domain(data.map(data => data.label));
// Update the axes
this.get('xAxis').scale(this.get('xScale'));
this.get('xAxisContainer').call(this.get('xAxis')).selectAll('text').attr("y", 0)
.attr("x", 9)
.attr("dy", ".35em")
.attr("transform", "rotate(40)")
.style("text-anchor", "start");
this.get('yAxis').scale(this.get('yScale'));
this.get('yAxisContainer').call(this.get('yAxis'));
let barsUpdate = this.get('barsContainer').selectAll('rect').data(data, data => data.label);
// Enter
let barsEnter = barsUpdate.enter()
.append('rect')
.attr('opacity', 0);
let barsExit = barsUpdate.exit();
let div = select('body')
.append("div")
.attr("class", "vert-tooltip");
// Update
let rafId;
barsEnter
.merge(barsUpdate)
.transition()
.attr('width', `${this.get('xScale').bandwidth()}px`)
.attr('height', data => `${this.get('barsHeight') - this.get('yScale')(data.count)}px`)
.attr('x', data => `${this.get('xScale')(data.label)}px`)
.attr('y', data => `${this.get('yScale')(data.count)}px`)
.attr('fill', data => this.get('colorScale')(data.count))
.attr('opacity', data => {
let selected = this.get('selectedLabel');
return (selected && data.label !== selected) ? '0.5' : '1.0';
})
.on('start', (data, index) => {
if (index === 0) {
(function updateTether() {
Tether.position()
rafId = requestAnimationFrame(updateTether);
})();
}
})
.on('end interrupt', (data, index) => {
if (index === 0) {
cancelAnimationFrame(rafId);
}
});
// Exit
barsExit
.transition()
.attr('opacity', 0)
.remove();
}
I have stripped some tooltip and click events to maintain clarity.
To add the labels I have tried to add the following in the renderChart() function:
barsEnter.selectAll("text")
.data(data)
.enter()
.append("text")
.text(function (d) { return d.count; })
.attr("x", function (d) { return xScale(d.label) + xScale.bandwidth() / 2; })
.attr("y", function (d) { return yScale(d.count) + 12; })
.style("fill", "white");
with the above code I receive an error to say that xScale and yScale are not found because they are not within this functions scope. If I use:
.attr("x", function (d) { return this.get('xScale')(d.label) + this.get('xScale').bandwidth() / 2; })
.attr("y", function (d) { return this.get('yScale')(d.count) + 12; })
I generate 'this.get' is not a function errors and the context of 'this' becomes the an object with the value of (d).
If I add the X and Y scales as variables to this function like:
let xScale = this.get('xScale')
let yScale = this.get('ySCale')
...
.attr("x", function (d) { return xScale(d.label) + xScale.bandwidth() / 2; })
.attr("y", function (d) { return yScale(d.count) + 12; })
Then the x and y attrs are returned as undefined. Please let me know if I have missed anything out.
Converting the function() {} syntax into arrow functions will allow you to maintain the this.
So:
function(d) { return this.get('xScale'); }
becomes
(d) => this.get('xScale')
or
(d) => {
return this.get('xScale');
}
For more information on arrow functions:
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
- https://hackernoon.com/javascript-es6-arrow-functions-and-lexical-this-f2a3e2a5e8c4

Get bounding box of individual countries from topojson

I want to get bounding boxes for each country from a topojson, but when I add them as svg rectangles they are bundled together towards 0,0.
Ive re-read the API and played around with the order of the bound coordinates, but that didn't change anything! Also, I tried to use the SVG method getBBox() on the country paths but that produced the same result.
Any ideas?
var width = 700,
height = 400,
bboxes = [];
d3.queue()
.defer(d3.json, "data/world.topojson")
.await(ready);
//Map projection
var proj = d3.geoMercator()
.scale(100)
.center([-0.0018057527730242487, 11.258678472759552]) //projection center
.translate([width / 2, height / 2]) //translate to center the map in view
//Generate paths based on projection
var myPath = d3.geoPath().projection(proj);
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
//Group for the map features
var map = svg.append("g")
.attr("class", "map");
function ready(error, geodata) {
if (error) return console.log(error); //unknown error, check the console
//Create a path for each map feature in the data
map.selectAll("path")
.data(topojson.feature(geodata, geodata.objects.subunits).features) //generate features from TopoJSON
.enter()
.append("path")
.attr("class", "country")
.attr("id", function(d) {
return d.id;
})
.attr("d", myPath);
bboxes = boundingExtent(topojson.feature(geodata, geodata.objects.subunits).features);
svg.selectAll("rect")
.data(bboxes)
.enter()
.append("rect")
.attr("id", function(d){
return d.id;
})
.attr("class", "bb")
.attr("x1", function(d) {
return d.x;
})
.attr("y1", function(d) {
return d.y;
})
.attr("width", function(d) {
return d.width;
})
.attr("height", function(d) {
return d.height;
})
}
function boundingExtent(features) {
var bounds= [];
for (var x in features) {
var boundObj = {};
thisBounds = myPath.bounds(features[x]);
boundObj.id = features[x].id;
boundObj.x = thisBounds[0][0];
boundObj.y = thisBounds[0][1];
boundObj.width = thisBounds[1][0] - thisBounds[0][0];
boundObj.height = thisBounds[1][1] - thisBounds[0][1];
boundObj.path = thisBounds;
bounds.push(boundObj)
}
return bounds;
}
function boundExtentBySvg(){
var countries = svg.selectAll(".country")
countries.each(function(d){
var box = d3.select(this).node().getBBox();
bboxes.push({id: d.id, x: box.x, y : box.y, width: box.width, height : box.height})
})
}
In these lines:
.attr("x1", function(d) {
return d.x;
})
.attr("y1", function(d) {
return d.y;
})
rect does not have an attribute of x1 or y1, I think you meant just x and y.
Here's your code running (note, I switched out the topojson file which caused slight code changes):
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
<script data-require="topojson.min.js#3.0.0" data-semver="3.0.0" src="https://unpkg.com/topojson#3.0.0"></script>
</head>
<body>
<svg width="700" height="400"></svg>
<script>
var width = 700,
height = 400,
bboxes = [];
d3.queue()
.defer(d3.json, "https://unpkg.com/world-atlas#1/world/110m.json")
.await(ready);
//Map projection
var proj = d3.geoMercator()
.scale(100)
.center([-0.0018057527730242487, 11.258678472759552]) //projection center
.translate([width / 2, height / 2]) //translate to center the map in view
//Generate paths based on projection
var myPath = d3.geoPath().projection(proj);
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
//Group for the map features
var map = svg.append("g")
.attr("class", "map");
function ready(error, geodata) {
if (error) return console.log(error); //unknown error, check the console
//Create a path for each map feature in the data
map.selectAll("path")
.data(topojson.feature(geodata, geodata.objects.countries).features) //generate features from TopoJSON
.enter()
.append("path")
.attr("class", "country")
.attr("id", function(d) {
return d.id;
})
.attr("d", myPath);
bboxes = boundingExtent(topojson.feature(geodata, geodata.objects.countries).features);
svg.selectAll("rect")
.data(bboxes)
.enter()
.append("rect")
.attr("id", function(d) {
return d.id;
})
.attr("class", "bb")
.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y;
})
.attr("width", function(d) {
return d.width;
})
.attr("height", function(d) {
return d.height;
})
.style("fill", "none")
.style("stroke", "steelblue");
}
function boundingExtent(features) {
var bounds = [];
for (var x in features) {
var boundObj = {};
thisBounds = myPath.bounds(features[x]);
boundObj.id = features[x].id;
boundObj.x = thisBounds[0][0];
boundObj.y = thisBounds[0][1];
boundObj.width = thisBounds[1][0] - thisBounds[0][0];
boundObj.height = thisBounds[1][1] - thisBounds[0][1];
boundObj.path = thisBounds;
bounds.push(boundObj)
}
console.log(bounds)
return bounds;
}
function boundExtentBySvg() {
var countries = svg.selectAll(".country")
countries.each(function(d) {
var box = d3.select(this).node().getBBox();
bboxes.push({
id: d.id,
x: box.x,
y: box.y,
width: box.width,
height: box.height
})
})
}
</script>
</body>
</html>

d3 treemap graphic don't recognize enter() and exit() data correctly

I'm trying to create dynamic treemap graphic with lovely d3.js library.
Here are sample of my code
element = d3.select(element[0]);
var margin = 10,
width = parseInt(element.style('width')) - margin * 2,
height = parseInt(element.style('height')) - margin * 2;
var color = d3.scale.category10();
var canvas = d3.select('.treemap').append('svg')
.attr('width', width)
.attr('height', height)
.attr('transform', 'translate(-.5,-.5)')
.style('margin', margin);
var treemap = d3.layout.treemap()
.size([width, height])
.value(function(d) { return d.value; })
.sticky(false);
function redraw(data) {
d3.selectAll('.cell').remove();
var treemapData = {};
treemapData.children = data.map(function(d) {
return {
name: d.name,
value: d.value
};
});
var leaves = treemap(treemapData);
var cells = canvas.selectAll("g")
.data(leaves);
var cellsEnter = cells.enter()
.append('rect')
.attr('class', 'cell')
.attr('x', function(d) { return d.x; })
.attr('y', function(d) { return d.y; })
.attr('width', function(d) { return d.dx; })
.attr('height', function(d) { return d.dy; })
.attr('fill', function(d) { return d.children ? null : color(d.name); })
.attr('stroke', "#fff")
.style('fill-opacity', 0);
console.log(cells.exit(), cells.enter());
}
And here I have stucked.
console.log() shows that whole new data are enter(), and none are exit() !
Input data presents like
[{value: 590, name:"A1"}, {...}, ...]
without root object field, so that's why I remapped data in treemapData object.
Š¢hanks that you at least spent your time for reading this post so far, hope you have any suggestions.
UDP. you can check working version of my code here: https://jsfiddle.net/qtbfm08k/
The following works:
remove d3.selectAll('.cell').remove();
use the code below
See the fiddle: https://jsfiddle.net/b6meLedn/4/
var cells = canvas.selectAll('.cell') //select all cells
.data(leaves); //map the data
cells.exit().remove(); //remove old extra elements
cells.enter()
.append('rect') //create new rectangles as necessary
.attr('class', 'cell')
cells //take all cells (old cells that have new data+extra new cells)
.attr('x', function(d) { return d.x; })
...

how to zoom only map and smiley could stay at same place and size

I having problem of zoom over map. The actual problem is when i zoom map, the location showing on map using smiley could also zoom but i don't want to zoom smiley. It could stay at same size and place. Sometime smiley get overlap so to avoid this i am trying to solve the above problem but i don't have idea how to transform attribute contains many things like images and text on map of d3.js. Please have a look at jsfiddle link and you can see that at japan 3 smiley get overlap and keep overlapped even after zooming map.
My JSfiddle link
my code is following:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
path {
stroke: white;
stroke-width: 0.25px;
fill: grey;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v0.min.js"></script>
<script>
var width = 960,
height = 500;
var data = [
{
"code":"TYO",
"city":"TOKYO",
"country":"JAPAN",
"lat":"35.68",
"lon":"139.76"
},
{
"code":"OSK",
"city":"Osaka",
"country":"JAPAN",
"lat":" 34.40",
"lon":"135.37"
},
{
"code":"HISH",
"city":"Hiroshima",
"country":"JAPAN",
"lat":"34.3853",
"lon":"132.4553"
},
{
"code":"BKK",
"city":"BANGKOK",
"country":"THAILAND",
"lat":"13.75",
"lon":"100.48"
},
{
"code":"DEL",
"city":"DELHI",
"country":"INDIA",
"lat":"29.01",
"lon":"77.38"
},
{
"code":"SEA",
"city":"SEATTLE",
"country":"USA",
"lat":"38.680632",
"lon":"-96.5001"
}
];
var projection = d3.geo.mercator()
.center([0, 5 ])
.scale(200)
.rotate([-180,0]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var path = d3.geo.path()
.projection(projection);
var g = svg.append("g");
// load and display the World
d3.json("world-110m2.json", function(error, topology) {
// load and display the cities
function drawMap(data){
var circle = g.selectAll("circle")
.data(data)
.enter()
.append("g")
circle.append("circle")
.attr("cx", function(d) {
return projection([d.lon, d.lat])[0];
})
.attr("cy", function(d) {
return projection([d.lon, d.lat])[1];
})
.attr("r", 5)
.style("fill", "red");
circle.append("image")
.attr("xlink:href", "http://fc08.deviantart.net/fs71/f/2013/354/8/7/blinking_smiley__animated__by_mondspeer-d6ylwn3.gif")//http://t2.gstatic.//com/images?q=tbn:ANd9GcT6fN48PEP2-z-JbutdhqfypsYdciYTAZEziHpBJZLAfM6rxqYX";})
.attr("class", "node")
.attr("x", function(d) {
return (projection([d.lon, d.lat])[0]) - 8;
})
.attr("y", function(d) {
return (projection([d.lon, d.lat])[1])-8;
})
.attr("width",20)
.attr("height",20)
//});
}
g.selectAll("path")
.data(topojson.object(topology, topology.objects.countries)
.geometries)
.enter()
.append("path")
.attr("d", path)
drawMap(data);
});
// zoom and pan
var zoom = d3.behavior.zoom()
.on("zoom",function() {
g.attr("transform","translate("+
d3.event.translate.join(",")+")scale("+d3.event.scale+")");
g.selectAll("circle")
.attr("d", path.projection(projection));
g.selectAll("path")
.attr("d", path.projection(projection));
});
svg.call(zoom)
</script>
</body>
</html>
Any body help me to zoom only map image not smiley
Implement semantic zooming :)
Try use this example to change your code :) :
Semantic zoom on map with circle showing capital
JSFIDDLE : http://jsfiddle.net/xf7222dg/2/
The code below shrinks the 'circles' depending on scale
var zoom = d3.behavior.zoom()
.on("zoom",function() {
g.attr("transform","translate("+
d3.event.translate.join(",")+")scale("+d3.event.scale+")");
g.selectAll("circle")
.attr("r", function(){
var self = d3.select(this);
var r = 8 / d3.event.scale; // set radius according to scale
self.style("stroke-width", r < 4 ? (r < 2 ? 0.5 : 1) : 2); // scale stroke-width
return r;
});
});
Here is it working with your smileys: http://jsfiddle.net/dmn0d11f/7/
You have to change the 'width' of the nodes (images) not the radius like with the circles. So select the nodes and instead of changing 'r' change 'width' :
g.selectAll(".node")
.attr("width", function(){
var self = d3.select(this);
var r = 28 / d3.event.scale; // set radius according to scale
self.style("stroke-width", r < 4 ? (r < 2 ? 0.5 : 1) : 2); // scale stroke-width
return r;
});

Change class of one element when hover over another element d3

I have a list of images and a list of image titles. I want to be able to show a hover state (change css) for the title when I mouse over its corresponding image, but I cannot figure out how to connect the two pieces of data. My code is below. I have it so that when you click on the top number the information appears beneath.
<!DOCTYPE html>
<html>
<head>
<script src="d3.v2.js"></script>
<title>Untitled</title>
</head>
<body>
<script type="text/javascript" >
function barStack(d) {
var l = d[0].length
while (l--) {
var posBase = 0, negBase = 0;
d.forEach(function(d) {
d=d[l]
d.size = Math.abs(d.y)
if (d.y<0) {
d.y0 = negBase
negBase-=d.size
} else
{
d.y0 = posBase = posBase + d.size
}
})
}
d.extent= d3.extent(d3.merge(d3.merge(d.map(function(e) { return e.map(function(f) { return [f.y0,f.y0-f.size]})}))))
return d
}
var ratiodata = [[{y:3.3}],[{y:-1.5}]]
var imageList = [
[3.3, 28, -1.5, 13, 857, 3, 4, 7, [{paintingfile:"676496.jpg", title:"Dessert1"}, {paintingfile:"676528.jpg", title: "Dessert2"}]
]]
var h=400
var w=1350
var margin=25
var color = d3.scale.category10()
var div = d3.select("body").append("div")
.attr("class", "imgtooltip")
.style("opacity", 0);
var x = d3.scale.ordinal()
.domain(['255'])
.rangeRoundBands([margin,w-margin], .1)
var y = d3.scale.linear()
.range([h-margin,0+margin])
var xAxis = d3.svg.axis().scale(x).orient("bottom").tickSize(6, 0)
var yAxis = d3.svg.axis().scale(y).orient("left")
barStack(ratiodata)
y.domain(ratiodata.extent)
var svg = d3.select("body")
.append("svg")
.attr("height",h)
.attr("width",w)
svg.selectAll(".series")
.data(ratiodata)
.enter()
.append("g")
.classed("series",true)
.style("fill","orange")
.selectAll("rect").data(Object)
.enter()
.append("rect")
.attr("x",function(d,i) { return x(x.domain()[i])})
.attr("y",function(d) { return y(d.y0)})
.attr("height",function(d) { return y(0)-y(d.size)})
.attr("width",x.rangeBand());
svg.selectAll("text")
.data(imageList)
.enter()
.append("text")
.text(function(d) {
return d[0];
})
.attr("x", function(d, i) {
return x(x.domain()[i]) + x.rangeBand() / 2;
})
.attr("y", function(d) {
return h - (32.1100917431*d[0] +150);
})
.attr("font-size", "16px")
.attr("fill", "#000")
.attr("text-anchor", "middle")
//.on("click", function(d) {console.log(d[1]);})
.on("click", function(d) {
//Update the tooltip position and value
d3.selectAll("ul")
.remove();
d3.selectAll("li")
.remove();
d3.select("#PaintingDetails")
.append("ul")
.selectAll("li")
.data(d[8])
.enter()
.append("li")
.text(function(d){
return (d.title);
});
d3.select("#imageBox")
.append("ul")
.selectAll("li")
.data(d[8])
.enter()
.append("li")
.classed("Myimageslist",true)
.append("img")
.classed("Myimages",true)
.attr("src", function(d){
return ("http://images.tastespotting.com/thumbnails/" + d.paintingfile);
})
.attr("align", "top");
d3.select(".Myimages")
.on("mouseover", function(){
d3.select("#PaintingDetails")
.selectAll("li")
.classed("selected", true)
});
});
svg.append("g").attr("class","axis x").attr("transform","translate (0 "+y(0)+")").call(xAxis);
// svg.append("g").attr("class","axis y").attr("transform","translate ("+x(margin)+" 0)").call(yAxis);
</script>
<div id="PaintingDetails"></div>
<div id="imageBox"></div>
</body>
</html>
The quick and dirty solution would be to simply use the index of the data element to figure out which title matches which image:
d3.selectAll(".Myimages")
.on("mouseover", function(d, i) {
d3.select("#PaintingDetails")
.selectAll("li")
.classed("selected", function(e, j) { return j == i; })
});

Resources