mirror of
https://github.com/plasma-umass/coz.git
synced 2024-09-11 04:55:25 +03:00
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:
parent
27b3d70e6e
commit
26d929681e
3
.gitignore
vendored
3
.gitignore
vendored
@ -39,3 +39,6 @@ tests/*/obj
|
||||
|
||||
bin/inspect/obj
|
||||
bin/inspect/inspect
|
||||
|
||||
.vscode
|
||||
node_modules
|
||||
|
14
README.md
Normal file
14
README.md
Normal 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/`
|
||||
|
27
index.htm
27
index.htm
@ -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
23
package.json
Normal 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"
|
||||
}
|
||||
}
|
974
profile.js
974
profile.js
File diff suppressed because it is too large
Load Diff
1
profile.js.map
Normal file
1
profile.js.map
Normal file
File diff suppressed because one or more lines are too long
626
profile.ts
Normal file
626
profile.ts
Normal 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
8
tsconfig.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "none",
|
||||
"target": "es5",
|
||||
"noImplicitAny": true,
|
||||
"sourceMap": true
|
||||
}
|
||||
}
|
162
ui.js
162
ui.js
@ -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\">×</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
1
ui.js.map
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"ui.js","sourceRoot":"","sources":["ui.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,EAAE,CAAC,CAAC,CAAQ,MAAO,CAAC,IAAI,IAAI,CAAQ,MAAO,CAAC,UAAU,CAAC,CAAC,CAAC;IACvD,KAAK,CAAC,wDAAwD,CAAC,CAAC;AAClE,CAAC;AAED,IAAI,eAAe,GAAY,SAAS,CAAC;AAEzC;IACE,MAAM,CAAQ,EAAE,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC,IAAI,EAAG,CAAC,KAAK,CAAC;AAC5D,CAAC;AAED,yBAAyB,KAAa,EAAE,IAAY;IAClD,IAAM,OAAO,GAAG,CAAC,CACf,uOAEY,KAAK,mBAAc,IAAI,iBAC5B,CAAC,CAAC;IACX,CAAC,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAEnC,4BAA4B;IAC5B,UAAU,CAAC;QACT,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE;YACnB,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACzB,CAAC,CAAC,CAAC;IACL,CAAC,EAAE,IAAI,CAAC,CAAC;AACX,CAAC;AAED,gBAAgB,MAAgB;IAC9B,EAAE,CAAC,CAAC,eAAe,KAAK,SAAS,CAAC;QAAC,MAAM,CAAC;IAE1C,0BAA0B;IAC1B,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IAElD,aAAa;IACb,eAAe,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAElC,kBAAkB;IAClB,eAAe,CAAC,UAAU,EAAE,CAAC;IAE7B,IAAI,OAAO,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC;SAC7B,MAAM,CAAC,KAAK,CAAC;SACb,KAAK,CAAC,UAAU,EAAE,UAAU,CAAC;SAC7B,KAAK,CAAC,SAAS,EAAE,IAAI,CAAC;SACtB,KAAK,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;IAEhC,uBAAuB;IACvB,IAAI,KAAK,GAAG,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC;SAC9B,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,IAAI,CAAC;SACjD,IAAI,CAAC,UAAS,CAAC;QACd,IAAI,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACzB,IAAI,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAC,CAAC,CAAC,CAAC;QACrC,MAAM,CAAC,QAAQ,CAAC;IAClB,CAAC,CAAC,CAAC;AACP,CAAC;AAED,4CAA4C;AAC5C,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC,EAAE,CAAC,OAAO,EAAE;IACzC,2BAA2B;IAC3B,EAAE,CAAC,MAAM,CAAC,wBAAwB,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAEtD,0BAA0B;IAC1B,EAAE,CAAC,MAAM,CAAC,wBAAwB,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;AAChE,CAAC,CAAC,CAAC;AAEH,2CAA2C;AAC3C,EAAE,CAAC,MAAM,CAAC,0BAA0B,CAAC,CAAC,EAAE,CAAC,OAAO,EAAE;IAChD,CAAC,CAAC,oBAAoB,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;AAC3C,CAAC,CAAC,CAAC;AAEH,mCAAmC;AACnC,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC,EAAE,CAAC,QAAQ,EAAE;IAC3C,IAAI,YAAY,GAAG,IAAI,CAAC;IACxB,IAAI,WAAW,GAAG,EAAE,CAAC,MAAM,CAAC,wBAAwB,CAAC,CAAC;IAEtD,EAAE,CAAC,MAAM,CAAC,wBAAwB,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC,CAAC;IAErG,WAAW,CAAC,OAAO,CAAC,UAAU,EAAE,KAAK,CAAC;SACnC,EAAE,CAAC,OAAO,EAAE;QACX,IAAI,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC9B,MAAM,CAAC,MAAM,GAAG,UAAS,KAAK;YAC5B,IAAI,QAAQ,GAAkB,KAAK,CAAC,MAAO,CAAC,MAAM,CAAC;YACnD,eAAe,GAAG,IAAI,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,cAAc,EAAE,eAAe,CAAC,CAAC;YACxH,MAAM,EAAE,CAAC;QACX,CAAC,CAAC;QAEF,MAAM,CAAC,OAAO,GAAG,UAAS,KAAK;YAC7B,OAAO,CAAC,KAAK,CAAC,mCAAmC,GAAU,KAAK,CAAC,MAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACvF,CAAC,CAAC;QAEF,mBAAmB;QACnB,MAAM,CAAC,UAAU,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAEzC,+BAA+B;QAC/B,YAAY,CAAC,KAAK,GAAG,EAAE,CAAC;IAC1B,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC;AAEH,iEAAiE;AACjE,EAAE,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC,EAAE,CAAC,OAAO,EAAE;IACxC,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACjD,MAAM,EAAE,CAAC;AACX,CAAC,CAAC,CAAC;AAEH,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;AAEhD,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,QAAQ,EAAE,cAAa,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAE7D,IAAI,eAAe,GAAG,CAAC,cAAc,EAAE,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC;AAEjG,IAAI,WAAW,GAAG,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC;KACvF,KAAK,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC;KACtB,IAAI,CAAC,OAAO,EAAE,uCAAuC,CAAC;KACtD,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC;KAC7B,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC;KACpB,IAAI,CAAC,UAAS,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;KAC/B,EAAE,CAAC,OAAO,EAAE,UAAS,CAAC;IACrB,IAAI,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC1B,EAAE,CAAA,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC;QAC/B,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;aAC/B,IAAI,CAAC,KAAK,EAAE,WAAW,GAAG,CAAC,GAAG,SAAS,CAAC;aACxC,EAAE,CAAC,MAAM,EAAE;YACV,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;YAC1B,eAAe,GAAG,IAAI,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC;YACvC,MAAM,EAAE,CAAC;QACX,CAAC,CAAC,CAAC;IACP,CAAC;IAAC,IAAI,CAAC,CAAC;QACN,eAAe,GAAG,IAAI,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC;QACvC,MAAM,EAAE,CAAC;IACX,CAAC;AACH,CAAC,CAAC,CAAC"}
|
130
ui.ts
Normal file
130
ui.ts
Normal 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">×</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();
|
||||
}
|
||||
});
|
Loading…
Reference in New Issue
Block a user