hide/Show Grid lines using a button D3 - javascript

I have a button which I have created using the Div element in my html file. This button is supposed to be linked to my d3.js file via the grid lines. In my JS file I have created supposedly user friendly horizontal and vertical grid lines which are supposed to guide the user position other visualization on the page and then be turned off after its used.
Problem is I'm new to JavaScript and D3 in general and I can't seem to figure out how to link my div buttons to my SVG created grid lines to create the hide/show effect even after sternly scrapping through stack over flow and google. I have tried different variations and ideas with no success.
Code for my button
<body>
<div id="option">
<input name="updateButton"
type="button"
value="On/Off"
onclick="updateGrid()"
/>
</div>
</body>
code for my grid lines
var width = 1500,
height = 800,
colors = d3.scale.category20();
var svg = d3.select('body')
.append('svg')
.attr('oncontextmenu', 'return false;')
.attr('width', width)
.attr('height', height);
//vertical lines
Ver= svg.selectAll(".vline").data(d3.range(26)).enter()
.append("line")
.attr("x1", function (d) {
return d * 80;
})
.attr("x2", function (d) {
return d * 80;
})
.attr("y1", function (d) {
return 0;
})
.attr("y2", function (d) {
return 800;
})
.style("stroke", "#eee");
// horizontal lines
hor= svg.selectAll(".vline").data(d3.range(26)).enter()
.append("line")
.attr("y1", function (d) {
return d * 60;
})
.attr("y2", function (d) {
return d * 60;
})
.attr("x1", function (d) {
return 0;
})
.attr("x2", function (d) {
return 1500;
})
.style("stroke", "#eee");

There are different ways to achieve what you want.
My advice here is, since you're already using D3, don't call a function inline. Instead of that, use D3 itself to listen to the button click:
var toggle = true;
d3.select("input").on("click", function() {
d3.selectAll("line").style("opacity", +(toggle = !toggle))
})
Here I'm simply toggling the opacity between 0 and 1. In case you don't know (since you said you're new to JavaScript and D3), +true is 1 and +false is 0 (that's why I'm using the unary plus), and !toggle inverts the boolean.
Here is a demo, using your code with some minor changes:
var width = 1500,
height = 800,
colors = d3.scale.category20();
var svg = d3.select('body')
.append('svg')
.attr('oncontextmenu', 'return false;')
.attr('width', width)
.attr('height', height);
//vertical lines
var ver = svg.selectAll(null).data(d3.range(26)).enter()
.append("line")
.attr("x1", function(d) {
return d * 80;
})
.attr("x2", function(d) {
return d * 80;
})
.attr("y1", function(d) {
return 0;
})
.attr("y2", function(d) {
return 800;
})
.style("stroke", "#666")
.style("shape-rendering", "crispEdges");
// horizontal lines
var hor = svg.selectAll(null).data(d3.range(26)).enter()
.append("line")
.attr("y1", function(d) {
return d * 60;
})
.attr("y2", function(d) {
return d * 60;
})
.attr("x1", function(d) {
return 0;
})
.attr("x2", function(d) {
return 1500;
})
.style("stroke", "#666")
.style("shape-rendering", "crispEdges");
var toggle = true;
d3.select("input").on("click", function() {
d3.selectAll("line").style("opacity", +(toggle = !toggle))
})
<script src="https://d3js.org/d3.v3.min.js"></script>
<div id="option">
<input name="updateButton" type="button" value="On/Off" />
</div>

Hope my code will help:
// I add a div container instead of the <body> tag
var svg = d3.select("#container")
.append('svg')
.attr('oncontextmenu', 'return false;')
.attr('width', 1500)
.attr('height', 800)
.style("border", "1px solid #ccc")
// initial
redraw("horizontal");
// I prefer 'd3.select("#option input").on("click", func)' style
function updateGrid(event){
var btu = d3.select(event.target);
if(btu.attr("value") === "On"){
btu.attr("value", "Off");
redraw("vertical");
}else{
btu.attr("value", "On");
redraw("horizontal");
}
}
function redraw(type){
var data = d3.range(26), update, enter;
// there are three stage of binding data: update, enter and exit.
// But I just need to define two stage in your case.
update = svg
.selectAll("line")
// all line to be marked with a specific value
.data(data, function(d){ return d; });
enter = update
.enter()
.append("line")
.attr({
x1: 0, x2: 0,
y1: 0, y2: 0,
stroke: "black",
"stroke-width": 1
});
if(type === "horizontal"){
update
.transition()
.duration(1000)
.attr({
x1: 0, x2: 1500,
y1: function(d){ return d * 60; },
y2: function(d){ return d * 60; }
})
}else{
update
.transition()
.duration(1000)
.attr({
x1: function(d){ return d * 60; },
x2: function(d){ return d * 60; },
y1: 0, y2: 800
})
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="option">
<input name="updateButton" type="button" value="On" onclick="updateGrid(event)" />
</div>
<div id="container"></div>

Related

Create an Animated Pulsing Circle with D3

I know that similar questions have been asked before here on stackoverflow, but I have fairly specific requirements. I'm trying to generate a pulsing dot for a D3 chart.
I modified some code on codepen.io and came up with this.
How would I do the same thing using a D3 transition() rather than the (cheesy) CSS classes?
Something more along the lines of:
circle = circle.transition()
.duration(2000)
.attr("stroke-width", 20)
.attr("r", 10)
.transition()
.duration(2000)
.attr('stroke-width', 0.5)
.attr("r", 200)
.ease('sine')
.each("end", repeat);
Feel free to fork my sample pen.
Thanks!
Have a look at the example on GitHub by whityiu
Note that this is using d3 version 3.
I have adapted that code to produce something like you are after in the fiddle below.
var width = 500,
height = 450,
radius = 2.5,
dotFill = "#700f44",
outlineColor = "#700f44",
pulseLineColor = "#e61b8a",
bgColor = "#000",
pulseAnimationIntervalId;
var nodesArray = [{
"x": 100,
"y": 100
}];
// Set up the SVG display area
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.attr("fill", bgColor)
.classed('visual-area', true);
var bgRect = d3.select("svg").append("rect")
.attr("width", width)
.attr("height", height);
var linkSet = null,
nodeSet = null;
// Create data object sets
nodeSet = svg.selectAll(".node").data(nodesArray)
.enter().append("g")
.attr("class", "node")
.on('click', function() {
// Clear the pulse animation
clearInterval(pulseAnimationIntervalId);
});
// Draw outlines
nodeSet.append("circle")
.classed("outline pulse", true)
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("fill", 'none')
.attr("stroke", pulseLineColor)
.attr("r", radius);
// Draw nodes on top of outlines
nodeSet.append("circle")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("fill", dotFill)
.attr("stroke", outlineColor)
.attr("r", radius);
// Set pulse animation on interval
pulseAnimationIntervalId = setInterval(function() {
var times = 100,
distance = 8,
duration = 1000;
var outlines = svg.selectAll(".pulse");
// Function to handle one pulse animation
function repeat(iteration) {
if (iteration < times) {
outlines.transition()
.duration(duration)
.each("start", function() {
d3.select(".outline").attr("r", radius).attr("stroke", pulseLineColor);
})
.attrTween("r", function() {
return d3.interpolate(radius, radius + distance);
})
.styleTween("stroke", function() {
return d3.interpolate(pulseLineColor, bgColor);
})
.each("end", function() {
repeat(iteration + 1);
});
}
}
if (!outlines.empty()) {
repeat(0);
}
}, 6000);
Fiddle

update parameter in while loop D3

I was hoping somebody could help, I'm completely new to javascript but have been learning it in order to start producing interactive outputs in D3.
So I've started with the basics and produced line graphs etc, now I want to add an interactive element.
So I have a line graph, a slider and a function, the question is how do I link these up? playing with some online examples I understand how I can get the slider to update attributes of objects such as text, but I want it to update parameters in a loop to perform a calculation, which then runs and gives the line graph output.
My code is as follows and I've annotated the loop which I want to update:
<!DOCTYPE html>
<style type="text/css">
path {
stroke-width: 2;
fill: none;
}
line {
stroke: black;
}
text {
font-family: Arial;
font-size: 9pt;
}
</style>
<body>
<p>
<label for="repRate"
style="display: inline-block; width: 240px; text-align: right">
R = <span id="repRate-value">…</span>
</label>
<input type="range" min="0.0" max="1.0" step="0.01" id="repRate">
</p>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
d3.select("#repRate").on("input", function() {
update(+this.value);
});
update(0.1);
function update(repRate) {
// adjust slider text
d3.select("#repRate-value").text(repRate);
d3.select("#repRate").property("value", repRate);
}
//This is the function I want to update when the slider moves, I want the parameter R to update
//with the slider value, then loop through and produce a new graph
function parHost (R){
var i = 0;
var result = [];
do {
//I want to be able to keep it as a loop or similar, so that I can add more complex
//equations into it, but for now I've kept it simple
Nt1 = R*i
result.push(Nt1++) ;
Nt = Nt1
i++;
}
while (i < 50);
return result};
var data = parHost(0.5),
w = 900,
h = 200,
marginY = 50,
marginX = 20,
y = d3.scale.linear().domain([0, d3.max(data)]).range([0 + marginX, h - marginX]),
x = d3.scale.linear().domain([0, data.length]).range([0 + marginY, w - marginY])
var vis = d3.select("body")
.append("svg:svg")
.attr("width", w)
.attr("height", h)
var g = vis.append("svg:g")
.attr("transform", "translate(0, 200)");
var line = d3.svg.line()
.x(function(d,i) { return x(i); })
.y(function(d) { return -1 * y(d); })
g.append("svg:path").attr("d", line(data)).attr('stroke', 'blue');
g.append("svg:line")
.attr("x1", x(0))
.attr("y1", -1 * y(0))
.attr("x2", x(w))
.attr("y2", -1 * y(0))
g.append("svg:line")
.attr("x1", x(0))
.attr("y1", -1 * y(0))
.attr("x2", x(0))
.attr("y2", -1 * y(d3.max(data)))
g.selectAll(".xLabel")
.data(x.ticks(5))
.enter().append("svg:text")
.attr("class", "xLabel")
.text(String)
.attr("x", function(d) { return x(d) })
.attr("y", 0)
.attr("text-anchor", "middle")
g.selectAll(".yLabel")
.data(y.ticks(4))
.enter().append("svg:text")
.attr("class", "yLabel")
.text(String)
.attr("x", 0)
.attr("y", function(d) { return -1 * y(d) })
.attr("text-anchor", "right")
.attr("dy", 4)
g.selectAll(".xTicks")
.data(x.ticks(5))
.enter().append("svg:line")
.attr("class", "xTicks")
.attr("x1", function(d) { return x(d); })
.attr("y1", -1 * y(0))
.attr("x2", function(d) { return x(d); })
.attr("y2", -1 * y(-0.3))
g.selectAll(".yTicks")
.data(y.ticks(4))
.enter().append("svg:line")
.attr("class", "yTicks")
.attr("y1", function(d) { return -1 * y(d); })
.attr("x1", x(-0.3))
.attr("y2", function(d) { return -1 * y(d); })
.attr("x2", x(0))
</script>
</body>
Any help with this would be much appreciated.
On each slider input event you have to update the parts of the chart (e.g. line, axisTicks, etc.) which depend on your data. You could e.g. extend your update function like this:
function update(repRate) {
// adjust slider text
d3.select("#repRate-value").text(repRate);
d3.select("#repRate").property("value", repRate);
// Generate new Data depending on slider value
var newData = parHost(repRate);
// Update the chart
drawChart(newData);
}
where the drawChart(newData) could look like this:
function drawChart(newData) {
// Delete the old elements
g.selectAll("*").remove();
g.append("svg:path").attr("d", line(newData)).attr('stroke', 'blue');
...
}
Another method is to declare data depending elements as variables and just change their attribute on an update (which i would recommend):
...
var dataLine = g.append("svg:path");
...
function drawChart(newData) {
path.attr("d", line(newData)).attr('stroke', 'blue');
}
Here is an example plunker.
Check out also this example.

How to make plotting possible on x-axis lines only? (d3.js)

I've just started with trying out the d3 library.
I am trying to create an interactive line chart where people can plot their own points. You can find it over here: http://jsfiddle.net/6FjJ2/
My question is: how can I make sure that plotting can only be done on the x-axis' lines? If you check out my example, you will see it kind of works, but with a lot of cheating. Check out the ok variable... What would be the correct way of achieving this? I have no idea how I can achieve this with a ... so I'm getting a lot of seperate 's.
var data = [2, 3, 4, 3, 4, 5],
w = 1000,
h = 300,
monthsData = [],
months = 18;
for(i = 0; i < months; i++) {
monthsData.push(i);
}
var max = d3.max(monthsData),
x = d3.scale.linear().domain([0, monthsData.length]).range([0, w]),
y = d3.scale.linear().domain([0, max]).range([h, 0]),
pointpos = [];
lvl = [0, 10],
lvly = d3.scale.linear().domain([0, d3.max(lvl)]).range([h, 0]);
svg = d3.select(".chart")
.attr("width", w)
.attr("height", h);
svg.selectAll('path.line')
// Return "data" array which will form the path coordinates
.data([data])
// Add path
.enter().append("svg:path")
.attr("d", d3.svg.line()
.x(function(d, i) { return x(i); })
.y(y));
// Y-axis ticks
ticks = svg.selectAll(".ticky")
// Change number of ticks for more gridlines!
.data(lvly.ticks(10))
.enter().append("svg:g")
.attr("transform", function(d) { return "translate(0, " + (lvly(d)) + ")"; })
.attr("class", "ticky");
ticks.append("svg:line")
.attr("y1", 0)
.attr("y2", 0)
.attr("x1", 0)
.attr("x2", w);
ticks.append("svg:text")
.text( function(d) { return d; })
.attr("text-anchor","end")
.attr("dy", 2)
.attr("dx", -4);
// X-axis ticks
ticks = svg.selectAll(".tickx")
.data(x.ticks(monthsData.length))
.enter().append("svg:g")
.attr("transform", function(d, i) { return "translate(" + (x(i)) + ", 0)"; })
.attr("class", "tickx");
ticks.append("svg:line")
.attr("y1", h)
.attr("y2", 0)
.attr("x1", 0)
.attr("x2", 0);
ticks.append("svg:text")
.text( function(d, i) { return i; })
.attr("y", h)
.attr("dy", 15)
.attr("dx", -2);
// var d = $(".tickx:first line").css({"stroke-width" : "2", opacity : "1"});
var line;
var ok = -55;
svg.on("mousedown", mouseDown)
.on("mouseup", mouseUp);
function mouseDown() {
var m = d3.mouse(this);
line = svg.append("line")
.data(monthsData)
/* .attr("x1", m[0]) */
.attr("x1", function(d, i) { pointpos.push(m[0]); ok += 55; return ok;})
.attr("y1", m[1])
.attr("x2", function(d, i) { return ok + 56; })
/* .attr("x2", function(d, i) {return 300; }) */
.attr("y2", m[1]);
svg.on("mousemove", mouseMove);
var m = d3.mouse(this);
var point = svg.append("circle")
.attr("cx", function(d, i) { return ok; })
.attr("cy", function(d, i) { return m[1]; })
.attr("r", 8);
lvl.push(100);
}
function mouseMove() {
var m = d3.mouse(this);
line.attr("y2", m[1]);
/* .attr("y1", m[0]); */
}
function mouseUp() {
// Change null to mousemove for a graph kinda draw mode
svg.on("mousemove", mouseMove);
}
Excuse my bad code!
Thanks in advance.
It looks like you need:
histogram layout for binning your points.
ordinal scales for restricting their x-axis positions according to the bin
As a sidenote, you can use d3.svg.axis to draw the axis for you.

Appending an element to an existing element in d3.js

I am trying to create a realtime barchart that plots values over time, using d3.js
This is how I am doing it.
var dataset = [ 5, 10, 15, 20, 25 ];
var w = 1800;
var h = 500;
var barPadding = 1;
setInterval(function(){
dataset.push(Math.floor(Math.random()*51));
draw();
},1000);
function draw(){
d3.select("svg").remove();
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect").data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i){return 12*i;})
.attr("y", function(d){return h -d*4; })
.attr("width", 11)
.attr("height", function(d) { return d * 4; })
.attr("fill", "teal")
.attr("fill", function(d) { return "rgb(0, 0, " + (d * 10) + ")";});
}
The problem is that I am redrawing the whole graph every time a new value is added to the data array.
How do I append a bar to the bar graph that is already drawn, every time a new value is added to the array, rather than redrawing it every time?
You're close, just stop redrawing the svg element. If you only need to add new elements, then that's what your draw function should do when it's called.
var dataset = [ 5, 10, 15, 20, 25 ];
var w = 1800;
var h = 300;
var barPadding = 1;
var container = d3.select("body").append("svg").attr("width", w).attr("height", h).append("g");
setInterval(function(){
dataset.push(Math.floor(Math.random()*51));
draw();
},1000);
function draw(){
container.selectAll("rect").data(dataset).enter().append("rect")
.attr("x", function(d, i){return 12*i;})
.attr("y", function(d){return h -d*4; })
.attr("width", 11)
.attr("height", function(d) { return d * 4; })
.attr("fill", "teal")
.attr("fill", function(d) { return "rgb(0, 0, " + (d * 10) + ")";});
}​
http://jsfiddle.net/Wexcode/LYqfU/
Not sure what kind of effect you're looking for, but have a look at this fiddle.
The following redraw function will keep adding to the barchart so it will continue to grow to the right:
function redraw() {
var rect = svg.selectAll("rect")
.data(dataset);
rect.enter().insert("rect", "line")
.attr("x", function(d, i) { return 12*(i+1); })
.attr("y", function(d) { return h -d*4 })
.attr("width", 11)
.attr("height", function(d) { return d * 4; })
.attr("fill", "teal")
.attr("fill", function(d) { return "rgb(0, 0, " + (d * 10) + ")";})
rect.transition()
.duration(800)
.attr("x", function(d, i) { return 12*i; });
}
Borrowed from an mbostock tutorial.

Interacting forces with D3.js

I'm trying to 'simulate' gravitational forces on particles as they move towards a 'focal' point. Practically speaking, I am trying to modify the following code so that the particles are pulled slightly off-course by the orange node on their way to the blue node. My problem is that I am having trouble conceptualising this using D3.js force directed layouts. I realise this is a pretty vague question, but any help is greatly appreciated! Image and code are below:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>Force Layouts - Quantitative Foci</title>
<script type="text/javascript" src="http://d3js.org/d3.v2.min.js?2.8.1"></script>
<style type="text/css">
circle {
stroke: #fff;
}
svg {
fill: #fff;
stroke: #000;
}
</style>
</head>
<body>
<div id="body">
<div id="chart"></div>
</div>
<script type="text/javascript">
var w = 1280,
h = 800,
color = d3.scale.category10();
var force = d3.layout.force()
.gravity(0)
.charge(-5)
.linkStrength(0)
.size([w, h]);
var links = force.links(),
nodes = force.nodes(),
centers = [
{type: 0, x: 3 * w / 6, y: 2 * h / 6, fixed: true},
{type: 1, x: 4 * w / 6, y: 4 * h / 6, fixed: true}
];
var svg = d3.select("#chart").append("svg:svg")
.attr("width", w)
.attr("height", h);
svg.append("svg:rect")
.attr("width", w)
.attr("height", h);
svg.selectAll("circle")
.data(centers)
.enter().append("svg:circle")
.attr("r", 12)
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.style("fill", fill)
.call(force.drag);
force.on("tick", function(e) {
var k = e.alpha * .1;
nodes.forEach(function(node) {
var center = centers[node.type];
node.x += (center.x - node.x) * k;
node.y += (center.y - node.y) * k;
});
svg.selectAll("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
svg.selectAll("line")
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
});
var p0;
svg.on("mousemove", function() {
var p1 = d3.svg.mouse(this),
a = {type: 0, x: p1[0], y: p1[1], px: (p0 || (p0 = p1))[0], py: p0[1]},
b = {type: 1, x: centers[1].x, y: centers[1].y, fixed:true},
link = {source: a, target: b};
p0 = p1;
svg.selectAll()
.data([a, b])
.enter().append("svg:circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 4.5)
.style("fill", fill)
.transition()
.delay(3000)
.attr("r", 1e-6)
.remove();
svg.insert("svg:line", "circle")
.data([link])
.transition()
.delay(3000)
.each("end", function() {
nodes.splice(nodes.indexOf(a), 1);
nodes.splice(nodes.indexOf(b), 1);
links.splice(links.indexOf(link), 1);
})
.remove();
nodes.push(a, b);
links.push(link);
force.start();
});
function fill(d) {
return color(d.type);
}
</script>
</body>
</html>
1 http://jsfiddle.net/fbW7T/1/ -- before.
[2] http://jsfiddle.net/fbW7T/2/ -- after, with Lephix' proposal.
First of all, your question is very interesting :).
Replace your force.on("tick", function(e) {...}) with following codes. Actually I just add a variable and 10 lines code in the function.
fr means the radius of a circle zone that particles should off-course from the orange node.
var fr = 100;
force.on("tick", function(e) {
var k = e.alpha * .1;
nodes.forEach(function(node) {
var center = centers[node.type];
node.x += (center.x - node.x) * k;
node.y += (center.y - node.y) * k;
if (node.type == 0) {
center = centers[1];
while (Math.abs(node.x - center.x) < fr && Math.abs(node.y - center.y) < fr) {
if (Math.abs(node.x - center.x) >= Math.abs(node.y - center.y)) {
node.x += (node.x - center.x)/Math.abs(node.x - center.x);
} else {
node.y += (node.y - center.y)/Math.abs(node.y - center.y);
}
}
}
});
svg.selectAll("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
svg.selectAll("line")
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
});

Categories

Resources