----------------------------------------------------------
Using a
Use a
Using HTML inputs with d3.js
Part of the attraction of using technologies like d3.js is that it expands the scope of what is possible in a web page. At the same time, there are many different options for displaying content on a page and plenty of ways of interacting with it.
Some of the most basic of capabilities has been the use of HTML entities that allow the entry of data on a page. This can take a range of different forms (pun intended) and the
<input>
tag is one of the most basic.What is an HTML input?
An HTML input is an element in HTML that allows a web page to input data. There are a range of different input types (with varying degrees of compatibility with browsers) and they are typically utilised inside a
<form>
element.
For example the following code allows a web page to place two fields on a web page so that a user can enter their first and last names in separate boxes;
<form>
First name: <input
type=
"text"
name=
"firstname"
><br>
Last name: <input
type=
"text"
name=
"lastname"
>
</form>
The page would then display the following;
A form input |
The range of input types is large and includes;
- text: A simple text field that a user can enter information into.
- radio: Buttons that let a user select only one of a limited number of choices.
- button: A clickable button that can activate JavaScript.
- range: A slider control for setting a number whose exact value is not important.
- number: A field for entering a number or toggling a number up and down.
… and many more. To check out others and get further background, it would be worth while visiting the Mozilla developer pages or w3schools.com.
While d3.js has the power to control and manipulate a web page to an extreme extent, sometimes it’s desirable to use a simple process to get a result. The following explanations will demonstrate a simple use case linking an HTML input with a d3.js element and will go on to provide examples of using multiple inputs, affecting multiple elements and using different input types. The examples are deliberately kept simple. They are intended to demonstrate functionality and to provide a starting position for you to go forward :-).
Using a range
input with d3.js
The first example we will follow will use a
range
input to adjust the radius of a circle.Adjust the radius of a circle |
THE CODE
The following is the full code for the example. A live version is available online at bl.ocks.org or GitHub. It is also available as the file ‘input-radius.html’ as a separate download with D3 Tips and Tricks. A a copy of most the files that appear in the book can be downloaded (in a zip file) when you download the book from Leanpub.
<!DOCTYPE html>
<meta
charset=
"utf-8"
>
<title>
Input test (circle)</title>
<p>
<label
for=
"nRadius"
style=
"display: inline-block; width: 240px; text-align: right"
>
radius = <span
id=
"nRadius-value"
>
…</span>
</label>
<input
type=
"range"
min=
"1"
max=
"150"
id=
"nRadius"
>
</p>
<script
src=
"http://d3js.org/d3.v3.min.js"
></script>
<script>
var
width
=
600
;
var
height
=
300
;
var
holder
=
d3
.
select
(
"body"
)
.
append
(
"svg"
)
.
attr
(
"width"
,
width
)
.
attr
(
"height"
,
height
);
// draw the circle
holder
.
append
(
"circle"
)
.
attr
(
"cx"
,
300
)
.
attr
(
"cy"
,
150
)
.
style
(
"fill"
,
"none"
)
.
style
(
"stroke"
,
"blue"
)
.
attr
(
"r"
,
120
);
// when the input range changes update the circle
d3
.
select
(
"#nRadius"
).
on
(
"input"
,
function
()
{
update
(
+
this
.
value
);
});
// Initial starting radius of the circle
update
(
120
);
// update the elements
function
update
(
nRadius
)
{
// adjust the text on the range slider
d3
.
select
(
"#nRadius-value"
).
text
(
nRadius
);
d3
.
select
(
"#nRadius"
).
property
(
"value"
,
nRadius
);
// update the circle radius
holder
.
selectAll
(
"circle"
)
.
attr
(
"r"
,
nRadius
);
}
</script>
THE EXPLANATION
As with the other examples in the book I will not go over some of the simpler lines of code that are covered in greater detail in earlier sections of the book and will concentrate on those sections that contain new concepts, code or look like they might need expanding :-).
The first section is the portion that sets out the html
range
input;<p>
<label
for=
"nRadius"
style=
"display: inline-block; width: 240px; text-align: right"
>
radius = <span
id=
"nRadius-value"
>
…</span>
</label>
<input
type=
"range"
min=
"1"
max=
"150"
id=
"nRadius"
>
</p>
The entire block is enclosed in a paragraph (
<p>
) tag so that is appears on a single line. It can be broken down into the label that occurs before the input slider which is given the id nRadius-value
and the input
proper.
The
for
attribute of the label
tag equals to the id attribute of the input element to bind them together. This allows us to update the text later as the slider is moved.
The
input
tag can include four attributes that specify restrictions on the operation of the slider;max
: specifies the maximum value allowedmin
: specifies the minimum value allowedstep
: specifies the number intervals as you move the slidervalue
: Specifies the default value
The
id
s supplied for both the label and the input are important since they provide the reference for our d3.js script.
The first portion of our JavaScript is fairly routine if you’ve been following along with the rest of the book.
var
width
=
600
;
var
height
=
300
;
var
holder
=
d3
.
select
(
"body"
)
.
append
(
"svg"
)
.
attr
(
"width"
,
width
)
.
attr
(
"height"
,
height
);
// draw the circle
holder
.
append
(
"circle"
)
.
attr
(
"cx"
,
300
)
.
attr
(
"cy"
,
150
)
.
style
(
"fill"
,
"none"
)
.
style
(
"stroke"
,
"blue"
)
.
attr
(
"r"
,
120
);
We append an SVG element to the
body
of our page and then we append a circle with some particular styling to the SVG element.
Then things start to get more interesting…
d3
.
select
(
"#nRadius"
).
on
(
"input"
,
function
()
{
update
(
+
this
.
value
);
});
We select our input using the
id
that we had declared earlier in the html (nRadius
). Then we use the .on
operator which adds what is called an ‘event listener’ to the element so that when there is a change in the element (in this case an adjustment of the slider of the input) a function is called (function()
) that in turn calls the update
function with the value from the input (+this.value
). We haven’t seen the update
function yet, but never fear, it’s coming.
We also call the update function with a specific value in the next line;
update
(
120
);
This might seem slightly redundant, but unless the function gets a value, the text associated with the range input doesn’t get a reading and remains on ‘…’ until the slider is moved.
Lastly we have our
update
function;function
update
(
nRadius
)
{
// adjust the text on the range slider
d3
.
select
(
"#nRadius-value"
).
text
(
nRadius
);
d3
.
select
(
"#nRadius"
).
property
(
"value"
,
nRadius
);
// update the circle radius
holder
.
selectAll
(
"circle"
)
.
attr
(
"r"
,
nRadius
);
}
The first part of the function selects the
label
associated with our input (with the id
, nRadius-value
) and applies the vaule that has been passed into the function (nRadius
). The next line selects the input itself and applies the value to it (this would be the equivalent of having value="<number here>"
as a property in the html).
Lastly, we select the circle element and apply the new radius value based on our input value
nRadius
(.attr("r", nRadius)
).
And there we have it, a fully adjustable radius for our circle controlled with an HTML input.
Maximum radius for our circle |
Using more than one input
In this example we will use two separate inputs (range type) to adjust the height and width of a rectangle.
Dual Inputs |
This is not too much of a stretch from the previous single input example with the radius of a circle, but it may be useful to reinforce the concept and illustrate something slightly different.
THE CODE
The following is the full code for the example. A live version is available online at bl.ocks.org or GitHub. It is also available as the file ‘input-double.html’ as a separate download with D3 Tips and Tricks. A a copy of most the files that appear in the book can be downloaded (in a zip file) when you download the book from Leanpub.
<!DOCTYPE html>
<meta
charset=
"utf-8"
>
<title>
Double Input Test</title>
<p>
<label
for=
"nHeight"
style=
"display: inline-block; width: 240px; text-align: right"
>
height = <span
id=
"nHeight-value"
>
…</span>
</label>
<input
type=
"range"
min=
"1"
max=
"280"
id=
"nHeight"
>
</p>
<p>
<label
for=
"nWidth"
style=
"display: inline-block; width: 240px; text-align: right"
>
width = <span
id=
"nWidth-value"
>
…</span>
</label>
<input
type=
"range"
min=
"1"
max=
"400"
id=
"nWidth"
>
</p>
<script
src=
"http://d3js.org/d3.v3.min.js"
></script>
<script>
var
width
=
600
;
var
height
=
300
;
var
holder
=
d3
.
select
(
"body"
)
.
append
(
"svg"
)
.
attr
(
"width"
,
width
)
.
attr
(
"height"
,
height
);
// draw a rectangle
holder
.
append
(
"rect"
)
.
attr
(
"x"
,
300
)
.
attr
(
"y"
,
150
)
.
style
(
"fill"
,
"none"
)
.
style
(
"stroke"
,
"blue"
)
.
attr
(
"height"
,
150
)
.
attr
(
"width"
,
200
);
// read a change in the height input
d3
.
select
(
"#nHeight"
).
on
(
"input"
,
function
()
{
updateHeight
(
+
this
.
value
);
});
// read a change in the width input
d3
.
select
(
"#nWidth"
).
on
(
"input"
,
function
()
{
updateWidth
(
+
this
.
value
);
});
// update the values
updateHeight
(
150
);
updateWidth
(
100
);
// Update the height attributes
function
updateHeight
(
nHeight
)
{
// adjust the text on the range slider
d3
.
select
(
"#nHeight-value"
).
text
(
nHeight
);
d3
.
select
(
"#nHeight"
).
property
(
"value"
,
nHeight
);
// update the rectangle height
holder
.
selectAll
(
"rect"
)
.
attr
(
"y"
,
150
-
(
nHeight
/
2
))
.
attr
(
"height"
,
nHeight
);
}
// Update the width attributes
function
updateWidth
(
nWidth
)
{
// adjust the text on the range slider
d3
.
select
(
"#nWidth-value"
).
text
(
nWidth
);
d3
.
select
(
"#nWidth"
).
property
(
"value"
,
nWidth
);
// update the rectangle width
holder
.
selectAll
(
"rect"
)
.
attr
(
"x"
,
300
-
(
nWidth
/
2
))
.
attr
(
"width"
,
nWidth
);
}
</script>
THE EXPLANATION
For the sake of brevity, this explanation will simply concentrate on the differences between the previous single input example and this one.
The declarations for the inputs in the HTML at the start of the code are simply duplicates of each other in terms of function;
<p>
<label
for=
"nHeight"
style=
"display: inline-block; width: 240px; text-align: right"
>
height = <span
id=
"nHeight-value"
>
…</span>
</label>
<input
type=
"range"
min=
"1"
max=
"280"
id=
"nHeight"
>
</p>
<p>
<label
for=
"nWidth"
style=
"display: inline-block; width: 240px; text-align: right"
>
width = <span
id=
"nWidth-value"
>
…</span>
</label>
<input
type=
"range"
min=
"1"
max=
"400"
id=
"nWidth"
>
</p>
The only significant difference is the declaration of the
id
’s for each input and it’s respective label.
The JavaScript selection of the inputs is more duplication;
d3
.
select
(
"#nHeight"
).
on
(
"input"
,
function
()
{
updateHeight
(
+
this
.
value
);
});
d3
.
select
(
"#nWidth"
).
on
(
"input"
,
function
()
{
updateWidth
(
+
this
.
value
);
});
Again the only substantive difference is the use of the appropriate
id
values.
The updating of the width and height is done via two different functions;
function
updateHeight
(
nHeight
)
{
// adjust the text on the range slider
d3
.
select
(
"#nHeight-value"
).
text
(
nHeight
);
d3
.
select
(
"#nHeight"
).
property
(
"value"
,
nHeight
);
// update the rectangle height
holder
.
selectAll
(
"rect"
)
.
attr
(
"y"
,
150
-
(
nHeight
/
2
))
.
attr
(
"height"
,
nHeight
);
}
// Update the width attributes
function
updateWidth
(
nWidth
)
{
// adjust the text on the range slider
d3
.
select
(
"#nWidth-value"
).
text
(
nWidth
);
d3
.
select
(
"#nWidth"
).
property
(
"value"
,
nWidth
);
// update the rectangle width
holder
.
selectAll
(
"rect"
)
.
attr
(
"x"
,
300
-
(
nWidth
/
2
))
.
attr
(
"width"
,
nWidth
);
}
The rectangle is selected using a common
rect
designator, so multiple rectangles could be controlled. But each function controls only a specific attribute (height or width).Rotate text with an input
This example is really just a derivative of the adjustment of a single attribute of an element.
I happen to think it’s just a little bit ‘neater’ because it includes text, but in reality, it’s just another attribute that can be adjusted.
THE EXPLANATION
We’ll dispense with the full code listing since it’s just a regurgitation of the adjusting of the radius of the circle example, but the code for the example is available online at bl.ocks.org or GitHub. It is also available as the file ‘input-text-rotate.html’ as a separate download with D3 Tips and Tricks. A a copy of most the files that appear in the book can be downloaded (in a zip file) when you download the book from Leanpub.
The only, thing of even a slight difference (other than some naming conventions) is the initial drawing of the text…
holder
.
append
(
"text"
)
.
style
(
"fill"
,
"black"
)
.
style
(
"font-size"
,
"56px"
)
.
attr
(
"dy"
,
".35em"
)
.
attr
(
"text-anchor"
,
"middle"
)
.
attr
(
"transform"
,
"translate(300,150) rotate(0)"
)
.
text
(
"d3noob.org"
);
… and the update function;
function
update
(
nAngle
)
{
// adjust the text on the range slider
d3
.
select
(
"#nAngle-value"
).
text
(
nAngle
);
d3
.
select
(
"#nAngle"
).
property
(
"value"
,
nAngle
);
// rotate the text
holder
.
select
(
"text"
)
.
attr
(
"transform"
,
"translate(300,150) rotate("
+
nAngle
+
")"
);
}
Use a number
input with d3.js
There are obviously different inputs that can be selected. The following example still rotates our text, but uses a
number
type of input to do it;<p>
<label
for=
"nValue"
style=
"display: inline-block; width: 240px; text-align: right"
>
angle = <span
id=
"nValue-value"
></span>
</label>
<input
type=
"number"
min=
"0"
max=
"360"
step=
"5"
value=
"0"
id=
"nValue"
>
</p>
we have set the
step
value to speed things up a bit when rotating, but it’s completely optional.
The input itself can be adjusted up or down using a mouse click or have a number typed into the input box.
Text rotation with a number input |
This type of input is slightly different from the range type since it isn’t fully supported under Firefox and as a result when I was testing it the arrow keys for going up and down weren’t present.
The full code for the example is available online at bl.ocks.org or GitHub. It is also available as the file ‘input-number-text.html’ as a separate download with D3 Tips and Tricks. A a copy of most the files that appear in the book can be downloaded (in a zip file) when you download the book from Leanpub.
Change more than one element with an input
The final example looking at using HTML inputs with d3.js incorporates a single input acting or two different elements. This might seem self evident, but if you’re as unfamiliar with HTML as I am (it’s embarrassing I know, but what can you do?) it may be of assistance.
The end result is to produce a single slider as a
range
input that rotates two separate text objects in different directions simultaneously.Dual text rotation |
THE CODE
The following is the full code for the example. A live version is available online at bl.ocks.org or GitHub. It is also available as the file ‘input-text-rotate-2.html’ as a separate download with D3 Tips and Tricks. A a copy of most the files that appear in the book can be downloaded (in a zip file) when you download the book from Leanpub.
<!DOCTYPE html>
<meta
charset=
"utf-8"
>
<title>
Input test</title>
<p>
<label
for=
"nAngle"
style=
"display: inline-block; width: 240px; text-align: right"
>
angle = <span
id=
"nAngle-value"
>
…</span>
</label>
<input
type=
"range"
min=
"0"
max=
"360"
id=
"nAngle"
>
</p>
<script
src=
"http://d3js.org/d3.v3.min.js"
></script>
<script>
var
width
=
600
;
var
height
=
300
;
var
holder
=
d3
.
select
(
"body"
)
.
append
(
"svg"
)
.
attr
(
"width"
,
width
)
.
attr
(
"height"
,
height
);
// draw d3.js text
holder
.
append
(
"text"
)
.
attr
(
"class"
,
"d3js"
)
.
style
(
"fill"
,
"black"
)
.
style
(
"font-size"
,
"56px"
)
.
attr
(
"dy"
,
".35em"
)
.
attr
(
"text-anchor"
,
"middle"
)
.
attr
(
"transform"
,
"translate(300,55) rotate(0)"
)
.
text
(
"d3.js"
);
// draw d3noob.org text
holder
.
append
(
"text"
)
.
attr
(
"class"
,
"d3noob"
)
.
style
(
"fill"
,
"black"
)
.
style
(
"font-size"
,
"56px"
)
.
attr
(
"dy"
,
".35em"
)
.
attr
(
"text-anchor"
,
"middle"
)
.
attr
(
"transform"
,
"translate(300,130) rotate(0)"
)
.
text
(
"d3noob.org"
);
// when the input range changes update the rectangle
d3
.
select
(
"#nAngle"
).
on
(
"input"
,
function
()
{
update
(
+
this
.
value
);
});
// Initial starting height of the rectangle
update
(
0
);
// update the elements
function
update
(
nAngle
)
{
// adjust the range text
d3
.
select
(
"#nAngle-value"
).
text
(
nAngle
);
d3
.
select
(
"#nAngle"
).
property
(
"value"
,
nAngle
);
// adjust d3.js text
holder
.
select
(
"text.d3js"
)
.
attr
(
"transform"
,
"translate(300,55) rotate("
+
nAngle
+
")"
);
// adjust d3noob.org text
holder
.
select
(
"text.d3noob"
)
.
attr
(
"transform"
,
"translate(300,130) rotate("
+
(
360
-
nAngle
)
+
")"
);
}
</script>
THE EXPLANATION
The explanation for this example differes from the others in the way that the d3.js elements (the two pieces of text) are initially appended and then updated.
When they are initially drawn…
holder
.
append
(
"text"
)
.
attr
(
"class"
,
"d3js"
)
.
style
(
"fill"
,
"black"
)
.
style
(
"font-size"
,
"56px"
)
.
attr
(
"dy"
,
".35em"
)
.
attr
(
"text-anchor"
,
"middle"
)
.
attr
(
"transform"
,
"translate(300,55) rotate(0)"
)
.
text
(
"d3.js"
);
holder
.
append
(
"text"
)
.
attr
(
"class"
,
"d3noob"
)
.
style
(
"fill"
,
"black"
)
.
style
(
"font-size"
,
"56px"
)
.
attr
(
"dy"
,
".35em"
)
.
attr
(
"text-anchor"
,
"middle"
)
.
attr
(
"transform"
,
"translate(300,130) rotate(0)"
)
.
text
(
"d3noob.org"
);
… both elements are declared with a
class
attribute that serves as a reference for the future updating. Here, the text ‘d3.js’ is given a class
name of d3js
and the text ‘d3noob.org’ is given a class
name of d3noob
.
Then when we call the
update
function each of the two text elements is adjusted seperately by selecting each based on the class
name that was applied in the initial setup;function
update
(
nAngle
)
{
// adjust the range text
d3
.
select
(
"#nAngle-value"
).
text
(
nAngle
);
d3
.
select
(
"#nAngle"
).
property
(
"value"
,
nAngle
);
// adjust d3.js text
holder
.
select
(
"text.d3js"
)
.
attr
(
"transform"
,
"translate(300,55) rotate("
+
nAngle
+
")"
);
// adjust d3noob.org text
holder
.
select
(
"text.d3noob"
)
.
attr
(
"transform"
,
"translate(300,130) rotate("
+
(
360
-
nAngle
)
+
")"
);
}
So the ‘d3.js’ text is selected using
text.d3js
and ‘d3noob.org’ is selected using text.d3noob
. That’s a pretty neat trick and a good lesson for applying specific transformations to specific objects.The description above (and heaps of other stuff) is in the D3 Tips and Tricks book that can be downloaded for free (or donate if you really want to :-)).
Hello, I am very new to d3 and svg, so please forgive me if the question is dummy.
ReplyDeleteI tried the example of "range input" with the slider and a circle. It works beautifully in Firefox but does not in IE11. Is there some sort of configuration that enables the same d3 code in IE11 or IE11 simply does not support the magic used in the example ?
Thank you, Pete.
I am not sure if my first post was ever published (cannot see it below this article) but it was about the slider example (with circle) not working. Specifically, I could not see the circle being painted. Well, I have found out that original example uses d3 v3. As soon as I changed to refer v4 I got the circle painted in IE11. However, it looks like even with version 4 of d3, there is an issue in IE 11 related to firing "onchange" event. The one assigned using d3 syntax does not work, so I hooked up the event directly to html "input" element and got it working. I expected that d3 somehow works around browser-specific issues and same d3 code can be used for all. Is this expected or not - could you please comment ? Thank you.
ReplyDeleteThanks for your questions and my apologies for not replying quickly. I am seldom able to be particularly quick on turning around questions I'm afraid.
DeleteNone the less, well done on working round the problem and your observations on cross browser support are interesting. This topic has been thrashed about quite a bit and from the outset, d3 appears to have taken the position that they will support a particular standard in relation to using svg in a browser and in IE has not necessairly been able to support those standards in some of their earlier versions. From what I understand this is improving, but it's not really an aim for d3 to work around browser specific issues as much as to support a browser standard. If browser manufacturers don't support the standard then there will be 'issues'.
Having said that I believe that there is scope for mitigating some of these problems in html, but they are certainly outside my skill set I'm afraid. The link here (http://stackoverflow.com/questions/15159002/d3-js-browser-support) has some additional details.