Custom comparisons, performance improvements

This commit is contained in:
Colin McLeod
2015-05-20 00:29:24 -07:00
parent 02fe76f43b
commit 6c1e3a7410
146 changed files with 1096 additions and 1062 deletions

View File

@@ -0,0 +1,123 @@
angular.module('app').directive('areaChart', function () {
return {
restrict: 'A',
scope:{
config: '=',
series: '=',
height: '=',
width: '='
},
link: function(scope, element) {
var width = scope.width,
height = scope.height,
series = scope.series,
config = scope.config,
labels = config.labels,
margin = {top: 15, right: 15, bottom: 35, left: 50},
w = width - margin.left - margin.right,
h = height - margin.top - margin.bottom,
fmt = d3.format('.3r'),
fmtLong = d3.format('.2f');
// Create chart
var svg = d3.select(element[0]).append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Define Axes
var x = d3.scale.linear().range([0, w]);
var y = d3.scale.linear().range([h, 0]);
var xAxis = d3.svg.axis().outerTickSize(0).orient("bottom").tickFormat(fmt);
var yAxis = d3.svg.axis().outerTickSize(0).orient("left").tickFormat(fmt);
// Define Area
var area = d3.svg.area().x(function(d) { return x(d[0]); }).y0(h).y1(function(d) { return y(d[1]); });
var gradient = svg.append("defs")
.append("linearGradient")
.attr("id", "gradient")
.attr("x1", "0%").attr("y1", "0%")
.attr("x2", "100%").attr("y2", "100%")
.attr("spreadMethod", "pad");
gradient.append("stop")
.attr("offset", "0%")
.attr("stop-color", "#ff8c0d")
.attr("stop-opacity", 1);
gradient.append("stop")
.attr("offset", "100%")
.attr("stop-color", "#ff3b00")
.attr("stop-opacity", 1);
// Create Y Axis SVG Elements
svg.append("g").attr("class", "y axis")
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", -40)
.attr("x", -h/2)
.attr("dy", ".1em")
.style("text-anchor", "middle")
.text(labels.yAxis.title + ' (' + labels.yAxis.unit + ')');
// Create X Axis SVG Elements
svg.append("g").attr("class", "x axis").attr("transform", "translate(0," + h + ")")
.append("text")
.attr("y", 30)
.attr("x", w/2)
.attr("dy", ".1em")
.style("text-anchor", "middle")
.text(labels.xAxis.title + ' (' + labels.xAxis.unit + ')');
// Create and Add tooltip
var tip = svg.append("g").style("display", "none");
tip.append("circle")
.attr("class", "marker")
.attr("r", 4);
tip.append("text").attr("class", 'label x').attr("y", -2);
tip.append("text").attr("class", 'label y').attr("y", '0.7em');
/**
* Watch for changes in the series data (mass changes, etc)
*/
scope.$watchCollection('series', render);
scope.$watchCollection('config.watch', render);
function render() {
var data = [];
var func = series.func;
for (var d = series.xMin; d <= series.xMax; d++) {
data.push([ d, func(d) ]);
}
// Update domain and scale for axes;
x.domain([series.xMin, series.xMax]);
xAxis.scale(x);
y.domain([data[data.length - 1][1], data[0][1]]);
yAxis.scale(y);
svg.selectAll(".y.axis").call(yAxis);
svg.selectAll(".x.axis").call(xAxis);
// Remove existing elements
svg.selectAll('path.area').remove();
svg.insert("path",':first-child') // Area/Path to appear behind everything else
.datum(data)
.attr("class", "area")
.attr('fill', 'url(#gradient)')
.attr("d", area)
.on("mouseover", function() { tip.style("display", null); })
.on("mouseout", function() { tip.style("display", "none"); })
.on('mousemove', function() {
var xPos = d3.mouse(this)[0], x0 = x.invert(xPos), y0 = func(x0), flip = (xPos > w * 0.75);
tip.attr("transform", "translate(" + x(x0) + "," + y(y0) + ")");
tip.selectAll('text.label').attr("x", flip? -10 : 10).style("text-anchor", flip? 'end' : 'start');
tip.select('text.label.x').text(fmtLong(x0) + ' ' + labels.xAxis.unit);
tip.select('text.label.y').text(fmtLong(y0) + ' ' + labels.yAxis.unit);
});
}
}
};
});

View File

@@ -25,76 +25,75 @@ angular.module('app').directive('barChart', ['$rootScope', function ($rootScope)
width: '='
},
link: function(scope, element) {
var color = d3.scale.ordinal().range([ "#7b6888", "#6b486b", "#3182bd", "#a05d56", "#d0743c"]),
var color = d3.scale.ordinal().range([ '#7b6888', '#6b486b', '#3182bd', '#a05d56', '#d0743c']),
width = scope.width,
height = scope.height,
labels = scope.facet.lbls,
fmt = $rootScope[scope.facet.fmt],
properties = scope.facet.prop? [scope.facet.prop] : scope.facet.props,
fmt = scope.facet.fmt,
properties = scope.facet.props,
unit = scope.facet.unit,
margin = {top: 10, right: 20, bottom: 35, left: 150},
w = width - margin.left - margin.right,
h = height - margin.top - margin.bottom;
w = width - margin.left - margin.right;
// Create chart
var svg = d3.select(element[0]).append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var svg = d3.select(element[0]).append('svg').attr('width', width);
var vis = svg.append('g').attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// Create and Add tooltip
var tip = d3.tip()
.attr('class', 'd3-tip')
.html(function(property, propertyIndex, buildIndex) {
return (labels? (labels[propertyIndex] + ": ") : '') + fmt(property.value) + ' ' + unit;
.html(function(property, propertyIndex) {
return (labels? (labels[propertyIndex] + ': ') : '') + fmt(property.value) + ' ' + unit;
});
svg.call(tip);
vis.call(tip);
// Create Y Axis SVG Elements
svg.append("g").attr("class", "y axis");
svg.selectAll('g.y.axis g text').each(insertLinebreaks);
vis.append('g').attr('class', 'y axis');
vis.selectAll('g.y.axis g text').each(insertLinebreaks);
// Create X Axis SVG Elements
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + h + ")")
.append("text")
.attr("y", 30)
.attr("x", w/2)
.attr("dy", ".1em")
.style("text-anchor", "middle")
vis.append('g')
.attr('class', 'x axis')
.append('text')
.attr('y', 30)
.attr('x', w/2)
.attr('dy', '.1em')
.style('text-anchor', 'middle')
.text(scope.facet.title + (unit? (' (' + unit + ')') : ''));
/**
* Watch for changes in the comparison array (ships added/removed, sorting)
*/
scope.$watch('data', function() {
var data = scope.data;
var maxVal = d3.max(data, function(d) { return d3.max(properties, function(prop) {return d[prop]; }); });
var y0 = d3.scale.ordinal().domain(data.map(bName)).rangeRoundBands([0, h],0.3);
var y1 = d3.scale.ordinal().domain(properties).rangeRoundBands([0, y0.rangeBand()]);
var x = d3.scale.linear().range([0, w]).domain([0, maxVal]);
var yAxis = d3.svg.axis().scale(y0).outerTickSize(0).orient("left");
var xAxis = d3.svg.axis().scale(x).outerTickSize(0).orient("bottom").tickFormat(d3.format('.2s'));
scope.$watchCollection('data', function() {
var data = scope.data,
height = 45 + (25 * data.length),
h = height - margin.top - margin.bottom,
maxVal = d3.max(data, function(d) { return d3.max(properties, function(p) {return d[p]; }); }),
y0 = d3.scale.ordinal().domain(data.map(bName)).rangeRoundBands([0, h],0.3),
y1 = d3.scale.ordinal().domain(properties).rangeRoundBands([0, y0.rangeBand()]),
x = d3.scale.linear().range([0, w]).domain([0, maxVal]),
yAxis = d3.svg.axis().scale(y0).outerTickSize(0).orient('left'),
xAxis = d3.svg.axis().scale(x).outerTickSize(0).orient('bottom').tickFormat(d3.format('.2s'));
// Update chart size
svg.attr('height', height);
// Remove existing elements
svg.selectAll('.ship').remove();
svg.selectAll('rect').remove();
vis.selectAll('.ship').remove();
vis.selectAll('rect').remove();
// Update X & Y Axis
svg.selectAll(".y.axis").call(yAxis);
svg.selectAll(".x.axis").call(xAxis);
vis.selectAll('.y.axis').call(yAxis);
vis.selectAll('.x.axis').attr('transform', 'translate(0,' + h + ')').call(xAxis);
// Update Y-Axis labels
svg.selectAll('g.y.axis g text').each(insertLinebreaks);
vis.selectAll('g.y.axis g text').each(insertLinebreaks);
var group = svg.selectAll(".ship")
var group = vis.selectAll('.ship')
.data(scope.data, bName)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(build) { return "translate(0," + y0(bName(build)) + ")"; });
.enter().append('g')
.attr('class', 'g')
.attr('transform', function(build) { return 'translate(0,' + y0(bName(build)) + ')'; });
group.selectAll("rect")
group.selectAll('rect')
.data(function(build) {
var o = [];
for (var i = 0; i < properties.length; i++) {
@@ -102,20 +101,20 @@ angular.module('app').directive('barChart', ['$rootScope', function ($rootScope)
}
return o;
})
.enter().append("rect")
.attr("height", y1.rangeBand())
.attr("x",0)
.attr("y", function(d) {return y1(d.name); })
.attr("width", function(d) { return x(d.value); })
.enter().append('rect')
.attr('height', y1.rangeBand())
.attr('x',0)
.attr('y', function(d) {return y1(d.name); })
.attr('width', function(d) { return x(d.value); })
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
.style("fill", function(d) { return color(d.name); });
.style('fill', function(d) { return color(d.name); });
});
scope.$on('$destroy', function() {
tip.destroy(); // Remove the tooltip from the DOM
})
});
}
};

View File

@@ -0,0 +1,80 @@
angular.module('app').directive('comparisonTable', ['$state', function ($state) {
function tblHeader(facets) {
var r1 = ['<tr class="main"><th rowspan="2" class="prop" prop="name">Ship</th><th rowspan="2" class="prop" prop="buildName">Build</th>'];
var r2 = [];
for (var i = 0, l = facets.length; i < l; i++) {
if (facets[i].active) {
var f = facets[i];
var p = f.props;
var pl = p.length;
r1.push('<th rowspan="', f.props.length == 1 ? 2 : 1,'" colspan="',pl,'"');
if (pl == 1) {
r1.push(' prop="',p[0],'" class="prop"');
} else {
for (var j = 0; j < pl; j++) {
r2.push('<th prop="', p[j], '" class="prop ', j === 0? 'lft' : '', ' ">' , f.lbls[j], '</th>');
}
}
r1.push('>', f.title ,'</th>');
}
}
r1.push('</tr><tr>');
r1.push(r2.join(''));
r1.push('</tr>');
return r1.join('');
}
function tblBody(facets, builds) {
var body = [];
if(builds.length === 0) {
return '<td colspan="100" class="cen">No builds added to comparison!</td';
}
for (var i = 0, l = builds.length; i < l; i++) {
var b = builds[i];
body.push('<tr class="tr">');
var href = $state.href('outfit',{shipId: b.id, code: b.code, bn: b.buildName});
body.push('<td class="tl"><a href="', href,'">', b.name,'</a></td>');
body.push('<td class="tl"><a href="', href,'">', b.buildName,'</a></td>');
for (var j = 0, fl = facets.length; j < fl; j++) {
if (facets[j].active) {
var f = facets[j];
var p = f.props;
for (var k = 0, pl = p.length; k < pl; k++) {
body.push('<td>', f.fmt(b[p[k]]), '<u> ', f.unit, '</u></td>');
}
}
}
body.push('</tr>');
}
return body.join('');
}
return {
restrict: 'A',
link: function (scope, element) {
var header = angular.element('<thead></thead>');
var body = angular.element('<tbody></tbody>');
element.append(header);
element.append(body);
var updateAll = function (){
header.html(tblHeader(scope.facets));
body.html(tblBody(scope.facets, scope.builds));
};
scope.$watchCollection('facets', updateAll);
scope.$watch('tblUpdate', updateAll);
scope.$watchCollection('builds', function() {
body.html(tblBody(scope.facets, scope.builds));
});
}
};
}]);

View File

@@ -1,4 +1,4 @@
angular.module('app').directive('componentSelect', function() {
angular.module('app').directive('componentSelect', function () {
// Generting the HTML in this manner is MUCH faster than using an angular template.
@@ -8,20 +8,34 @@ angular.module('app').directive('componentSelect', function() {
var o = opts[i];
var id = o.id || (o.class + o.rating); // Common components' ID is their class and rating
list.push('<li class="', o.name? 'lc' : 'c');
if(wrap && o.class != prevClass) list.push(' cl');
if (cid == o.id) list.push(' active');
if(wrap && o.class != prevClass) {
list.push(' cl');
}
if (cid == o.id) {
list.push(' active');
}
list.push((o.maxmass && mass > o.maxmass)? ' disabled"' : '" cpid="', id, '">');
if(o.mode) {
list.push('<svg cpid="', id, '" class="icon lg"><use xlink:href="#mount-', o.mode , '"></use></svg> ');
}
list.push(o.class, o.rating);
if(o.mode) {
list.push('/' + o.mode);
if(o.missile) {
list.push(o.missile);
}
}
if(o.name) list.push(' ' + o.name);
if(o.name) {
list.push(' ' + o.name);
}
list.push('</li>');
prevClass = o.class;
prevRating= o.rating;
@@ -49,7 +63,7 @@ angular.module('app').directive('componentSelect', function() {
if(groups) {
// At present time slots with grouped options (Hardpoints and Internal) can be empty
list.push('<div class="empty-c" cpid="empty">EMPTY</div>');
for (g in groups) {
for (var g in groups) {
var grp = groups[g];
var grpCode = grp[Object.keys(grp)[0]].grp; // Nasty operation to get the grp property of the first/any single component
list.push('<div id="', grpCode ,'" class="select-group">', g, '</div><ul>');

View File

@@ -1,4 +1,4 @@
angular.module('app').directive('shipyardHeader', ['lodash','$rootScope', 'Persist', 'ShipsDB', function (_, $rootScope, Persist, ships) {
angular.module('app').directive('shipyardHeader', ['lodash','$window','$rootScope', 'Persist', 'ShipsDB', function (_, $window, $rootScope, Persist, ships) {
return {
restrict: 'E',
@@ -8,6 +8,7 @@ angular.module('app').directive('shipyardHeader', ['lodash','$rootScope', 'Persi
scope.openedMenu = null;
scope.ships = ships;
scope.allBuilds = Persist.builds;
scope.allComparisons = Persist.comparisons;
scope.bs = Persist.state;
// Insurance options and management here for now.
@@ -17,9 +18,9 @@ angular.module('app').directive('shipyardHeader', ['lodash','$rootScope', 'Persi
{ name:'Alpha', pct: 0.025 },
{ name:'Beta', pct: 0.035 }
]
}
};
var insIndex = _.findIndex($rootScope.insurance.opts, 'name', localStorage.getItem('insurance'));
var insIndex = _.findIndex($rootScope.insurance.opts, 'name', $window.localStorage.getItem('insurance'));
$rootScope.insurance.current = $rootScope.insurance.opts[insIndex != -1? insIndex : 0];
// Close menus if a navigation change event occurs
@@ -33,8 +34,8 @@ angular.module('app').directive('shipyardHeader', ['lodash','$rootScope', 'Persi
});
scope.updateInsurance = function(){
localStorage.setItem('insurance', $rootScope.insurance.current.name);
}
$window.localStorage.setItem('insurance', $rootScope.insurance.current.name);
};
scope.openMenu = function (e, menu) {
e.stopPropagation();
@@ -59,8 +60,7 @@ angular.module('app').directive('shipyardHeader', ['lodash','$rootScope', 'Persi
$rootScope.hideAbout = function (){
$rootScope.showAbout = false;
}
};
}
};
}]);