Conversion to TypeScript, ability to toggle progress points, scale axis to min/max speedup values, ignore smoothing errors when too few data points.

This commit is contained in:
John Vilk 2016-10-06 11:44:49 -04:00
parent 27b3d70e6e
commit 26d929681e
11 changed files with 1395 additions and 574 deletions

3
.gitignore vendored
View File

@ -39,3 +39,6 @@ tests/*/obj
bin/inspect/obj
bin/inspect/inspect
.vscode
node_modules

14
README.md Normal file
View File

@ -0,0 +1,14 @@
# Causal Profile Viewer
Prerequisites for building:
* Install NodeJS.
To build, run `npm install`.
To use, simply run an HTTP server from the directory. For example:
1. Install `http-server`: `npm i -g http-server`
2. Run `http-server` in this directory
3. Open a browser to `http://localhost:8080/`

View File

@ -16,17 +16,17 @@
<div class="container-fluid">
<div class="row">
<div id="sidebar" class="col-sm-3 col-md-3 col-lg-2 sidebar">
<h3 class="">Causal Profile</h3>
<button type="button" id="load-profile-btn" class="btn btn-primary btn-block" data-toggle="modal" data-target="#load-profile-dlg">
<i class="fa fa-folder-open"></i> Load a Profile
</button>
<div id="legend">
<h4>Progress Points</h4>
<p class="legend-entry noseries">None</p>
</div> <!-- /legend -->
<div id="sortby">
<h4 id="sortby_label">Sort By</h4>
<select id="sortby_field" class="form-control" disabled aria-labelledby="sortby_label">
@ -36,7 +36,7 @@
<option value="min_speedup">Min Speedup</option>
</select>
</div> <!-- /sortby -->
<div id="minpoints">
<h4 id="minpoints_label">Minimum Points</h4>
<input type="range" id="minpoints_field" min="0" max="20" step="1" value="10" list="ticks" aria-labeledby="minpoints_label">
@ -49,16 +49,21 @@
</datalist>
<p id="minpoints_display" class="text-center">10</p>
</div> <!-- /minpoint -->
</div> <!-- /sidebar -->
<div class="col-sm-9 col-sm-offset-3 col-md-9 col-md-offset-3 col-lg-10 col-lg-offset-2">
<div id="plot-area" class="col-md-12"></div>
<div class="row">
<div id="warning-area" class="col-md-12"></div>
</div>
<div class="row">
<div id="plot-area" class="col-md-12"></div>
</div>
</div> <!-- /main content -->
</div> <!-- /row -->
</div> <!-- /container-fluid -->
<!-- File open dialog (hidden) -->
<div class="modal fade" id="load-profile-dlg" tabindex="-1" role="dialog" aria-labelledby="load-profile-dlg-title" aria-hidden="true">
<div class="modal-dialog">
@ -101,10 +106,10 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.6.3/d3-tip.min.js"></script>
<script src="lib/science/science.v1.min.js"></script>
<!-- Causal profile loading and plotting code -->
<script src="profile.js"></script>
<!-- The UI code -->
<script src="ui.js"></script>
</body>

23
package.json Normal file
View File

@ -0,0 +1,23 @@
{
"name": "coz",
"version": "1.0.0",
"description": "Displays results from a causal profile.",
"repository": {
"type": "git",
"url": "git+https://github.com/jvilk/Coz.git"
},
"scripts": {
"prepublish": "tsc"
},
"license": "BSD-2-Clause",
"bugs": {
"url": "https://github.com/jvilk/Coz/issues"
},
"homepage": "https://github.com/jvilk/Coz#readme",
"devDependencies": {
"@types/bootstrap": "^3.3.32",
"@types/d3": "^3.5.36",
"@types/jquery": "^2.0.33",
"typescript": "^2.0.3"
}
}

File diff suppressed because it is too large Load Diff

1
profile.js.map Normal file

File diff suppressed because one or more lines are too long

626
profile.ts Normal file
View File

@ -0,0 +1,626 @@
type ExperimentData = ThroughputData | LatencyData;
declare let science: any;
interface ExperimentResult {
name: string;
progress_points: Point[];
}
interface Point {
name: string;
measurements: Measurement[]
}
interface Measurement {
speedup: number;
progress_speedup: number;
}
interface ThroughputData {
type: 'throughput';
delta: number;
duration: number;
}
interface LatencyData {
type: 'latency';
arrivals: number;
departures: number;
delta: number;
duration: number;
}
type Line = Experiment | ThroughputPoint | LatencyPoint;
interface Experiment {
type: 'experiment';
selected: string;
speedup: number;
duration: number;
}
interface ThroughputPoint {
type: 'throughput-point' | 'progress-point';
name: string;
delta: number;
}
interface LatencyPoint {
type: 'latency-point';
name: string;
arrivals: number;
departures: number;
difference: number;
}
function parseLine(s: string): Line {
if(s[0] == '{') {
return JSON.parse(s);
} else {
let parts = s.split('\t');
let obj: {
type: string;
[key: string]: string | number
} = { type: parts[0] };
for (let i = 0; i < parts.length; i++) {
const equals_index = parts[i].indexOf('=');
if (equals_index === -1) continue;
let key = parts[i].substring(0, equals_index);
let value: string | number = parts[i].substring(equals_index + 1);
if (key === 'type' && obj.type === 'progress-point') {
key = 'point-type';
} else if (key === 'delta' || key === 'time' || key === 'duration') {
value = parseInt(value);
} else if (key === 'speedup') {
value = parseFloat(value);
}
obj[key] = value;
}
return <Line> <any> obj;
}
}
function max_normalized_area(d: ExperimentResult): number {
let max_normalized_area = 0;
for (let i = 0; i < d.progress_points.length; i++) {
let area = 0;
for(let j = 1; j < d.progress_points[i].measurements.length; j++) {
let prev_data = d.progress_points[i].measurements[j-1];
let current_data = d.progress_points[i].measurements[j];
let avg_progress_speedup = (prev_data.progress_speedup + current_data.progress_speedup) / 2;
area += avg_progress_speedup * (current_data.speedup - prev_data.speedup);
let normalized_area = area / current_data.speedup;
if (normalized_area > max_normalized_area) max_normalized_area = normalized_area;
}
}
return max_normalized_area;
}
function max_progress_speedup(d: ExperimentResult): number {
let max_progress_speedup = 0;
for (let i in d.progress_points) {
for (let j in d.progress_points[i].measurements) {
let progress_speedup = d.progress_points[i].measurements[j].progress_speedup;
if (progress_speedup > max_progress_speedup) max_progress_speedup = progress_speedup;
}
}
return max_progress_speedup;
}
function min_progress_speedup(d: ExperimentResult): number {
let min_progress_speedup = 0;
for (let i in d.progress_points) {
for (let j in d.progress_points[i].measurements) {
let progress_speedup = d.progress_points[i].measurements[j].progress_speedup;
if (progress_speedup < min_progress_speedup) min_progress_speedup = progress_speedup;
}
}
return min_progress_speedup;
}
const sort_functions: {[name: string]: (a: ExperimentResult, b: ExperimentResult) => number} = {
alphabetical: function(a: ExperimentResult, b: ExperimentResult): number {
if(a.name > b.name) return 1;
else return -1;
},
impact: function(a: ExperimentResult, b: ExperimentResult): number {
if (max_normalized_area(b) > max_normalized_area(a)) return 1;
else return -1;
},
max_speedup: function(a: ExperimentResult, b: ExperimentResult): number {
if (max_progress_speedup(b) > max_progress_speedup(a)) return 1;
else return -1;
},
min_speedup: function(a: ExperimentResult, b: ExperimentResult): number {
if (min_progress_speedup(a) > min_progress_speedup(b)) return 1;
else return -1;
}
};
class Profile {
private _data: {[location: string]: {
[progressPoint: string]: {
[speedupAmount: number]: ExperimentData
}
}}= {};
private _disabled_progress_points: string[] = [];
private _plot_container: d3.Selection<any>;
private _plot_legend: d3.Selection<any>;
private _get_min_points: () => number;
private _display_warning: (title: string, msg: string) => void;
private _progress_points: string[] = null;
constructor(profile_text: string, container: d3.Selection<any>, legend: d3.Selection<any>, get_min_points: () => number, display_warning: (title: string, msg: string) => void) {
this._plot_container = container;
this._plot_legend = legend;
this._get_min_points = get_min_points;
this._display_warning = display_warning;
let lines = profile_text.split('\n');
let experiment: Experiment = null;
for (let i = 0; i < lines.length; i++) {
if (lines[i].length == 0) continue;
let entry = parseLine(lines[i]);
if (entry.type === 'experiment') {
experiment = entry;
} else if (entry.type === 'throughput-point' || entry.type === 'progress-point') {
this.addThroughputMeasurement(experiment, entry);
} else if(entry.type === 'latency-point') {
this.addLatencyMeasurement(experiment, entry);
}
}
}
public ensureDataEntry<T extends ExperimentData>(selected: string, point: string, speedup: number, initial_value: T): T {
if (!this._data[selected]) this._data[selected] = {};
if (!this._data[selected][point]) this._data[selected][point] = {};
if (!this._data[selected][point][speedup]) this._data[selected][point][speedup] = initial_value;
return <T> this._data[selected][point][speedup];
}
public addThroughputMeasurement(experiment: Experiment, point: ThroughputPoint) {
let entry = this.ensureDataEntry(experiment.selected, point.name, experiment.speedup, {
delta: 0,
duration: 0,
type: 'throughput'
});
// Add new delta and duration to data
entry.delta += point.delta;
entry.duration += experiment.duration;
}
public addLatencyMeasurement(experiment: Experiment, point: LatencyPoint) {
let entry = this.ensureDataEntry(experiment.selected, point.name, experiment.speedup, {
arrivals: 0,
departures: 0,
delta: 0,
duration: 0,
type: 'latency'
});
entry.arrivals += point.arrivals;
entry.departures += point.departures;
// Compute a running weighted average of the difference between arrivals and departures
if (entry.duration === 0) {
entry.delta = point.difference;
} else {
// Compute the new total duration of all experiments combined (including the new one)
let total_duration = entry.duration + experiment.duration;
// Scale the difference down by the ratio of the prior and total durations. This scale factor will be closer to 1 than 0, so divide first for better numerical stability
entry.delta *= entry.duration / total_duration;
// Add the contribution to average difference from the current experiment. The scale factor will be close to zero, so multiply first for better numerical stability.
entry.delta += (point.difference * experiment.duration) / total_duration;
}
// Update the total duration
entry.duration += experiment.duration;
}
public getProgressPoints(): string[] {
if (this._progress_points) {
return this._progress_points;
}
let points: string[] = [];
for (var selected in this._data) {
for (var point in this._data[selected]) {
if (points.indexOf(point) === -1) points.push(point);
}
}
// Stable order.
return this._progress_points = points.sort();
}
public getSpeedupData(min_points: number): ExperimentResult[] {
const progress_points = this.getProgressPoints().filter((pp) => this._disabled_progress_points.indexOf(pp) === -1);
let result: ExperimentResult[] = [];
for (let selected in this._data) {
let points: Point[] = [];
let points_with_enough = 0;
for(let i = 0; i < progress_points.length; i++) {
// Set up an empty record for this progress point
let point = {
name: progress_points[i],
measurements: new Array<Measurement>()
};
// Get the data for this progress point, if any
let point_data = this._data[selected][progress_points[i]];
// Check to be sure the point was observed and we have baseline (zero speedup) data
if (point_data !== undefined && point_data[0] !== undefined && point_data[0].delta > 0) {
// Compute the baseline progress period
let baseline_period = point_data[0].duration / point_data[0].delta;
// Loop over measurements and compute progress speedups in D3-friendly format
let measurements: Measurement[] = [];
for (let speedup in point_data) {
// Skip this speedup measurement if period is undefined
if (point_data[speedup].delta == 0) continue;
// Compute progress period for this speedup size
let period = point_data[speedup].duration / point_data[speedup].delta;
let progress_speedup = (baseline_period - period) / baseline_period;
// Skip really large negative values
if (progress_speedup >= -1) {
// Add entry to measurements
measurements.push({
speedup: +speedup,
progress_speedup: progress_speedup
});
}
}
// Sort measurements by speedup
measurements.sort(function(a, b) { return a.speedup - b.speedup; });
// Use these measurements if we have enough different points
if (measurements.length >= min_points) {
points_with_enough++;
point.measurements = measurements;
}
}
points.push(point);
}
if (points_with_enough > 0) {
result.push({
name: selected,
progress_points: points
});
}
}
return result;
}
public drawLegend() {
let container = this._plot_legend;
const progress_points = this.getProgressPoints();
let legend_entries_sel = container.selectAll('p.legend-entry').data(progress_points);
legend_entries_sel.enter().append('p').attr('class', 'legend-entry');
// Remove the noseries class from legend entries
legend_entries_sel.classed('noseries', false).text('');
legend_entries_sel.append('i')
.attr('class', (d, i) => { return `fa fa-circle${this._disabled_progress_points.indexOf(d) !== -1 ? '-o' : ''} series${i % 4}`; })
.on('click', (d, i) => {
const ind = this._disabled_progress_points.indexOf(d);
if (ind !== -1) {
// Re-enable.
this._disabled_progress_points.splice(ind, 1);
} else if (this._disabled_progress_points.length + 1 < progress_points.length) {
// Disable.
this._disabled_progress_points.push(d);
} else {
// This is the last enabled progress point. Forbid disabling it.
this._display_warning("Warning", `At least one progress point must be enabled.`);
}
this.drawPlots(true);
this.drawLegend();
});
legend_entries_sel.append('span')
.attr('class', 'path')
.text(function(d) { return d; });
// Remove defunct legend entries
legend_entries_sel.exit().remove();
}
public drawPlots(no_animate: boolean): void {
const container = this._plot_container;
const min_points = this._get_min_points();
const speedup_data = this.getSpeedupData(min_points);
let min_speedup = Infinity;
let max_speedup = -Infinity;
for (let i = 0; i < speedup_data.length; i++) {
const result = speedup_data[i];
const result_min = min_progress_speedup(result);
const result_max = max_progress_speedup(result);
if (result_min < min_speedup) {
min_speedup = result_min;
}
if (result_max > max_speedup) {
max_speedup = result_max;
}
}
// Give some wiggle room to display points.
min_speedup *= 1.05;
max_speedup *= 1.05;
/****** Compute dimensions ******/
const container_width = parseInt(container.style('width'));
// Add columns while maintaining a target width
let cols = 1;
while (container_width / (cols + 1) >= 300) cols++;
const div_width = container_width / cols;
const div_height = 190;
const svg_width = div_width - 10;
const svg_height = div_height - 40;
const margins = {left: 55, right: 20, top: 10, bottom: 35};
const plot_width = svg_width - margins.left - margins.right;
const plot_height = svg_height - margins.top - margins.bottom;
const radius = 3;
const tick_size = 6;
// Formatters
const axisFormat = d3.format('.0%');
const percentFormat = d3.format('+.1%');
// Scales
let xscale = d3.scale.linear().domain([0, 1]);
let yscale = d3.scale.linear().domain([min_speedup, max_speedup]);
// Axes
let xaxis = d3.svg.axis()
.scale(xscale)
.orient('bottom')
.ticks(5)
.tickSize(tick_size)
.tickFormat(axisFormat);
//.tickSize(tick_size, 0, 0)
//.tickFormat(axisFormat);
let yaxis = (<d3.svg.Axis> (<any> d3.svg.axis()
.scale(yscale)
.orient('left')
.ticks(5))
.tickSize(tick_size, 0, 0))
.tickFormat(axisFormat);
// Gridlines
let xgrid = d3.svg.axis().scale(xscale).orient('bottom').tickFormat('');
let ygrid = d3.svg.axis().scale(yscale).orient('left').tickFormat('').ticks(5);
// Tooltip
let tip = (<any> d3).tip()
.attr('class', 'd3-tip')
.offset([-5, 0])
.html(function (d: Measurement) {
return '<strong>Line Speedup:</strong> ' + percentFormat(d.speedup) + '<br>' +
'<strong>Progress Speedup:</strong> ' + percentFormat(d.progress_speedup);
})
.direction(function (d: Measurement) {
if (d.speedup > 0.8) return 'w';
else return 'n';
});
/****** Add or update divs to hold each plot ******/
let plot_div_sel = container.selectAll('div.plot')
.data(speedup_data, function(d) { return d.name; });
let plot_x_pos = function(d: any, i: number) {
let col = i % cols;
return (col * div_width) + 'px';
}
let plot_y_pos = function(d: any, i: number) {
let row = (i - (i % cols)) / cols;
return (row * div_height) + 'px';
}
// First, remove divs that are disappearing
plot_div_sel.exit().transition().duration(200)
.style('opacity', 0).remove();
// Insert new divs with zero opacity
plot_div_sel.enter().append('div')
.attr('class', 'plot')
.style('margin-bottom', -div_height+'px')
.style('opacity', 0)
.style('width', div_width);
// Sort plots by the chosen sorting function
plot_div_sel.sort(sort_functions[(<any> d3.select('#sortby_field').node()).value]);
// Move divs into place. Only animate if we are not on a resizing redraw
if (!no_animate) {
plot_div_sel.transition().duration(400).delay(200)
.style('top', plot_y_pos)
.style('left', plot_x_pos)
.style('opacity', 1);
} else {
plot_div_sel.style('left', plot_x_pos)
.style('top', plot_y_pos);
}
/****** Insert, remove, and update plot titles ******/
let plot_title_sel = plot_div_sel.selectAll('div.plot-title').data(function(d) { return [d.name]; });
plot_title_sel.enter().append('div').attr('class', 'plot-title');
plot_title_sel.text(function(d) { return d; })
.classed('path', true)
.style('width', div_width+'px');
plot_title_sel.exit().remove();
/****** Update scales ******/
xscale.domain([0, 1]).range([0, plot_width]);
yscale.domain([min_speedup, max_speedup]).range([plot_height, 0]);
/****** Update gridlines ******/
(<any> xgrid).tickSize(-plot_height, 0, 0);
(<any> ygrid).tickSize(-plot_width, 0, 0);
/****** Insert and update plot svgs ******/
let plot_svg_sel = plot_div_sel.selectAll('svg').data([1]);
plot_svg_sel.enter().append('svg');
plot_svg_sel.attr('width', svg_width)
.attr('height', svg_height)
.call(tip);
plot_svg_sel.exit().remove();
/****** Add or update plot areas ******/
let plot_area_sel = <d3.selection.Update<ExperimentResult>> <any> plot_svg_sel.selectAll('g.plot_area').data([0]);
plot_area_sel.enter().append('g').attr('class', 'plot_area');
plot_area_sel.attr('transform', `translate(${margins.left}, ${margins.top})`);
plot_area_sel.exit().remove();
/****** Add or update clip paths ******/
let clippath_sel = plot_area_sel.selectAll('#clip').data([0]);
clippath_sel.enter().append('clipPath').attr('id', 'clip');
clippath_sel.exit().remove();
/****** Add or update clipping rectangles to clip paths ******/
let clip_rect_sel = clippath_sel.selectAll('rect').data([0]);
clip_rect_sel.enter().append('rect');
clip_rect_sel.attr('x', -radius - 1)
.attr('y', 0)
.attr('width', plot_width + 2 * radius + 2)
.attr('height', plot_height);
clip_rect_sel.exit().remove();
/****** Select plots areas, but preserve the real speedup data ******/
plot_area_sel = plot_div_sel.select('svg').select('g.plot_area');
/****** Add or update x-grids ******/
let xgrid_sel = plot_area_sel.selectAll('g.xgrid').data([0]);
xgrid_sel.enter().append('g').attr('class', 'xgrid');
xgrid_sel.attr('transform', `translate(0, ${plot_height})`).call(xgrid);
xgrid_sel.exit().remove();
/****** Add or update y-grids ******/
let ygrid_sel = plot_area_sel.selectAll('g.ygrid').data([0]);
ygrid_sel.enter().append('g').attr('class', 'ygrid');
ygrid_sel.call(ygrid);
ygrid_sel.exit().remove();
/****** Add or update x-axes ******/
let xaxis_sel = plot_area_sel.selectAll('g.xaxis').data([0]);
xaxis_sel.enter().append('g').attr('class', 'xaxis');
xaxis_sel.attr('transform', `translate(0, ${plot_height})`).call(xaxis);
xaxis_sel.exit().remove();
/****** Add or update x-axis titles ******/
let xtitle_sel = plot_area_sel.selectAll('text.xtitle').data([0]);
xtitle_sel.enter().append('text').attr('class', 'xtitle');
xtitle_sel.attr('x', xscale(0.5))
.attr('y', 32) // Approximate height of the x-axis
.attr('transform', `translate(0, ${plot_height})`)
.style('text-anchor', 'middle')
.text('Line speedup');
xtitle_sel.exit().remove();
/****** Add or update y-axes ******/
let yaxis_sel = plot_area_sel.selectAll('g.yaxis').data([0]);
yaxis_sel.enter().append('g').attr('class', 'yaxis');
yaxis_sel.call(yaxis);
yaxis_sel.exit().remove();
/****** Add or update y-axis title ******/
let ytitle_sel = plot_area_sel.selectAll('text.ytitle').data([0]);
ytitle_sel.enter().append('text').attr('class', 'ytitle');
ytitle_sel.attr('x', -yscale(0)) // x and y are flipped because of rotation
.attr('y', -50) // Approximate width of y-axis
.attr('transform', 'rotate(-90)')
.style('text-anchor', 'middle')
.style('alignment-baseline', 'central')
.text('Program Speedup');
ytitle_sel.exit().remove();
/****** Add or update x-zero line ******/
let xzero_sel = plot_area_sel.selectAll('line.xzero').data([0]);
xzero_sel.enter().append('line').attr('class', 'xzero');
xzero_sel.attr('x1', xscale(0))
.attr('y1', 0)
.attr('x2', xscale(0))
.attr('y2', plot_height + tick_size);
xzero_sel.exit().remove();
/****** Add or update y-zero line ******/
let yzero_sel = plot_area_sel.selectAll('line.yzero').data([0]);
yzero_sel.enter().append('line').attr('class', 'yzero');
yzero_sel.attr('x1', -tick_size)
.attr('y1', yscale(0))
.attr('x2', plot_width)
.attr('y2', yscale(0));
yzero_sel.exit().remove();
/****** Add or update series ******/
let progress_points = this.getProgressPoints();
let series_sel = plot_area_sel.selectAll('g.series')
.data(function(d) { return d.progress_points; }, function(d) { return d.name; });
series_sel.enter().append('g');
series_sel.attr('class', function(d, k) {
// Use progress point's position in array to assign it a stable color, no matter
// which points are enabled for display.
return `series series${(progress_points.indexOf(d.name)) % 5}`; })
.attr('style', 'clip-path: url(#clip);');
series_sel.exit().remove();
/****** Add or update trendlines ******/
// Configure a loess smoother
let loess = science.stats.loess()
.bandwidth(0.4)
.robustnessIterations(5);
// Create an svg line to draw the loess curve
let line = d3.svg.line().x(function(d) { return xscale(d[0]); })
.y(function(d) { return yscale(d[1]); })
.interpolate('basis');
// Apply the loess smoothing to each series, then draw the lines
let lines_sel = series_sel.selectAll('path').data(function(d) {
let xvals = d.measurements.map(function(e) { return e.speedup; });
let yvals = d.measurements.map(function(e) { return e.progress_speedup; });
let smoothed_y: number[] = [];
try {
smoothed_y = loess(xvals, yvals);
} catch (e) {
// Bandwidth too small error. Ignore and proceed with empty smoothed line.
}
// Speedup is always zero for a line speedup of zero
smoothed_y[0] = 0;
if (xvals.length > 5) return [d3.zip(xvals, smoothed_y)];
else return [d3.zip(xvals, yvals)];
});
lines_sel.enter().append('path');
lines_sel.attr('d', line);
lines_sel.exit().remove();
/****** Add or update points ******/
let points_sel = series_sel.selectAll('circle').data(function(d) { return d.measurements; });
points_sel.enter().append('circle').attr('r', radius);
points_sel.attr('cx', function(d) { return xscale(d.speedup); })
.attr('cy', function(d) { return yscale(d.progress_speedup); })
.on('mouseover', function(d, i) {
d3.select(this).classed('highlight', true);
tip.show(d, i);
})
.on('mouseout', function(d, i) {
d3.select(this).classed('highlight', false);
tip.hide(d, i);
});
points_sel.exit().remove();
}
}

8
tsconfig.json Normal file
View File

@ -0,0 +1,8 @@
{
"compilerOptions": {
"module": "none",
"target": "es5",
"noImplicitAny": true,
"sourceMap": true
}
}

162
ui.js
View File

@ -1,113 +1,105 @@
// Ensure the brower supports the File API
if (!window.File || !window.FileReader) {
alert('The File APIs are not fully supported in this browser.');
alert('The File APIs are not fully supported in this browser.');
}
var current_profile = undefined;
function get_min_points() {
return d3.select('#minpoints_field').node().value;
}
function display_warning(title, text) {
var warning = $("<div class=\"alert alert-warning alert-dismissible\" role=\"alert\">\n <button type=\"button\" class=\"close\" data-dismiss=\"alert\" aria-label=\"Close\"><span aria-hidden=\"true\">&times;</span></button>\n <strong>" + title + ":</strong> " + text + "\n </div>");
$('#warning-area').append(warning);
// Fade out after 5 seconds.
setTimeout(function () {
warning.fadeOut(500, function () {
warning.alert('close');
});
}, 5000);
}
function update(resize) {
if(current_profile == undefined) return;
// Enable the sortby field
d3.select('#sortby_field').attr('disabled', null);
// Get the minimum points setting
var min_points = d3.select('#minpoints_field').node().value;
// Draw plots
current_profile.drawPlots(d3.select('#plot-area'), min_points, resize);
// Draw the legend
current_profile.drawLegend(d3.select('#legend'));
var tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden");
// Shorten path strings
var paths = d3.selectAll('.path')
.classed('path', false).classed('shortpath', true)
.text(function(d) {
var parts = d.split('/');
var filename = parts[parts.length-1];
return filename;
if (current_profile === undefined)
return;
// Enable the sortby field
d3.select('#sortby_field').attr('disabled', null);
// Draw plots
current_profile.drawPlots(resize);
// Draw the legend
current_profile.drawLegend();
var tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden");
// Shorten path strings
var paths = d3.selectAll('.path')
.classed('path', false).classed('shortpath', true)
.text(function (d) {
var parts = d.split('/');
var filename = parts[parts.length - 1];
return filename;
});
}
// Set a handler for the load profile button
d3.select('#load-profile-btn').on('click', function() {
// Reset the filename field
d3.select('#load-profile-filename').attr('value', '');
// Disable the open button
d3.select('#load-profile-open-btn').classed('disabled', true);
d3.select('#load-profile-btn').on('click', function () {
// Reset the filename field
d3.select('#load-profile-filename').attr('value', '');
// Disable the open button
d3.select('#load-profile-open-btn').classed('disabled', true);
});
// Set a handler for the fake browse button
d3.select('#load-profile-browse-btn').on('click', function() {
$('#load-profile-file').trigger('click');
d3.select('#load-profile-browse-btn').on('click', function () {
$('#load-profile-file').trigger('click');
});
// Set a handler for file selection
d3.select('#load-profile-file').on('change', function() {
var file_browser = this;
var open_button = d3.select('#load-profile-open-btn');
d3.select('#load-profile-filename').attr('value', file_browser.value.replace(/C:\\fakepath\\/i, ''));
open_button.classed('disabled', false)
.on('click', function() {
var reader = new FileReader();
reader.onload = function(event) {
var contents = event.target.result;
current_profile = new Profile(contents);
update();
};
reader.onerror = function(event) {
console.error("Unable to read file. Error code: " + event.target.error.code);
};
// Read the profile
reader.readAsText(file_browser.files[0]);
// Clear the file browser value
file_browser.value = '';
d3.select('#load-profile-file').on('change', function () {
var file_browser = this;
var open_button = d3.select('#load-profile-open-btn');
d3.select('#load-profile-filename').attr('value', file_browser.value.replace(/C:\\fakepath\\/i, ''));
open_button.classed('disabled', false)
.on('click', function () {
var reader = new FileReader();
reader.onload = function (event) {
var contents = event.target.result;
current_profile = new Profile(contents, d3.select('#plot-area'), d3.select('#legend'), get_min_points, display_warning);
update();
};
reader.onerror = function (event) {
console.error("Unable to read file. Error code: " + event.target.error.code);
};
// Read the profile
reader.readAsText(file_browser.files[0]);
// Clear the file browser value
file_browser.value = '';
});
});
// Update the plots and minpoints display when dragged or clicked
d3.select('#minpoints_field').on('input', function() {
d3.select('#minpoints_display').text(this.value);
update();
d3.select('#minpoints_field').on('input', function () {
d3.select('#minpoints_display').text(this.value);
update();
});
d3.select('#sortby_field').on('change', update);
d3.select(window).on('resize', function() { update(true); });
d3.select(window).on('resize', function () { update(true); });
var sample_profiles = ['blackscholes', 'dedup', 'ferret', 'fluidanimate', 'sqlite', 'swaptions'];
var samples_sel = d3.select('#samples').selectAll('.sample-profile').data(sample_profiles)
.enter().append('button')
.enter().append('button')
.attr('class', 'btn btn-sm btn-default sample-profile')
.attr('data-dismiss', 'modal')
.attr('loaded', 'no')
.text(function(d) { return d; })
.on('click', function(d) {
var sel = d3.select(this);
if(sel.attr('loaded') != 'yes') {
.text(function (d) { return d; })
.on('click', function (d) {
var sel = d3.select(this);
if (sel.attr('loaded') != 'yes') {
d3.select('body').append('script')
.attr('src', 'profiles/' + d + '.coz.js')
.on('load', function() {
.attr('src', 'profiles/' + d + '.coz.js')
.on('load', function () {
sel.attr('loaded', 'yes');
current_profile = eval(d + '_profile');
update();
});
} else {
});
}
else {
current_profile = eval(d + '_profile');
update();
}
});
}
});
//# sourceMappingURL=ui.js.map

1
ui.js.map Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"ui.js","sourceRoot":"","sources":["ui.ts"],"names":[],"mappings":"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"}

130
ui.ts Normal file
View File

@ -0,0 +1,130 @@
// Ensure the brower supports the File API
if (!(<any> window).File || !(<any> window).FileReader) {
alert('The File APIs are not fully supported in this browser.');
}
let current_profile: Profile = undefined;
function get_min_points(): number {
return (<any> d3.select('#minpoints_field').node()).value;
}
function display_warning(title: string, text: string): void {
const warning = $(
`<div class="alert alert-warning alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<strong>${title}:</strong> ${text}
</div>`);
$('#warning-area').append(warning);
// Fade out after 5 seconds.
setTimeout(() => {
warning.fadeOut(500, () => {
warning.alert('close');
});
}, 5000);
}
function update(resize?: boolean) {
if (current_profile === undefined) return;
// Enable the sortby field
d3.select('#sortby_field').attr('disabled', null);
// Draw plots
current_profile.drawPlots(resize);
// Draw the legend
current_profile.drawLegend();
let tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden");
// Shorten path strings
let paths = d3.selectAll('.path')
.classed('path', false).classed('shortpath', true)
.text(function(d) {
let parts = d.split('/');
let filename = parts[parts.length-1];
return filename;
});
}
// Set a handler for the load profile button
d3.select('#load-profile-btn').on('click', function() {
// Reset the filename field
d3.select('#load-profile-filename').attr('value', '');
// Disable the open button
d3.select('#load-profile-open-btn').classed('disabled', true);
});
// Set a handler for the fake browse button
d3.select('#load-profile-browse-btn').on('click', function() {
$('#load-profile-file').trigger('click');
});
// Set a handler for file selection
d3.select('#load-profile-file').on('change', function() {
let file_browser = this;
let open_button = d3.select('#load-profile-open-btn');
d3.select('#load-profile-filename').attr('value', file_browser.value.replace(/C:\\fakepath\\/i, ''));
open_button.classed('disabled', false)
.on('click', function() {
var reader = new FileReader();
reader.onload = function(event) {
let contents: string = (<any> event.target).result;
current_profile = new Profile(contents, d3.select('#plot-area'), d3.select('#legend'), get_min_points, display_warning);
update();
};
reader.onerror = function(event) {
console.error("Unable to read file. Error code: " + (<any> event.target).error.code);
};
// Read the profile
reader.readAsText(file_browser.files[0]);
// Clear the file browser value
file_browser.value = '';
});
});
// Update the plots and minpoints display when dragged or clicked
d3.select('#minpoints_field').on('input', function() {
d3.select('#minpoints_display').text(this.value);
update();
});
d3.select('#sortby_field').on('change', update);
d3.select(window).on('resize', function() { update(true); });
let sample_profiles = ['blackscholes', 'dedup', 'ferret', 'fluidanimate', 'sqlite', 'swaptions'];
let samples_sel = d3.select('#samples').selectAll('.sample-profile').data(sample_profiles)
.enter().append('button')
.attr('class', 'btn btn-sm btn-default sample-profile')
.attr('data-dismiss', 'modal')
.attr('loaded', 'no')
.text(function(d) { return d; })
.on('click', function(d) {
var sel = d3.select(this);
if(sel.attr('loaded') != 'yes') {
d3.select('body').append('script')
.attr('src', 'profiles/' + d + '.coz.js')
.on('load', function() {
sel.attr('loaded', 'yes');
current_profile = eval(d + '_profile');
update();
});
} else {
current_profile = eval(d + '_profile');
update();
}
});