Skip to content

Commit 7b0eaf5

Browse files
committed
add typescript example
1 parent 15c57c9 commit 7b0eaf5

7 files changed

Lines changed: 515 additions & 19 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,5 @@ jspm_packages
3535

3636
# Optional REPL history
3737
.node_repl_history
38+
39+
/examples/*_ts.js

README.md

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,7 +1104,6 @@ free and commerical charting library.
11041104
- Crossfilter (https://square.github.io/crossfilter/) - Fast Multidimensional Filtering for Coordinated Views
11051105
- ...
11061106

1107-
11081107
<a id="typescript"></a>
11091108

11101109
# TypeScript and D3
@@ -1142,7 +1141,8 @@ Moreover, the TypeScript compiler is able to derive a lot which one can use to o
11421141
```ts
11431142
let x = 5; // can be derived from the assignment
11441143

1145-
function add(a: number, b: number) { // same for return type
1144+
function add(a: number, b: number) {
1145+
// same for return type
11461146
return a + b;
11471147
}
11481148

@@ -1154,39 +1154,38 @@ One can declare the types of variables and functions in ones own code. However,
11541154

11551155
So, when installing D3 using NPM one can install the typings alongside. Usually it is just by prepending the `@types/` scope.
11561156

1157-
```bash
1157+
```sh
11581158
npm install d3 @types/d3
11591159
```
11601160

11611161
Due to some heavy typing a D3 Selection (such as returned by `d3.select` or `d3.selectAll`) has four generic arguments:
1162-
1. the element type of the selected element, e.g. `d3.selectAll("div")` will be a `HTMLDivElement`.
1163-
1. the data type bound to this element, e.g. `d3.selectAll("div").data([1, 2, 3])` will be a `number`.
1164-
1. the element type of the parent element, e.g. `d3.select("body").selectAll("div").data([1, 2, 3])` will be a `HTMLBodyElement`.
1165-
1. the data type of the parent element, e.g. `d3.select("body").datum("data").selectAll("div").data([1, 2, 3])` will be a `string`.
1162+
1163+
1. the element type of the selected element, e.g. `d3.selectAll("div")` will be a `HTMLDivElement`.
1164+
1. the data type bound to this element, e.g. `d3.selectAll("div").data([1, 2, 3])` will be a `number`.
1165+
1. the element type of the parent element, e.g. `d3.select("body").selectAll("div").data([1, 2, 3])` will be a `HTMLBodyElement`.
1166+
1. the data type of the parent element, e.g. `d3.select("body").datum("data").selectAll("div").data([1, 2, 3])` will be a `string`.
11661167

11671168
In my experience the third and fourth argument are barely of any use of which it can be simplified and set to e.g. `unknown` or `any`.
11681169

1169-
One can specify the type in more detail by specifing the generic argument of the function. This is useful when the selector is more complex that just the element type. e.g. `d3.select<SVGGElement>(".chart")`
1170+
One can specify the type in more detail by specifing the generic argument of the function. This is useful when the selector is more complex that just the element type. e.g. `d3.select<SVGGElement, unknown>(".chart")`
11701171

1171-
TODO
1172-
* proper full binding example
1173-
* convert the bar chart to full TypeScript
1174-
* this context specification and usage
1175-
*
1172+
---
1173+
1174+
Barchart final results in TypeScript [barchart07_final_ts.html](examples/barchart07_final_ts.html) [![Open in CodePen][codepen]](https://codepen.io/sgratzl/pen/gObqdEG)
11761175

11771176
## Hints
11781177

11791178
### Axis
11801179

1181-
The typings declare that an axis `d3.axisLeft` can just be called on a `SVGGElement` thus one has to make sure that the typings are correct of the selection. For example:
1180+
The typings declare that an axis `d3.axisLeft`, ... can just be called on a `SVGGElement` thus one has to make sure that the typings are correct of the selection. For example:
11821181

11831182
```ts
11841183
const axis = d3.axisLeft();
1185-
d3.select<SVGGElement>('.axis.x').call(axis);
1184+
d3.select<SVGGElement, unknown>(".axis.x").call(axis);
11861185
```
11871186

11881187

1189-
1188+
---
11901189

11911190
Thank You
11921191

examples/barchart07_final_ts.html

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
6+
<title>Student's First Barchart</title>
7+
<meta name="description" content="Student's First Barchart" />
8+
<meta name="viewport" content="width=device-width, initial-scale=1" />
9+
<script src="https://cdn.polyfill.io/v2/polyfill.min.js"></script>
10+
<script src="../web_modules/d3/dist/d3.js"></script>
11+
<link href="./style.css" rel="stylesheet" />
12+
<style>
13+
rect {
14+
fill: steelblue;
15+
fill-opacity: 0.8;
16+
}
17+
rect:hover {
18+
fill-opacity: 1;
19+
}
20+
.axis {
21+
font-size: smaller;
22+
}
23+
</style>
24+
</head>
25+
<body>
26+
<h1>Student's First Barchart</h1>
27+
28+
<div>
29+
<strong>Filter:</strong>
30+
<label><input type="checkbox" name="US" value="1" id="filter-us-only" />US only</label>
31+
</div>
32+
33+
<script src="./barchart07_final_ts.js"></script>
34+
</body>
35+
</html>

examples/barchart07_final_ts.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
const margin = { top: 40, bottom: 10, left: 120, right: 20 };
2+
const width = 800 - margin.left - margin.right;
3+
const height = 600 - margin.top - margin.bottom;
4+
5+
// Creates sources <svg> element
6+
const svg = d3
7+
.select("body")
8+
.append("svg")
9+
.attr("width", width + margin.left + margin.right)
10+
.attr("height", height + margin.top + margin.bottom);
11+
12+
// Group used to enforce margin
13+
const g = svg.append("g").attr("transform", `translate(${margin.left},${margin.top})`);
14+
g.append("g").attr("class", "x axis");
15+
g.append("g").attr("class", "y axis");
16+
17+
// delcare the Data Element Type for proper typing
18+
interface IElem {
19+
temperature: number;
20+
location: {
21+
country: string;
22+
city: string;
23+
};
24+
}
25+
26+
// Global variable for all data
27+
let data: IElem[];
28+
29+
// Scales setup
30+
const xscale = d3.scaleLinear().range([0, width]);
31+
const yscale = d3
32+
.scaleBand()
33+
.rangeRound([0, height])
34+
.paddingInner(0.1);
35+
36+
// Axis setup
37+
const xaxis = d3.axisTop(xscale);
38+
const yaxis = d3.axisLeft(yscale);
39+
40+
/////////////////////////
41+
42+
d3.json("weather.json").then((json: IElem[]) => {
43+
data = json;
44+
45+
update(data);
46+
});
47+
48+
function update(new_data: IElem[]) {
49+
//update the scales
50+
// need to use the ! to tell TypeScript that it will always return a number
51+
xscale.domain([0, d3.max(new_data, d => d.temperature)!]);
52+
yscale.domain(new_data.map(d => d.location.city));
53+
//render the axis
54+
// specify the generic argument to enforce being a SVGGEelement
55+
g.select<SVGGElement>(".x.axis")
56+
.transition()
57+
.call(xaxis);
58+
g.select<SVGGElement>(".y.axis")
59+
.transition()
60+
.call(yaxis);
61+
62+
// Render the chart with new data
63+
64+
// DATA JOIN use the key argument for ensurign that the same DOM element is bound to the same data-item
65+
const rect = g
66+
.selectAll("rect")
67+
.data(new_data, d => (d as IElem).location.city) // key argument cannot be properly typed
68+
.join(
69+
// ENTER
70+
// new elements
71+
enter => {
72+
const rect_enter = enter.append("rect").attr("x", 0);
73+
rect_enter.append("title");
74+
return rect_enter;
75+
},
76+
// UPDATE
77+
// update existing elements
78+
update => update,
79+
// EXIT
80+
// elements that aren't associated with data
81+
exit => exit.remove()
82+
);
83+
84+
// ENTER + UPDATE
85+
// both old and new elements
86+
rect
87+
.transition()
88+
.attr("height", yscale.bandwidth())
89+
.attr("width", d => xscale(d.temperature)!)
90+
.attr("y", d => yscale(d.location.city)!);
91+
92+
rect.select("title").text(d => d.location.city);
93+
}
94+
95+
//interactivity
96+
d3.select<HTMLInputElement, unknown>("#filter-us-only").on("change", function() {
97+
// This will be triggered when the user selects or unselects the checkbox
98+
// since we typed the select such that this is a HTMLInputElement we can just use the this context and have proper autocompletion
99+
const checked = this.checked;
100+
if (checked) {
101+
// Checkbox was just checked
102+
103+
// Keep only data element whose country is US
104+
const filtered_data = data.filter(d => d.location.country === "US");
105+
106+
update(filtered_data); // Update the chart with the filtered data
107+
} else {
108+
// Checkbox was just unchecked
109+
update(data); // Update the chart with all the data we have
110+
}
111+
});

0 commit comments

Comments
 (0)