sort tiles on the dashboard
This commit is contained in:
@@ -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++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user