/*
Copyright 2008-2010 Gephi
Authors : Eduardo Ramos <eduramiba@gmail.com>
Website : http://www.gephi.org
This file is part of Gephi.
Gephi is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
Gephi is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Gephi. If not, see <http://www.gnu.org/licenses/>.
*/
package org.gephi.datalab.impl;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import org.gephi.data.attributes.api.AttributeColumn;
import org.gephi.data.attributes.api.AttributeOrigin;
import org.gephi.data.attributes.api.AttributeTable;
import org.gephi.data.attributes.api.AttributeType;
import org.gephi.data.attributes.api.AttributeUtils;
import org.gephi.data.attributes.type.TimeInterval;
import org.gephi.datalab.api.AttributeColumnsMergeStrategiesController;
import org.gephi.datalab.api.AttributeColumnsController;
import org.gephi.dynamic.api.DynamicController;
import org.gephi.dynamic.api.DynamicModel;
import org.gephi.graph.api.Attributes;
import org.gephi.utils.StatisticsUtils;
import org.openide.util.Lookup;
import org.openide.util.lookup.ServiceProvider;
/**
* Implementation of the AttributeColumnsMergeStrategiesController interface
* declared in the Data Laboratory API.
* @author Eduardo Ramos <eduramiba@gmail.com>
* @see AttributeColumnsMergeStrategiesController
*/
@ServiceProvider(service = AttributeColumnsMergeStrategiesController.class)
public class AttributeColumnsMergeStrategiesControllerImpl implements AttributeColumnsMergeStrategiesController {
public AttributeColumn joinWithSeparatorMerge(AttributeTable table, AttributeColumn[] columnsToMerge, AttributeType newColumnType, String newColumnTitle, String separator) {
if (table == null || columnsToMerge == null) {
throw new IllegalArgumentException("Table or columns can't be null");
}
AttributeColumnsController ac = Lookup.getDefault().lookup(AttributeColumnsController.class);
AttributeColumn newColumn;
newColumn = ac.addAttributeColumn(table, newColumnTitle, newColumnType != null ? newColumnType : AttributeType.STRING);//Create as STRING column by default. Then it can be duplicated to other type.
if (newColumn == null) {
return null;
}
final int newColumnIndex = newColumn.getIndex();
if (separator == null) {
separator = "";
}
Object value;
StringBuilder sb;
final int columnsCount = columnsToMerge.length;
for (Attributes row : ac.getTableAttributeRows(table)) {
sb = new StringBuilder();
for (int i = 0; i < columnsCount; i++) {
value = row.getValue(columnsToMerge[i].getIndex());
if (value != null) {
sb.append(value.toString());
if (i < columnsCount - 1) {
sb.append(separator);
}
}
}
row.setValue(newColumnIndex, sb.toString());
}
return newColumn;
}
public AttributeColumn booleanLogicOperationsMerge(AttributeTable table, AttributeColumn[] columnsToMerge, BooleanOperations[] booleanOperations, String newColumnTitle) {
AttributeUtils attributeUtils = AttributeUtils.getDefault();
AttributeColumnsController ac = Lookup.getDefault().lookup(AttributeColumnsController.class);
if (table == null || columnsToMerge == null || !attributeUtils.areAllColumnsOfType(columnsToMerge, AttributeType.BOOLEAN) || booleanOperations == null || booleanOperations.length != columnsToMerge.length - 1) {
throw new IllegalArgumentException("All columns have to be boolean columns, table, columns or operations can't be null and operations length must be columns length -1");
}
AttributeColumn newColumn;
newColumn = ac.addAttributeColumn(table, newColumnTitle, AttributeType.BOOLEAN);
if (newColumn == null) {
return null;
}
final int newColumnIndex = newColumn.getIndex();
Boolean value;
Boolean secondValue;
for (Attributes row : ac.getTableAttributeRows(table)) {
value = (Boolean) row.getValue(columnsToMerge[0].getIndex());
value = value != null ? value : false;//Use false if null
for (int i = 0; i < booleanOperations.length; i++) {
secondValue = (Boolean) row.getValue(columnsToMerge[i + 1].getIndex());
secondValue = secondValue != null ? secondValue : false;//Use false if null
switch (booleanOperations[i]) {
case AND:
value = value && secondValue;
break;
case OR:
value = value || secondValue;
break;
case XOR:
value = value ^ secondValue;
break;
case NAND:
value = !(value && secondValue);
break;
case NOR:
value = !(value || secondValue);
break;
}
}
row.setValue(newColumnIndex, value);
}
return newColumn;
}
public AttributeColumn mergeNumericColumnsToTimeInterval(AttributeTable table, AttributeColumn startColumn, AttributeColumn endColumn, double defaultStart, double defaultEnd) {
checkTableAndOneColumn(table, startColumn, endColumn);
AttributeColumn timeIntervalColumn = getTimeIntervalColumn(table);
final int timeIntervalColumnIndex = timeIntervalColumn.getIndex();
final int startColumnIndex = startColumn != null ? startColumn.getIndex() : -1;
final int endColumnIndex = endColumn != null ? endColumn.getIndex() : -1;
final boolean isStartColumnNumeric = startColumn != null ? AttributeUtils.getDefault().isNumberColumn(startColumn) : false;
final boolean isEndColumnNumeric = endColumn != null ? AttributeUtils.getDefault().isNumberColumn(endColumn) : false;
AttributeColumnsController ac = Lookup.getDefault().lookup(AttributeColumnsController.class);
Object value;
double start, end;
TimeInterval timeInterval;
for (Attributes row : ac.getTableAttributeRows(table)) {
if (startColumnIndex != -1) {
value = row.getValue(startColumnIndex);
if (value != null) {
if (isStartColumnNumeric) {
start = ((Number) value).doubleValue();
} else {
start = parseDouble(value.toString(), defaultStart);
}
} else {
start = defaultStart;
}
} else {
start = defaultStart;
}
if (endColumnIndex != -1) {
value = row.getValue(endColumnIndex);
if (value != null) {
if (isEndColumnNumeric) {
end = ((Number) value).doubleValue();
} else {
end = parseDouble(value.toString(), defaultEnd);
}
} else {
end = defaultEnd;
}
} else {
end = defaultEnd;
}
if (!Double.isInfinite(start) && !Double.isInfinite(end) && start > end) {
//When start>end, check what column was provided and keep its value. If both columns were provided, set an infinite interval:
if (startColumnIndex == -1) {
start = Double.NEGATIVE_INFINITY;
} else if (endColumnIndex == -1) {
end = Double.POSITIVE_INFINITY;
} else {
start = Double.NEGATIVE_INFINITY;
end = Double.POSITIVE_INFINITY;
}
}
timeInterval = new TimeInterval(start, end);
row.setValue(timeIntervalColumnIndex, timeInterval);
}
Lookup.getDefault().lookup(DynamicController.class).setTimeFormat(DynamicModel.TimeFormat.DOUBLE);
return timeIntervalColumn;
}
public AttributeColumn mergeDateColumnsToTimeInterval(AttributeTable table, AttributeColumn startColumn, AttributeColumn endColumn, SimpleDateFormat dateFormat, String defaultStartDate, String defaultEndDate) {
checkTableAndOneColumn(table, startColumn, endColumn);
if (dateFormat == null) {
throw new IllegalArgumentException("Date format can't be null can't be null");
}
AttributeColumn timeIntervalColumn = getTimeIntervalColumn(table);
final int timeIntervalColumnIndex = timeIntervalColumn.getIndex();
final int startColumnIndex = startColumn != null ? startColumn.getIndex() : -1;
final int endColumnIndex = endColumn != null ? endColumn.getIndex() : -1;
double defaultStart = parseDateToDouble(dateFormat, defaultStartDate, Double.NEGATIVE_INFINITY);
double defaultEnd = parseDateToDouble(dateFormat, defaultEndDate, Double.POSITIVE_INFINITY);
AttributeColumnsController ac = Lookup.getDefault().lookup(AttributeColumnsController.class);
Object value;
double start, end;
TimeInterval timeInterval;
for (Attributes row : ac.getTableAttributeRows(table)) {
if (startColumnIndex != -1) {
value = row.getValue(startColumnIndex);
start = parseDateToDouble(dateFormat, value != null ? value.toString() : null, defaultStart);
} else {
start = defaultStart;
}
if (endColumnIndex != -1) {
value = row.getValue(endColumnIndex);
end = parseDateToDouble(dateFormat, value != null ? value.toString() : null, defaultEnd);
} else {
end = defaultEnd;
}
if (!Double.isInfinite(start) && !Double.isInfinite(end) && start > end) {
//When start>end, check what column was provided and keep its value. If both columns were provided, set an infinite interval:
if (startColumnIndex == -1) {
start = Double.NEGATIVE_INFINITY;
} else if (endColumnIndex == -1) {
end = Double.POSITIVE_INFINITY;
} else {
start = Double.NEGATIVE_INFINITY;
end = Double.POSITIVE_INFINITY;
}
}
timeInterval = new TimeInterval(start, end);
row.setValue(timeIntervalColumnIndex, timeInterval);
}
Lookup.getDefault().lookup(DynamicController.class).setTimeFormat(DynamicModel.TimeFormat.DATE);
return timeIntervalColumn;
}
public AttributeColumn averageNumberMerge(AttributeTable table, AttributeColumn[] columnsToMerge, String newColumnTitle) {
checkTableAndColumnsAreNumberOrNumberList(table, columnsToMerge);
AttributeColumnsController ac = Lookup.getDefault().lookup(AttributeColumnsController.class);
AttributeColumn newColumn;
newColumn = ac.addAttributeColumn(table, newColumnTitle, AttributeType.BIGDECIMAL);//Create as BIGDECIMAL column by default. Then it can be duplicated to other type.
if (newColumn == null) {
return null;
}
final int newColumnIndex = newColumn.getIndex();
BigDecimal average;
for (Attributes row : ac.getTableAttributeRows(table)) {
average = StatisticsUtils.average(ac.getRowNumbers(row, columnsToMerge));
row.setValue(newColumnIndex, average);
}
return newColumn;
}
public AttributeColumn firstQuartileNumberMerge(AttributeTable table, AttributeColumn[] columnsToMerge, String newColumnTitle) {
checkTableAndColumnsAreNumberOrNumberList(table, columnsToMerge);
AttributeColumnsController ac = Lookup.getDefault().lookup(AttributeColumnsController.class);
AttributeColumn newColumn;
newColumn = ac.addAttributeColumn(table, newColumnTitle, AttributeType.BIGDECIMAL);//Create as BIGDECIMAL column by default. Then it can be duplicated to other type.
if (newColumn == null) {
return null;
}
if (newColumn == null) {
return null;
}
final int newColumnIndex = newColumn.getIndex();
BigDecimal Q1;
for (Attributes row : ac.getTableAttributeRows(table)) {
Q1 = StatisticsUtils.quartile1(ac.getRowNumbers(row, columnsToMerge));
row.setValue(newColumnIndex, Q1);
}
return newColumn;
}
public AttributeColumn medianNumberMerge(AttributeTable table, AttributeColumn[] columnsToMerge, String newColumnTitle) {
checkTableAndColumnsAreNumberOrNumberList(table, columnsToMerge);
AttributeColumnsController ac = Lookup.getDefault().lookup(AttributeColumnsController.class);
AttributeColumn newColumn;
newColumn = ac.addAttributeColumn(table, newColumnTitle, AttributeType.BIGDECIMAL);//Create as BIGDECIMAL column by default. Then it can be duplicated to other type.
if (newColumn == null) {
return null;
}
final int newColumnIndex = newColumn.getIndex();
BigDecimal median;
for (Attributes row : ac.getTableAttributeRows(table)) {
median = StatisticsUtils.median(ac.getRowNumbers(row, columnsToMerge));
row.setValue(newColumnIndex, median);
}
return newColumn;
}
public AttributeColumn thirdQuartileNumberMerge(AttributeTable table, AttributeColumn[] columnsToMerge, String newColumnTitle) {
checkTableAndColumnsAreNumberOrNumberList(table, columnsToMerge);
AttributeColumnsController ac = Lookup.getDefault().lookup(AttributeColumnsController.class);
AttributeColumn newColumn;
newColumn = ac.addAttributeColumn(table, newColumnTitle, AttributeType.BIGDECIMAL);//Create as BIGDECIMAL column by default. Then it can be duplicated to other type.
if (newColumn == null) {
return null;
}
final int newColumnIndex = newColumn.getIndex();
BigDecimal Q3;
for (Attributes row : ac.getTableAttributeRows(table)) {
Q3 = StatisticsUtils.quartile3(ac.getRowNumbers(row, columnsToMerge));
row.setValue(newColumnIndex, Q3);
}
return newColumn;
}
public AttributeColumn interQuartileRangeNumberMerge(AttributeTable table, AttributeColumn[] columnsToMerge, String newColumnTitle) {
checkTableAndColumnsAreNumberOrNumberList(table, columnsToMerge);
AttributeColumnsController ac = Lookup.getDefault().lookup(AttributeColumnsController.class);
AttributeColumn newColumn;
newColumn = ac.addAttributeColumn(table, newColumnTitle, AttributeType.BIGDECIMAL);//Create as BIGDECIMAL column by default. Then it can be duplicated to other type.
if (newColumn == null) {
return null;
}
final int newColumnIndex = newColumn.getIndex();
BigDecimal IQR, Q1, Q3;
Number[] rowNumbers;
for (Attributes row : ac.getTableAttributeRows(table)) {
rowNumbers = ac.getRowNumbers(row, columnsToMerge);
Q3 = StatisticsUtils.quartile3(rowNumbers);
Q1 = StatisticsUtils.quartile1(rowNumbers);
if (Q3 != null && Q1 != null) {
IQR = Q3.subtract(Q1);
} else {
IQR = null;
}
row.setValue(newColumnIndex, IQR);
}
return newColumn;
}
public AttributeColumn sumNumbersMerge(AttributeTable table, AttributeColumn[] columnsToMerge, String newColumnTitle) {
checkTableAndColumnsAreNumberOrNumberList(table, columnsToMerge);
AttributeColumnsController ac = Lookup.getDefault().lookup(AttributeColumnsController.class);
AttributeColumn newColumn;
newColumn = ac.addAttributeColumn(table, newColumnTitle, AttributeType.BIGDECIMAL);//Create as BIGDECIMAL column by default. Then it can be duplicated to other type.
if (newColumn == null) {
return null;
}
final int newColumnIndex = newColumn.getIndex();
BigDecimal sum;
for (Attributes row : ac.getTableAttributeRows(table)) {
sum = StatisticsUtils.sum(ac.getRowNumbers(row, columnsToMerge));
row.setValue(newColumnIndex, sum);
}
return newColumn;
}
public AttributeColumn minValueNumbersMerge(AttributeTable table, AttributeColumn[] columnsToMerge, String newColumnTitle) {
checkTableAndColumnsAreNumberOrNumberList(table, columnsToMerge);
AttributeColumnsController ac = Lookup.getDefault().lookup(AttributeColumnsController.class);
AttributeColumn newColumn;
newColumn = ac.addAttributeColumn(table, newColumnTitle, AttributeType.BIGDECIMAL);//Create as BIGDECIMAL column by default. Then it can be duplicated to other type.
if (newColumn == null) {
return null;
}
final int newColumnIndex = newColumn.getIndex();
BigDecimal min;
for (Attributes row : ac.getTableAttributeRows(table)) {
min = StatisticsUtils.minValue(ac.getRowNumbers(row, columnsToMerge));
row.setValue(newColumnIndex, min);
}
return newColumn;
}
public AttributeColumn maxValueNumbersMerge(AttributeTable table, AttributeColumn[] columnsToMerge, String newColumnTitle) {
checkTableAndColumnsAreNumberOrNumberList(table, columnsToMerge);
AttributeColumnsController ac = Lookup.getDefault().lookup(AttributeColumnsController.class);
AttributeColumn newColumn;
newColumn = ac.addAttributeColumn(table, newColumnTitle, AttributeType.BIGDECIMAL);//Create as BIGDECIMAL column by default. Then it can be duplicated to other type.
if (newColumn == null) {
return null;
}
final int newColumnIndex = newColumn.getIndex();
BigDecimal max;
for (Attributes row : ac.getTableAttributeRows(table)) {
max = StatisticsUtils.maxValue(ac.getRowNumbers(row, columnsToMerge));
row.setValue(newColumnIndex, max);
}
return newColumn;
}
/*************Private methods:*************/
private AttributeColumn getTimeIntervalColumn(AttributeTable table) {
AttributeColumn column = table.getColumn(DynamicModel.TIMEINTERVAL_COLUMN);
if (column == null) {
column = table.addColumn(DynamicModel.TIMEINTERVAL_COLUMN, "Time Interval", AttributeType.TIME_INTERVAL, AttributeOrigin.PROPERTY, null);
}
return column;
}
private double parseDouble(String number, double defaultValue) {
if (number == null) {
return defaultValue;
}
try {
return Double.parseDouble(number);
} catch (Exception ex) {
return defaultValue;
}
}
private double parseDateToDouble(SimpleDateFormat dateFormat, String date, double defaultValue) {
if (date == null) {
return defaultValue;
}
try {
Date d = dateFormat.parse(date);
Calendar cal = Calendar.getInstance();
cal.setTime(d);
return cal.getTimeInMillis();
} catch (Exception ex) {
return defaultValue;
}
}
private void checkTableAndOneColumn(AttributeTable table, AttributeColumn startColumn, AttributeColumn endColumn) {
if (table == null) {
throw new IllegalArgumentException("Table can't be null");
}
if (startColumn == null && endColumn == null) {
throw new IllegalArgumentException("Only one column could be null");
}
}
private void checkTableAndColumnsAreNumberOrNumberList(AttributeTable table, AttributeColumn[] columns) {
if (table == null) {
throw new IllegalArgumentException("Table can't be null");
}
checkColumnsAreNumberOrNumberList(columns);
}
private void checkColumnsAreNumberOrNumberList(AttributeColumn[] columns) {
if (columns == null || !AttributeUtils.getDefault().areAllNumberOrNumberListColumns(columns)) {
throw new IllegalArgumentException("All columns have to be number or number list columns and can't be null");
}
}
}