sort tiles on the dashboard

This commit is contained in:
2018-04-28 19:03:07 +02:00
parent 913057c6df
commit 2da54432ff
7 changed files with 183 additions and 49 deletions

View File

@@ -9,8 +9,7 @@ import org.lucares.pdb.plot.api.AggregatedData;
import org.lucares.pdb.plot.api.Limit; import org.lucares.pdb.plot.api.Limit;
public interface DataSeries { public interface DataSeries {
public static final Comparator<? super DataSeries> BY_NUMBER_OF_VALUES = ( public static final Comparator<? super DataSeries> BY_NUMBER_OF_VALUES = (a, b) -> {
a, b) -> {
return a.getValues() - b.getValues(); return a.getValues() - b.getValues();
}; };
@@ -19,7 +18,7 @@ public interface DataSeries {
return result < 0 ? -1 : (result > 0 ? 1 : 0); return result < 0 ? -1 : (result > 0 ? 1 : 0);
}; };
public static final Comparator<? super DataSeries> BY_NAME = (a,b) -> { public static final Comparator<? super DataSeries> BY_NAME = (a, b) -> {
return a.getTitle().compareToIgnoreCase(b.getTitle()); return a.getTitle().compareToIgnoreCase(b.getTitle());
}; };
@@ -28,10 +27,13 @@ public interface DataSeries {
public int getId(); public int getId();
public String getTitle(); public String getTitle();
public int getValues(); public int getValues();
public long getMaxValue(); public long getMaxValue();
public void setStyle(String style); public void setStyle(String style);
public String getStyle(); public String getStyle();
public AggregatedData getAggregatedData(); public AggregatedData getAggregatedData();
@@ -64,7 +66,7 @@ public interface DataSeries {
case NO_LIMIT: case NO_LIMIT:
return DataSeries.BY_NAME; return DataSeries.BY_NAME;
} }
throw new IllegalStateException("unhandled enum: "+ limitBy); throw new IllegalStateException("unhandled enum: " + limitBy);
} }
static void sortAndLimit(final List<DataSeries> dataSeries, final Limit limitBy, final int limit) { static void sortAndLimit(final List<DataSeries> dataSeries, final Limit limitBy, final int limit) {
@@ -84,30 +86,27 @@ public interface DataSeries {
} }
} }
static void setColors(List<DataSeries> dataSeries){ static void setColors(final List<DataSeries> dataSeries) {
int i = 0; int i = 0;
for (DataSeries dataSerie : dataSeries) { for (final DataSeries dataSerie : dataSeries) {
final int numColors = GnuplotColorPalettes.DEFAULT.size(); final int numColors = GnuplotColorPalettes.DEFAULT.size();
final int numDashTypes = DashTypes.DEFAULT.size(); final int numDashTypes = DashTypes.DEFAULT.size();
GnuplotColor color = GnuplotColorPalettes.DEFAULT.get(i % numColors); final GnuplotColor color = GnuplotColorPalettes.DEFAULT.get(i % numColors);
if (dataSerie.getAggregatedData() != null){ if (dataSerie.getAggregatedData() != null) {
// color = color.brighter(); // color = color.brighter();
} }
final String dashType = DashTypes.DEFAULT.get((i/numColors) % numDashTypes); final String dashType = DashTypes.DEFAULT.get((i / numColors) % numDashTypes);
String style = String.format("lt %s dt %s ",// final String style = String.format("lt %s dt %s ", //
color.getColor(),// color.getColor(), //
dashType// dashType//
); );
dataSerie.setStyle(style); dataSerie.setStyle(style);
i++; i++;
} }
} }
} }

View File

@@ -10,6 +10,7 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.SortedSet; import java.util.SortedSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -24,8 +25,8 @@ import org.lucares.pdbui.domain.AutocompleteProposalByValue;
import org.lucares.pdbui.domain.AutocompleteResponse; import org.lucares.pdbui.domain.AutocompleteResponse;
import org.lucares.pdbui.domain.PlotRequest; import org.lucares.pdbui.domain.PlotRequest;
import org.lucares.pdbui.domain.PlotResponse; import org.lucares.pdbui.domain.PlotResponse;
import org.lucares.pdbui.domain.PlotResponseStats;
import org.lucares.performance.db.PerformanceDb; import org.lucares.performance.db.PerformanceDb;
import org.lucares.recommind.logs.DataSeries;
import org.lucares.recommind.logs.InternalPlottingException; import org.lucares.recommind.logs.InternalPlottingException;
import org.lucares.recommind.logs.NoDataPointsException; import org.lucares.recommind.logs.NoDataPointsException;
import org.lucares.recommind.logs.PlotResult; import org.lucares.recommind.logs.PlotResult;
@@ -94,7 +95,7 @@ public class PdbController implements HardcodedValues {
// TODO the UI should cancel requests that are in flight before sending a plot // TODO the UI should cancel requests that are in flight before sending a plot
// request // request
if (plotterLock.tryLock()) { if (plotterLock.tryLock(5, TimeUnit.SECONDS)) {
try { try {
final PlotResult result = plotter.plot(plotSettings); final PlotResult result = plotter.plot(plotSettings);
@@ -105,7 +106,8 @@ public class PdbController implements HardcodedValues {
? WEB_IMAGE_OUTPUT_PATH + "/" + result.getThumbnailName() ? WEB_IMAGE_OUTPUT_PATH + "/" + result.getThumbnailName()
: imageUrl; : imageUrl;
return new PlotResponse(DataSeries.toMap(result.getDataSeries()), imageUrl, thumbnailUrl); final PlotResponseStats stats = PlotResponseStats.fromDataSeries(result.getDataSeries());
return new PlotResponse(stats, imageUrl, thumbnailUrl);
} catch (final NoDataPointsException e) { } catch (final NoDataPointsException e) {
throw new NotFoundException(e); throw new NotFoundException(e);
} finally { } finally {

View File

@@ -1,14 +1,12 @@
package org.lucares.pdbui.domain; package org.lucares.pdbui.domain;
import java.util.Map;
public class PlotResponse { public class PlotResponse {
private String imageUrl = ""; private String imageUrl = "";
private Map<String, Integer> dataSeries; private PlotResponseStats stats;
private final String thumbnailUrl; private String thumbnailUrl;
public PlotResponse(final Map<String, Integer> dataSeries, final String imageUrl, final String thumbnailUrl) { public PlotResponse(final PlotResponseStats stats, final String imageUrl, final String thumbnailUrl) {
this.dataSeries = dataSeries; this.stats = stats;
this.imageUrl = imageUrl; this.imageUrl = imageUrl;
this.thumbnailUrl = thumbnailUrl; this.thumbnailUrl = thumbnailUrl;
} }
@@ -25,16 +23,21 @@ public class PlotResponse {
return thumbnailUrl; return thumbnailUrl;
} }
public Map<String, Integer> getDataSeries() { public PlotResponseStats getStats() {
return dataSeries; return stats;
} }
public void setDataSeries(final Map<String, Integer> dataSeries) { public void setStats(final PlotResponseStats stats) {
this.dataSeries = dataSeries; this.stats = stats;
}
public void setThumbnailUrl(final String thumbnailUrl) {
this.thumbnailUrl = thumbnailUrl;
} }
@Override @Override
public String toString() { public String toString() {
return imageUrl + " " + dataSeries + " " + thumbnailUrl; return "PlotResponse [imageUrl=" + imageUrl + ", stats=" + stats + ", thumbnailUrl=" + thumbnailUrl + "]";
} }
} }

View File

@@ -0,0 +1,55 @@
package org.lucares.pdbui.domain;
import java.util.List;
import org.lucares.recommind.logs.DataSeries;
public class PlotResponseStats {
private long maxValue;
private int values;
public PlotResponseStats() {
super();
}
public PlotResponseStats(final long maxValue, final int values) {
this.maxValue = maxValue;
this.values = values;
}
public long getMaxValue() {
return maxValue;
}
public void setMaxValue(final long maxValue) {
this.maxValue = maxValue;
}
public int getValues() {
return values;
}
public void setValues(final int values) {
this.values = values;
}
@Override
public String toString() {
return "PlotResponseStats [maxValue=" + maxValue + ", values=" + values + "]";
}
public static PlotResponseStats fromDataSeries(final List<DataSeries> dataSeries) {
int values = 0;
long maxValue = 0;
for (final DataSeries dataSerie : dataSeries) {
values += dataSerie.getValues();
maxValue = Math.max(maxValue, dataSerie.getMaxValue());
}
return new PlotResponseStats(maxValue, values);
}
}

View File

@@ -71,9 +71,12 @@ textarea {
/ 1fr / 1fr
} }
#navigation { #navigation-bar {
grid-area: navigation; grid-area: navigation;
background-color: #aaa; background-color: #ccc;
}
#navigation {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }

View File

@@ -237,10 +237,51 @@ Vue.component('result-view-dashboard-item', {
<div class="dashboard-item"> <div class="dashboard-item">
<div class="error-message" v-if="dashboardItem.error">{{ dashboardItem.error }}</div> <div class="error-message" v-if="dashboardItem.error">{{ dashboardItem.error }}</div>
<img v-bind:src="dashboardItem.thumbnailUrl" v-if="dashboardItem.thumbnailUrl" /> <img v-bind:src="dashboardItem.thumbnailUrl" v-if="dashboardItem.thumbnailUrl" />
<div class="fieldValue">{{ dashboardItem.fieldValue }}</div> <div class="fieldValue">{{ dashboardItem.fieldValue }} ({{ dashboardItem.stats.values }}) ({{ dashboardItem.stats.maxValue }})</div>
</div>` </div>`
}); });
Vue.component('navigation-bar-dashboard', {
props: ['dashboard'],
methods: {
sort: function() {
sortTiles();
}
},
computed: {
navigationVisible: function() {
return data.dashboard.tiles.length > 0;
}
},
template: `
<div id="navigation-bar-dashboard" v-if="navigationVisible">
<label for="dashboardSortBy">Sort by:</label>
<select id="dashboardSortBy" name="dashboardSortBy" v-model="dashboard.sortBy" @change="sort">
<option value="DEFAULT"></option>
<option value="MAX_VALUE_DESC">max value desc</option>
<option value="MAX_VALUE_ASC">max value asc</option>
<option value="VALUES_DESC">#values desc</option>
<option value="VALUES_ASC">#values asc</option>
</select>
<!--
<label for="dashboardFilter">Filter by:</label>
<select id="dashboardFilter" name="dashboardFilter" v-model="dashboard.filter">
<option value="NONE">none</option>
<option value="HIGHER_THAN">higher</option>
<option value="LOWER_THAN">lower</option>
<option value="HIGHER_AVG_THAN">higher avg.</option>
<option value="LOWER_AVG_THAN">lower avg.</option>
<option value="HIGHER_PERCENTILE_THAN">higher percentile</option>
<option value="LOWER_PERCENTILE_THAN">lower percentile</option>
<option value="MORE_THAN">more</option>
<option value="LESS_THAN">less</option>
</select>
-->
</div>
`
});
Vue.component('navigation-bar', { Vue.component('navigation-bar', {
props: [], props: [],
methods: { methods: {
@@ -405,10 +446,13 @@ Vue.component('navigation-bar', {
computed: { computed: {
navigationDisabled: function() { navigationDisabled: function() {
return !data.resultView.imageUrl; return !data.resultView.imageUrl;
},
navigationVisible: function() {
return data.dashboard.tiles.length == 0;
} }
}, },
template: ` template: `
<div id="navigation"> <div id="navigation" v-if="navigationVisible">
<button id="nav_left" v-on:click="dateLeftShift" :disabled="navigationDisabled" title="Show Older Values"><i class="fa fa-angle-double-left" aria-hidden="true"></i></button> <button id="nav_left" v-on:click="dateLeftShift" :disabled="navigationDisabled" title="Show Older Values"><i class="fa fa-angle-double-left" aria-hidden="true"></i></button>
<button id="nav_left_half" v-on:click="dateHalfLeftShift" :disabled="navigationDisabled" title="Show Older Values"><i class="fa fa-angle-left" aria-hidden="true"></i></button> <button id="nav_left_half" v-on:click="dateHalfLeftShift" :disabled="navigationDisabled" title="Show Older Values"><i class="fa fa-angle-left" aria-hidden="true"></i></button>
<div> <div>
@@ -653,7 +697,7 @@ var rootView = new Vue({
} }
}); });
initInvaders('result-image'); initInvaders('result');
} // end window.on-load } // end window.on-load
@@ -692,7 +736,9 @@ var data = {
loadingGameActive: false loadingGameActive: false
}, },
dashboard: { dashboard: {
tiles: [] tiles: [],
sortBy: "MAX_VALUE_DESC",
filter: "NONE"
} }
}; };
@@ -858,8 +904,10 @@ function createDashboardItem(fieldValues, originalQuery, field, imageHeight, ima
data.dashboard.tiles.push({ data.dashboard.tiles.push({
fieldValue: fieldValue, fieldValue: fieldValue,
thumbnailUrl: response.thumbnailUrl, thumbnailUrl: response.thumbnailUrl,
imageUrl: response.imageUrl imageUrl: response.imageUrl,
stats: response.stats
}); });
sortTiles();
createDashboardItem(fieldValues, originalQuery, field, imageHeight, imageWidth); createDashboardItem(fieldValues, originalQuery, field, imageHeight, imageWidth);
}; };
const error = function(e) { const error = function(e) {
@@ -886,6 +934,27 @@ function createDashboardItem(fieldValues, originalQuery, field, imageHeight, ima
} }
} }
function sortTiles() {
switch (data.dashboard.sortBy) {
case "DEFAULT":
data.dashboard.tiles.sort(function(a, b){return a.fieldValue.localeCompare(b.fieldValue);});
break;
case "VALUES_ASC":
data.dashboard.tiles.sort(function(a, b){return a.stats.values - b.stats.values;});
break;
case "VALUES_DESC":
data.dashboard.tiles.sort(function(a, b){return b.stats.values - a.stats.values;});
break;
case "MAX_VALUE_ASC":
data.dashboard.tiles.sort(function(a, b){return a.stats.maxValue - b.stats.maxValue;});
break;
case "MAX_VALUE_DESC":
data.dashboard.tiles.sort(function(a, b){return b.stats.maxValue - a.stats.maxValue;});
break;
}
}
function postJson(url, requestData, successCallback, errorCallback) { function postJson(url, requestData, successCallback, errorCallback) {
$.ajax({ $.ajax({

View File

@@ -18,7 +18,10 @@
<body> <body>
<div id="app"> <div id="app">
<search-bar v-bind="{ 'searchBar': searchBar }"></search-bar> <search-bar v-bind="{ 'searchBar': searchBar }"></search-bar>
<navigation-bar v-bind="{ 'searchBar': searchBar }"></navigation-bar> <div id="navigation-bar">
<navigation-bar v-bind="{ 'searchBar': searchBar }"></navigation-bar>
<navigation-bar-dashboard v-bind="{ 'dashboard': dashboard }"></navigation-bar-dashboard>
</div>
<div id="result"> <div id="result">
<result-view v-bind="{ 'searchBar': searchBar, 'resultView': resultView }"></result-view> <result-view v-bind="{ 'searchBar': searchBar, 'resultView': resultView }"></result-view>
<result-view-dashboard v-bind="{ 'dashboard': dashboard }"></result-view-dashboard> <result-view-dashboard v-bind="{ 'dashboard': dashboard }"></result-view-dashboard>