1 | ## D3
|
2 |
|
3 | ### Streaming Financial Chart
|
4 |
|
5 | Meanwhile, a cool graphic where the *stream* is an infinitely generated series of random numbers...
|
6 |
|
7 | Adapted from [Streaming Financial Chart](https://bl.ocks.org/ColinEberhardt/ab7805a9a7af9717e86adc1656fa98d9).
|
8 |
|
9 |
|
10 | ```d3/playable
|
11 | smartdown.importCssCode(
|
12 | `
|
13 | .bollinger .area {
|
14 | fill: #9cf;
|
15 | fill-opacity: 0.5;
|
16 | }
|
17 |
|
18 | .bollinger .line {
|
19 | stroke: #06c;
|
20 | }
|
21 | .chart {
|
22 | height: 200px !important;
|
23 | }
|
24 |
|
25 | `);
|
26 |
|
27 | // window.d3 = window.d3v4;
|
28 | var target = this.div;
|
29 | target.classList.add('chart');
|
30 |
|
31 | // create some test data
|
32 | const stream = fc.randomFinancial().stream();
|
33 | const data = stream.take(110);
|
34 |
|
35 | function renderChart() {
|
36 | if (target.style.display === 'none' && window.intervalId) {
|
37 | window.clearInterval(window.intervalId);
|
38 | return;
|
39 | }
|
40 |
|
41 | // add a new datapoint and remove an old one
|
42 | data.push(stream.next());
|
43 | data.shift();
|
44 |
|
45 | const container = d3.select(target);
|
46 |
|
47 | // Create and apply the bollinger algorithm
|
48 | const bollingerAlgorithm = fc.indicatorBollingerBands()
|
49 | .value(function(d) {
|
50 | return d.close;
|
51 | });
|
52 | const bollingerData = bollingerAlgorithm(data);
|
53 | const mergedData = data.map(function(d, i) {
|
54 | return Object.assign({}, d, {
|
55 | bollinger: bollingerData[i]
|
56 | });
|
57 | });
|
58 |
|
59 | // Offset the range to include the full bar for the latest value
|
60 | const DAY_MS = 1000 * 60 * 60 * 24;
|
61 | const xTicks = 10;// $('#streaming-chart').width() >= 700 ? 10 : 5;
|
62 | const xExtent = fc.extentDate()
|
63 | .accessors([function(d) {
|
64 | return d.date;
|
65 | }])
|
66 | .padUnit('domain')
|
67 | .pad([DAY_MS * -bollingerAlgorithm.period()(mergedData), DAY_MS]);
|
68 |
|
69 | // ensure y extent includes the bollinger bands
|
70 | const yExtent = fc.extentLinear()
|
71 | .accessors([
|
72 | function(d) {
|
73 | return Math.max(d.bollinger.upper, d.high);
|
74 | },
|
75 | function(d) {
|
76 | return Math.min(d.bollinger.lower, d.low);
|
77 | }
|
78 | ]);
|
79 |
|
80 | // create a chart
|
81 | const chart = fc.chartSvgCartesian(
|
82 | d3.scaleTime(),
|
83 | d3.scaleLinear()
|
84 | )
|
85 | .xDomain(xExtent(mergedData))
|
86 | .xTicks(xTicks)
|
87 | .yDomain(yExtent(mergedData))
|
88 | .chartLabel('Streaming Candlestick');
|
89 |
|
90 | // Create the gridlines and series
|
91 | const gridlines = fc.annotationSvgGridline().xTicks(xTicks);
|
92 | const candlestick = fc.seriesSvgCandlestick();
|
93 |
|
94 | const bollingerBands = function() {
|
95 | const area = fc.seriesSvgArea()
|
96 | .mainValue(function(d) {
|
97 | return d.bollinger.upper;
|
98 | })
|
99 | .baseValue(function(d) {
|
100 | return d.bollinger.lower;
|
101 | });
|
102 |
|
103 | const upperLine = fc.seriesSvgLine()
|
104 | .mainValue(function(d) {
|
105 | return d.bollinger.upper;
|
106 | });
|
107 |
|
108 | const averageLine = fc.seriesSvgLine()
|
109 | .mainValue(function(d) {
|
110 | return d.bollinger.average;
|
111 | });
|
112 |
|
113 | const lowerLine = fc.seriesSvgLine()
|
114 | .mainValue(function(d) {
|
115 | return d.bollinger.lower;
|
116 | });
|
117 |
|
118 | const crossValue = function(d) {
|
119 | return d.date;
|
120 | };
|
121 | area.crossValue(crossValue);
|
122 | upperLine.crossValue(crossValue);
|
123 | averageLine.crossValue(crossValue);
|
124 | lowerLine.crossValue(crossValue);
|
125 |
|
126 | const bollingerMulti = fc.seriesSvgMulti()
|
127 | .series([area, upperLine, lowerLine, averageLine])
|
128 | .decorate(function(g, datum, index) {
|
129 | g.enter()
|
130 | .attr('class', function(_, i) {
|
131 | return 'multi bollinger ' + ['area', 'upper', 'lower', 'average'][i];
|
132 | });
|
133 | });
|
134 |
|
135 | return bollingerMulti;
|
136 | };
|
137 |
|
138 | // add them to the chart via a multi-series
|
139 | const multi = fc.seriesSvgMulti()
|
140 | .series([gridlines, bollingerBands(), candlestick]);
|
141 |
|
142 | chart.plotArea(multi);
|
143 |
|
144 | container
|
145 | .style('margin-left', '20px')
|
146 | .datum(mergedData)
|
147 | .call(chart);
|
148 | }
|
149 |
|
150 | // re-render the chart every 200ms
|
151 | renderChart();
|
152 |
|
153 | if (window.intervalId) {
|
154 | window.clearInterval(window.intervalId);
|
155 | }
|
156 |
|
157 | window.intervalId = setInterval(renderChart, 200);
|
158 |
|
159 | ```
|
160 |
|
161 |
|
162 | ### Smelly London Line Graph
|
163 |
|
164 | One of the visualizations available via Smelly London is a [Line Chart](https://github.com/Smelly-London/Smelly-London/tree/master/charts). I made some slight changes so that it can be rendered in Smartdown:
|
165 |
|
166 |
|
167 |
|
168 | ```d3/playable
|
169 | var renderDiv = this.div;
|
170 | renderDiv.classList.add('foo');
|
171 |
|
172 | smartdown.importCssCode(
|
173 | `
|
174 | body .foo svg {
|
175 | font: 10px sans-serif;
|
176 | background: lightyellow;
|
177 | }
|
178 |
|
179 | .foo .axis path,
|
180 | .foo .axis line {
|
181 | fill: none;
|
182 | stroke: #000;
|
183 | shape-rendering: crispEdges;
|
184 | }
|
185 |
|
186 | .foo .x.axis path {
|
187 | display: none;
|
188 | }
|
189 |
|
190 | .foo .line {
|
191 | fill: none;
|
192 | stroke: #2091c1;
|
193 | stroke-width: 1.5px;
|
194 | }
|
195 | `);
|
196 |
|
197 |
|
198 | var margin = {top: 20, right: 20, bottom: 30, left: 50},
|
199 | width = 960 - margin.left - margin.right,
|
200 | height = 500 - margin.top - margin.bottom;
|
201 |
|
202 | // var formatDate = d3.time.format("%d-%b-%y");
|
203 | var formatDateCarto = d3.timeParse("%Y/%m/%d");
|
204 |
|
205 |
|
206 | var x = d3.scaleTime()
|
207 | .range([0, width]);
|
208 |
|
209 | var y = d3.scaleLinear()
|
210 | .range([height, 0]);
|
211 |
|
212 | var xAxis = d3.axisBottom()
|
213 | .scale(x);
|
214 |
|
215 | var yAxis = d3.axisLeft()
|
216 | .scale(y);
|
217 |
|
218 | var line = d3.line()
|
219 | .x(function(d) { return x(d.date); })
|
220 | .y(function(d) { return y(d.number_of_smells); });
|
221 |
|
222 | var svg = d3.select(renderDiv).append("svg")
|
223 | .attr("width", width + margin.left + margin.right)
|
224 | .attr("height", height + margin.top + margin.bottom)
|
225 | .append("g")
|
226 | .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
|
227 |
|
228 | // d3.tsv("data/all_records.tsv", type, function(error, data) {
|
229 | // d3.tsv("data/output/Barnet.tsv", type, function(error, data) {
|
230 | // d3.tsv("data/output/Barnet.tsv", type, function(error, data) {
|
231 | // d3.tsv("data/output/Croydon.tsv", type, function(error, data) {
|
232 | d3.tsv("https://rawcdn.githack.com/Smelly-London/Smelly-London/master/charts/data/output/Tower Hamlets.tsv", type, function(error, data) {
|
233 |
|
234 | if (error) throw error;
|
235 |
|
236 | x.domain(d3.extent(data, function(d) { return d.date; }));
|
237 | y.domain(d3.extent(data, function(d) { return d.number_of_smells; }));
|
238 |
|
239 | svg.append("g")
|
240 | .attr("class", "x axis")
|
241 | .attr("transform", "translate(0," + height + ")")
|
242 | .call(xAxis);
|
243 |
|
244 | svg.append("g")
|
245 | .attr("class", "y axis")
|
246 | .call(yAxis)
|
247 | .append("text")
|
248 | .attr("transform", "rotate(-90)")
|
249 | .attr("y", 6)
|
250 | .attr("dy", ".71em")
|
251 | .style("text-anchor", "end")
|
252 | .text("Number of smells");
|
253 |
|
254 | svg.append("path")
|
255 | .datum(data)
|
256 | .attr("class", "line")
|
257 | .attr("d", line);
|
258 | });
|
259 |
|
260 | function type(d) {
|
261 | d.date = formatDateCarto(d.date);
|
262 | d.number_of_smells = +d.number_of_smells;
|
263 | return d;
|
264 | }
|
265 |
|
266 | ```
|
267 |
|
268 | ---
|
269 |
|
270 | [Back to Home](:@Home)
|