Commit 6c8d7d6f authored by Bruno López Trigo's avatar Bruno López Trigo

Finalizada versión beta de interface gráfica para datasets de exemplo

parent e62d5ba7
package brunolopez.expliclas.explainer;
import brunolopez.expliclas.exceptions.NotFoundEx;
import brunolopez.expliclas.models.Explanation;
import java.io.IOException;
public interface ExplainerManager {
Explanation getGlobalExplanation(String token, String dataset, String algorithm, String language) throws IOException;
Explanation getGlobalExplanation(String token, String dataset, String algorithm, String language) throws IOException, NotFoundEx;
}
package brunolopez.expliclas.explainer;
import brunolopez.expliclas.exceptions.NotFoundEx;
import brunolopez.expliclas.json.MapperJSON;
import brunolopez.expliclas.models.Explanation;
import brunolopez.expliclas.models.GlobalConfig;
......@@ -24,20 +25,23 @@ public class ExplainerManagerImpl implements ExplainerManager {
}
@Override
public Explanation getGlobalExplanation(String token, String dataset, String algorithm, String language) throws IOException {
public Explanation getGlobalExplanation(String token, String dataset, String algorithm, String language) throws IOException, NotFoundEx {
File config, global, log;
switch (language) {
case "en":
this.generator = new ClauseGeneratorEn();
if (token.isEmpty()) {
config = new File(this.BASE + dataset + "/" + dataset + ".json");
log = new File(this.BASE + dataset + "/" + algorithm + "/" + dataset + ".arff." + algorithm + ".log.txt");
} else {
config = new File(this.BASE + dataset + "/" + dataset + ".json");
log = new File(this.BASE + dataset + "/" + algorithm + "/" + dataset + ".arff." + algorithm + ".log.txt");
if(!config.exists()){
config = new File(this.BASE + "tmp/" + token + "/" + dataset + "/" + dataset + ".json");
log = new File(this.BASE + "tmp/" + token + "/" + dataset + "/" + algorithm + "/" + dataset + ".arff." + algorithm + ".log.txt");
}
if(!config.exists() || !log.exists()){
throw new NotFoundEx("Classifier not found");
}
global = new File(this.BASE + "global/config.json");
this.extractor = new InfoExtractor(this.mapper.readConfigJSON(config), log);
......@@ -46,14 +50,19 @@ public class ExplainerManagerImpl implements ExplainerManager {
break;
case "es":
this.generator = new ClauseGeneratorEs();
if (token.isEmpty()) {
config = new File(this.BASE + dataset + "/" + dataset + "-" + language + ".json");
log = new File(this.BASE + dataset + "/" + algorithm + "/" + dataset + ".arff." + algorithm + ".log.txt");
} else {
config = new File(this.BASE + dataset + "/" + dataset + "-" + language + ".json");
log = new File(this.BASE + dataset + "/" + algorithm + "/" + dataset + ".arff." + algorithm + ".log.txt");
if(!config.exists()){
config = new File(this.BASE + "tmp/" + token + "/" + dataset + "/" + dataset + "-" + language + ".json");
log = new File(this.BASE + "tmp/" + token + "/" + dataset + "/" + algorithm + "/" + dataset + ".arff." + algorithm + ".log.txt");
}
if(!config.exists() || !log.exists()){
throw new NotFoundEx("Classifier not found");
}
global = new File(this.BASE + "global/config-" + language + ".json");
this.extractor = new InfoExtractor(this.mapper.readConfigJSON(config), log);
......@@ -62,13 +71,18 @@ public class ExplainerManagerImpl implements ExplainerManager {
break;
case "gl":
this.generator = new ClauseGeneratorGl();
if (token.isEmpty()) {
config = new File(this.BASE + dataset + "/" + dataset + "-" + language + ".json");
log = new File(this.BASE + dataset + "/" + algorithm + "/" + dataset + ".arff." + algorithm + ".log.txt");
} else {
config = new File(this.BASE + dataset + "/" + dataset + "-" + language + ".json");
log = new File(this.BASE + dataset + "/" + algorithm + "/" + dataset + ".arff." + algorithm + ".log.txt");
if(!config.exists()){
config = new File(this.BASE + "tmp/" + token + "/" + dataset + "/" + dataset + "-" + language + ".json");
log = new File(this.BASE + "tmp/" + token + "/" + dataset + "/" + algorithm + "/" + dataset + ".arff." + algorithm + ".log.txt");
}
if(!config.exists() || !log.exists()){
throw new NotFoundEx("Classifier not found");
}
global = new File(this.BASE + "global/config-" + language + ".json");
......@@ -92,6 +106,7 @@ public class ExplainerManagerImpl implements ExplainerManager {
String dataset = this.extractor.getDatasetName();
ArrayList<String> conseqNames = this.extractor.getConsequentNames();
double globalPercentage = this.extractor.getGlobalPercentage();
explanation.setPrecision(globalPercentage);
double percentageConfused = this.extractor.getConfusedConseqPercentage();
int cycleSize = this.extractor.getCycleSize();
ArrayList<String> confusedConsequents = new ArrayList();
......
......@@ -3,6 +3,8 @@ package brunolopez.expliclas.models;
import java.util.ArrayList;
public class Explanation {
private Double precision;
private ArrayList<String> clauses;
public Explanation() {
......@@ -16,5 +18,13 @@ public class Explanation {
public void addClause(String clause){
this.clauses.add(clause);
}
public Double getPrecision() {
return precision;
}
public void setPrecision(Double precision) {
this.precision = precision;
}
}
package brunolopez.expliclas.services;
import brunolopez.expliclas.exceptions.NotFoundEx;
import brunolopez.expliclas.explainer.ExplainerManager;
import brunolopez.expliclas.explainer.ExplainerManagerImpl;
import brunolopez.expliclas.models.DatasetConfig;
......@@ -71,6 +72,8 @@ public class ExplainerService {
return Response.status(Response.Status.OK).entity(explanation).build();
} catch (IOException ex) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(new SimpleMessage("Error reading config")).build();
} catch (NotFoundEx ex) {
return Response.status(Response.Status.NOT_FOUND).entity(new SimpleMessage(ex.getMessage())).build();
}
......
......@@ -3,7 +3,7 @@
"attributes": [
{
"id": "Color",
"name": "color",
"name": "Color",
"type": "numericAtt",
"properties": [
{
......@@ -54,7 +54,7 @@
},
{
"id": "Bitterness",
"name": "bitterness",
"name": "Bitterness",
"type": "numericAtt",
"properties": [
{
......@@ -97,7 +97,7 @@
},
{
"id": "Strength",
"name": "strength",
"name": "Strength",
"type": "numericAtt",
"properties": [
{
......
......@@ -3,7 +3,7 @@
"attributes": [
{
"id": "Color",
"name": "color",
"name": "Color",
"type": "numericAtt",
"properties": [
{
......@@ -54,7 +54,7 @@
},
{
"id": "Bitterness",
"name": "bitterness",
"name": "Bitterness",
"type": "numericAtt",
"properties": [
{
......@@ -97,7 +97,7 @@
},
{
"id": "Strength",
"name": "strength",
"name": "Strength",
"type": "numericAtt",
"properties": [
{
......
......@@ -3,7 +3,7 @@
"attributes": [
{
"id": "Color",
"name": "color",
"name": "Color",
"type": "numericAtt",
"properties": [
{
......@@ -54,7 +54,7 @@
},
{
"id": "Bitterness",
"name": "bitterness",
"name": "Bitterness",
"type": "numericAtt",
"properties": [
{
......@@ -97,7 +97,7 @@
},
{
"id": "Strength",
"name": "strength",
"name": "Strength",
"type": "numericAtt",
"properties": [
{
......
#Generated by Maven
#Fri Oct 19 18:43:34 CEST 2018
#Mon Oct 22 15:44:55 CEST 2018
version=1.0
groupId=brunolopez
artifactId=expliclas-api
......@@ -129,6 +129,16 @@
}
}
},
"@material-ui/lab": {
"version": "3.0.0-alpha.21",
"resolved": "https://registry.npmjs.org/@material-ui/lab/-/lab-3.0.0-alpha.21.tgz",
"integrity": "sha512-TzjTrOxnEQQACNJx/TWV5BGSi8LLzo0p//hhDxrZhXDOTcJfLhrlZYbXuRhdshStN3daTcVLEM2C6a6dhuQGmQ==",
"requires": {
"@babel/runtime": "7.1.2",
"classnames": "^2.2.5",
"keycode": "^2.1.9"
}
},
"@types/jss": {
"version": "9.5.7",
"resolved": "https://registry.npmjs.org/@types/jss/-/jss-9.5.7.tgz",
......@@ -4546,8 +4556,8 @@
"bundled": true,
"optional": true,
"requires": {
"abbrev": "1.1.0",
"osenv": "0.1.4"
"abbrev": "1",
"osenv": "^0.1.4"
}
},
"npmlog": {
......
......@@ -6,6 +6,7 @@
"dependencies": {
"@material-ui/core": "^3.2.2",
"@material-ui/icons": "^3.0.1",
"@material-ui/lab": "^3.0.0-alpha.21",
"react": "^16.3.2",
"react-d3-tree": "^1.11.0",
"react-dom": "^16.3.2",
......
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import InputLabel from '@material-ui/core/InputLabel';
import MenuItem from '@material-ui/core/MenuItem';
import FormControl from '@material-ui/core/FormControl';
import Select from '@material-ui/core/Select';
const styles = theme => ({
button: {
display: 'block',
marginTop: theme.spacing.unit * 2,
},
formControl: {
width: '60%',
margin: 'auto'
},
});
class AlternativesSelect extends React.Component {
state = {
alternative: 0,
open: false,
};
handleChange = event => {
this.setState({ [event.target.name]: event.target.value });
this.props.changeAlternative(event.target.value);
};
handleClose = () => {
this.setState({ open: false });
};
handleOpen = () => {
this.setState({ open: true });
};
render() {
const { classes } = this.props;
return (
this.props.alternatives.length > 1 &&
<FormControl className={classes.formControl}>
<InputLabel htmlFor="demo-controlled-open-select">Alternatives</InputLabel>
<Select
open={this.state.open}
onClose={this.handleClose}
onOpen={this.handleOpen}
value={this.state.alternative}
onChange={this.handleChange}
inputProps={{
name: 'alternative',
id: 'demo-controlled-open-select',
}}
>
{this.props.alternatives.map((alternative, index) => {
return <MenuItem key={index} value={index}>Alternative {index+1}</MenuItem>
})}
</Select>
</FormControl>
);
}
}
AlternativesSelect.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(AlternativesSelect);
\ No newline at end of file
......@@ -22,8 +22,8 @@ export default class CenteredTree extends Component {
const dimensions = this.treeContainer.getBoundingClientRect();
this.setState({
translate: {
x: 100,
y: dimensions.height / 2
x: dimensions.width / 2,
y: 25
},
});
......@@ -39,12 +39,13 @@ export default class CenteredTree extends Component {
{this.state.translate && this.props.treeData ?
<Tree
ref="tree"
zoom={0.4}
onMouseOut={this.mouseOutTree}
onMouseOver={this.mouseOverTree}
data={this.props.treeData}
data={[this.props.treeData]}
collapsible={this.state.collapsible}
translate={this.state.translate}
orientation='horizontal'
orientation='vertical'
pathFunc="diagonal"
/> : "Loading "}
</div>
......
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
import {Typography, Drawer, Chip } from '@material-ui/core';
import ControlledExpansionPanels from './ControlledExpansionPanels';
import PanoramaFishEye from '@material-ui/icons/PanoramaFishEye';
import CropSquare from '@material-ui/icons/CropSquare';
import Reorder from '@material-ui/icons/Reorder';
import High from '@material-ui/icons/SentimentVerySatisfied';
import Medium from '@material-ui/icons/SentimentSatisfied';
import Low from '@material-ui/icons/SentimentVeryDissatisfied';
const styles = {
sideBar: {
padding: 20
},
list: {
width: 250,
},
fullList: {
width: 'auto',
},
configBar: {
flexDirection: 'row',
justifyContent: 'space-around',
display: '-webkit-flex',
webkitFlexWrap: 'wrap',
flexWrap: 'wrap'
},
chipDataset: {
backgroundColor: '#212121',
fontSize: 16,
margin: 5
},
upper: {
textTransform: 'uppercase',
color: '#283593'
},
chipAttributes: {
fontSize: 16,
backgroundColor: '#283593',
margin: 5
},
chipConsequents: {
fontSize: 16,
backgroundColor: '#C5CAE9',
color: '#283593',
margin: 5
},
chipHigh: {
fontSize: 16,
backgroundColor: '#4caf50',
margin: 5
},
chipMedium: {
fontSize: 16,
backgroundColor: '#ffa000',
margin: 5
},
chipLow: {
fontSize: 16,
backgroundColor: '#f4511e',
margin: 5
}
};
class ConfigurationPanel extends React.Component {
constructor(props){
super(props);
this.state = {
left: true,
};
this.closeConfiguration = this.closeConfiguration.bind(this);
}
closeConfiguration(){
this.setState({left: false});
}
toggleDrawer = (side, open) => () => {
this.setState({
[side]: open,
});
};
render() {
const { classes } = this.props;
const sideList = (
<div className={classes.sideBar}>
<Typography variant="h3" component="h3">
{this.props.config.dataset}
</Typography>
<hr></hr>
<Typography variant="h6" component="h6" className={classes.upper}>
<PanoramaFishEye/> {this.props.language.labels[this.props.language.id].attributes}
</Typography>
<ControlledExpansionPanels closeConfiguration={this.closeConfiguration} isDefault={this.props.isDefault} classify={this.props.classify} attributes={this.props.config.attributes}></ControlledExpansionPanels>
<Typography variant="h6" component="h6" className={classes.upper}>
<CropSquare /> {this.props.language.labels[this.props.language.id].consequents}
</Typography>
<ControlledExpansionPanels changeMarked={this.props.changeMarked} closeConfiguration={this.closeConfiguration} consequents={this.props.config.consequents}></ControlledExpansionPanels>
</div>
);
return (
<div>
<div className={classes.configBar}>
<Button variant="contained" color="primary" onClick={this.toggleDrawer('left', true)}>Open configuration</Button>
<Chip className={classes.chipDataset} color="primary" icon={<Reorder />} label={this.props.config.dataset}/>
<Chip className={classes.chipAttributes} color="primary" icon={<PanoramaFishEye />} label={'Attributes: ' + this.props.config.attributes.length}/>
<Chip className={classes.chipConsequents} color="primary" icon={<CropSquare />} label={'Consequents: ' + this.props.config.consequents.length}/>
{this.props.explanation.precision >= 80 &&
<Chip className={classes.chipHigh} color="primary" icon={<High />} label={'Precision: ' + this.props.explanation.precision + '%'}/>}
{this.props.explanation.precision >= 60 && this.props.explanation.precision < 80 &&
<Chip className={classes.chipMedium} color="primary" icon={<Medium />} label={'Precision: ' + this.props.explanation.precision + '%'}/>}
{this.props.explanation.precision < 60 &&
<Chip className={classes.chipLow} color="primary" icon={<Low />} label={'Precision: ' + this.props.explanation.precision + '%'}/>}
</div>
<Drawer
open={this.state.left}
onClose={this.toggleDrawer('left', false)}
>
<div
tabIndex={0}
role="button"
>
{sideList}
</div>
</Drawer>
</div>
);
}
}
ConfigurationPanel.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(ConfigurationPanel);
\ No newline at end of file
......@@ -10,7 +10,8 @@ import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import Divider from '@material-ui/core/Divider';
import { Input } from '@material-ui/core';
import { Input, InputAdornment, Button } from '@material-ui/core';
import Slider from '@material-ui/lab/Slider';
const styles = theme => ({
root: {
......@@ -18,9 +19,9 @@ const styles = theme => ({
marginTop: 20
},
heading: {
fontSize: theme.typography.pxToRem(15),
flexBasis: '33.33%',
flexShrink: 0,
fontSize: 16
},
list: {
backgroundColor: theme.palette.background.paper
......@@ -29,6 +30,27 @@ const styles = theme => ({
fontSize: theme.typography.pxToRem(15),
color: theme.palette.text.secondary,
},
slider: {
width: '70%',
},
percentage: {
paddingLeft: 20
},
input: {
width: '30%',
marginLeft: 30,
fontSize: 16
},
inputAtt: {
width: '100%'
},
button: {
margin: 5,
width: '50%'
},
selected: {
backgroundColor: '#C5CAE9',
}
});
class ControlledExpansionPanels extends React.Component {
......@@ -37,8 +59,13 @@ class ControlledExpansionPanels extends React.Component {
super(props);
this.state = {
expanded: null,
percentage: 5,
marked: []
};
this.handleChangeAtt = this.handleChangeAtt.bind(this);
this.handleChangeSlider = this.handleChangeSlider.bind(this);
this.classify = this.classify.bind(this);
this.markConsequent = this.markConsequent.bind(this);
}
componentDidMount() {
......@@ -58,6 +85,44 @@ class ControlledExpansionPanels extends React.Component {
}
markConsequent(event){
var marked;
if(this.state.marked.length === 2 || this.state.marked.length === 0){
marked = this.state.marked;
var index = -1;
for(var i=0; i<marked.length; i++){
if(marked[i] === (event.currentTarget.id - 1))
index = i;
}
if (index === -1)
marked = [event.currentTarget.id - 1];
else
marked.splice(index);
} else {
marked = this.state.marked;
if(marked[0] === (event.currentTarget.id - 1))
marked = [];
else{