I am trying to draw a vertical line in Dimple. I have seen this post: How to draw a vertical line with dimple?
However, the example provided there is not working for me. It is only adding a point where instead I'd like a line to be drawn. I cannot find anything in the docs, nor on StackOverflow, nor via Googling and I would think this should be an easy thing to do- but so far it has proven not to be. Any help would be appreciated.
Fiddle: http://jsfiddle.net/4w6gkq5s/27/
<!DOCTYPE html>
<html>
<body>
<div id="chartContainer">
<script src="http://dimplejs.org/lib/d3.v3.4.8.js"></script>
<script src="http://dimplejs.org/dist/dimple.v2.1.2.min.js"></script>
<script type="text/javascript">
var svg = dimple.newSvg("#chartContainer", 590, 400);
d3.tsv("http://dimplejs.org/data/example_data.tsv", function (data123) {
var data = [{'name': "name1", "percentChange": 120}, {'name': "name2", "percentChange": 80}, {'name': "name3", "percentChange": 100}];
var myChart = new dimple.chart(svg, data);
myChart.defaultColors = [ new dimple.color("#177c5f") ];
myChart.defaultColors = [
new dimple.color("#3d5739"), // green
];
var x2 = myChart.addMeasureAxis("x", "percentChange");
x2.title = "Percent of last month's sales"
var y = myChart.addCategoryAxis("y", "name");
y.addOrderRule("name");
y.title = null;
y.showGridLines = true;
/*Regional average*/
var y2 = myChart.addPctAxis("y", "Dummy");
var s3 = myChart.addSeries("Regional", dimple.plot.area, [x2, y2]);
s3.data = [{
"Regional": "Regional",
"Dummy": 1,
"percentChange": 105
}];
var s = myChart.addSeries(["percentChange", "name"], dimple.plot.bar);
s.barGap = .86;
// Set some custom display elements for each series shape
s.afterDraw = function (shape, data) {
var shape = d3.select(shape);
// Add some bar labels for the yValue
svg.append("text")
.attr("x", parseFloat(shape.attr("x")) + 40)
.attr("y", parseFloat(shape.attr("y")) - 5)
.style("text-anchor", "middle")
.style("font-size", "16px")
.style("fill", "#73b7e8")
.style("pointer-events", "none")
.text(data.cy);
};
s.addEventHandler("mouseover", onHover);
s.addEventHandler("mouseleave", onLeave);
myChart.draw();
function onHover(e) {
e.selectedShape[0][0].style.fill = "#00924f";
};
function onLeave(e) {
e.selectedShape[0][0].style.fill = "#3d5739";
};
});
</script>
</div>
</body>
</html>
That approach won't work with a measure axis on x. However the solution is actually much simpler in this case. After drawing you can add a line with a bit of d3:
svg.append("line")
.attr("x1", x._scale(105))
.attr("x2", x._scale(105))
.attr("y1", myChart._yPixels())
.attr("y2", myChart._yPixels() + myChart._heightPixels())
.style("stroke", "red")
.style("stroke-dasharray", "3");
Fiddle: http://jsfiddle.net/d3jo0uu5/1/
This uses a couple of the internal methods in dimple for height and width as well as the x axis scale for positioning the line horizontally. I've made it red and dashed but changing the style lines you can format it however you wish - or set a class instead and set the appearance in css.
Related
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>
I have a simple D3 simulation that looks like this
When I click the 'remove 1 and 4' button I want to remove nodes 1 and 4 from the simulation. The result, however, is the following:
Visually it seems to have removed 3 and 4 (not 1 and 4).
My code is below. Any ideas what I need to do to make this work correctly?
I'm assuming I've fundamentally misunderstood how updating nodes works in d3. Any help appreciated.
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<!DOCTYPE html>
<meta charset="utf-8">
<script type="text/javascript" src="http://d3js.org/d3.v3.js"></script>
<body>
<button>remove 1 and 4</button>
<script>
var width = 400,
height = 400;
var nodes = [1, 2, 3, 4].map(function(x) { return { name: x}});
var force = d3.layout.force()
.size([width, height])
.nodes(nodes)
.linkDistance(30)
.charge(-500)
.on("tick", tick);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var content = svg.append("g");
var nodesData = force.nodes(),
nodeElement = content.selectAll(".node");
function tick() {
nodeElement.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}
d3.selectAll('button').on('click', function() {
//remove 1 and 4
nodesData = [
nodesData[1],
nodesData[2]
]
refresh();
});
var WIDTH = 100;
//
// this logic is slightly modified from http://bl.ocks.org/tgk/6068367
//
function refresh() {
force.nodes(nodesData)
nodeElement = nodeElement.data(nodesData);
var nodeGroup = nodeElement.enter()
.append('g')
.attr("class", "node");
// node text
nodeGroup.append("text")
.attr("class", "nodetext")
.attr("dx", WIDTH/2)
.attr("dy", "14px")
.text(function(n) { return 'node '+n.name })
nodeElement.exit().remove();
force.start();
}
refresh();
</script>
You can solve your problem by adding a "key" function to the .data call inside the refresh function: nodeElement = nodeElement.data(nodesData, function(d){ return d.name });.
The problem you saw is not specific to updating nodes. Ordinarily, selections work based off of index of the data array. So if first D3 had [a,b,c,d] and now it has [a,d], it's going to take the first two elements ([a,b]) unless you tell it the key that defines each item in the array. That's what the function above does.
For more see https://github.com/d3/d3-selection/blob/master/README.md#selection_data
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;
});
I have problem adding text in my histogram. I can do this in more simple example.
I try to do this:
// try to add bar value
var barnum = g.selectAll('text')
.data(layout)
.enter()
.append("text")
.attr('y',-10)
.attr('x',10)
.attr("text-anchor", "middle")
.style("fill","black")
.text('testtest')
.style("pointer-events", "none")
;
barnum.transition();
I can't see any text in my figure. The code include definition is here:
var dateFormat = d3.time.format("%Y-%m-%d");
var g;
var data;
var margin = {top: 30, right: 30, bottom: 80, left: 80},
width = 500 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var cx = 10;
var numberBins = 5;
var dispatch = d3.dispatch(chart, "hover");
function chart(container) {
g = container;
update();
}
chart.update = update;
function update() {
// create hist layout
var hist = d3.layout.histogram()
.value(function(d) { return d.selectvar })
.range([d3.min(data, function(d){ return d.selectvar }) , d3.max(data, function(d){ return d.selectvar }) ])
.bins(numberBins);
var layout = hist(data);
var maxLength = d3.max(layout, function(d) { return d.length });
var widthScale = d3.scale.linear()
.domain([0, maxLength])
.range([0, width])
var yScale = d3.scale.ordinal()
.domain(d3.range(numberBins))
.rangeBands([height, 0], 0)
var colorScale = d3.scale.category20();
// create svg
var rects = g.selectAll("rect")
.data(layout)
rects.enter().append("rect")
rects .transition()
.duration(500)
.attr({
y: function(d,i) {
return yScale(i)
},
x: 50,
height: yScale.rangeBand(),
width: function(d,i) {
return widthScale(d.length)
},
fill: function(d, i) { return colorScale(i) }
});
rects.exit().transition().remove();
// try to add bar value
var barnum = g.selectAll('text')
.data(layout)
.enter()
.append("text")
.attr('y',-10)
.attr('x',10)
.attr("text-anchor", "middle")
.style("fill","black")
.text('testtest')
.style("pointer-events", "none")
;
barnum.transition();
is there something wrong with my way to create svg element? I found out some successful case use append('g') from the beginning. New to d3.js! thank you.
You're using d3.dispatch, which is documented on a page titled Internals. That doesn't mean you shouldn't use it, but rather, it shouldn't be your first choice.
You're correct that there's "something wrong with my way to create svg element" -- you're not creating one! Try:
var svg = d3.select("body").append("svg");
var g = svg.append("g");
At this point, you need to have a good understanding of the DOM, the SVG standard, CSS selectors, and D3's selection API to make things work. You don't tell D3 to put labels on your bars and that's it. You have to instruct it what elements to create, and where, keeping track of translates and offsets and stuff like that. You're best off copying and studying one of Mike Bostock's many examples.
D3 is not learned quickly. You need to invest time learning it before you can make any chart you like.
I try to change the radius of the circles with class 'minima' in a bubble-plot from dimple.js. However, only stroke and opacity get updated, but not the size.
chart.draw();
d3.selectAll(".minima")
.style("stroke", "#000000") // works
.attr("opacity", "0.2") // works
.attr("r", 25); // doesn't work
I don't see, what I am doing wrong here.
Background-info: I want only the 'minima-circles' to be bigger than the others without declaring a z-axis for the size, because I don't want this size-values to appear in the tooltip / pop-up.
Thanks for any idea!
Following from my comment above, this works but isn't very robust:
var svg = dimple.newSvg("body", 800, 600);
var data = [
{ "Word":"Hello", "Awesomeness":2000 },
{ "Word":"World", "Awesomeness":3000 }
];
var chart = new dimple.chart(svg, data);
chart.addCategoryAxis("x", "Word");
chart.addMeasureAxis("y", "Awesomeness");
var s = chart.addSeries("Word", dimple.plot.bubble);
chart.draw();
setTimeout(function () {
svg.selectAll("circle.Hello")
.attr("opacity", 0.2)
.attr("r", 100);
}, 100);
http://jsfiddle.net/T6ZDL/4/