Skip to content

Commit b89a647

Browse files
committed
Work in progress on the Haul Chart
- Re: issue #57
1 parent 8ac01d5 commit b89a647

File tree

3 files changed

+148
-88
lines changed

3 files changed

+148
-88
lines changed

root/css/override.css

+12
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,18 @@ body {
335335
}
336336

337337

338+
/*
339+
D3 charts
340+
*/
341+
342+
.chart {
343+
background-color: #eeeeee;
344+
}
345+
346+
.xyplot {
347+
background-color: #bebebe;
348+
}
349+
338350
/*
339351
VisJS library overrides
340352
*/

root/js/Explorer.js

+3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ define([
2222
'viewmodels/BookmarkViewModel',
2323
'viewmodels/FiresViewModel',
2424
'viewmodels/FireLookoutEditor',
25+
'viewmodels/FireLookoutView',
2526
'viewmodels/GlobeViewModel',
2627
'viewmodels/InfoViewModel',
2728
'viewmodels/LayersViewModel',
@@ -74,6 +75,7 @@ define([
7475
BookmarkViewModel,
7576
FiresViewModel,
7677
FireLookoutEditor,
78+
FireLookoutView,
7779
GlobeViewModel,
7880
InfoViewModel,
7981
LayersViewModel,
@@ -210,6 +212,7 @@ define([
210212
new WeatherScoutEditor(weatherScoutEditorHtml);
211213
new FireLookoutEditor(fireLookoutEditorHtml);
212214
new WeatherScoutView(this.globe);
215+
new FireLookoutView(this.globe);
213216
new WildfireView();
214217

215218
// Marker tab content

root/js/viewmodels/FireLookoutView.js

+133-88
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
* http://www.opensource.org/licenses/mit-license.php
55
*/
66

7+
/*global define, WorldWind*/
8+
79
/**
810
* FireLookout content module.
911
*
@@ -27,19 +29,35 @@ define(['knockout',
2729
* The view model for an individual FireLookout.
2830
* @constructor
2931
*/
30-
function FireLookoutView() {
32+
function FireLookoutView(globe) {
3133
var self = this;
32-
this.margin = {top: 20, right: 15, bottom: 60, left: 60};
33-
34+
this.globe = globe;
35+
36+
// Haul Chart dimensions
37+
this.haulChartContainter = null;
38+
this.margin = {top: 20, right: 15, bottom: 30, left: 40};
3439
this.width = 400 - this.margin.left - this.margin.right;
3540
this.height = 300 - this.margin.top - this.margin.bottom;
36-
this.btuDomain = [10, 11000];
37-
this.rosDomain = [1, 1100];
41+
// Chart x/y domains
42+
this.btuDomain = [10, 11000]; // btu/ft^2
43+
this.rosDomain = [0.1, 1100]; // chains/hr
44+
// Chart colors for fire behavior adjectives
45+
this.COLOR_LOW = WorldWind.Color.colorFromBytes(128, 127, 255, 200); // blue
46+
this.COLOR_MODERATE = WorldWind.Color.colorFromBytes(127, 193, 151, 200); // green
47+
this.COLOR_ACTIVE = WorldWind.Color.colorFromBytes(255, 179, 130, 200); // tan
48+
this.COLOR_VERY_ACTIVE = WorldWind.Color.colorFromBytes(255, 128, 255, 200); // magenta
49+
this.COLOR_EXTREME = WorldWind.Color.colorFromBytes(253, 128, 124, 200); // orange
50+
// Flame Length thresholds
51+
this.FL_THRESHOLD_LOW = 1;
52+
this.FL_THRESHOLD_MODERATE = 3;
53+
this.FL_THRESHOLD_ACTIVE = 7;
54+
this.FL_THRESHOLD_VERY_ACTIVE = 15;
55+
3856

3957

4058
// Define custom Knockout bindings for a FireLookout.
4159
ko.bindingHandlers.visualizeWildfireDiamond = {
42-
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
60+
init: function (element, valueAccessor, allBindings, viewModel/*deprecated*/, bindingContext) {
4361
// This will be called when the binding is first applied to an element
4462
// Set up any initial state, event handlers, etc. here
4563
},
@@ -50,19 +68,26 @@ define(['knockout',
5068
}
5169
};
5270
ko.bindingHandlers.visualizeHaulChart = {
53-
init: function (element, valueAccessor, allBindings, lookout, bindingContext) {
71+
init: function (element, valueAccessor, allBindings, viewModel/*deprecated*/, bindingContext) {
5472
// This will be called when the binding is first applied to an element
5573
// Set up any initial state, event handlers, etc. here
56-
self.createHaulChart(element);
74+
console.log("visualizeHaulChart.init called");
75+
self.haulChartContainter = element;
76+
// Draw the initial haul chart
77+
var lookout = bindingContext.$data; // observable
78+
self.createHaulChart(lookout.fireBehavior()); // observable
5779
},
58-
update: function (element, valueAccessor, allBindings, lookout, bindingContext) {
80+
update: function (element, valueAccessor, allBindings, viewModel/*deprecated*/, bindingContext) {
5981
// This will be called once when the binding is first applied to an element,
6082
// and again whenever any observables/computeds that are accessed change.
61-
self.updateHaulChart(element, lookout);
83+
console.log("visualizeHaulChart.update called");
84+
// Update the haul chart
85+
var lookout = bindingContext.$data; // observable
86+
self.updateHaulChart(lookout.fireBehavior()); // observable
6287
}
6388
};
6489
ko.bindingHandlers.visualizeFuelModel = {
65-
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
90+
init: function (element, valueAccessor, allBindings, viewModel/*deprecated*/, bindingContext) {
6691
// This will be called when the binding is first applied to an element
6792
// Set up any initial state, event handlers, etc. here
6893
},
@@ -73,7 +98,7 @@ define(['knockout',
7398
}
7499
};
75100
ko.bindingHandlers.visualizeFireBehavior = {
76-
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
101+
init: function (element, valueAccessor, allBindings, viewModel/*deprecated*/, bindingContext) {
77102
// This will be called when the binding is first applied to an element
78103
// Set up any initial state, event handlers, etc. here
79104
},
@@ -86,11 +111,9 @@ define(['knockout',
86111
}
87112

88113
/**
89-
* Display the wildfire's timeline with a vis.js Timeline.
90-
* See: http://visjs.org/docs/timeline/
91-
*
114+
* Display the Wildfire Diamond representing the current fire behavior
92115
* @param {type} element DOM element where the Timeline will be attached
93-
* @param {type} wildfire The Wildfire model element
116+
* @param {type} lookout
94117
*/
95118
FireLookoutView.prototype.drawWildfireDiamond = function (element, lookout) {
96119
// TODO: Draw .png as a placemark for future d3 development
@@ -99,121 +122,143 @@ define(['knockout',
99122

100123

101124
/**
102-
* Display the percent contained in a radial progress chart via d3,
103-
* with the uncontained percentage displayed in red (e.g., uncontrolled fire).
104-
* See: https://github.com/d3/d3/blob/master/API.md
125+
* Display the Haul Chart
105126
*
106-
* @param {type} element DOM element where the Timeline will be attached
107-
* @param {type} wildfire The Wildfire model element
127+
* @param {type} fireBehavior The fire behavior to be visualized
108128
*/
109-
FireLookoutView.prototype.createHaulChart = function (element, lookout) {
110-
var fireBehavior = lookout.fireBehavior;
111-
var heatRelease = (fireBehavior === null ? 0 : fireBehavior.heatRelease),
112-
rateOfSpreadMax = (fireBehavior === null ? 0 : fireBehavior.rateOfSpreadMax),
113-
rateOfSpreadFlanking = (fireBehavior === null ? 0 : fireBehavior.rateOfSpreadFlanking),
114-
rateOfSpreadBacking = (fireBehavior === null ? 0 : fireBehavior.rateOfSpreadNoWindNoSlope),
115-
data = [[heatRelease, rateOfSpreadMax], [heatRelease, rateOfSpreadFlanking]];
129+
FireLookoutView.prototype.createHaulChart = function (fireBehavior) {
130+
console.log("createHaulChart called");
131+
this.width = parseInt(d3.select(this.haulChartContainter).style('width'), 10);
132+
this.width = this.width - this.margin.left - this.margin.right;
133+
134+
var heatRelease = (Number.isNaN(fireBehavior.heatRelease) ? 0 : Number(fireBehavior.heatRelease)),
135+
rateOfSpreadHead = (Number.isNaN(fireBehavior.rateOfSpreadMax) ? 0 : Number(fireBehavior.rateOfSpreadMax)) / 1.1, //ft/min to ch/hr
136+
rateOfSpreadFlanking = (Number.isNaN(fireBehavior.rateOfSpreadFlanking) ? 0 : Number(fireBehavior.rateOfSpreadFlanking)) / 1.1,
137+
rateOfSpreadBacking = (Number.isNaN(fireBehavior.rateOfSpreadBacking) ? 0 : Number(fireBehavior.rateOfSpreadBacking)) / 1.1,
138+
dataset = [
139+
[heatRelease, rateOfSpreadHead],
140+
[heatRelease, rateOfSpreadFlanking],
141+
[heatRelease, rateOfSpreadBacking]];
116142

117143
var x = d3.scaleLog() // btu
118144
.domain(this.btuDomain)
119145
.range([0, this.width]);
120146

121-
var y = d3.scaleLog() // ros ft/min
147+
var y = d3.scaleLog() // ros ch/hr
122148
.domain(this.rosDomain)
123149
.range([this.height, 0]);
124150

125-
var chart = d3.select(element)
151+
var chart = d3.select(this.haulChartContainter)
126152
.append('svg:svg')
127153
.attr('width', this.width + this.margin.right + this.margin.left)
128154
.attr('height', this.height + this.margin.top + this.margin.bottom)
129-
.attr('class', 'chart')
155+
.attr('class', 'chart');
130156

131-
var main = chart.append('g')
157+
var xyPlot = chart.append('g')
132158
.attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')')
133159
.attr('width', this.width)
134160
.attr('height', this.height)
135-
.attr('class', 'main')
161+
.attr('class', 'xyplot');
136162

137163
// draw the x axis
138-
var xAxis = d3.axisBottom(x);
139-
main.append('g')
164+
var xAxis = d3.axisBottom(x)
165+
.ticks(4, d3.format(",.0f"));
166+
xyPlot.append('g')
140167
.attr('transform', 'translate(0,' + this.height + ')')
141-
.attr('class', 'main axis btu')
142-
.call(xAxis);
168+
.attr('class', 'xyplot axis btu')
169+
.call(xAxis);
143170

144171
// draw the y axis
145-
var yAxis = d3.axisLeft(y);
146-
main.append('g')
172+
var yAxis = d3.axisLeft(y)
173+
.ticks(5, d3.format(",.0f"));
174+
xyPlot.append('g')
147175
.attr('transform', 'translate(0,0)')
148-
.attr('class', 'main axis ros')
176+
.attr('class', 'xyplot axis ros')
149177
.call(yAxis);
150178

151-
var g = main.append("svg:g")
179+
var g = xyPlot.append("svg:g")
152180
.attr('id', 'plot');
153181

154182
g.selectAll("scatter-dots")
155-
.data(data)
183+
.data(dataset)
156184
.enter().append("svg:circle")
157185
.attr("cx", function (d) {
158186
return x(d[0]);
159187
})
160188
.attr("cy", function (d) {
161189
return y(d[1]);
162190
})
163-
.attr("r", 8);
191+
.attr("r", 4);
164192
};
165193
/**
166-
* Display the percent contained in a radial progress chart via d3,
167-
* with the uncontained percentage displayed in red (e.g., uncontrolled fire).
168-
* See: https://github.com/d3/d3/blob/master/API.md
169-
*
170-
* @param {type} element DOM element where the Timeline will be attached
171-
* @param {type} wildfire The Wildfire model element
194+
* Update the Haul Chart.
195+
* @param {type} fireBehavior The fire behavior data
172196
*/
173-
FireLookoutView.prototype.updateHaulChart = function (element, lookout) {
174-
var self = this;
175-
// Everytime the selected FireLookout is changed, we need to
176-
// subscribe to the lookout's fire behavior observable to update
177-
// the chart when the fire behavior changes.
178-
//
179-
// TODO: does the leak if we don't unsubscribe?
180-
lookout.fireBehavior.subscribe(function (fireBehavior) {
181-
182-
var heatRelease = (fireBehavior === null ? 0 : fireBehavior.heatRelease),
183-
rateOfSpreadMax = (fireBehavior === null ? 0 : fireBehavior.rateOfSpreadMax),
184-
rateOfSpreadFlanking = (fireBehavior === null ? 0 : fireBehavior.rateOfSpreadFlanking),
185-
rateOfSpreadBacking = (fireBehavior === null ? 0 : fireBehavior.rateOfSpreadNoWindNoSlope),
186-
data = [[heatRelease, rateOfSpreadMax], [heatRelease, rateOfSpreadFlanking]];
187-
188-
var x = d3.scaleLog() // btu
189-
.domain(self.btuDomain)
190-
.range([0, self.width]);
191-
192-
var y = d3.scaleLog() // ros ft/min
193-
.domain(self.rosDomain)
194-
.range([self.height, 0]);
197+
FireLookoutView.prototype.updateHaulChart = function (fireBehavior) {
198+
console.log("updateHaulChart called");
199+
200+
var heatRelease = (Number.isNaN(fireBehavior.heatRelease) ? 0 : Number(fireBehavior.heatRelease)),
201+
rateOfSpreadHead = (Number.isNaN(fireBehavior.rateOfSpreadMax) ? 0 : Number(fireBehavior.rateOfSpreadMax))/ 1.1,
202+
rateOfSpreadFlanking = (Number.isNaN(fireBehavior.rateOfSpreadFlanking) ? 0 : Number(fireBehavior.rateOfSpreadFlanking))/ 1.1,
203+
rateOfSpreadBacking = (Number.isNaN(fireBehavior.rateOfSpreadBacking) ? 0 : Number(fireBehavior.rateOfSpreadBacking))/ 1.1,
204+
dataset = [
205+
[heatRelease, rateOfSpreadHead],
206+
[heatRelease, rateOfSpreadFlanking],
207+
[heatRelease, rateOfSpreadBacking]];
195208

196-
var chart = d3.select(element);
197-
var g = chart.selectAll("#plot");
209+
// TODO: Rescale to fire behavior vals
210+
var x = d3.scaleLog() // btu
211+
.domain(this.btuDomain)
212+
.range([0, this.width]);
213+
var y = d3.scaleLog() // ros ft/min
214+
.domain(this.rosDomain)
215+
.range([this.height, 0]);
198216

199-
g.selectAll("scatter-dots")
200-
.data(data)
201-
.attr("cx", function (d) {
202-
return x(d[0]);
203-
})
204-
.attr("cy", function (d) {
205-
return y(d[1]);
206-
})
207-
.attr("r", 8);
208-
});
217+
var chart = d3.select(this.haulChartContainter);
218+
var g = chart.selectAll("#plot");
219+
220+
// Update the values
221+
g.selectAll("circle")
222+
.data(dataset)
223+
.attr("cx", function (d) {
224+
return x(d[0]);
225+
})
226+
.attr("cy", function (d) {
227+
return y(d[1]);
228+
})
229+
.attr("r", 4);
230+
231+
// // Update all circles
232+
// chart.selectAll('circle')
233+
// .data(dataset)
234+
// .transition() // Transition 1
235+
// .duration(1000)
236+
// .ease('circle')
237+
// .each('start', function() {
238+
// d3.select(this)
239+
// .attr('fill', 'gray')
240+
// .attr('r', 2);
241+
// })
242+
// .attr('cx', function(d) {
243+
// return xScale(d[0]);
244+
// })
245+
// .attr('cy', function(d) {
246+
// return yScale(d[1]);
247+
// })
248+
// .transition() // Transition 2, equiv to below
249+
// .duration(250)
250+
// .attr('fill', 'teal')
251+
// .attr('r', 4);
252+
//
253+
254+
209255
};
210256

211257
/**
212-
* Display the wildfire size as a category in a segmented chart via d3.
213-
* See: https://github.com/d3/d3/blob/master/API.md
214-
*
215-
* @param {type} element DOM element where the Timeline will be attached
216-
* @param {type} wildfire The Wildfire model element
258+
* Render a photo and description of the current fuel model
259+
* @param {type} element
260+
* @param {type} wildfire
261+
* @returns {undefined}
217262
*/
218263
FireLookoutView.prototype.drawFuelModel = function (element, wildfire) {
219264

0 commit comments

Comments
 (0)