I'm doing some tests with d3.js regarding zooming. At the moment, I have successfully implemented geometric zoom in my test, but it has a drawback: the elements under the zoomed g are being scaled. As I understood it, this could be solved by using semantic zooming.
The problem is that I need scale in my test, as I'm syncing it with a jQuery.UI slider value.
On the other hand, I would like the text elements being resized to maintain their size after a zoom operation.
I have an example of my current attempt here.
I'm having trouble changing my code to fit this purpose. Can any one share some insight/ideas?
For your solution I have merged 2 examples:
Semantic Zooming
Programmatic Zooming
Code snippets:
function zoom() {
text.attr("transform", transform);
var scale = zoombehavior.scale();
//to make the scale rounded to 2 decimal digits
scale = Math.round(scale * 100) / 100;
//setting the slider to the new value
$("#slider").slider( "option", "value", scale );
//setting the slider text to the new value
$("#scale").val(scale);
}
//note here we are not handling the scale as its Semantic Zoom
function transform(d) {
//translate string
return "translate(" + x(d[0]) + "," + y(d[1]) + ")";
}
function interpolateZoom(translate, scale) {
zoombehavior
.scale(scale)//we are setting this zoom only for detecting the scale for slider..we are not zoooming using scale.
.translate(translate);
zoom();
}
var slider = $(function() {
$("#slider").slider({
value: zoombehavior.scaleExtent()[0],//setting the value
min: zoombehavior.scaleExtent()[0],//setting the min value
max: zoombehavior.scaleExtent()[1],//settinng the ax value
step: 0.01,
slide: function(event, ui) {
var newValue = ui.value;
var center = [centerX, centerY],
extent = zoombehavior.scaleExtent(),
translate = zoombehavior.translate(),
l = [],
view = {
x: translate[0],
y: translate[1],
k: zoombehavior.scale()
};
//translate w.r.t the center
translate0 = [(center[0] - view.x) / view.k, (center[1] - view.y) / view.k];
view.k = newValue;//the scale as per the slider
//the translate after the scale(so we are multiplying the translate)
l = [translate0[0] * view.k + view.x, translate0[1] * view.k + view.y];
view.x += center[0] - l[0];
view.y += center[1] - l[1];
interpolateZoom([view.x, view.y], view.k);
}
});
});
I am zooming w.r.t. 250,250 which is the center of the clip circle.
Working code here (have added necessary comments)
Hope this helps!
To do what you want, you need to refactor the code first a little bit. With d3, it is good practice to use data() to append items to a selection, rather than using for loops.
So this :
for(i=0; i<7; i++){
pointsGroup.append("text")
.attr("x", function(){
var plusOrMinus = Math.random() < 0.5 ? -1 : 1;
var randx = Math.random();
return Math.floor(plusOrMinus*randx*75)+centerx;
})
.attr("y", function(){
var plusOrMinus = Math.random() < 0.5 ? -1 : 1;
var randy = Math.random();
return Math.floor(plusOrMinus*randy*75)+centery;
})
.html("star")
.attr("class", "point material-icons")
.on("click", function(){console.log("click!");});
}
Becomes this
var arr = [];
for(i=0; i<7; i++){
var plusOrMinus = Math.random() < 0.5 ? -1 : 1;
var randx = Math.random();
var x = Math.floor(plusOrMinus*randx*75)+centerx;
var plusOrMinus = Math.random() < 0.5 ? -1 : 1;
var randy = Math.random();
var y = Math.floor(plusOrMinus*randy*75)+centery;
arr.push({"x":x,"y":y});
}
pointsGroup.selectAll("text")
.data(arr)
.enter()
.append("text")
.attr("x", function(d,i){
return d.x;// This corresponds to arr[i].x
})
.attr("y", function(d,i){
return d.y;// This corresponds to arr[i].y
})
.html("star")
.attr("class", "point material-icons")
.on("click", function(){console.log("click!");});
This way, you can access individual coordinates using for instance.attr("x",function(d,i){ //d.x is equal to arr.x, i is item index in the selection});
Then, to achieve your goal, rather than changing the scale of your items, you should use a linear scale to change each star position.
First, add linear scales to your fiddle and apply them to the zoom:
var scalex = d3.scale.linear();
var scaley = d3.scale.linear();
var zoom = d3.behavior.zoom().x(scalex).y(scaley).scaleExtent([1, 5]).on('zoom', onZoom);
And finally on zoom event apply the scale to each star's x and y
function onZoom(){
d3.selectAll("text")
.attr("x",function(d,i){
return scalex(d.x);
})
.attr("y",function(d,i){
return scaley(d.y);
});
}
At this point, zoom will work without the slider. To add the slider, simply change manually the zoom behavior scale value during onSlide event, then call onZoom.
function onSlide(scale){
var sc = $("#slider").slider("value");
zoom.scale(sc);
onZoom();
}
Note: I used this config for the slider:
var slider = $(function() {
$( "#slider" ).slider({
value: 1,
min: 1,
max: 5,
step: 0.1,
slide: function(event, ui){
onSlide(5/ui.value);
}
});
});
Please note that at this point the zoom from the ui is performed relative to (0,0) at this point, and not your "circle" window center. To fix it I simplified the following function from the programmatic example, that computes valid translate and scale to feed to the zoom behavior.
// To handle center zooming
var width = 500;
var height = 600;
function zoomClick(sliderValue) {
var center = [width / 2, height / 2],
extent = zoom.scaleExtent(),
translate = zoom.translate();
var view = {
x: zoom.translate()[0],
y: zoom.translate()[1],
k: zoom.scale()
};
var target_zoom = sliderValue;
if (target_zoom < extent[0] || target_zoom > extent[1]) {
return false;
}
var translate0 = [(center[0] - view.x) / view.k, (center[1] - view.y) / view.k];
view.k = target_zoom;
var l = [];
l = [translate0[0] * view.k + view.x, translate0[1] * view.k + view.y];
view.x += center[0] - l[0];
view.y += center[1] - l[1];
// [view.x view.y] is valid translate
// view.k is valid scale
// Then, simply feed them to the zoom behavior
zoom
.scale(view.k)
.translate([view.x, view.y]);
// and call onZoom to update points position
onZoom();
}
then just change onSlide to use this new function every time slider moves
function onSlide(scale){
var sc = $("#slider").slider("value");
zoomClick(sc);
}
Full snippet
function onZoom(){
d3.selectAll("text")
.attr("x",function(d,i){
return scalex(d.x);
})
.attr("y",function(d,i){
return scaley(d.y);
});
}
function onSlide(scale){
var sc = $("#slider").slider("value");
zoomClick(sc);
}
var scalex = d3.scale.linear();
var scaley = d3.scale.linear();
var zoom = d3.behavior.zoom().x(scalex).y(scaley).scaleExtent([1, 5]).on('zoom', onZoom);
var svg = d3.select("body").append("svg")
.attr("height", "500px")
.attr("width", "500px")
.call(zoom)
.on("mousedown.zoom", null)
.on("touchstart.zoom", null)
.on("touchmove.zoom", null)
.on("touchend.zoom", null);
var centerx = 250,
centery = 250;
var circleGroup = svg.append("g")
.attr("id", "circleGroup");
var circle = circleGroup.append("circle")
.attr("cx", "50%")
.attr("cy", "50%")
.attr("r", 150)
.attr("class", "circle");
var pointsParent = svg.append("g").attr("clip-path", "url(#clip)").attr("id", "pointsParent");
var pointsGroup = pointsParent.append("g")
.attr("id", "pointsGroup");
var arr = [];
for(i=0; i<7; i++){
var plusOrMinus = Math.random() < 0.5 ? -1 : 1;
var randx = Math.random();
var x = Math.floor(plusOrMinus*randx*75)+centerx;
var plusOrMinus = Math.random() < 0.5 ? -1 : 1;
var randy = Math.random();
var y = Math.floor(plusOrMinus*randy*75)+centery;
arr.push({"x":x,"y":y});
}
pointsGroup.selectAll("text")
.data(arr)
.enter()
.append("text")
.attr("x", function(d,i){
return d.x;// This corresponds to arr[i].x
})
.attr("y", function(d,i){
return d.y;// This corresponds to arr[i].y
})
.html("star")
.attr("class", "point material-icons")
.on("click", function(){console.log("click!");});
zoom(pointsGroup);
var clip = svg.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:circle")
.attr("id", "clip-circ")
.attr("cx", centerx)
.attr("cy", centery)
.attr("r", 149);
var slider = $(function() {
$( "#slider" ).slider({
value: 1,
min: 1,
max: 5,
step: 0.1,
slide: function(event, ui){
onSlide(5/ui.value);
}
});
});
// To handle center zooming
var width = 500;
var height = 600;
function zoomClick(sliderValue) {
var target_zoom = 1,
center = [width / 2, height / 2],
extent = zoom.scaleExtent(),
translate = zoom.translate();
var view = {x: zoom.translate()[0], y: zoom.translate()[1], k: zoom.scale()};
target_zoom = sliderValue;
if (target_zoom < extent[0] || target_zoom > extent[1]) { return false; }
var translate0 = [];
translate0 = [(center[0] - view.x) / view.k, (center[1] - view.y) / view.k];
view.k = target_zoom;
var l = [];
l = [translate0[0] * view.k + view.x, translate0[1] * view.k + view.y];
view.x += center[0] - l[0];
view.y += center[1] - l[1];
zoom
.scale(view.k)
.translate([view.x, view.y]);
onZoom();
}
body {
font: 10px sans-serif;
}
text {
font: 10px sans-serif;
}
.circle{
stroke: black;
stroke-width: 2px;
fill: white;
}
.point{
fill: goldenrod;
cursor: pointer;
}
.blip{
fill: black;
}
#slider{
width: 200px;
margin: auto;
}
<!DOCTYPE html>
<html>
<head>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons"
rel="stylesheet">
<link href="https://code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css" rel="stylesheet" type="text/css" />
<script src="https://code.jquery.com/jquery-1.11.3.js"></script>
<script src="https://code.jquery.com/ui/1.11.4/jquery-ui.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script>
<meta charset=utf-8 />
<title>d3.JS slider zoom</title>
</head>
<body>
<div id="slider"></div>
</body>
</html>
Related
I'm currently working on a circular progress bar. I've been able to create the progress bar but I need to at border-radius to the end and start of the progress and also change de font-size of the % number. I've searched around and in theory, the border-radius is added with stroke-linecap="round", but that doesn't seem to work for me. I haven't been able to find anything regarding the font-size.Adding a shadow to the bar would also be great, but that's not truly necessary.
I have already looked at this answer but I can't seem to get it right.
function drawProgress(percentage, element, svg) {
if (svg) {
svg.selectAll("*").remove();
}
var wrapper = element;
var start = 0;
var colours = {
fill: '#3F88FB',
track: '#DDDDDD',
text: '#444444',
}
var radius = 34;
var border = 8;
var strokeSpacing = 4;
var endAngle = Math.PI * 2;
var formatText = d3.format('.0%');
var boxSize = radius * 2;
var count = percentage;
var progress = start;
var step = percentage < start ? -0.01 : 0.01;
//Define the circle
var circle = d3.svg.arc()
.startAngle(0)
.innerRadius(radius)
.outerRadius(radius - border);
//setup SVG wrapper
svg = d3.select(wrapper)
.append('svg')
.attr('width', boxSize)
.attr('height', boxSize);
// ADD Group container
var g = svg.append('g')
.attr('transform', 'translate(' + boxSize / 2 + ',' + boxSize / 2 + ')');
//Setup track
var track = g.append('g').attr('class', 'radial-progress');
track.append('path')
.attr('fill', colours.track)
.attr('stroke-width', strokeSpacing + 'px')
.attr('d', circle.endAngle(endAngle));
//Add colour fill
var value = track.append('path')
.attr('fill', colours.fill)
.attr('stroke-width', strokeSpacing + 'px')
.attr('stroke-linecap', 'round');
//Add text value
var numberText = track.append('text')
.attr('fill', colours.text)
.attr('text-anchor', 'middle')
.attr('dy', '0.5rem');
//update position of endAngle
value.attr('d', circle.endAngle(endAngle * percentage));
//update text value
numberText.text(formatText(percentage));}
var svgVisitas;
drawProgress(50 / 100, document.getElementById('radialprogressVisitas'), svgVisitas);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="radialprogressVisitas"></div>
Thank you. If you need anything else, please specify it in the comments and I'll edit the question. The project is run on Visual Basic.
I'm a freshman using js and d3. How can I zoom a scatter plot matrix chart?
What I did,
I used svg to show the scatter plot matrix, following the example https://bl.ocks.org/Fil/6d9de24b31cb870fed2e6178a120b17d
Since the performance was too bad when the records over 10 thousands and the matrix size is 10*10, I changed the point draw with canvas
Axes are using svg and the dots are drew by canvas
Even if it spends some time to draw the chart with canvas, the page will not hang and oom when running 10*10 matrix over 10 thousands records
I'm not sure it's a formal way to do this.
On the other hand, I want to zoom this chart but I have no idea how can I do this.
From my understanding,
The axes splits into several parts according to the number of matrix, such as 10, including x-axis and y-axis
If I want to rescale each matrix cell, such as the cell on row 0 and column 0, how can I do this? I just tired with d3.event.transform.rescaleX/rescaleY to rescale the axes, this seems to work but how can I do on the canvas, how can I get the valid dots to redraw?
On the other hand, if I only want to zoom the whole chart not the single cell(That means, if I click on cell(0,0), this cell will zoom until it fill the whole chart and other cells will not be seen), how can I do this? I used modal to show the scaled large svg by viewBox="0 0 ' + width * scalar + ' ' + height, is there any other way to show image in large format?
draw_spm = function(data) {
var width = 700, traits = d3.keys(data[0]),
domain = {}, n = traits.length,
pointRadius = 1;
var size = width / n,
padding = size / 10;
var x = d3.scaleLinear().range([padding / 2, size - padding / 2]),
y = d3.scaleLinear().range([size - padding / 2, padding / 2]);
var x_axis = d3.axisBottom().scale(x).ticks(6),
y_axis = d3.axisLeft().scale(y).ticks(6);
traits.forEach(function(t) {
domain[t] = d3.extent(data, function(d) { return d[t]; });
});
x_axis.tickSize(size * n);
y_axis.tickSize(-size * n);
var zoom = d3.zoom()
.on('zoom', zoomed);
var svg = d3.select('#spm-svg')
.attr('class', 'plot svg-scatterplot')
.attr('width', size * n + 4*padding)
.attr('height', size * n + 4*padding)
.append('g')
.attr('transform', 'translate('+4*padding+','+padding/2+')');
var x_axis_svg = svg.selectAll('.x.axis')
.data(traits)
.enter().append('g')
.attr('class', 'x axis')
.attr('transform', function(d, i) { return 'translate('+size*i+',0)'; })
.each(function(d) { x.domain(domain[d]); d3.select(this).call(x_axis); });
var y_axis_svg = svg.selectAll('.y.axis')
.data(traits)
.enter().append('g')
.attr('class', 'y axis')
.attr('transform', function(d, i) { return 'translate(0,'+size*i+')'; })
.each(function(d) { y.domain(domain[d]); d3.select(this).call(y_axis); });
var canvas = d3.select('#spm-canvas')
.attr('width', size*n+4*padding)
.attr('height', size*n+4*padding)
.style('transform', 'translate('+4*padding+','+padding/2+')');
var ctx = canvas.node().getContext('2d');
ctx.fillStyle = 'steelblue';
var cell = svg.selectAll('.cell')
.data(cross(traits, traits))
.enter().append('g')
.attr('class', 'cell')
.attr('transform', function(d) {
return 'translate(' + d.i*size + ',' + d.j*size + ')';
})
.each(draw);
canvas.call(zoom).on('dblclick.zoom', null);
function draw(p) {
var cell = d3.select(this);
ctx.resetTransform();
ctx.transform(1, 0, 0, 1, p.i*size+4*padding, p.j*size+padding/2);
x.domain(domain[p.x]);
y.domain(domain[p.y]);
function draw_point(d) {
ctx.beginPath();
ctx.arc(x(d[p.x]), y(d[p.y]), pointRadius, 0, 2*Math.PI);
ctx.closePath();
ctx.fill();
ctx.stroke();
}
cell.append('rect')
.attr('class', 'frame')
.attr('x', padding / 2)
.attr('y', padding / 2)
.attr('width', size - padding)
.attr('height', size - padding);
data.forEach(function(d) {
draw_point(d);
});
}
function zoomed() {
// how to do this?
};
function cross(a, b) {
var c = [], n = a.length, m = b.length, i, j;
for (i = -1; ++i < n;)
for (j = -1; ++j < m;)
c.push({x: a[i], i: i, y: b[j], j: j});
return c;
}
};
cols = ['x0','x1','x2','x3','x4'];
function _data() {
var d = {};
for (var i = 0; i < cols.length; i++) {
d[cols[i]] = Math.floor(Math.random() * 10000);
}
return d;
}
var data = [];
for (var i = 0; i < 1000; i++) {
data[i] = _data();
}
draw_spm(data);
.svg-scatterplot .axis,.frame {shape-rendering:crispEdges;}
.svg-scatterplot .axis line {stroke:#ddd;}
.svg-scatterplot .axis path {display:none;}
.svg-scatterplot .cell text {font-weight:bold;text-transform: capitalize;fill: black;}
.svg-scatterplot .frame {fill:none;stroke:#aaa;}
.svg-scatterplot circle {fill-opacity:.7;}
.svg-scatterplot circle.hidden {fill:#ccc !important;}
.svg-scatterplot .extent {fill:#000;fill-opacity:.125;stroke:#fff;}
.plot {position: absolute;}
#spm-canvas {z-index: 2;}
#spm-svg {z-index: 1;}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg id="spm-svg" class="plot"></svg>
<canvas id="spm-canvas" class="plot"></canvas>
Thanks for your help
Inspired by https://www.nytimes.com/interactive/2018/03/19/upshot/race-class-white-and-black-men.html
I am trying to create a animation that will have markers move from one point to multiple levels in another point in the y axis. Thanks to detailed pages by Amelia and Mike Bostock in both bl.ocks.org and stackoverflow. I have got so far to get the circles and animate it. But, I am not able to make each marker loop over by the pathlevel and do the transition
Pathlevel, here, indicates whether they are high, middle or low (1,2,3).
The entire code in using d3.v4 has been pasted below. what I am missing?
Thanks for your help.
<!DOCTYPE html>
<meta charset="utf-8">
<title>SANKEY Experiment</title>
<style>
</style>
<body>
<script type="text/javascript" src="d3.v4.js"></script>
<script>
//ref: very important for path animation: https://stackoverflow.com/questions/21226523/trace-path-with-dom-object/21228701#21228701
//ref: clustered force layout: https://bl.ocks.org/mbostock/7881887
//ref: data manipulation: http://learnjsdata.com/iterate_data.html
var margin = {top: 30, right: 10, bottom: 30, left: 20},
width = 500 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var series = [{x:5,y:10},{x:150,y:10}],
series2 = [{x:5,y:10},{x:50,y:15},{x:100,y:30},{x:150,y:30}],
series3 = [{x:5,y:10},{x:50,y:22},{x:100,y:50},{x:150,y:50}];
var data = [{"dim":"a","pos":"high","pathlevel":1,"x1":1,"y1":10,"x2":150,"y2":8},
{"dim":"b","pos":"high","pathlevel":1,"x1":1,"y1":10,"x2":150,"y2":8},
{"dim":"a","pos":"mid","pathlevel":2,"x1":1,"y1":10,"x2":150,"y2":28},
{"dim":"b","pos":"mid","pathlevel":2,"x1":1,"y1":10,"x2":150,"y2":28},
{"dim":"a","pos":"low","pathlevel":3,"x1":1,"y1":10,"x2":150,"y2":48},
{"dim":"b","pos":"low","pathlevel":3,"x1":1,"y1":10,"x2":150,"y2":48}]
var x = d3.scaleLinear()
.domain([5,150])
.range([0,width]);
var y = d3.scaleLinear()
.domain([10,50])
.range([0,height]);
var line = d3.line()
.curve(d3.curveCardinal)
.x(function(d) { return x(d.x); })
.y(function(d) { return y(d.y); });
var svg = d3.select("body").append("svg").attr("height",600).attr("width",600);
var chart = svg.append("g").attr("transform","translate("+margin.left+","+margin.top+")");
var update = function(series,k){
chart.append("path")
.attr("class","route"+k)
.attr("fill","none")
.attr("stroke","blue")
.attr("d",line(series));
}
update(series,1);
update(series2,2);
update(series3,3);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//create transistions along the path //////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
var colorScale = d3.scaleOrdinal()
.domain(['a','b'])
.range(['orange','darkblue']);
//Get path start point for placing marker
var path = d3.select('.route1')
var startPoint = path.attr("d").split(",")[1];
//path example["M12.885906040268456", "84.48979591836735C12.885906040268456", "84.48979591836735", "241.07382550335572", "84.48979591836735",
//"318.9261744966443", "84.48979591836735C396.7785234899329", "84.48979591836735", "480", "84.48979591836735", "480", "84.48979591836735"]
//selecting class route which represents the path. d represents the path that is held by the path object. in that we split by comma and take the first
console.log(startPoint);
var glider = function(data,p){//p for path level
var marker = chart.selectAll(".marker")
.data(data)
.enter().append('circle')
.attr("class","marker")
.attr("fill",function(d){ return colorScale(d.dim);})
//.attr("x",function(d,i){return x(d.x1)+i*10;})
//.attr("y",function(d,i){return y(d.y1)+i*10;})
.attr("r",5);
//.attr("width",10)
//.attr("height",10);
var simulation = d3.forceSimulation()
.force("x",d3.forceX().strength(0.05))
.force("y",d3.forceY().strength(0.01))
.force('charge', d3.forceManyBody().strength(20))
.force("collide",d3.forceCollide(function(d){return y(d.y1)+4;}))
.alphaTarget(.03)
.restart();
simulation.nodes(data)
.on('tick',ticked);
function ticked(){
marker
.attr("cx",function(d){ return d.x;})
.attr("cy",function(d){ return d.y;})
}//end of ticked
//marker.transition().duration(3000).delay(200)
// .attr("x",function(d,i){return x(d.x2)+i*10;});
function translateAlong(path) {
var l = path.getTotalLength();
return function (d) {
return function (t) {
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")";//Move marker
}
}
}//end of translateAlong
console.log(marker);
function transition(){
var path2 = d3.select('.route'+p);
marker.attr("transform", "translate(" + startPoint + ")").transition().duration(3000).delay(function(d,i) { return i * 100; })
.attrTween("transform",translateAlong(path2.node()));
//.attr("x",function(d,i){return x(d.x2)+i*10;});
}//end of transition
transition();
}
/*var check = d3.map(data, function(d){return d.pathlevel;}).keys(); //for getting unique values from a column
check.forEach(function(i){
datapoints = data.filter(function(d){return d.pathlevel==i});
console.log(i);
glider(datapoints,i);
});*/
data1 = data.filter(function(d){return d.pathlevel==1});
data2 = data.filter(function(d){return d.pathlevel==2});
data3 = data.filter(function(d){return d.pathlevel==3});
//glider(data1,1);
//glider(data2,2);
glider(data3,3);
//glider(data,2);
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*function createPathTween(d, i, a) {
var path = this.parentNode.getElementsByTagName("path")[1];
//i.e., go from this <circle> -> parent <g> -> array of child <path> elements
//-> first (and only) element in that array
//console.log(path);
var l = path.getTotalLength();
return function(t) {
var p = path.getPointAtLength(t * l);
console.log(p);
return "translate(" + p.x + "," + p.y + ")";
};
}//end of tweenpath*/
</script>
</body>
I am working on horizontal segment bar chart. I want to make it so that the bar chart will animate the colour transition between individual segments depending on the value that is generated randomly every few seconds.
At the beginning I set two variables midRange and highRange that split my segments into 3 groups - green, yellow and red. Then I create an 2 arrays. rectArrays holds my segments/rectangles. colorArray holds the colour for each rectangle.
In animate() function I am using these arrays for transition purposes. At the moment first 25 segments should be animated as green then few yellow and the remaining segments should be red. The transition between colours does not work when more than 25 segments should be turned on. They are all either yellow or red. It seems like the transition is remembering only the colour that is stored on the last index before exiting the for loop in my animate function. There are 3 cases so the animation can go from left to right and vice versa.
This picture shows the undesired effect.First half of the segments should be green and remein 5 yellow. But for some reason they are all yellow.
Here is my fiddle code
Thank you for any suggestions
var configObject = {
svgWidth : 1000,
svgHeight : 500,
minValue : 1,
maxValue : 100,
midRange : 50,
highRange : 75,
numberOfSegments : 50
};
//define variables
var newValue;
var gaugeValue = configObject.minValue - 1;
var mySegmentMappingScale;
var reverseScale;
var rectArray=[];
var segmentIndex=configObject.maxValue/configObject.numberOfSegments;
var dynamicArc=true;
var staticArc="yellow";
var gradientArray=[];
var colorArray=[];
var rectWidth=(configObject.svgWidth/1.5)/configObject.numberOfSegments;
var rectPadding=3;
getColor();
setmySegmentMappingScale();
//define svg
var svg = d3.select("body").append("svg")
.attr("width", configObject.svgWidth)
.attr("height", configObject.svgHeight)
.append("g")
.attr("transform", 'translate('+ 0 +',' + configObject.svgHeight/2 + ')');
var valueLabel= svg.append("text")
.attr('x',0)
.attr('y', (configObject.svgHeight/13)+15)
.attr('transform',"translate(" + 0 + "," + 0 + ")")
.text(configObject.minValue)
.attr('fill', "white");
var HueGreenIndex=1;
var HueYellowIndex=1;
var HueRedIndex=1;
function addGradient(c){
//debugger
if (c=="green"){
var hslString =d3.hsl(HueGreenIndex + 160, .40, .29).toString();
HueGreenIndex=HueGreenIndex+0.5;
return hslString;
}
else if(c=="yellow"){
var hslString=d3.hsl(HueYellowIndex + 39, .67, .57).toString();
HueYellowIndex=HueYellowIndex+0.5;
return hslString;
}
else if (c=="red"){
var hslString=d3.hsl(1+HueRedIndex , 1, .58).toString();
HueRedIndex=HueRedIndex+0.10;
return hslString;
}
}
function getColor(){
if (dynamicArc){
for(i = 0; i <= configObject.numberOfSegments; i++){
if(i<=(configObject.numberOfSegments/100)*configObject.midRange){
//gradientArray.push(addGradient("green"));
colorArray.push("green");
}
else if(i > ((configObject.numberOfSegments/100)*configObject.midRange) && i<= ((configObject.numberOfSegments/100)*configObject.highRange)){
//gradientArray.push(addGradient("yellow"));
colorArray.push("yellow");
}
else if (i > ((configObject.numberOfSegments/100)*configObject.highRange)){
//gradientArray.push(addGradient("red"));
colorArray.push("red");
}
}
}
else{
if (staticArc=="green"){
//gradientArray.push(addGradient("green"));
colorArray.push("green")
}
else if(staticArc=="yellow"){
//gradientArray.push(addGradient("yellow"));
colorArray.push("yellow")
}
else {
//gradientArray.push(addGradient("red"));
colorArray.push("red")
}
}
}
for(i = 0; i <= configObject.numberOfSegments; i++){
var myRect=svg.append("rect")
.attr("fill", "#2D2D2D")
.attr("x",i * rectWidth)
.attr("y", 0)
.attr("id","rect"+i)
.attr("width", rectWidth-rectPadding)
.attr("height", configObject.svgHeight/13);
rectArray.push(myRect);
}
//define scale
function setmySegmentMappingScale(){
var domainArray = [];
var x=0;
for(i = configObject.minValue; i <= configObject.maxValue+1; i = i + (configObject.maxValue - configObject.minValue)/configObject.numberOfSegments){
if(Math.floor(i) != domainArray[x-1]){
var temp=Math.floor(i);
domainArray.push(Math.floor(i));
x++;
}
}
var rangeArray = [];
for(i = 0; i <= configObject.numberOfSegments+1; i++){// <=
rangeArray.push(i);
}
mySegmentMappingScale = d3.scale.threshold().domain(domainArray).range(rangeArray);
reverseScale= d3.scale.threshold().domain(rangeArray).range(domainArray);
}
function widgetScale (x,y,r){
return (x*r)/y;
}
//generate random number
function generate(){
var randomNumber = Math.random() * (configObject.maxValue - configObject.minValue) + configObject.minValue;
newValue = Math.floor(randomNumber);
animateSVG();
}
function animateSVG(){
var previousSegment = mySegmentMappingScale(gaugeValue) -1;
var newSegment = mySegmentMappingScale(newValue) -1;
if(previousSegment <= -1 && newSegment > -1){
for(i = 0; i <= newSegment; i++){
var temp=colorArray[i];
rectArray[i].transition()
.ease("linear")
.duration(50)
.delay(function(d){return i * 90})
.styleTween("fill", function() { return d3.interpolateRgb( getComputedStyle(this).getPropertyValue("fill"), temp );});
valueLabel.transition()
.ease("linear")
.duration(50)
.delay(function(d){return i * 90})
.text(i==newSegment ? newValue : i*segmentIndex);
valueLabel.transition()
.ease("linear")
.duration(50)
.delay(function(d){return i * 90})
.attr("transform","translate(" + (i * (rectWidth)+(rectWidth)) + "," + 0 + ")")
}
}
else if(newSegment > previousSegment){
for(i = previousSegment; i <= newSegment; i++){
var temp=colorArray[i];
rectArray[i].transition()
.ease("linear")
.duration(50)
.delay(function(d){return i * 90})
.styleTween("fill", function() { return d3.interpolateRgb( getComputedStyle(this).getPropertyValue("fill"),temp);});
valueLabel.transition()
.ease("linear")
.duration(50)
.delay(function(d){return i * 90})
.text(i==newSegment ? newValue : i*segmentIndex);
valueLabel.transition()
.ease("linear")
.duration(50)
.delay(function(d){return i * 90})
.attr("transform","translate(" + (i * (rectWidth)+(rectWidth)) + "," + 0 + ")")
}
}
else if(newSegment < previousSegment){
for(i = previousSegment; i > newSegment; i--){
var temp=colorArray[i];
rectArray[i].transition()
.ease("linear")
.duration(50)
.delay(function(d){return Math.abs(i -previousSegment)*90})
.styleTween("fill", function() { return d3.interpolateRgb( getComputedStyle(this).getPropertyValue("fill"),"#2D2D2D"); });
valueLabel.transition()
.ease("linear")
.duration(50)
.delay(function(d){return Math.abs(i -previousSegment)*90})
.text(i==newSegment+1 ? newValue : i*segmentIndex);
valueLabel.transition()
.ease("linear")
.duration(50)
.delay(function(d){return Math.abs(i -previousSegment)*90})
.attr("transform","translate(" + (i * (rectWidth)-(rectWidth)) + "," + 0 + ")")
}
}
gaugeValue = newValue;
}
setInterval(function() {
generate()
}, 6000);
If you want that every styleTween gets a different i instance, you'll have to use let, not var.
Just change:
var temp = colorArray[i];
To:
let temp = colorArray[i];
Here is the updated fiddle: https://jsfiddle.net/x2mL97x7/
I am yet a noob for d3 and javascript,while I was writing some code to deal with drag the dots and zoom the coordinate, something weird happened, the dots call the drag function,an outer group() element call the zoom, but when I dragging the dot, the outer group change its translate attribute.Code comes below:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>zoom test</title>
</head>
<body></body>
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js">
</script>
<script type="text/javascript" charset="utf-8">
function addElem(container,elem){
return container.append(elem);
}
function createLinearScale(dx,dy,rx,ry){
return d3.scale.linear()
.domain([dx,dy])
.range([rx,ry])
}
function appendWithData(container,selector,type,id,classed,dataset){
var result = container.selectAll(selector)
.data(dataset)
.enter()
.append(type);
if(id)
result.attr("id",id);
if(classed)
result.classed(classed,true);
return result;
}
function getElem(selector){
return d3.select(selector);
}
function getAxis(){
return d3.svg.axis();
}
function drag(){
return d3.behavior.drag();
}
function getThis(){
return d3.select("this");
}
function zoom(){
return d3.behavior.zoom();
}
function arrayDelete(array,target,condition){
for (var i = 0, l = array.length; i < l; i ++) {
var v = arr[i];
if((target = v) && condition){
array.splice(i,1);
}
}
}
</script>
<script type="text/javascript" charset="utf-8">
/**
* Set frame for page
*
*/
var body = getElem("body");
var svg = addElem(body,"svg");
var outer = addElem(svg,"g");
var target = addElem(outer,"g");
var x_axis = addElem(target,"g");
var y_axis = addElem(target,"g");
var dots = addElem(target,"g");
var data = [
[ 5, 20 ],
[ 480, 90 ],
[ 250, 50 ],
[ 100, 33 ],
[ 330, 95 ],
[ 410, 12 ],
[ 475, 44 ],
[ 25, 67 ],
[ 85, 21 ],
[ 220, 88 ]
];
/**
* Add axis to chart
*
*/
var height = 500;
var width = 960;
var x_scale = createLinearScale(0, d3.max(data,function(d){return d[0];}) + 50, 0, width - 10);
var y_scale = createLinearScale(0, d3.max(data, function(d){return d[1];}) + 50, height - 10, 0);
var ax_scale = createLinearScale(0, width - 10, 0, d3.max(data,function(d){return d[0];}) + 50);
var ay_scale = createLinearScale(height -10, 0, 0, d3.max(data, function(d){ return d[1];}) + 50);
var xaxis = getAxis().scale(x_scale).orient("bottom").ticks(30);
var yaxis = getAxis().scale(y_scale).orient("right").ticks(10);
x_axis.attr("transform",function(d){return "translate(0," + (height - 10) + ")"}).call(xaxis);
y_axis.attr("transform",function(d){return "translate(0,0)"}).call(yaxis);
/**
* Add dots * */
var dot = appendWithData(dots,"circle","circle","","datum",data);
var text = appendWithData(dots,"text","text","","index",data);
dot.data(data).attr({
"id" : function(d,i){return "datum" +i;},
"tag": function(d,i){return i + "";},
"cx" : function(d){return x_scale(d[0]).toFixed(0)},
"cy" : function(d){return y_scale(d[1]).toFixed(0)},
"fill" : function(d){return "rgb(0," + (d[1] * 5) % 255 + ",53)"},
"r" : 10 });
text.data(data).attr({
"id" : function(d,i){return "index" + i;},
"tag": function(d,i){return i + ""},
"y" : function(d){return y_scale(d[1]).toFixed(0)},
"x" : function(d){return x_scale(d[0]).toFixed(0)},
"transform" : "translate(15,-15)"
})
.text(function(d){return d[0] + "," + d[1]});
var flag = 1;
var drag = drag();
function dragstart(){
console.log("dragstart")
var cur = d3.select(this);
console.log("drag");
cur.transition().ease("elastc").attr({
"r" : 15
});
}
function dragging(){
flag = 0;
var cur = d3.select(this);
var tag = cur.attr("tag");
cir = d3.select("circle[tag='" + tag + "']");
txt = d3.select("text[tag='" + tag + "']");
console.log(cur);
console.log(txt);
var cur_x = d3.event.x;
var cur_y = d3.event.y;
//target.attr("transform","translate(0,0)");
cir.attr({
"cx" : function(d){return cur_x.toFixed(0)},
"cy" : function(d){return cur_y.toFixed(0)},
//"fill" : function(d){return "rgb(0," + (y_scale(cur_y) * 5) % 255 + ",53)"},
});
txt.attr({
"x" : function(d){return cur_x.toFixed(0)},
"y" : function(d){return cur_y.toFixed(0)},
})
.text(new Number(ax_scale(cur_x)).toFixed(0) + "," + new Number(ay_scale(cur_y)).toFixed(0));
}
function dragged(){
var cur = d3.select(this);
cur.transition().ease("elastc").attr({
"r" : 10
});
flag = 1;
}
drag.on("dragstart",dragstart)
.on("drag",dragging)
.on("dragend",dragged);
dot.call(drag);;
var zoom = zoom();
function zoomed(){
//if(flag){
console.log("zoomed");
outer.attr("transform","translate(" + d3.event.translate + ") scale(" + d3.event.scale + ")");
//}
}
zoom.on("zoom",zoomed);
outer.call(zoom);
</script>
</html>
As you see, I set a toggle(the variable called flag) to solve the trigger problem,but it cause a new problem, I can't zoom with wheel when the mouse is aloft a blank space.
If I understand the question correctly, you want the zooming on the outer while you want dragging on the dots. In that case, there were two problems with your program:
Empty g as the outer: A g element is just an empty container and is unable to capture events inside itself. If you want to capture mouse actions anywhere inside the g, you need to include an invisible background rect inside it with pointer-events: all. Also, you want this element to appear before all other elements in the DOM so that it is indeed in the background:
var bgRect = addElem(outer, "rect");
bgRect.attr('fill', 'none')
.attr('stroke', 'none')
.attr('width', width)
.attr('height', height);
var target = addElem(outer, "g");
// ...
Update: See this question as well: d3.js - mouseover event not working properly on svg group
Transitioning on zooming: The "expected" zoom behavior is that the point under the mouse pointer stays static while everything around the pointer zooms in/out. Hence, the container needs to both scale and transition.
function zoomed(){
console.log("zoomed");
outer.attr("transform","translate(" + d3.event.translate + ") scale(" + d3.event.scale + ")");
}
However, in your case, you do not want the container to transition so that the axis stays firmly rooted at the origin (0, 0). So:
function zoomed(){
console.log("zoomed");
outer.attr("transform","translate(" + [0,0] + ") scale(" + d3.event.scale + ")");
}
Working demo with these changes: http://jsfiddle.net/S3GsC/