Here is my code, i am adding a series of rectangle - http://jsfiddle.net/nikunj2512/74qrC/6/
i want to add a slider like a image slider which has left and right arrow buttons for navigation or something similar, so that user can navigate through the rectangle boxes.
I don't know how to achieve this thing.
This is the d3.js code which is creating the rectangle boxes:
var width = 4000,
height = 200,
margin = 2,
nRect = 20,
rectWidth = (width - (nRect - 1) * margin) / nRect,
svg = d3.select('#chart').append('svg')
.attr('width', width)
.attr('height', height);
var data = d3.range(nRect),
posScale = d3.scale.linear()
.domain(d3.extent(data))
.range([0, width - rectWidth]);
svg.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('x', posScale)
.attr('width', rectWidth)
.attr('height', height);
This was fun... So, I think I suggested this approach in your other question, but basically I apply a clip path to the rectangles:
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", clipWidth)
.attr("height", clipHeight);
var g = svg.append("g");
g.selectAll("rect").data(data)
.enter()
.append('rect')
.attr("class", "area").attr("clip-path", "url(#clip)")
.attr('x', xScale)
.attr('width', rectWidth)
.attr('height', rectHeight)
.style('fill', d3.scale.category20());
...and then I skew the domain up or down and update the plot using a transition with a delay of 500ms:
var update = function(){
g.selectAll("rect")
.transition().duration(500)
.attr('x', xScale);
};
d3.select("#left").on("click", function(){
xScale.domain([xScale.domain()[0] - 1, xScale.domain()[1] - 1]);
update();
});
and voila, a working jsFiddle: http://jsfiddle.net/reblace/74qrC/9/
Now, the trick is to load images in those boxes, but you should be able to do some googling for how to apply images to svg elements and there's plenty of resources out there to help you do that.
Related
I am trying to convert a bubble chart from d3v3 to v4. Running into x,y,d missing variables?
In this version -- a rect is applied to the svg - and then a circle is cut -- so its like an inverse bubble chart.
I am keen to work out a set radius for the chart as a maxium -- if it should act like a score between 0 and 100? What kind of math to apply that a max radius has been reached to signify that the value is very big?
I also tried to have the svg mask adapt - if the browser or its container changed size -- ideally would want it to response during the change - rather than resizeEnd
//version 3
https://jsfiddle.net/8ag1vf6e/1/
//current version 4
https://jsfiddle.net/d56g9r0y/
// filters go in defs element
var defs = innversebubble.append("defs");
var mask = defs.append("mask")
.attr("id", "myMask");
mask.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", width)
.attr("height", height)
.style("fill", "white")
.style("opacity", 1);
var invisiblebubble = mask.append("circle")
.data(data);
//create a fixed bubble first
invisiblebubble
.attr("cx", "50%")
.attr("cy", "50%")
.attr("r", function(d) {
return d.value - 20;
});
//now mask the fixed circle
var masker = defs.append(function() {
return mask.node().cloneNode(true)
})
.attr("id", "myMaskForPointer")
.select("rect")
.style("fill", "white")
.style("opacity", 1);
invisiblebubble
.attr("r", 10);
//apply the rest of the chart elements
var rect = innversebubble
.attr("class", "series")
.append("g")
.attr("transform", "translate(0,0)")
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", "100%")
.attr("height", "100%")
.attr("mask", "url(#myMask)")
.style("fill", backcolor)
.style("opacity", backopacity);
//animate this circle
invisiblebubble
.attr("cx", "50%")
.attr("cy", "50%")
.transition()
.duration(1800)
.attr("r", 10)
.transition()
.duration(900)
.attr("r", function(d) {
return d.value;
});
latest jsfiddle - 15th June -- needs fixing
https://jsfiddle.net/xmrtahns/
"I am keen to work out a set radius for the chart as a maxium -- if it should act like a score between 0 and 100? What kind of math to apply that a max radius has been reached to signify that the value is very big?
I also tried to have the svg mask adapt - if the browser or its container changed size -- ideally would want it to response during the change - rather than resizeEnd"
I've fixed the conversion and the data source - but still need issues to resolve.
var backcolor = $this.data("color");
var backopacity = $this.data("opacity");
var width = $this.data("width");
var height = $this.data("height");
var data = [{
"label": $this.data("label-name"),
"centralLabel": $this.data("central-label"),
"xPer": $this.data("displace-left"),
"yPer": $this.data("displace-top"),
"value": $this.data("bubble-value")
}];
http://jsfiddle.net/hLymw8et/2/
--I am keen to work out a set radius for the chart as a maximum -- if it should act like a score between 0 and 100?
--What kind of math to apply that a max radius has been reached to signify that the value is very big?
--I also tried to have the svg mask adapt - if the browser or its container changed size -- ideally would want it to response during the change - rather than resizeEnd –
In this jsFiddle I combine D3 with interact.js, both working on SVG. There's a group that contains a rect and an image. The group class is resizable and that works fine. The problem is that the rect, when resized, should clip the image (i.e. the image should never be out of the rectangle borders) but it does not. I use a D3 clipPath for that, but it's not working. What is the problem?
var svg = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 200);
var g = svg.append('g')
.attr('class', 'resize-me');
var rect = g.append('rect')
.attr('stroke', 'blue')
.attr('x', 0)
.attr('y', 0)
.attr("width", 200)
.attr("height", 200)
.attr('stroke-width', 2)
.attr('stroke', 'white')
.attr('fill', 'grey');
var image = g.append('image')
.attr('x', 0)
.attr('y', 0)
.attr('width', 128)
.attr('height', 128)
.attr("xlink:href", imageUrl);
interact('.resize-me')
.resizable({
edges: { left: true, right: true, bottom: true, top: true }
})
.on('resizemove', function(event) {
var target = event.target;
var rect = target.childNodes[0];
var img = target.childNodes[1];
var x = (parseFloat(target.getAttribute('endx')) || 0)
var y = (parseFloat(target.getAttribute('endy')) || 0)
rect.setAttribute('width', event.rect.width);
rect.setAttribute('height', event.rect.height);
x += event.deltaRect.left
y += event.deltaRect.top
rect.setAttribute('transform', 'translate(' + x + ', ' + y + ')')
rect.setAttribute('endx', x)
rect.setAttribute('endy', y)
// clip image
svg.append('defs')
.append('clipPath')
.attr('id', 'clip2')
.append('rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', event.rect.width)
.attr('height', event.rect.height);
image.attr("clip-path", "url(#clip2)");
});
Do you want the image to always expand to fill the grey box? https://jsfiddle.net/alexander_L/u607w315/12/ (version 12 for image expanding)
Also, you are appending <def/> tags every time the event fires:
It gets quickly out of hand.
You should either attach the <def/> to some dummy one element array data and use the d3.js update pattern or simpler you could just create the <def/> tag in your source code and update the attribute of that same tag each time.
You can do it once at the start:
var def = svg.append('defs')
.append('clipPath')
.attr('id', 'clip2')
.append('rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', 200)
.attr('height', 200);
And then use this variable and update during your event:
def.attr('x', 0)
.attr('y', 0)
.attr('width', event.rect.width)
.attr('height', event.rect.height);
Then you avoid this issue.
https://jsfiddle.net/alexander_L/u607w315/11/ (version 11 for image clipping)
Do you want this behaviour:
The image is clipped when the grey box is smaller in one dimension than the image?
UPDATE
Since the OP noticed a bug in the original code which causes the grey box to always snap back to at least the height or width of the image, I tried to also solve this problem.
However, I also noticed some odd behaviour, that the top left corner of the box could not be extended further up or left so I fixed that first: https://jsfiddle.net/alexander_L/u607w315/25/
See the .gif of the new behaviour and the old bug the OP mentioned:
I have a map-container which shows world map, that is supposed to be responsive but it does not work when I run it and resize the page, I cant find whats the issue. what it is expected when screen is resized. here's my code
var tooltipMap = d3.select("body")
.append("div")
.attr("class", "map-tooltip shadow")
.style("opacity", 0);
var projection = d3.geo.mercator()
.center([0, 62 ])
.scale(150)
.rotate([0, 0]);
var svg = d3.select("#map-container").append("svg")
.attr("width", width)
.attr("height", height)
.attr('viewBox', "0 0 "+(width)+" "+(height))
.attr('preserveAspectRatio', 'xMinYMid')
.attr("class","chart");
var path = d3.geo.path()
.projection(projection);
var defs = svg.append('svg:defs');
defs.append("svg:pattern")
.attr("id", "bgImg")
.attr("width", 6)
.attr("height", 6)
.attr("patternUnits", "userSpaceOnUse")
.append("svg:image")
.attr("xlink:href", '../assets/img/map-tile.png')
.attr("width", 6)
.attr("height", 6)
.attr("x", 0)
.attr("y", 0);
var g = svg.append("g")
.attr("width", width+160)
.attr("height", height);
// load and display the World
d3.json("om.json", function (error, topology) {
g.selectAll("path")
.data(topojson.object(topology, topology.objects.countries).geometries)
.enter()
.append("path")
.attr("fill", "url(#bgImg)")
.attr("d", path);
});
<script src="js/d3.v3.min.js"></script>
<div id="map-container"></div>
Basically as of what appears,
.attr("class","chart");
does not work, wondering why ?
Solution -
CSS - strange as hell, but this was the issue my css was not placed perfectly. !!! Facepalm !!!
.chart{ width:100%; height:100%; }
I'm trying to rotate rectangles according to some data. With, the following code, the rotation applies to the whole line. How can I get each "rect" to have his own rotation applied, keeping them on the same line?
let canevas = d3.select("body")
.append("svg")
.attr("width", 1000)
.attr("height", 1000);
let rectangles = d3.select("svg")
.selectAll("rect")
.data([10, 20, 30, 40])
.enter()
.append("rect")
.attr("x", (d, i) => (i * 30) + 30)
.attr("y", 20)
.attr("width", 20)
.attr("height", 20)
.attr("transform", (d) => `rotate(${d})`);
That's the normal behaviour of rotate, since the rotate function of the translate attribute rotates the elements around the origin of the coordinates system (0,0).
An easy solution is setting the positions in the same translate:
let canevas = d3.select("body")
.append("svg")
.attr("width",300)
.attr("height",100);
let rectangles = d3.select("svg")
.selectAll("rect")
.data([10,20,30,40])
.enter()
.append("rect")
.attr("width",20)
.attr("height",20)
.attr("transform", (d,i) => `translate(${(i*30)+30},30) rotate(${d})`);
<script src="https://d3js.org/d3.v4.min.js"></script>
I have a vertical bar chart that is grouped in pairs. I was trying to play around with how to flip it horizontally. In my case, the keywords would appear on the y axis, and the scale would appear on the x-axis.
I tried switching various x/y variables, but that of course just produced funky results. Which areas of my code do I need to focus on in order to switch it from vertical bars to horizontal ones?
My JSFiddle: Full Code
var xScale = d3.scale.ordinal()
.domain(d3.range(dataset.length))
.rangeRoundBands([0, w], 0.05);
// ternary operator to determine if global or local has a larger scale
var yScale = d3.scale.linear()
.domain([0, d3.max(dataset, function (d) {
return (d.local > d.global) ? d.local : d.global;
})])
.range([h, 0]);
var xAxis = d3.svg.axis()
.scale(xScale)
.tickFormat(function (d) {
return dataset[d].keyword;
})
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(5);
var commaFormat = d3.format(',');
//SVG element
var svg = d3.select("#searchVolume")
.append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Graph Bars
var sets = svg.selectAll(".set")
.data(dataset)
.enter()
.append("g")
.attr("class", "set")
.attr("transform", function (d, i) {
return "translate(" + xScale(i) + ",0)";
});
sets.append("rect")
.attr("class", "local")
.attr("width", xScale.rangeBand() / 2)
.attr("y", function (d) {
return yScale(d.local);
})
.attr("x", xScale.rangeBand() / 2)
.attr("height", function (d) {
return h - yScale(d.local);
})
.attr("fill", colors[0][1])
;
sets.append("rect")
.attr("class", "global")
.attr("width", xScale.rangeBand() / 2)
.attr("y", function (d) {
return yScale(d.global);
})
.attr("height", function (d) {
return h - yScale(d.global);
})
.attr("fill", colors[1][1])
;
sets.append("rect")
.attr("class", "global")
.attr("width", xScale.rangeBand() / 2)
.attr("y", function (d) {
return yScale(d.global);
})
.attr("height", function (d) {
return h - yScale(d.global);
})
.attr("fill", colors[1][1])
;
I just did the same thing last night, and I basically ended up rewriting the code as it was quicker than fixing all the bugs but here's the tips I can give you.
The biggest issues with flipping the x and y axis will be with things like return h - yScale(d.global) because height is calculated from the "top" of the page not the bottom.
Another key thing to remember is that when you set .attr("x", ..) make sure you set it to 0 (plus any padding for the left side) so = .attr("x", 0)"
I used this tutorial to help me think about my own code in terms of horizontal bars instead - it really helped
http://hdnrnzk.me/2012/07/04/creating-a-bar-graph-using-d3js/
here's my own code making it horizontal if it helps:
var w = 600;
var h = 600;
var padding = 30;
var xScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d){
return d.values[0]; })]) //note I'm using an array here to grab the value hence the [0]
.range([padding, w - (padding*2)]);
var yScale = d3.scale.ordinal()
.domain(d3.range(dataset.length))
.rangeRoundBands([padding, h- padding], 0.05);
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h)
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", 0 + padding)
.attr("y", function(d, i){
return yScale(i);
})
.attr("width", function(d) {
return xScale(d.values[0]);
})
.attr("height", yScale.rangeBand())
An alternative is to rotate the chart (see this). This is a bit hacky as then you need to maintain the swapped axes in your head (the height is actually the width etc), but it is arguably simpler if you already have a working vertical chart.
An example of rotating the chart is below. You might need to rotate the text as well to make it nice.
_chart.select('g').attr("transform","rotate(90 200 200)");
Here is the procedure I use in this case:
1) Inverse all Xs and Ys
2) Remember that the 0 for y is on top, thus you will have to inverse lots of values as previous values for y will be inversed (you don't want your x axis to go from left to right) and the new y axis will be inversed too.
3) Make sure the bars display correctly
4) Adapt legends if there are problems
This question may help in the sense that it shows how to go from horizontal bar charts to vertical: d3.js histogram with positive and negative values