how to perform d3 drag and drop - javascript

Having Issue with d3.drag for angular 4. whenever I drag the rectangle object , it is moving fine for first time. After release of mousepress and again trying to drag the rectangle, it is going back to previous event and not able to make mouse control on draggable object. Please give solution to my problem.
import { Component,Input, ElementRef, OnInit } from '#angular/core';
import * as d3 from 'd3';
interface LineData{
xVal: number,
yVal:number
}
#Component({
selector: 'app-line-chart',
template:'<svg height="500" width="500" ></svg>',
styleUrl: []
})
export class LineChartComponent implements OnInit {
#Input() data : LineData[];
private parentNativeElement : any;
constructor(private element:ElementRef) {
this.parentNativeElement = element.nativeElement;
}
ngOnInit() {
var width = 300;
var height = 300;
var margin = {top: 10, right: 10, bottom: 30, left: 10}
var x = d3.scaleLinear().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var xAxis = d3.axisBottom(x)
.scale(x)
.ticks(5);
var yAxis = d3.axisLeft(y)
.scale(y)
.ticks(5);
var valueline:any = d3.line()
.x(function (d) {
return x(d['xVal']);
})
.y(function (d) {
return y(d['yVal']);
});
console.log(valueline);
var svg = d3.select("svg");
d3.select(this.parentNativeElement)
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g");
// Get the data
var data = [
{
"xVal": 1,
"yVal": 2
},
{
"xVal": 2,
"yVal": 4
},
{
"xVal": 3,
"yVal": 1
},
{
"xVal": 4,
"yVal": 5
},
{
"xVal": 5,
"yVal": 3
}
];
// Scale the range of the data
x.domain(d3.extent(data,
function (d) {
return d.xVal;
}));
y.domain([
0, d3.max(data,
function (d) {
return d.yVal;
})
]);
let color = d3.scaleOrdinal(d3.schemeCategory10);
svg.append("path").datum(data).attr("class","path")// Add the valueline path.
.attr("fill", "none")
.attr("stroke", "red")
.attr("stroke-width", 1.5)
.attr("d", valueline(data)).attr("class", "line");
let rectangle:any = d3.range(1).map(function(){
return{
x: Math.floor(Math.random()*width),
y: Math.floor(Math.random()*height)
};
});
console.log(rectangle);
let dragRect = svg.selectAll('g').data(rectangle).enter().append("g")
dragRect.append("rect")
.attr("x",function(d){return d['x'];})
.attr("y",function(d){return d['y'];})
.attr("height",50)
.attr("width",50).style("fill", "steelblue");
svg.selectAll('g').attr("transform",
"translate(" + margin.left + "," + margin.top + ")").data(rectangle)
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
function dragstarted(d){
d3.select(this).raise().classed("active",true);
}
function dragged(d){
d.xVal = x.invert(d3.event.x);
d.yVal = y.invert(d3.event.y);
d3.select(this).select("rect")
.attr("x", x(d.xVal))
.attr("y", y(d.yVal))
.attr("transform","translate("+d.xVal+","+d.yVal+")")
console.log(d);
}
function dragended(d){
d3.select(this).raise().classed("active",false);
//d3.select('rect#no-drag').on('mousedown.drag',null);
}
svg.append("g") // Add the X Axis
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g") // Add the Y Axis
.attr("class", "y axis"`enter code here`)
.call(yAxis);
}
}

The basic problem seems to be that dragged function is not remembering the x & y between successive drag events.
To do this, you need
d3.select(this)
.attr("x", d.x = x(d.xVal))
.attr("y", d.y = y(d.yVal))
instead of
d3.select(this)
.attr("x", x(d.xVal))
.attr("y", y(d.yVal))
Run this code snippet to check it out
console.clear()
var width = 300;
var height = 300;
var margin = {top: 10, right: 10, bottom: 30, left: 10}
var x = d3.scaleLinear().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var xAxis = d3.axisBottom(x).scale(x).ticks(5);
var yAxis = d3.axisLeft(y).scale(y).ticks(5);
var valueline = d3.line()
.x(function (d) { return x(d['xVal']); })
.y(function (d) { return y(d['yVal']); });
var svg = d3.select("svg");
d3.select(this.parentNativeElement)
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g");
// Get the data
var data = [
{
"xVal": 1,
"yVal": 2
},
{
"xVal": 2,
"yVal": 4
},
{
"xVal": 3,
"yVal": 1
},
{
"xVal": 4,
"yVal": 5
},
{
"xVal": 5,
"yVal": 3
}
];
// Scale the range of the data
x.domain(d3.extent(data,
function (d) {
return d.xVal;
}));
y.domain([
0, d3.max(data,
function (d) {
return d.yVal;
})
]);
let color = d3.scaleOrdinal(d3.schemeCategory10);
svg.append("path").datum(data).attr("class","path")
.attr("fill", "none")
.attr("stroke", "red")
.attr("stroke-width", 1.5)
.attr("d", valueline(data)).attr("class", "line");
let rectangle = d3.range(3).map(function() {
return {
x: Math.floor(Math.random()*width),
y: Math.floor(Math.random()*height)
};
});
let dragRect = svg.selectAll('g').data(rectangle).enter()
.append("g")
dragRect.append("rect")
.attr("x",function(d){return d['x'];})
.attr("y",function(d){return d['y'];})
.attr("height", 50)
.attr("width", 50)
.style("fill", "steelblue")
svg.selectAll('rect')
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.data(rectangle)
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
);
const dragBounds = {}
const tickHeight = 10;
function setDragBounds(subject) {
dragBounds.top = 0 - margin.top;
dragBounds.left = 0 - margin.left;
dragBounds.bottom = height - tickHeight - subject.attr('height');
dragBounds.right = width - margin.right - subject.attr('width');
}
function dragstarted(d){
/*
Calculate drag bounds at dragStart because it's one event vs many
events if done in 'dragged()'
*/
setDragBounds(d3.select(this))
d3.select(this).raise().classed("active", true);
}
function dragged(d){
d3.select(this)
.attr("x", getX(d.x = d3.event.x) )
.attr("y", getY(d.y = d3.event.y) );
}
function getX(x) {
return x < dragBounds.left ? dragBounds.left
: x > dragBounds.right ? dragBounds.right
: x
}
function getY(y) {
return y < dragBounds.top ? dragBounds.top
: y > dragBounds.bottom ? dragBounds.bottom
: y
}
function dragended(d){
d3.select(this).classed("active", false);
}
svg.append("g") // Add the X Axis
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
svg.append("g") // Add the Y Axis
.attr("class", "y axis")
.call(yAxis);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg height="500" width="500" ></svg>
Note I attached the drag events to rect instead of g, which I assume was a typo.
Keeping within bounds
To constrain the drag to bounds the way I found works best is a max/min function on the x & y values.
function dragged(d){
d3.select(this)
.attr("x", getX(d.x = d3.event.x) )
.attr("y", getY(d.y = d3.event.y) );
}
function getX(x) {
return x < dragBounds.left ? dragBounds.left
: x > dragBounds.right ? dragBounds.right
: x
}
function getY(y) {
return y < dragBounds.top ? dragBounds.top
: y > dragBounds.bottom ? dragBounds.bottom
: y
}
Bounds are set at drag start to avoid repetition of any calculation.
const dragBounds = {}
const tickHeight = 10;
function setDragBounds(subject) {
dragBounds.top = 0 - margin.top;
dragBounds.left = 0 - margin.left;
dragBounds.bottom = height - tickHeight - subject.attr('height');
dragBounds.right = width - margin.right - subject.attr('width');
}
function dragstarted(d){
/*
Calculate drag bounds at dragStart because it's one event vs many
events if done in 'dragged()'
*/
setDragBounds(d3.select(this))
d3.select(this).raise().classed("active", true);
}

Related

How to plot data points on multi-line chart with multiple y axes

I am trying to add data points to my line chart with multiple y axes. Click here for my fiddle.
//after restructuring dataset array
var data = [{
data: [{
x: 0,
y: 0
}, {
x: 10,
y: 10
}, {
x: 20,
y: 20
}, {
x: 30,
y: 30
}, {
x: 40,
y: 40
}],
yAxis: 0,
}, {
data: [{
x: 0,
y: 0
}, {
x: 10,
y: 200
}, {
x: 20,
y: 300
}, {
x: 30,
y: 400
}, {
x: 40,
y: 500
}],
yAxis: 1,
}];
const margin = {
left: 20,
right: 20,
top: 20,
bottom: 80
};
const svg = d3.select('svg');
svg.selectAll("*").remove();
const width = 200 - margin.left - margin.right;
const height = 200 - margin.top - margin.bottom;
const g = svg.append('g').attr('transform', `translate(${80},${margin.top})`);
//************* Axes and Gridlines ***************
const xAxisG = g.append('g');
const yAxisG = g.append('g');
xAxisG.append('text')
.attr('class', 'axis-label')
.attr('x', width / 3)
.attr('y', -10)
.style('fill', 'black')
.text(function(d) {
return "X Axis";
});
yAxisG.append('text')
.attr('class', 'axis-label')
.attr('id', 'yAxisLabel0')
.attr('x', -height / 2)
.attr('y', -15)
.attr('transform', `rotate(-90)`)
.style('text-anchor', 'middle')
.style('fill', 'black')
.text(function(d) {
return "Y Axis 1";
});
// interpolator for X axis -- inner plot region
var x = d3.scaleLinear()
.domain([0, d3.max(xValueArray)])
.range([0, width])
.nice();
var yScale = new Array();
for (var i = 0; i < 2; i++) {
// interpolator for Y axis -- inner plot region
var y = d3.scaleLinear()
.domain([0, d3.max(arr[i])])
.range([0, height])
.nice();
yScale.push(y);
}
const xAxis = d3.axisTop()
.scale(x)
.ticks(5)
.tickPadding(2)
.tickSize(-height)
const yAxis = d3.axisLeft()
.scale(yScale[0])
.ticks(5)
.tickPadding(2)
.tickSize(-width);
yAxisArray = new Array();
yAxisArray.push(yAxis);
for (var i = 1; i < 2; i++) {
var yAxisSecondary = d3.axisLeft()
.scale(yScale[i])
.ticks(5)
yAxisArray.push(yAxisSecondary);
}
svg.append("g")
.attr("class", "x axis")
.attr("transform", `translate(80,${height-80})`)
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("id", "ySecAxis0")
.attr("transform", "translate(80,20)")
.call(yAxis);
var translation = 50;
var textTranslation = 0;
var yLabelArray = ["Y Axis 1", "Y Axis 2"];
//loop starts from 1 as primary y axis is already plotted
for (var i = 1; i < 2; i++) {
svg.append("g")
.attr("transform", "translate(" + translation + "," + 20 + ")")
.attr("id", "ySecAxis" + i)
.call(yAxisArray[i]);
yAxisG.append('text')
.attr('x', -height / 2)
.attr('y', -60)
.attr('transform', `rotate(-90)`)
.attr("id", "yAxisLabel" + i)
.style('text-anchor', 'middle')
.style('fill', 'black')
.text(yLabelArray[i]);
translation -= 40;
textTranslation += 40;
}
//************* Lines and Data Points ***************
var colors = ["blue", "red"];
var thisScale;
var line = d3.line()
.x(d => x(d.x))
.y(d => thisScale(d.y))
.curve(d3.curveLinear);
var paths = g.selectAll("foo")
.data(data)
.enter()
.append("path");
paths.attr("stroke", function (d,i){return colors[i]})
.attr("d", d => {
thisScale = yScale[d.yAxis]
return line(d.data);
})
.attr("stroke-width", 2)
.attr("id", function (d,i){return "line" + i})
.attr("fill", "none");
var points = g.selectAll("dot")
.data(data)
.enter()
.append("circle");
points.attr("cx", function(d) { return x(d.x)} )
.attr("cy", function(d,i) { return yScale[i](d.y); } )
.attr("r", 3)
.attr("class", function (d,i){return "blackDot" + i})
.attr("clip-path", "url(#clip)")
Right now the console log is showing these errors: Error: attribute cx: Expected length, "NaN". Error: attribute cy: Expected length, "NaN". It seems like I am not attributing the correct cx and cy to points, but I can't figure out what I am doing wrongly. Any help is greatly appreciated!
Your data structure is an array of objects, each one containing an inner array with the real coordinates for the circles. Therefore, that single enter selection will not work.
With minimal refactoring, my solution here is appending groups according to the objects, and then, for each one, appending circles according to the inner arrays. For that cumbersome yScale to work you cannot rely on the circle's indices anymore, so I'm using a local variable here:
var pointsGroup = g.selectAll(null)
.data(data)
.enter()
.append("g")
.attr("fill", function(d, i) {
local.set(this, yScale[i])
return colors[i];
});
var points = pointsGroup.selectAll(null)
.data(function(d) {
return d.data
})
.enter()
.append("circle")
.attr("cx", function(d) {
return x(d.x)
})
.attr("cy", function(d, i) {
return local.get(this)(d.y);
})
//etc...
Here is the code with those changes:
var local = d3.local();
var xValueArray = [0, 10, 20, 30, 40];
var arr = [
[0, 10, 20, 30, 40],
[0, 200, 300, 400, 500]
];
var dataset = [
[{
x: 0,
y: 0
}, {
x: 10,
y: 10
}, {
x: 20,
y: 20
}, {
x: 30,
y: 30
}, {
x: 40,
y: 40
}],
[{
x: 0,
y: 0
}, {
x: 10,
y: 200
}, {
x: 20,
y: 300
}, {
x: 30,
y: 400
}, {
x: 40,
y: 500
}]
];
var data = [];
for (var i = 0; i < 2; i++) {
data.push({
"data": dataset[i],
"yAxis": i
})
}
console.log(data);
//after restructuring dataset array
var data = [{
data: [{
x: 0,
y: 0
}, {
x: 10,
y: 10
}, {
x: 20,
y: 20
}, {
x: 30,
y: 30
}, {
x: 40,
y: 40
}],
yAxis: 0,
}, {
data: [{
x: 0,
y: 0
}, {
x: 10,
y: 200
}, {
x: 20,
y: 300
}, {
x: 30,
y: 400
}, {
x: 40,
y: 500
}],
yAxis: 1,
}];
const margin = {
left: 20,
right: 20,
top: 20,
bottom: 80
};
const svg = d3.select('svg');
svg.selectAll("*").remove();
const width = 200 - margin.left - margin.right;
const height = 200 - margin.top - margin.bottom;
const g = svg.append('g').attr('transform', `translate(${80},${margin.top})`);
//************* Axes and Gridlines ***************
const xAxisG = g.append('g');
const yAxisG = g.append('g');
xAxisG.append('text')
.attr('class', 'axis-label')
.attr('x', width / 3)
.attr('y', -10)
.style('fill', 'black')
.text(function(d) {
return "X Axis";
});
yAxisG.append('text')
.attr('class', 'axis-label')
.attr('id', 'yAxisLabel0')
.attr('x', -height / 2)
.attr('y', -15)
.attr('transform', `rotate(-90)`)
.style('text-anchor', 'middle')
.style('fill', 'black')
.text(function(d) {
return "Y Axis 1";
});
// interpolator for X axis -- inner plot region
var x = d3.scaleLinear()
.domain([0, d3.max(xValueArray)])
.range([0, width])
.nice();
var yScale = new Array();
for (var i = 0; i < 2; i++) {
// interpolator for Y axis -- inner plot region
var y = d3.scaleLinear()
.domain([0, d3.max(arr[i])])
.range([0, height])
.nice();
yScale.push(y);
}
const xAxis = d3.axisTop()
.scale(x)
.ticks(5)
.tickPadding(2)
.tickSize(-height)
const yAxis = d3.axisLeft()
.scale(yScale[0])
.ticks(5)
.tickPadding(2)
.tickSize(-width);
yAxisArray = new Array();
yAxisArray.push(yAxis);
for (var i = 1; i < 2; i++) {
var yAxisSecondary = d3.axisLeft()
.scale(yScale[i])
.ticks(5)
yAxisArray.push(yAxisSecondary);
}
svg.append("g")
.attr("class", "x axis")
.attr("transform", `translate(80,${height-80})`)
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("id", "ySecAxis0")
.attr("transform", "translate(80,20)")
.call(yAxis);
var translation = 50;
var textTranslation = 0;
var yLabelArray = ["Y Axis 1", "Y Axis 2"];
//loop starts from 1 as primary y axis is already plotted
for (var i = 1; i < 2; i++) {
svg.append("g")
.attr("transform", "translate(" + translation + "," + 20 + ")")
.attr("id", "ySecAxis" + i)
.call(yAxisArray[i]);
yAxisG.append('text')
.attr('x', -height / 2)
.attr('y', -60)
.attr('transform', `rotate(-90)`)
.attr("id", "yAxisLabel" + i)
.style('text-anchor', 'middle')
.style('fill', 'black')
.text(yLabelArray[i]);
translation -= 40;
textTranslation += 40;
}
//************* Mouseover ***************
var tooltip = d3.select("body")
.append("div")
.style("opacity", 0)
.attr("class", "tooltip")
.style("background-color", "white")
.style("border", "solid")
.style("border-width", "1px")
.style("border-radius", "5px")
.style("padding", "10px")
.style("position", "absolute")
var mouseover = function(d) {
tooltip
.html("x: " + d.x + "<br/>" + "y: " + d.y)
.style("opacity", 1)
.style("left", (d3.mouse(this)[0] + 90) + "px")
.style("top", (d3.mouse(this)[1]) + "px")
}
// A function that change this tooltip when the leaves a point: just need to set opacity to 0 again
var mouseleave = function(d) {
tooltip
.transition()
.duration(200)
.style("opacity", 0)
}
//************* Lines and Data Points ***************
var colors = ["blue", "red"];
var thisScale;
var line = d3.line()
.x(d => x(d.x))
.y(d => thisScale(d.y))
.curve(d3.curveLinear);
var paths = g.selectAll("foo")
.data(data)
.enter()
.append("path");
paths.attr("stroke", function(d, i) {
return colors[i]
})
.attr("d", d => {
thisScale = yScale[d.yAxis]
return line(d.data);
})
.attr("stroke-width", 2)
.attr("id", function(d, i) {
return "line" + i
})
.attr("fill", "none");
var pointsGroup = g.selectAll(null)
.data(data)
.enter()
.append("g")
.attr("fill", function(d, i) {
local.set(this, yScale[i])
return colors[i];
});
var points = pointsGroup.selectAll(null)
.data(function(d) {
return d.data
})
.enter()
.append("circle")
.attr("cx", function(d) {
return x(d.x)
})
.attr("cy", function(d, i) {
return local.get(this)(d.y);
})
.attr("r", 3)
.attr("class", function(d, i) {
return "blackDot" + i
})
.attr("clip-path", "url(#clip)")
.on("mouseover", mouseover)
.on("mouseleave", mouseleave)
//plot lines (hard-coding)
/*var lineFunction1 = d3.line()
.x(function(d) {
return x(d.x);
})
.y(function(d) {
return yScale[0](d.y);
})
.curve(d3.curveLinear);
var lineFunction2 = d3.line()
.x(function(d) {
return x(d.x);
})
.y(function(d) {
return yScale[1](d.y);
})
.curve(d3.curveLinear);
var path1 = g.append("path")
.attr("class", "path" + 0)
.attr("id", "line" + 0)
.attr("d", lineFunction1(data[0]))
.attr("stroke", "blue")
.attr("stroke-width", 2)
.attr("fill", "none")
.attr("clip-path", "url(#clip)");
var path2 = g.append("path")
.attr("class", "path" + 1)
.attr("id", "line" + 1)
.attr("d", lineFunction2(data[1]))
.attr("stroke", "red")
.attr("stroke-width", 2)
.attr("fill", "none")
.attr("clip-path", "url(#clip)");*/
//plot lines and points using for loop
/*for (var i = 0; i < 2; i++) {
var lineFunction = d3.line()
.x(function(d) {
return x(d.x);
})
.y(function(d) {
return yScale[i](d.y);
})
.curve(d3.curveLinear);
var paths = g.append("path")
.attr("class", "path" + i)
.attr("id", "line" + i)
.attr("d", lineFunction(data[i]))
.attr("stroke", colors[i])
.attr("stroke-width", 2)
.attr("fill", "none")
.attr("clip-path", "url(#clip)")
//plot a circle at each data point
g.selectAll(".dot")
.data(data[i])
.enter().append("circle")
.attr("cx", function(d) { return x(d.x)} )
.attr("cy", function(d) { return yScale[i](d.y); } )
.attr("r", 3)
.attr("class", "blackDot" + i)
.attr("clip-path", "url(#clip)")
.on("mouseover", mouseover)
.on("mouseleave", mouseleave)
}*/
//************* Legend ***************
var legend = svg.selectAll(".legend")
.data(data)
.enter().append("g")
legend.append("rect")
.attr("x", width + 65)
.attr("y", function(d, i) {
return 30 + i * 20;
})
.attr("width", 18)
.attr("height", 4)
.style("fill", function(d, i) {
return colors[i];
})
legend.append("text")
.attr("x", width + 60)
.attr("y", function(d, i) {
return 30 + i * 20;
})
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d, i) {
return "Value" + (i + 1);
})
.on("click", function(d, i) {
// Determine if current line is visible
let opacity = d3.select("#line" + i).style("opacity");
let newOpacity;
if (opacity == 0) {
newOpacity = 1;
} else {
newOpacity = 0
}
d3.select("#line" + i).style("opacity", newOpacity);
d3.selectAll(".blackDot" + i).style("opacity", newOpacity);
d3.select("#ySecAxis" + i).style("opacity", newOpacity);
d3.select("#yAxisLabel" + i).style("opacity", newOpacity);
});
//************* Zoom & Brush***************
const margin2 = {
left: 80,
right: 0,
top: 80,
bottom: 0
};
const height2 = height - margin2.top - margin2.bottom;
var xZoom = d3.scaleLinear().range([0, width]);
var yZoom = d3.scaleLinear().range([height2, 0]);
var xAxis2 = d3.axisTop(xZoom);
var brush = d3.brushX()
.extent([
[0, 0],
[width, height2]
])
.on("brush end", brushed);
var zoom = d3.zoom()
.scaleExtent([1, Infinity])
.translateExtent([
[0, 0],
[width, height]
])
.extent([
[0, 0],
[width, height]
])
.on("zoom", zoomed);
var clip = svg.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("width", width)
.attr("height", height)
.attr("x", 0)
.attr("y", 0);
xZoom.domain(x.domain());
yZoom.domain(y.domain());
var context = svg.append("g")
.attr("class", "context")
.attr("transform", "translate(" + margin2.left + "," + 125 + ")");
context.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height2 + ")")
.call(xAxis2);
context.append("g")
.attr("class", "brush")
.call(brush)
.call(brush.move, x.range());
function brushed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom") return;
var s = d3.event.selection || xZoom.range();
x.domain(s.map(xZoom.invert, xZoom));
svg.select(".x.axis").call(xAxis);
//svg.select(".path0").attr("d", lineFunction1(data[0]));
//svg.select(".path1").attr("d", lineFunction2(data[1]));
for (var i = 0; i < 2; i++) {
//svg.select(".path" + i).attr("d", lineFunction(data[i]));
g.selectAll(".blackDot" + i)
.attr("cx", function(d) {
return x(d.x);
})
.attr("cy", function(d) {
return yScale[i](d.y);
})
.attr("r", 3)
}
}
function zoomed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush") return;
var t = d3.event.transform;
x.domain(t.rescaleX(xZoom).domain());
svg.select(".x.axis").transiton(t).call(xAxis);
//svg.select(".path0").transiton(t).attr("d", lineFunction1(data[0]));
//svg.select(".path1").transiton(t).attr("d", lineFunction2(data[1]));
for (var i = 0; i < 2; i++) {
//svg.select(".path" + i).attr("d", lineFunction(data[i]));
g.selectAll(".blackDot" + i)
.attr("cx", function(d) {
return x(d.x);
})
.attr("cy", function(d) {
return yScale[i](d.y);
})
.attr("r", 3)
}
}
.xy_chart {
position: relative;
left: 70px;
top: 100px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<svg class="xy_chart"></svg>
Pay attention to the fact that one of the circles has an incorrect cy value. So, I'd suggest you to change your y scale approach.

How to assign a specific id to each line in a multi-line chart with multiple y-axes

I am trying to assign a specific id to each line in a multi-line chart with multiple y-axes so that I can create an interactive legend which toggles the lines on and off when the legend is clicked. Here is the link to my fiddle.
var xValueArray = [0, 10, 20, 30, 40];
var arr = [[0, 10, 20, 30, 40], [0, 200, 300, 400, 500]];
//data array is obtained after structuring arr array
var data = [[{x: 0, y: 0}, {x: 10, y: 10}, {x: 20, y: 20}, {x: 30, y: 30}, {x: 40, y: 40}], [{x: 0, y: 0}, {x: 10, y: 200}, {x: 20, y: 300}, {x: 30, y: 400}, {x: 40, y: 500}]];
const margin = {
left: 20,
right: 20,
top: 20,
bottom: 80
};
const svg = d3.select('svg');
svg.selectAll("*").remove();
const width = 200 - margin.left - margin.right;
const height = 200 - margin.top - margin.bottom;
//const g = svg.append('g').attr('transform', `translate(${margin.left},${margin.top})`);
const g = svg.append('g').attr('transform', `translate(${80},${margin.top})`);
//************* Axes and Gridlines ***************
const xAxisG = g.append('g');
const yAxisG = g.append('g');
xAxisG.append('text')
.attr('class','axis-label' )
.attr('x', width / 3)
.attr('y', -10)
.style('fill', 'black')
.text(function(d) {
return "X Axis";
});
yAxisG.append('text')
.attr('class','axis-label' )
.attr('id', 'primaryYLabel')
.attr('x', -height / 2 )
.attr('y', -15)
.attr('transform', `rotate(-90)`)
.style('text-anchor', 'middle')
.style('fill', 'black')
.text(function(d) {
return "Y Axis 1";
});
// interpolator for X axis -- inner plot region
var x = d3.scaleLinear()
.domain([0, d3.max(xValueArray)])
.range([0, width])
.nice();
var yScale = new Array();
for (var i = 0; i < 2; i++){
// interpolator for Y axis -- inner plot region
var y = d3.scaleLinear()
.domain([0, d3.max(arr[i])])
.range([0,height])
.nice();
yScale.push(y);
}
const xAxis = d3.axisTop()
.scale(x)
.ticks(5)
.tickPadding(2)
.tickSize(-height)
const yAxis = d3.axisLeft()
.scale(yScale[0])
.ticks(5)
.tickPadding(2)
.tickSize(-width);
yAxisArray = new Array();
yAxisArray.push(yAxis);
for (var i = 1; i < 2; i++){
var yAxisSecondary = d3.axisLeft()
.scale(yScale[i])
.ticks(5)
yAxisArray.push(yAxisSecondary);
}
svg.append("g")
.attr("class", "x axis")
.attr("transform", `translate(80,${height-80})`)
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(80,20)")
.call(yAxis);
var colors = ["blue", "red"];
//plot lines
for (var i = 0; i < 2; i++){
var lineFunction = d3.line()
.x(function(d) {return x(d.x); })
.y(function(d) {return yScale[i](d.y); })
.curve(d3.curveLinear);
//plot lines
var paths = g.append("path")
.attr("class", "path1")
.attr("id", "blueLine")
.attr("d", lineFunction(data[i]))
.attr("stroke", colors[i])
.attr("stroke-width", 2)
.attr("fill", "none")
.attr("clip-path", "url(#clip)")
//plot a circle at each data point
g.selectAll(".dot")
.data(data[i])
.enter().append("circle")
.attr("cx", function(d) { return x(d.x)} )
.attr("cy", function(d) { return yScale[i](d.y); } )
.attr("r", 3)
.attr("class", "blackDot")
.attr("clip-path", "url(#clip)")
}
var translation = 50;
var textTranslation = 0;
var yLabelArray = ["Y Axis 1", "Y Axis 2"];
//loop starts from 1 as primary y axis is already plotted
for (var i = 1; i < 2; i++){
svg.append("g")
.attr("transform", "translate(" + translation + "," + 20 + ")")
.call(yAxisArray[i]);
yAxisG.append('text')
.attr('x', -height / 2 )
.attr('y', -60)
.attr('transform', `rotate(-90)`)
.style('text-anchor', 'middle')
.style('fill', 'black')
.text(yLabelArray[i]);
translation -= 40;
textTranslation += 40;
}
//************* Legend ***************
var legend = svg.selectAll(".legend")
.data(data)
.enter().append("g")
legend.append("rect")
.attr("x", width + 65)
.attr("y", 30)
.attr("width", 18)
.attr("height", 4)
.style("fill", "blue")
legend.append("text")
.attr("x", width + 60)
.attr("y", 30)
.attr("dy", ".35em")
.style("text-anchor", "end")
.on("click", function(){
// Determine if current line is visible
var active = blueLine.active ? false : true,
newOpacity = active ? 0 : 1;
// Hide or show the elements
d3.select("#blueLine").style("opacity", newOpacity);
// Update whether or not the elements are active
blueLine.active = active;
})
.text(function(d) {
return "Value1";
});
var legend1 = svg.selectAll(".legend")
.data(data)
.enter().append("g")
legend1.append("rect")
.attr("x", width + 65)
.attr("y", 50)
.attr("width", 18)
.attr("height", 4)
.style("fill", "red")
legend1.append("text")
.attr("x", width + 60)
.attr("y", 50)
.attr("dy", ".35em")
.style("text-anchor", "end")
.on("click", function(){
// Determine if current line is visible
var active = blueLine.active ? false : true,
newOpacity = active ? 0 : 1;
// Hide or show the elements
d3.select("#blueLine").style("opacity", newOpacity);
// Update whether or not the elements are active
blueLine.active = active;
})
.text(function(d) {
return "Value2";
});
var pointLegend = svg.selectAll(".pointLegend")
.data(data)
.enter().append("g")
pointLegend.append("circle")
.attr("r", 3)
.attr("cx", width + 70)
.attr("cy", 70)
pointLegend.append("text")
.attr("x", width + 60)
.attr("y", 70)
.attr("dy", ".35em")
.style("text-anchor", "end")
.on("click", function(d){
// Determine if dots are visible
var active = d.active ? false : true,
newOpacity = active ? 0 : 1;
// Hide or show the elements
d3.selectAll(".blackDot").style("opacity", newOpacity);
// Update whether or not the elements are active
d.active = active;
})
.text(function(d) {
return "Data";
});
The fact that the chart can be plotted means the for loops are viable to draw the chart. I am using the for loops as I want my chart to be plotted given the user's input where I have a parameter named fieldCount in my actual code to track the number of series in my user's input thus I am using "2" in the for loop as I only have 2 arrays in my data array for simplification.
From the fiddle, I am only able to toggle the blue line using my clickable legend and not the red line as I am assigning same "blueLine" id to all my lines. How can I assign specific ids to specific lines so that I can toggle the lines using my legend and is there a way to code the legend so that I don't have to declare so many legend variables? Any help is greatly appreciated!
When you plot the lines you can pass the index i of the for loop into the id for the path as
//plot lines
var paths = g.append("path")
.attr("class", "path1")
.attr("id", "line" + i)
Then when you perform the on click function you can check the opacity as:
.on("click", function(d, i) {
// Determine if current line is visible
let opacity = d3.select("#line" + i).style("opacity");
let newOpacity;
if (opacity == 0) {
newOpacity = 1;
}else {
newOpacity = 0
}
d3.select("#line" + i).style("opacity", newOpacity);
});
Also in your code you are drawing two legends unnecessarily. I have fixed that issue as well.
Here is a working fiddle with the solution:
https://jsfiddle.net/7dgek9wq/1/
Full working example below:
var xValueArray = [0, 10, 20, 30, 40];
var arr = [
[0, 10, 20, 30, 40],
[0, 200, 300, 400, 500]
];
//data array is obtained after structuring arr array
var data = [
[{
x: 0,
y: 0
}, {
x: 10,
y: 10
}, {
x: 20,
y: 20
}, {
x: 30,
y: 30
}, {
x: 40,
y: 40
}],
[{
x: 0,
y: 0
}, {
x: 10,
y: 200
}, {
x: 20,
y: 300
}, {
x: 30,
y: 400
}, {
x: 40,
y: 500
}]
];
const margin = {
left: 20,
right: 20,
top: 20,
bottom: 80
};
const svg = d3.select('svg');
svg.selectAll("*").remove();
const width = 200 - margin.left - margin.right;
const height = 200 - margin.top - margin.bottom;
//const g = svg.append('g').attr('transform', `translate(${margin.left},${margin.top})`);
const g = svg.append('g').attr('transform', `translate(${80},${margin.top})`);
//************* Axes and Gridlines ***************
const xAxisG = g.append('g');
const yAxisG = g.append('g');
xAxisG.append('text')
.attr('class', 'axis-label')
.attr('x', width / 3)
.attr('y', -10)
.style('fill', 'black')
.text(function(d) {
return "X Axis";
});
yAxisG.append('text')
.attr('class', 'axis-label')
.attr('id', 'primaryYLabel')
.attr('x', -height / 2)
.attr('y', -15)
.attr('transform', `rotate(-90)`)
.style('text-anchor', 'middle')
.style('fill', 'black')
.text(function(d) {
return "Y Axis 1";
});
// interpolator for X axis -- inner plot region
var x = d3.scaleLinear()
.domain([0, d3.max(xValueArray)])
.range([0, width])
.nice();
var yScale = new Array();
for (var i = 0; i < 2; i++) {
// interpolator for Y axis -- inner plot region
var y = d3.scaleLinear()
.domain([0, d3.max(arr[i])])
.range([0, height])
.nice();
yScale.push(y);
}
const xAxis = d3.axisTop()
.scale(x)
.ticks(5)
.tickPadding(2)
.tickSize(-height)
const yAxis = d3.axisLeft()
.scale(yScale[0])
.ticks(5)
.tickPadding(2)
.tickSize(-width);
yAxisArray = new Array();
yAxisArray.push(yAxis);
for (var i = 1; i < 2; i++) {
var yAxisSecondary = d3.axisLeft()
.scale(yScale[i])
.ticks(5)
yAxisArray.push(yAxisSecondary);
}
svg.append("g")
.attr("class", "x axis")
.attr("transform", `translate(80,${height-80})`)
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(80,20)")
.call(yAxis);
var colors = ["blue", "red"];
//plot lines
for (var i = 0; i < 2; i++) {
var lineFunction = d3.line()
.x(function(d) {
return x(d.x);
})
.y(function(d) {
return yScale[i](d.y);
})
.curve(d3.curveLinear);
//plot lines
var paths = g.append("path")
.attr("class", "path1")
.attr("id", "line" + i)
.attr("d", lineFunction(data[i]))
.attr("stroke", colors[i])
.attr("stroke-width", 2)
.attr("fill", "none")
.attr("clip-path", "url(#clip)")
//plot a circle at each data point
g.selectAll(".dot")
.data(data[i])
.enter().append("circle")
.attr("cx", function(d) {
return x(d.x)
})
.attr("cy", function(d) {
return yScale[i](d.y);
})
.attr("r", 3)
.attr("class", "blackDot")
.attr("clip-path", "url(#clip)")
}
var translation = 50;
var textTranslation = 0;
var yLabelArray = ["Y Axis 1", "Y Axis 2"];
//loop starts from 1 as primary y axis is already plotted
for (var i = 1; i < 2; i++) {
svg.append("g")
.attr("transform", "translate(" + translation + "," + 20 + ")")
.call(yAxisArray[i]);
yAxisG.append('text')
.attr('x', -height / 2)
.attr('y', -60)
.attr('transform', `rotate(-90)`)
.style('text-anchor', 'middle')
.style('fill', 'black')
.text(yLabelArray[i]);
translation -= 40;
textTranslation += 40;
}
//************* Legend ***************
var legend = svg.selectAll(".legend")
.data(data)
.enter().append("g")
legend.append("rect")
.attr("x", width + 65)
.attr("y", function(d, i) {
return 30 + i * 20;
})
.attr("width", 18)
.attr("height", 4)
.style("fill", function(d, i) {
return colors[i];
})
legend.append("text")
.attr("x", width + 60)
.attr("y", function(d, i) {
return 30 + i * 20;
})
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d, i) {
return "Value" + (i + 1);
})
.on("click", function(d, i) {
// Determine if current line is visible
let opacity = d3.select("#line" + i).style("opacity");
let newOpacity;
if (opacity == 0) {
newOpacity = 1;
}else {
newOpacity = 0
}
d3.select("#line" + i).style("opacity", newOpacity);
});
var pointLegend = svg.selectAll(".pointLegend")
.data(data)
.enter().append("g")
pointLegend.append("circle")
.attr("r", 3)
.attr("cx", width + 70)
.attr("cy", 70)
pointLegend.append("text")
.attr("x", width + 60)
.attr("y", 70)
.attr("dy", ".35em")
.style("text-anchor", "end")
.on("click", function(d) {
// Determine if dots are visible
var active = d.active ? false : true,
newOpacity = active ? 0 : 1;
// Hide or show the elements
d3.selectAll(".blackDot").style("opacity", newOpacity);
// Update whether or not the elements are active
d.active = active;
})
.text(function(d) {
return "Data";
});
.xy_chart {
position: relative;
left: 70px;
top: 100px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<svg class="xy_chart"></svg>

How to toggle lines in a line chart using a legend

This question is actually a continuation of the question from here. I have made some changes to my fiddle and code since then but I am still facing the same problem. Link to my fiddle and code can be found here.
I am using a for loop to plot the lines as I want the chart to be dynamic which means the number of lines is drawn according to the number of arrays in the data array. In this case, there are 2 arrays in my data array as shown below.
var data = [[{x: 0, y: 0}, {x: 10, y: 10}, {x: 20, y: 20}, {x: 30, y: 30}, {x: 40, y: 40}],
[{x: 0, y: 0}, {x: 10, y: 200}, {x: 20, y: 300}, {x: 30, y: 400}, {x: 40, y: 500}]];
From my fiddle, the blue line will be toggled on and off when I click on both 'Y-Axis 1' and 'Y-Axis 2'. However, I want the red line to be toggled on and off when I click on Y-Axis 2. This is happening because I am assigning the same id to both lines in this piece of code.
//************* Plotting of graph ***************
var colors = ["blue", "red"];
//plot of chart
for (var i = 0; i < 2; i++){
var lineFunction = d3.line()
.x(function(d) {return x(d.x); })
.y(function(d) {return yScale[i](d.y); })
.curve(d3.curveLinear);
//plot lines
var paths = g.append("path")
.attr("class", "path1")
.attr("id", "blueLine")
.attr("d", lineFunction(data[i]))
.attr("stroke", colors[i])
.attr("stroke-width", 2)
.attr("fill", "none")
.attr("clip-path", "url(#clip)")
//plot a circle at each data point
g.selectAll(".dot")
.data(data[i])
.enter().append("circle")
.attr("cx", function(d) { return x(d.x)} )
.attr("cy", function(d) { return yScale[i](d.y); } )
.attr("r", 3)
.attr("class", "blackDot")
.attr("clip-path", "url(#clip)")
.on("mouseover", mouseover )
.on("mouseleave", mouseleave )
}
Is there a better way to plot the lines so that I can assign a specific id to each line being plotted and toggle the lines according to the legend? I have tried using forEach() but can't seem to get it to work. Any help is greatly appreciated!
First of all: you should not use a loop (for, while, forEach etc...) to append elements in a D3 code. That's not idiomatic, and you'll end up bending over backwards to fix things, like this very question will demonstrate.
The simplest fix without refactoring the code for a more idiomatic one, which will take a lot of work, is using the indices for setting the lines' IDs...
var paths = g.append("path")
.attr("class", "path1")
.attr("id", "blueLine" + i)
... and then, in the click listener, using this cumbersome and awkward window property, which is the elements' IDs:
.on("click", function(d, i) {
var active = window["blueLine" + i].active ? false : true,
newOpacity = active ? 0 : 1;
d3.select("#blueLine" + i).style("opacity", newOpacity);
window["blueLine" + i].active = active;
});
Here is your code with those changes:
var xValueArray = [0, 10, 20, 30, 40];
var arr = [
[0, 10, 20, 30, 40],
[0, 200, 300, 400, 500]
];
//data array is obtained after structuring arr array
var data = [
[{
x: 0,
y: 0
}, {
x: 10,
y: 10
}, {
x: 20,
y: 20
}, {
x: 30,
y: 30
}, {
x: 40,
y: 40
}],
[{
x: 0,
y: 0
}, {
x: 10,
y: 200
}, {
x: 20,
y: 300
}, {
x: 30,
y: 400
}, {
x: 40,
y: 500
}]
];
const margin = {
left: 20,
right: 20,
top: 20,
bottom: 80
};
const svg = d3.select('svg');
svg.selectAll("*").remove();
const width = 200 - margin.left - margin.right;
const height = 200 - margin.top - margin.bottom;
//const g = svg.append('g').attr('transform', `translate(${margin.left},${margin.top})`);
const g = svg.append('g').attr('transform', `translate(${80},${margin.top})`);
//************* Axes and Gridlines ***************
const xAxisG = g.append('g');
const yAxisG = g.append('g');
xAxisG.append('text')
.attr('class', 'axis-label')
.attr('x', width / 3)
.attr('y', -10)
.style('fill', 'black')
.text(function(d) {
return "X Axis";
});
yAxisG.append('text')
.attr('class', 'axis-label')
.attr('id', 'primaryYLabel')
.attr('x', -height / 2)
.attr('y', -15)
.attr('transform', `rotate(-90)`)
.style('text-anchor', 'middle')
.style('fill', 'black')
.text(function(d) {
return "Y Axis 1";
});
// interpolator for X axis -- inner plot region
var x = d3.scaleLinear()
.domain([0, d3.max(xValueArray)])
.range([0, width])
.nice();
var yScale = new Array();
for (var i = 0; i < 2; i++) {
// interpolator for Y axis -- inner plot region
var y = d3.scaleLinear()
.domain([0, d3.max(arr[i])])
.range([0, height])
.nice();
yScale.push(y);
}
const xAxis = d3.axisTop()
.scale(x)
.ticks(5)
.tickPadding(2)
.tickSize(-height)
const yAxis = d3.axisLeft()
.scale(yScale[0])
.ticks(5)
.tickPadding(2)
.tickSize(-width);
yAxisArray = new Array();
yAxisArray.push(yAxis);
for (var i = 1; i < 2; i++) {
var yAxisSecondary = d3.axisLeft()
.scale(yScale[i])
.ticks(5)
yAxisArray.push(yAxisSecondary);
}
svg.append("g")
.attr("class", "x axis")
.attr("transform", `translate(80,${height-80})`)
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(80,20)")
.call(yAxis);
//************* Mouseover ***************
var tooltip = d3.select("body")
.append("div")
.style("opacity", 0)
.attr("class", "tooltip")
.style("background-color", "white")
.style("border", "solid")
.style("border-width", "1px")
.style("border-radius", "5px")
.style("padding", "10px")
.style("position", "absolute")
// A function that change this tooltip when the user hover a point.
// Its opacity is set to 1: we can now see it. Plus it set the text and position of tooltip depending on the datapoint (d)
var mouseover = function(d) {
tooltip
.html("x: " + d.x + "<br/>" + "y: " + d.y)
.style("opacity", 1)
.style("left", (d3.mouse(this)[0] + 90) + "px")
.style("top", (d3.mouse(this)[1]) + "px")
}
// A function that change this tooltip when the leaves a point: just need to set opacity to 0 again
var mouseleave = function(d) {
tooltip
.transition()
.duration(200)
.style("opacity", 0)
}
//************* Plotting of graph ***************
var colors = ["blue", "red"];
//plot of chart
for (var i = 0; i < 2; i++) {
var lineFunction = d3.line()
.x(function(d) {
return x(d.x);
})
.y(function(d) {
return yScale[i](d.y);
})
.curve(d3.curveLinear);
//plot lines
var paths = g.append("path")
.attr("class", "path1")
.attr("id", "blueLine" + i)
.attr("d", lineFunction(data[i]))
.attr("stroke", colors[i])
.attr("stroke-width", 2)
.attr("fill", "none")
.attr("clip-path", "url(#clip)")
//plot a circle at each data point
g.selectAll(".dot")
.data(data[i])
.enter().append("circle")
.attr("cx", function(d) {
return x(d.x)
})
.attr("cy", function(d) {
return yScale[i](d.y);
})
.attr("r", 3)
.attr("class", "blackDot")
.attr("clip-path", "url(#clip)")
.on("mouseover", mouseover)
.on("mouseleave", mouseleave)
}
var translation = 50;
var textTranslation = 0;
var yLabelArray = ["Y Axis 1", "Y Axis 2"];
//loop starts from 1 as primary y axis is already plotted
for (var i = 1; i < 2; i++) {
svg.append("g")
.attr("transform", "translate(" + translation + "," + 20 + ")")
.call(yAxisArray[i]);
yAxisG.append('text')
.attr('x', -height / 2)
.attr('y', -60)
.attr('transform', `rotate(-90)`)
.style('text-anchor', 'middle')
.style('fill', 'black')
.text(yLabelArray[i]);
translation -= 20;
textTranslation += 20;
}
//************* Legend ***************
var legend = svg.selectAll('.legend')
.data(data)
.enter()
.append('g')
.attr('class', 'legend');
legend.append('rect')
.attr('x', width - 5)
.attr('y', function(d, i) {
return (i * 20) + 120;
})
.attr('width', 18)
.attr('height', 4)
.attr("fill", function(d, i) {
return colors[i]
});
legend.append('text')
.attr('x', width - 10)
.attr('y', function(d, i) {
return (i * 20) + 120;
})
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d, i) {
return yLabelArray[i]
})
.on("click", function(d, i) {
//Determine if current line is visible
var active = window["blueLine" + i].active ? false : true,
newOpacity = active ? 0 : 1;
//Hide or show the elements
d3.select("#blueLine" + i).style("opacity", newOpacity);
//Update whether or not the elements are active
window["blueLine" + i].active = active;
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<svg class="xy_chart"></svg>

Apply zoom pan and axis rescale in d3

I have created scattered chart in D3.
It's working fine but I have a requirement to add zooming and axis rescaling to the chart.
Since I am pretty much new to d3 I am not able to do it.I have seen some example about it but I am able apply the zooming, panning etc code in my chart.
Here is my code-
var margin = {
top: 35,
right: 10,
bottom: 40,
left: 80
},
width = width - margin.left - margin.right,
height = height - margin.top - margin.bottom;
var xValue = function(d){
return d[measureArray[1]];
},
x = d3.scale.linear()
.range([0, width*.98]),
xMap = function(d,i) {
return x(xValue(d));
},
make_x_axis = function() {
return d3.svg.gridaxis()
.scale(x)
.orient("bottom")
},
xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(function(d) {
return d;
});
var yValue = function(d){
return d[measureArray[0]];
},
y = d3.scale.linear()
.range([height*.98, 0]),
yMap = function(d,i){
return y(yValue(d));
},
make_y_axis = function() {
return d3.svg.gridaxis()
.scale(y)
.orient("left")
},
yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(function(d) {
// if(typeof displayY !=="undefined" && displayY =="Yes"){
// if(yAxisFormat==""){
return d;
});
var zValue = function(d){
return d[measureArray[2]];
},
zScale = d3.scale.linear()
.range([height*.98, 0]),
zMap = function(d) {
return zScale(zValue(d));
};
var color = d3.scale.category10();
var svg = d3.select("body") .append("svg")
.attr("id", "svg_" + div)
.attr("viewBox", "0 0 "+(width + margin.left + margin.right)+" "+(height + margin.top + margin.bottom+ 17.5 )+" ")
.classed("svg-content-responsive", true)
.append("g")
.attr("transform", "translate(" + (margin.left*.7) + "," + (margin.top+3) + ")");
var tooltip = d3.select("#"+divId).append("div")
.attr("class", "tooltip")
.style("opacity", 0);
data.forEach(function(d) {
d[measureArray[2]] = +d[measureArray[2]]
d[measureArray[1]] = +d[measureArray[1]];
d[measureArray[0]] = +d[measureArray[0]];
});
x.domain([d3.min(data, xValue)-1, d3.max(data, xValue)+1]);
y.domain([d3.min(data, yValue)-1, d3.max(data, yValue)+1]);
// }
if(typeof chartData[divId]["displayX"]!="undefined" && chartData[divId]["displayX"]!="" && chartData[divId]["displayX"]!="Yes"){}else{
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("class", "label")
.attr("x", width)
.attr("y", -6)
.style("text-anchor", "end")
.text(measureArray[1]);
}
// y-axis
if(typeof chartData[divId]["displayY"]!="undefined" && chartData[divId]["displayY"]!="" && chartData[divId]["displayY"]!="Yes"){}else{
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text(measureArray[0]);
}
var max = maximumValue(data, measureArray[2]);
var min = minimumValue(data, measureArray[2]);
var temp = {};
temp["min"] = min;
temp["max"] = max;
svg.selectAll(".circle")
.data(data)
.enter().append("circle")
.attr("index_value", function(d, i) {
return "index-" + d[columns[1]].replace(/[^a-zA-Z0-9]/g, '', 'gi');
})
.attr("class", function(d, i) {
return "bars-Bubble-index-" + d[columns[1]].replace(/[^a-zA-Z0-9]/g, '', 'gi')+div;
})
.attr("r", function(d,i){
// var scale = d3.scale.linear().domain([temp["max"], temp["min"]]).range(["38", "12"]);
// var radius = scale(data[i][measureArray[2]]);
return 6;
})
.attr("cx", xMap)
.attr("cy", yMap)
.attr("opacity",".6")
.attr("fill", 'red')
.attr("id",function(d,i) {
return d[columns[0]]+":"+d[columns[1]];
})
.attr("onclick", fun);
Working fiddle.
You can do it like this:
//define zoom behavior
var zoom = d3.behavior.zoom()
.on("zoom", draw);
//make a rectangle for receiving all the zoom/pan action.
svg.append("rect")
.attr("class", "pane")
.attr("width", width)
.attr("height", height)
.call(zoom);
//make a clip path so that the circle don't go out of the graph.
svg.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("x", x(0))
.attr("y", y(1))
.attr("width", x(1) - x(0))
.attr("height", y(0) - y(1));
Add the following class to the style(so that the rectangle pane is not visible) note: that the fill is none:
rect.pane {
cursor: move;
fill: none;
pointer-events: all;
}
After defining the domain, set the zoom x and y
x.domain([d3.min(data, xValue) - 1, d3.max(data, xValue) + 1]);
y.domain([d3.min(data, yValue) - 1, d3.max(data, yValue) + 1]);
// set the zoom for x and y
zoom.x(x);
zoom.y(y);
Make a group for all the circle so that its within the clippath
circlegroup = svg.append("g").attr("clip-path", "url(#clip)");
circlegroup.selectAll(".circle")...
Define the draw function which will be called on zoom and pan:
function draw() {
svg.select("g.x.axis").call(xAxis);//zoom of x axis
svg.select("g.y.axis").call(yAxis);//zoom of y axis
//update the position of the circle on zoom/pan
svg.selectAll("circle").attr("cx", xMap)
.attr("cy", yMap)
}
working code here

Dynamically scale data in an interactive chart, in which the data gets altered?

I have made an interactive bar chart, where the bars can be dragged up and down to adjust the data.
As the bar is dragged beyond the current max/min of the y-axis domain, the y-axis scales accordingly. However, I cannot get the rest of the bars to scale accordingly (i.e: if I increase one bar's value to a new extreme, the other bars should shrink along with the new scale)
I have a JS Fiddle here with everything that works so far.
// canvas properties
var margin =
{
top: 40,
bottom: 40,
right: 30,
left: 50
}
var w = 960 - margin.left - margin.right,
h = 500 - margin.top - margin.bottom;
// initiating axes
var x = d3.scale.ordinal()
.rangeRoundBands([0, w], 0.1);
var y = d3.scale.linear()
.range([h, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickSize(0)
.tickPadding(6);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var zeroline = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickSize(0)
.tickFormat('')
var svg = d3.select("body").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 + ")");
var newValue;
var data = [
{name: "A", value: -15},
{name: "B", value: -20},
{name: "C", value: -22},
{name: "D", value: -18},
{name: "E", value: 2},
{name: "F", value: 6},
{name: "G", value: 26},
{name: "H", value: 18}
];
function type(d)
{
d.value = +d.value;
return d;
}
function generateChart(error, data)
{
/* ========== Parse Data & Create Axes ========== */
// create a new property called y (needed for d3.events)
var data = data.map(function (d, i)
{
return {
name: d.name,
value: d.value,
y: d.value
}
});
var max = d3.max(data, function (d) { return d.y; });
var min = -max;
y.domain([min, max]).nice();
x.domain(data.map(function (d) { return d.name; }));
var zz = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (h / 2) + ")")
.call(zeroline);
var xx = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (h + 20) + ")")
.call(xAxis);
var yy = svg.append("g")
.attr("class", "y axis")
.call(yAxis);
/* ========== Drag Behaviour for Rectangles ========== */
var drag = d3.behavior.drag()
.on("drag", resize);
/* ========== Create Rectangles ========== */
var DataBar = svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", function (d) { return "bar bar--" + (d.value < 0 ? "negative" : "positive"); })
.attr("id", function (d) { return (d.value < 0 ? "negative" : "positive"); })
.attr("x", function (d) { return x(d.name); })
.attr("y", function (d) { return y(Math.max(0, d.value)); })
.attr("width", x.rangeBand())
.attr("height", function (d) { return Math.abs(y(d.value) - y(0)); })
.attr("cursor", "ns-resize")
.call(drag);
/* ========== Drag Functions ========== */
function resize(d)
{
if (d3.select(this)[0][0].id == 'positive')
{
d.y = d3.event.y;
if (y.invert(d.y) >= 0) // positive -> postive
{
var barHeight = -(d.y - y(0));
var bar = d3.select(this);
bar.attr("y", function (d) { return d.y; })
.attr("height", barHeight)
.style("fill", "steelblue");
}
else if (y.invert(d.y) < 0) // positive -> negative
{
var barHeight = Math.abs((d.y) - y(0))
var dragy = d3.event.y
barHeight += dragy - (d.y);
var bar = d3.select(this)
bar.attr("height", barHeight)
.attr("y", y(0))
.style("fill", "darkorange");
}
newValue = y.invert(d.y);
}
else if (d3.select(this)[0][0].id == 'negative')
{
var barHeight = Math.abs(y(d.y) - y(0))
var dragy = d3.event.y
if (y.invert(dragy) < 0) // negative -> negative
{
barHeight += dragy - y(d.y);
var bar = d3.select(this)
bar.attr("height", barHeight)
.attr("y", y(0))
.style("fill", "darkorange");
}
else if (y.invert(dragy) >= 0) // negative -> positive
{
var barHeight = -(dragy - y(0));
var bar = d3.select(this);
bar.attr("y", function (d) { return dragy; })
.attr("height", barHeight)
.style("fill", "steelblue");
}
//newValue = y.invert(dragy);
}
var max = d3.max(data, function (d) { return d.value; });
var min = -max;
var update = [];
if (newValue > max)// || newValue < min)
{
y.domain([-newValue, newValue]).nice();
yy.call(yAxis)
}
}
}
generateChart('error!', data)
(Quick note: the y-axis rescaling only works with the initial blue bars at the moment.)
Add the following block of code after the if (newValue > max) { ... } block:
var selectedObjectName = d3.select(this).data()[0].name;
svg.selectAll("rect.bar").filter(function (d){
return (d.name != selectedObjectName);})
.attr("height", function (d) { return Math.abs(y(d.value) - y(0));})
.attr("y", function (d) { return y(Math.max(0, d.value)); });
The idea is to select all the rectangles, filter out the currently selected one, and re-adjust the height and y coordinate of the remaining rectangles. Fiddle

Categories

Resources