Using D3.js v4, I'm trying to update the radius of a circle on button click.
The problem is that, instead of smooth transitions between the radii the circle is redrawn (going from 'radius1' to 0 and only then to 'radius2') upon each update.
Here's the complete code:
https://jsfiddle.net/4r6hp9my/
Here's the circle update code snippet:
var circles = svg.selectAll('circle').data(dataset);
var enter = circles
.enter()
.append('circle')
.attrs({
cx: w/2,
cy: h/2,
fill: colorsScale,
r: function(d,i){
if(i == myCounter){
var x = rScale(d)
return x;
}
}
});
d3.select('#theButton')
.on('click',function(){
myCounter++
if(myCounter == dataset.length){
myCounter = 0;
};
updateData()
});
function updateData(){
circles
.merge(enter)
.transition()
.attr('r',function(d,i){
if(i == myCounter){
return rScale(d);
}
});
labels
.text(function(d,i){
if(i == myCounter){
return d;
}
});
};
As mentioned by echonax, the issue is you're creating multiple circles based on the dataset. To get the smooth transition, draw only one circle, and update the radius based on 'myCounter'. An example:
var dataset = [2184,2184,3460,2610,2610,2452,842,1349,2430];
var myCounter = 0;
//svg dimensions
var h = 200;
var w = 200;
var svg = d3.select('body')
.append('svg')
.attrs({
width: w,
height: h
})
.classed('middle',true);
//color mapping
var colorsScale = d3.scaleLinear()
.domain([d3.min(dataset),d3.max(dataset)])
.range(['#FFB832','#C61C6F']);
//radius mapping
var rScale = d3.scaleLinear().domain([0, d3.max(dataset)]).range([0,50])
//labels
var label = svg.append("text").attrs({
x: w/2,
y: 20
}).text(function(){ return dataset[myCounter] });
//draw the circles
var circle = svg.append('circle')
.attrs({
cx: w/2,
cy: h/2,
fill: function() { return colorsScale(dataset[myCounter]) },
r: function() { return rScale(dataset[myCounter]) }
});
//button click
d3.select('#theButton')
.on('click',function(){
updateData()
});
function updateData(){
myCounter++;
if ( myCounter > dataset.length - 1 ) myCounter = 0;
circle.transition().attr('r',function(){ return rScale(dataset[myCounter]) }).attr('fill', function() { return colorsScale(dataset[myCounter]) });
label.text(function(){ return dataset[myCounter] });
};
html, body{
height: 100%;
}
.middle{
margin: 100px auto;
}
#theButton{
position: absolute;
left:50px;
top:50px;
}
<script src="https://d3js.org/d3.v4.js"></script>
<script src="https://d3js.org/d3-selection-multi.v1.min.js"></script>
<button id="theButton" type="button">Click Me!</button>
Based on your data, there are a couple of times that the circle won't change as the data is the same, but the transition should work when it does.
Related
I have two axes which movement is limited to zero tick in all four directions (with redrawChart(transform) function). It means, 0 tick is always visible.
But after zooming (mouse scroll or double click), I can't figure out the correct formula for the same constraint.
I tried to use transform.k, but it didn't work.
How I can limit the movement of the axes to 0 tick, after zoom event?
function init()
{
class TrendChart {
constructor(width, height, visual_margin, data_margin) {
this.xlen = 0;
this.canvas_w = width;
this.canvas_h = height;
this.visual_margin = visual_margin;
this.data_margin = data_margin;
this.chart_height = height - 2*visual_margin;
this.chart_width = width - 2*visual_margin;
this.ctx;
this.svg;
this.y;
this.x;
this.axis_bottom;
this.axis_left;
this.g_axis_bottom;
this.g_axis_left;
// 192847 //'#142340'
this.color_scheme = {odd_col_bg:'#192847', even_col_bg:'#142340', axis_color:'#ff0000', label_color:'#ffffff',
series:{'0':{stroke_style: '#00ffdd', fill_style: '#00ffdd'},
'1':{stroke_style: '#ffaa00', fill_style: '#ffcc00'}}
};
}
showBG() {
var canvas = d3.select('#chart').append('canvas')
.attr('width', this.canvas_w)
.attr('height', this.canvas_h)
.style('background-color','blue')
.style('width', this.canvas_w+'px')
.style('height', this.canvas_h+'px')
.style('position','absolute')
.style('top', 0)
.style('left', 0);
this.ctx = canvas.node().getContext("2d");
this.svg = d3.select('#chart').append('svg')
.attr('width', this.canvas_w)
.attr('height', this.canvas_h)
.style('width', this.canvas_w+'px')
.style('height', this.canvas_h+'px')
.style('position','absolute')
.style('top', 0)
.style('left', 0)
.call(d3.zoom()
//.scaleExtent([1, 50])
// .translateExtent([[-(this.canvas_w/2 - this.visual_margin), -(this.canvas_h/2 - this.visual_margin)]
// , [this.canvas_w*3/2 - this.visual_margin, this.canvas_h*3/2 - this.visual_margin]])
.on("zoom", () => this.zoom_function(d3.event.transform)))
}
showAxis(y_minval,y_maxval, data_len){
d3.selectAll(".axis").remove();
this.y = d3.scaleLinear()
.domain([y_minval-this.data_margin, y_maxval+ this.data_margin])
.range([this.chart_height+this.visual_margin, this.visual_margin])
//.nice();
this.x = d3.scaleLinear()
.domain([-(data_len-1), data_len-1])
.range([this.visual_margin, this.chart_width+this.visual_margin])
//.nice();
this.axis_bottom = d3.axisBottom(this.x);
this.svg.append("g")
.attr("class", "axis")
.attr("id", "xaxis")
.attr("transform", 'translate(0,'+ (this.canvas_h-this.visual_margin) +')')
.call(this.axis_bottom);
var axis_bottom_ticks = this.axis_bottom.scale().ticks();
var xaxis_stroke_width = 1;
this.axis_left = d3.axisLeft(this.y);
this.g_axis_left = this.svg.append("g")
.attr("class", "axis")
.attr("id", "yaxis")
.attr("transform", 'translate(' + this.visual_margin +', 0)')
.call(this.axis_left);
var yaxis_stroke_width = 1;
this.g_axis_bottom = d3.select("#xaxis");
this.g_axis_left = d3.select("#yaxis");
this.setSizeOfAxisTick('x', 0, -1*this.chart_height);
this.setSizeOfAxisTick('y', 0, this.chart_width);
}
showChart(data_len){
this.showAxis(-1, 1, data_len);
}
redrawChart(transform){
if(Math.abs(transform.x/transform.k)>(this.canvas_w/2 - this.visual_margin)){
var new_transform = Math.floor(this.canvas_w/2) - this.visual_margin - 1;
transform.x = transform.x>0 ? new_transform*transform.k: -1*new_transform*transform.k;
}
if(Math.abs(transform.y/transform.k)>(this.canvas_h/2 - this.visual_margin)){
var new_transform = Math.floor(this.canvas_h/2) - this.visual_margin - 1;
transform.y = transform.y>0 ? new_transform*transform.k: -1*new_transform*transform.k;
}
const scale_x = transform.rescaleX(this.x);
const scale_y = transform.rescaleY(this.y);
this.g_axis_bottom.call(this.axis_bottom.scale(scale_x));
this.g_axis_left.call(this.axis_left.scale(scale_y));
this.setSizeOfAxisTick('x', 0, -1*this.chart_height);
this.setSizeOfAxisTick('y', 0, this.chart_width);
}
zoom_function(transform){
this.ctx.save();
this.redrawChart(transform);
this.ctx.restore();
}
setSizeOfAxisTick(axis, number, size){
var xaxis_element = document.getElementById(axis+'axis');
var all_ticks = xaxis_element.querySelectorAll(".tick");
var res = Array.from(all_ticks).find(v => Number(v.textContent) == Number(number));
if(res)
res.querySelector("line").setAttribute((axis=='x'?'y':'x')+'2', size);
}
}//end class
///*********************************************************///
chart = new TrendChart(400, 200, 40, 0.1);
chart.showBG();
chart.showChart(1113);
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Trend Chart Test</title>
<script src="https://d3js.org/d3.v5.min.js"></script>
<style>
.tick text {color: #ffffff !important;}
.axis line {stroke: #ff0000 !important;}
svg { shape-rendering: crispEdges;}
</style>
</head>
<body onload="init()">
<div id="chart"></div>
<script src="./trend.js"></script>
</body>
</html>
I found the answer.
function init()
{
class TrendChart {
constructor(width, height, visual_margin, data_margin) {
this.xlen = 0;
this.canvas_w = width;
this.canvas_h = height;
this.visual_margin = visual_margin;
this.data_margin = data_margin;
this.chart_height = height - 2*visual_margin;
this.chart_width = width - 2*visual_margin;
this.ctx;
this.svg;
this.y;
this.x;
this.axis_bottom;
this.axis_left;
this.g_axis_bottom;
this.g_axis_left;
this.xaxis_0_pos = width/2;
this.yaxis_0_pos = height/2;
// 192847 //'#142340'
this.color_scheme = {odd_col_bg:'#192847', even_col_bg:'#142340', axis_color:'#ff0000', label_color:'#ffffff',
series:{'0':{stroke_style: '#00ffdd', fill_style: '#00ffdd'},
'1':{stroke_style: '#ffaa00', fill_style: '#ffcc00'}}
};
}
showBG() {
var canvas = d3.select('#chart').append('canvas')
.attr('width', this.canvas_w)
.attr('height', this.canvas_h)
.style('background-color','blue')
.style('width', this.canvas_w+'px')
.style('height', this.canvas_h+'px')
.style('position','absolute')
.style('top', 0)
.style('left', 0);
this.ctx = canvas.node().getContext("2d");
this.svg = d3.select('#chart').append('svg')
.attr('width', this.canvas_w)
.attr('height', this.canvas_h)
.style('width', this.canvas_w+'px')
.style('height', this.canvas_h+'px')
.style('position','absolute')
.style('top', 0)
.style('left', 0)
.call(d3.zoom()
//.scaleExtent([1, 50])
// .translateExtent([[-(this.canvas_w/2 - this.visual_margin), -(this.canvas_h/2 - this.visual_margin)]
// , [this.canvas_w*3/2 - this.visual_margin, this.canvas_h*3/2 - this.visual_margin]])
.on("zoom", () => this.zoom_function(d3.event.transform)))
}
showAxis(y_minval,y_maxval, data_len){
d3.selectAll(".axis").remove();
this.y = d3.scaleLinear()
.domain([y_minval-this.data_margin, y_maxval+ this.data_margin])
.range([this.chart_height+this.visual_margin, this.visual_margin])
//.nice();
this.x = d3.scaleLinear()
.domain([-(data_len-1), data_len-1])
.range([this.visual_margin, this.chart_width+this.visual_margin])
//.nice();
this.axis_bottom = d3.axisBottom(this.x);
this.svg.append("g")
.attr("class", "axis")
.attr("id", "xaxis")
.attr("transform", 'translate(0,'+ (this.canvas_h-this.visual_margin) +')')
.call(this.axis_bottom);
var axis_bottom_ticks = this.axis_bottom.scale().ticks();
var xaxis_stroke_width = 1;
this.axis_left = d3.axisLeft(this.y);
this.g_axis_left = this.svg.append("g")
.attr("class", "axis")
.attr("id", "yaxis")
.attr("transform", 'translate(' + this.visual_margin +', 0)')
.call(this.axis_left);
var yaxis_stroke_width = 1;
this.g_axis_bottom = d3.select("#xaxis");
this.g_axis_left = d3.select("#yaxis");
this.setSizeOfAxisTick('x', 0, -1*this.chart_height);
this.setSizeOfAxisTick('y', 0, this.chart_width);
}
showChart(data_len){
this.showAxis(-1, 1, data_len);
}
redrawChart(transform){
var new_xaxis_0_pos = this.xaxis_0_pos*transform.k+transform.x;
var new_yaxis_0_pos = this.yaxis_0_pos*transform.k+transform.y
//console.log(new_xaxis_0_pos, new_yaxis_0_pos);
if(new_xaxis_0_pos<this.visual_margin+1){
transform.x = this.visual_margin-this.xaxis_0_pos*transform.k+1;
}
else if(new_xaxis_0_pos>this.canvas_w-this.visual_margin-1){
transform.x = this.canvas_w-this.visual_margin-this.xaxis_0_pos*transform.k-1;
}
if(new_yaxis_0_pos<this.visual_margin+1){
transform.y = this.visual_margin-this.yaxis_0_pos*transform.k+1;
}
else if(new_yaxis_0_pos>this.canvas_h-this.visual_margin-1){
transform.y = this.canvas_h-this.visual_margin-this.yaxis_0_pos*transform.k-1;
}
const scale_x = transform.rescaleX(this.x);
const scale_y = transform.rescaleY(this.y);
this.g_axis_bottom.call(this.axis_bottom.scale(scale_x));
this.g_axis_left.call(this.axis_left.scale(scale_y));
this.setSizeOfAxisTick('x', 0, -1*this.chart_height);
this.setSizeOfAxisTick('y', 0, this.chart_width);
}
zoom_function(transform){
this.ctx.save();
this.redrawChart(transform);
this.ctx.restore();
}
setSizeOfAxisTick(axis, number, size){
var xaxis_element = document.getElementById(axis+'axis');
var all_ticks = xaxis_element.querySelectorAll(".tick");
var res = Array.from(all_ticks).find(v => Number(v.textContent) == Number(number));
if(res)
res.querySelector("line").setAttribute((axis=='x'?'y':'x')+'2', size);
}
}//end class
///***********************************************************************************///
chart = new TrendChart(400, 200, 40, 0.5);
chart.showBG();
chart.showChart(8113);
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Trend Chart Test</title>
<script src="https://d3js.org/d3.v5.min.js"></script>
<style>
.tick text {color: #ffffff !important;}
.axis line {stroke: #ff0000 !important;}
svg { shape-rendering: crispEdges;}
</style>
</head>
<body onload="init()">
<div id="chart"></div>
<script src="./trend.js"></script>
</body>
</html>
I am working on a project with D3.js that displays regions of interest (ROI) which are <g> elements with one <polygon> and one <text>. I noticed that zooming becomes very slow when there are a lot of ROI and it seems that this is mostly because of the texts, i.e. when they are hidden with display: none, zoom is much faster. The zoom speed is different in every browser: Firefox is quite fast, Chrome is average and Edge is slow.
I tried to speed up the text rendering by using the CSS property text-rendering: optimizeSpeed; but the difference is marginal. I noticed that some fonts are faster to render than others. Currently the best results I obtained is by using font-family: monospace;.
So my question is: How to zoom faster in an SVG drawing with D3? Is there a font that is known to be faster to render than others? Or is there a CSS, SVG or D3 trick that could help?
You can test the zoom speed with the snippet. If you click on the button, the text will be hidden an zooming will be much faster.
"use strict";
// Create data with this structure:
// DATA = [{ name: "", coords: [x0, y0, x1, y1, x2, y2, x3, y3]}]
var nbPolyX = 100;
var nbPolyY = 50;
var sqSize = 800 / nbPolyX;
var DATA = [];
for (let idY = 0; idY < nbPolyY; idY++) {
for (let idX = 0; idX < nbPolyX; idX++) {
DATA.push({
name: "x" + idX + "y" + idY,
coords: [
idX * sqSize + 1, idY * sqSize + 1,
(idX + 1) * sqSize - 1, idY * sqSize + 1,
(idX + 1) * sqSize - 1, (idY + 1) * sqSize - 1,
idX * sqSize + 1, (idY + 1) * sqSize - 1
]
})
}
}
var SVGELEM = {};
var ZOOMER = {};
var TRNSF = {
k: 1,
x: 0,
y: 0
};
var ZOOMER = {};
var GZOOM = {};
var ROI = {};
var POLY = {};
var TXT = {};
var BUTTON = {};
addButton();
addSVG();
function addSVG() {
ZOOMER = d3
.zoom()
.scaleExtent([0.9, 40])
.on("zoom", function () {
onZoom();
});
SVGELEM = d3.select("body").append("svg")
.attr("width", nbPolyX * sqSize)
.attr("height", nbPolyY * sqSize)
.call(ZOOMER);
GZOOM = SVGELEM.append("g");
ROI = GZOOM.selectAll("g")
.data(DATA)
.enter()
.append("g")
.classed("roi", true);
POLY = ROI.selectAll("polygon")
.data(function (d) {
return [d.coords];
})
.enter()
.append("polygon")
.attr("points", function (d) {
return d;
});
TXT = ROI.selectAll("text")
.data(function (d) {
var nbElem = d.coords.length;
// Polygon mean point X.
var xMean = 0;
for (let index = 0; index < nbElem - 1; index += 2) {
xMean += d.coords[index];
}
xMean /= nbElem / 2;
// Polygon mean point Y.
var yMean = 0;
for (let index = 1; index < nbElem; index += 2) {
yMean += d.coords[index];
}
yMean /= nbElem / 2;
// Return value.
var ret = {
name: d.name,
x: xMean,
y: yMean
};
return [ret];
})
.enter()
.append("text")
.attr("x", function (d) {
return d.x;
})
.attr("y", function (d) {
return d.y;
})
.text(function (d) {
return d.name;
});
}
function addButton() {
BUTTON = d3.select("body").append("button")
.text("HIDE TEXT")
.on("click", function btnOnClick() {
btnOnClick.state = !btnOnClick.state;
d3.selectAll("text").classed("cl_display_none", btnOnClick.state);
if (btnOnClick.state) d3.select(this).text("SHOW TEXT");
else d3.select(this).text("HIDE TEXT");
});
}
function onZoom() {
if (d3.event !== null) TRNSF = d3.event.transform;
GZOOM.attr(
"transform",
"translate(" + TRNSF.x + "," + TRNSF.y + ") scale(" + TRNSF.k + ")"
);
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>SVG ZOOM SPEED</title>
<style>
body {
text-align: center;
font-family: monospace;
display: block;
margin: 0 auto;
}
svg {
margin: 10px auto;
border: 1px solid;
display: block;
}
.roi polygon {
shape-rendering: optimizeSpeed;
vector-effect: non-scaling-stroke;
fill: rgba(0, 255, 0, 0.25);
stroke: rgba(0, 255, 0, 1);
stroke-width: 1px;
}
.roi text {
text-rendering: optimizeSpeed;
font-family: monospace;
font-size: 1px;
text-anchor: middle;
dominant-baseline: middle;
}
.cl_display_none {
display: none;
}
button {
width: 150px;
height: 50px;
font-family: monospace;
font-size: 15pt;
margin: 0;
}
</style>
</head>
<body>
<h3>SVG ZOOM SPEED</h3>
<p>Use the mouse wheel to zoom in and out the SVG drawing then hide the text with the button and observe the speed difference. Test it in different browsers.</p>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="zoom_speed.js"></script>
</body>
</html>
I draw a Polygon using D3 mouse events as shown in this fiddle.
Below is the method that get's the polygon's bounding box and sets the polygon's bounding box properties.
function completePolygon() {
d3.select('g.outline').remove();
gPoly = svgCanvas.append('g')
.classed("polygon", true);
polyPoints.splice(polyPoints.length - 1);
polyEl = gPoly.append("polygon")
.attr("points", polyPoints);
for (var i = 0; i < polyPoints.length; i++) {
gPoly.append('circle')
.attr("cx", polyPoints[i][0])
.attr("cy", polyPoints[i][1])
.attr("r", 4)
.call(dragBehavior);
}
isDrawing = false;
isDragging = true;
bbox = polyEl._groups[0][0].getBBox();
var bbox2 = gPoly._groups[0][0].getBBox();
//Altering the bounding box's attribute of polygon
bbox.x = 0;
bbox.y = 0;
bbox.width = 50;
bbox.height = 50;
gPoly.attr("transform", "translate(" + 0 + "," + 0 + ")");
// polyEL.attr("transform", "translate(" + 0 + "," + 0 + ")");
//
// gPoly.call(d3.drag().on("drag", movePolygon(bbox)));
}
I want to make the entire polygon draggable. I tried getting the Bounding Box of the drawn Polygon and setting the X and Y coordinates to 0 then translating it on drag like I did for the circle and rectangle elements in this fiddle but changing any of the polygon's bounding box properties don't seem to have an affect on the polygon element. However translating for the polygon works.
Is there any other way other than looping through the polygon's 2 dimensional array of coordinates and updating all the coordinate points on to implement a draggable polygon?
I'm really not following all this getBBox() stuff. Why don't you drag the element using the traditional way?
gPoly.call(d3.drag().on("drag", function(d) {
d3.select(this).attr("transform", "translate(" +
(d.x = d3.event.x) + "," + (d.y = d3.event.y) + ")")
}));
Here is your code with that change:
d3.select('#poly').on('click', function() {
new Polygon();
});
var w = 600,
h = 500;
var svgCanvas = d3.select('body').append('svg').attr("width", w).attr("height", h);
function Polygon() {
var polyPoints = [];
var gContainer = svgCanvas.append('g').classed("outline", true);
var isDrawing = false;
var isDragging = false;
var linePoint1, linePoint2;
var startPoint;
var bbox;
var boundingRect;
var shape;
var gPoly;
var polyDraw = svgCanvas.on("mousedown", setPoints)
.on("mousemove", drawline)
.on("mouseup", decidePoly);
var dragBehavior = d3.drag().on("drag", alterPolygon);
// var dragPolygon = d3.drag().on("drag", movePolygon(bbox));
//On mousedown - setting points for the polygon
function setPoints() {
if (isDragging) return;
isDrawing = true;
var plod = d3.mouse(this);
linePoint1 = {
x: plod[0],
y: plod[1]
};
polyPoints.push(plod);
var circlePoint = gContainer.append("circle")
.attr("cx", linePoint1.x)
.attr("cy", linePoint1.y)
.attr("r", 4)
.attr("start-point", true)
.classed("handle", true)
.style("cursor", "pointer");
// on setting points if mousedown on a handle
if (d3.event.target.hasAttribute("handle")) {
completePolygon()
}
}
//on mousemove - appending SVG line elements to the points
function drawline() {
if (isDrawing) {
linePoint2 = d3.mouse(this);
gContainer.select('line').remove();
gContainer.append('line')
.attr("x1", linePoint1.x)
.attr("y1", linePoint1.y)
.attr("x2", linePoint2[0] - 2) //arbitary value must be substracted due to circle cursor hover not working
.attr("y2", linePoint2[1] - 2); // arbitary values must be tested
}
}
//On mouseup - Removing the placeholder SVG lines and adding polyline
function decidePoly() {
gContainer.select('line').remove();
gContainer.select('polyline').remove();
var polyline = gContainer.append('polyline').attr('points', polyPoints);
gContainer.selectAll('circle').remove();
for (var i = 0; i < polyPoints.length; i++) {
var circlePoint = gContainer.append('circle')
.attr('cx', polyPoints[i][0])
.attr('cy', polyPoints[i][1])
.attr('r', 4)
.attr("handle", true)
.classed("handle", true);
}
}
//Called on mousedown if mousedown point if a polygon handle
function completePolygon() {
d3.select('g.outline').remove();
gPoly = svgCanvas.append('g')
.classed("polygon", true);
polyPoints.splice(polyPoints.length - 1);
//console.log(polyPoints);
polyEl = gPoly.append("polygon")
.attr("points", polyPoints);
for (var i = 0; i < polyPoints.length; i++) {
gPoly.append('circle')
.attr("cx", polyPoints[i][0])
.attr("cy", polyPoints[i][1])
.attr("r", 4)
.call(dragBehavior);
}
isDrawing = false;
isDragging = true;
bbox = polyEl._groups[0][0].getBBox();
var bbox2 = gPoly._groups[0][0].getBBox();
bbox.x = 0;
bbox.y = 0;
bbox.width = 50;
bbox.height = 50;
// debugger;
gPoly.datum({
x: 0,
y: 0
})
//console.log(bbox);
gPoly.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"
});
// polyEL.attr("transform", "translate(" + 0 + "," + 0 + ")");
//
gPoly.call(d3.drag().on("drag", function(d) {
d3.select(this).attr("transform", "translate(" + (d.x = d3.event.x) + "," + (d.y = d3.event.y) + ")")
}));
}
//Altering polygon coordinates based on handle drag
function alterPolygon() {
if (isDrawing === true) return;
var alteredPoints = [];
var selectedP = d3.select(this);
var parentNode = d3.select(this.parentNode);
//select only the elements belonging to the parent <g> of the selected circle
var circles = d3.select(this.parentNode).selectAll('circle');
var polygon = d3.select(this.parentNode).select('polygon');
var pointCX = d3.event.x;
var pointCY = d3.event.y;
//rendering selected circle on drag
selectedP.attr("cx", pointCX).attr("cy", pointCY);
//loop through the group of circle handles attatched to the polygon and push to new array
for (var i = 0; i < polyPoints.length; i++) {
var circleCoord = d3.select(circles._groups[0][i]);
var pointCoord = [circleCoord.attr("cx"), circleCoord.attr("cy")];
alteredPoints[i] = pointCoord;
}
//re-rendering polygon attributes to fit the handles
polygon.attr("points", alteredPoints);
bbox = parentNode._groups[0][0].getBBox();
console.log(bbox);
}
function movePolygon() {
}
function prepareTransform(bboxVal) {
var originalPosition = {
x: bboxVal.x,
y: bboxVal.y
};
console.log(bboxVal);
console.log(bbox);
bbox.x = 0;
bbox.y = 0;
// //render a bounding box
// shape.rectEl.attr("x", bbox.x).attr("y", bbox.y).attr("height", bbox.height).attr("width", bbox.width);
//
// //drag points
// shape.pointEl1.attr("cx", bbox.x).attr("cy", bbox.y).attr("r", 4);
// shape.pointEl2.attr("cx", (bbox.x + bbox.width)).attr("cy", (bbox.y + bbox.height)).attr("r", 4);
// shape.pointEl3.attr("cx", bbox.x + bbox.width).attr("cy", bbox.y).attr("r", 4);
// shape.pointEl4.attr("cx", bbox.x).attr("cy", bbox.y + bbox.height).attr("r", 4);
return originalPosition;
}
}
h1 {
text-align: center;
}
.svg-container {
display: inline-block;
position: relative;
width: 100%;
padding-bottom: 60%;
/* depends on svg ratio, for my zebra height/width = 1.2 so padding-bottom = 50% * 1.2 = 60% */
vertical-align: middle;
/* top | middle | bottom ... do what you want */
}
.my-svg {
/* svg into : object, img or inline */
display: block;
position: absolute;
top: 0;
left: 0;
width: 100%;
/* only required for <img /> */
z-index: 0;
}
svg {
border: solid 1px rgba(221, 61, 16, 0.71);
}
.rectangle {
fill: lightblue;
stroke: blue;
stroke-width: 2px;
fill-opacity: 0.5;
}
.rectangle-bind {
fill: none;
stroke: #081c4e;
stroke-width: 1px;
stroke-dasharray: 5;
}
circle {
fill: lightgreen;
stroke: green;
stroke-width: 2px;
fill-opacity: 0.5;
}
.rect-active {
fill: #1578db;
}
.pointC-active {
fill: #FF8F00;
}
.circle-active {
fill: #4CAF50;
}
path {
fill: #ffb7b3;
stroke: #ff4736;
stroke-width: 5px;
}
polygon {
fill: #b6eeff;
fill-opacity: 0.5;
stroke: #0067ff;
stroke-width: 2px;
}
line {
fill: none;
stroke: #cd08ff;
stroke-width: 2px;
}
polyline {
fill: none;
stroke: #563aff;
stroke-width: 2px;
}
circle.handle {
fill: yellow;
stroke: #cb9c0f;
stroke-width: 2px;
cursor: pointer;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<button id='poly'>Poly</button>
PS: Don't use _groups[0][0]. Use node() instead.
I have the following data in a csv file called BarData.csv:
Fruit,dt,amount
Apple,12/28/2016,-1256
Apple,12/29/2016,-500
Apple,12/30/2016,3694
Apple,12/31/2016,5586
Apple,1/1/2017,4558
Apple,1/2/2017,6696
Apple,1/3/2017,7757
Apple,1/4/2017,8528
Apple,1/5/2017,5543
Apple,1/6/2017,3363
Apple,1/7/2017,5464
Pear,12/25/2017,250
Pear,12/26/2017,669
Pear,12/27/2017,441
Pear,12/28/2017,159
Pear,12/29/2017,357
Pear,12/30/2017,775
Pear,12/31/2017,669
The following html, css, and javascript is in one .html file:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>BAR SINGLE FUNCTION</title>
<script src="http://d3js.org/d3.v3.js"></script>
<style type="text/css">
#radioDiv {
top: 45px;
font-family: verdana;
font-size: 8px;
width: 455px;
}
#TOPbarChart {
position: absolute;
top: 50px;
left: 30px;
width: 750px;
height: 195px;
}
.axis--y path,
.axis--x path {
display: none;
}
.axis--x line,
.axis--y line {
stroke: black;
fill: none;
stroke-width: 2px
}
.yAxis text,
.xAxis text {
font: 7pt Verdana;
stroke: none;
fill: black;
}
.title,
.titleX {
font-family: Verdana;
font-size: 10px;
}
</style>
</head>
<body>
<div id="radioDiv">
<label>
<input id="radioFrt" type="radio" name="frt" value="Apple" class="radioB" checked> APPLE
</label>
<label>
<input type="radio" name="frt" value="Pear" class="radioB"> PEAR
</label>
</div>
<div id="TOPbarChart"></div>
<script type="text/javascript">
var currentFruit = "Apple";
var currentColr = "#00a5b6";
var barDataCSV_Dly = "BarData.csv";
//
//
// radio button
document.getElementById("radioFrt").checked = true;
d3.selectAll('input[name="frt"]').on("change", function change() {
currentFruit = this.value;
TOPbarChart(currentFruit, currentColr);
});
//FORMATS
var parseDate = d3.time.format("%m/%d/%Y").parse;
//
// BASIC SIZING
//
function barChartBasics() {
var margin = {
top: 25,
right: 35,
bottom: 25,
left: 70
},
width = 550 - margin.left - margin.right,
height = 155 - margin.top - margin.bottom,
colorBar = d3.scale.category20(),
barPaddingFine = 1,
barPaddingThick = 2;
return {
margin: margin,
width: width,
height: height,
colorBar: colorBar,
barPaddingFine: barPaddingFine,
barPaddingThick: barPaddingThick
};
}
// create svg element
var basics = barChartBasics();
var svg = d3.select("#TOPbarChart")
.append("svg")
.attr({
"width": basics.width + basics.margin.left + basics.margin.right,
"height": basics.height + basics.margin.top + basics.margin.bottom,
id: "svgTOPbarChart"
});
// create svg group
var plot = svg
.append("g")
.attr({
"transform": "translate(" + basics.margin.left + "," + basics.margin.top + ")",
id: "svgPlotTOPbarChart"
});
var axisPadding = 2;
var leftAxisGroup = svg
.append('g')
.attr({
transform: 'translate(' + (basics.margin.left - axisPadding) + ',' + (basics.margin.top) + ')',
'class': "yAxis axis--y",
id: "yAxisGTOPbarChart"
});
var bottomAxisGroup = svg
.append('g')
.attr({
'class': "xAxis axis--x",
id: "xAxisGTOPbarChart"
});
var titleTxt = svg.append("text")
.attr({
x: basics.margin.left + 12,
y: 20,
'class': "title",
'text-anchor': "start"
})
// create scales with ranges
var xScale = d3.time.scale().range([0, basics.width]);
var yScale = d3.scale.linear().range([basics.height, 0]);
function TOPbarChart(
frt, colorChosen) {
// get the data
d3.csv(barDataCSV_Dly, function(rows) {
TOPbarData = rows.map(function(d) {
return {
"Fruit": d.Fruit,
"dt": parseDate(d.dt),
"amount": +d.amount
};
}).filter(function(row) {
if (row['Fruit'] == frt) {
return true;
}
});
// create domains for the scales
xScale.domain(d3.extent(TOPbarData, function(d) {
return d.dt;
}));
var amounts = TOPbarData.map(function(d) {
return d.amount;
});
var yMax = d3.max(amounts);
var yMin = d3.min(amounts);
var yMinFinal = 0;
if (yMin < 0) {
yMinFinal = yMin;
}
yScale.domain([yMinFinal, yMax]);
// introduce the bars
// var plot = d3.select("#svgPlotTOPbarChart")
var sel = plot.selectAll("rect")
.data(TOPbarData);
sel.enter()
.append("rect")
.attr({
x: function(d, i) {
return xScale(d.dt);
},
y: function(d) {
return yScale(d.amount);
},
width: (basics.width / TOPbarData.length - basics.barPaddingFine),
height: function(d) {
return basics.height - yScale(d.amount);
},
fill: colorChosen,
'class': "bar"
});
// this little function will create a small ripple affect during transition
var dlyRipple = function(d, i) {
return i * 100;
};
sel
.transition()
.duration(dlyRipple) //1000
.attr({
x: function(d, i) {
return xScale(d.dt);
},
y: function(d) {
return yScale(d.amount);
},
width: (basics.width / TOPbarData.length - basics.barPaddingFine),
height: function(d) {
return basics.height - yScale(d.amount);
},
fill: colorChosen
});
sel.exit().remove();
// add/transition y axis - with ticks and tick markers
var axisY = d3.svg.axis()
.orient('left')
.scale(yScale)
.tickFormat(d3.format("s")) // use abbreviations, e.g. 5M for 5 Million
.outerTickSize(0);
leftAxisGroup.transition().duration(1000).call(axisY);
// add/transition x axis - with ticks and tick markers
var axisX = d3.svg.axis()
.orient('bottom')
.scale(xScale);
bottomAxisGroup
.attr({
transform: 'translate(' + (basics.margin.left + ((basics.width / TOPbarData.length) / 2)) + ',' + (basics.margin.top + basics.height) + ')',
})
.transition().duration(1000).call(axisX.ticks(5));
titleTxt.text("Daily: last " + TOPbarData.length + " days");
// console.log(TOPbarData.length)
});
}
//
//
//
//
TOPbarChart(currentFruit, currentColr);
//
//
//
//
</script>
</body>
</html>
When all the data is positive everything is pretty much ok - but when some of the data is negative we can see the result in this plunker demo:
http://plnkr.co/edit/1hudJYkRq2MnuIlwxXZi?p=preview
How do I amend the code so that:
- the negative bars are shown?
- the base of the positive bars moves vertically up when negative numbers are included?
- the vertical movement is also included in the transition?
Above is more than 1 question but help on any would be appreciated.
The key is to play with the y and height attributes of the bars to position them correctly.
For y, change it to:
y: function(d) {
return yScale(Math.max(0, d.amount));
},
And for the height, change it to:
height: function(d) {
return Math.abs(yScale(d.amount) - yScale(0));
},
You can then style the negative bars to make them a different color.
Check the updated Plunkr - http://plnkr.co/edit/q7dQsPW0PiPuwFTy8gLN?p=preview
Edit:
For the coloring part, you can achieve it with a 1 liner if you want to reduce lines and want more simplicity.
Instead of:
fill: function(d) {
var col = colorChosen
if (d.amount < 0) {
col = "#FF0000";
}
return col;
},
});
You can do:
fill: function(d) {
return d.amount < 0 ? "#FF0000" : colorChosen;
},
I am using d3.js for my graphs. To select a node, user will double click on the particular node. My program will change the opacity to 0.1 of the selected node to show that a node is selected. In the same way, a user can select many nodes where the opacity will be changed accordingly. The New requirement is to apply color for all the selected nodes(opacity = 0.1). For that i need to get all the nodes which has the opacity = 0.1. I am working as like this.
node.style("fill", function (o) {
debugger;
if (o.name == selectednode.name) {
o.colorname = color;
return color;
} else {
return o.colorname;
}
});
This will change only the color of the last selected node. i need to apply color of all the nodes where its opacity is 0.1.
Try this way.
var selectedNodes = nodes.filter(function(d) {
return d3.select(this).style("opacity") == 0.1;
});
selectedNodes.style("fill", newColor).style("opacity", "1");
Demo:
Double click nodes to select. Then, click Highlight Selected button.
var svg = d3.select("#container").append("svg")
.attr("width", 500)
.attr("height", 500);
var data = [{
x: 100,
y: 70
}, {
x: 200,
y: 200
}, {
x: 300,
y: 70
},{
x: 150,
y: 250
}, {
x: 320,
y: 170
}, {
x: 380,
y: 250
}]
var nodes = svg.selectAll(".node").data(data).enter().append("circle").attr("cx", function(d) {
return d.x
}).attr("cy", function(d) {
return d.y
}).attr("r", 12).on("dblclick", function() {
d3.select(this).style("opacity", "0.1");
});
function highlightSelected() {
var selectedNodes = nodes.filter(function(d) {
return d3.select(this).style("opacity") == 0.1;
});
selectedNodes.style("fill", "green").style("opacity", "1");
}
svg {
background-color: black;
}
circle {
fill: red;
}
input {
position:absolute;
left: 385px;
top: 13px;
color: blue;
font-weight: bold;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="container">
<input type="button" onclick="highlightSelected()" value="Highlight selected" />
</div>