/*
* This file is part of ADDIS (Aggregate Data Drug Information System).
* ADDIS is distributed from http://drugis.org/.
* Copyright © 2009 Gert van Valkenhoef, Tommi Tervonen.
* Copyright © 2010 Gert van Valkenhoef, Tommi Tervonen, Tijs Zwinkels,
* Maarten Jacobs, Hanno Koeslag, Florin Schimbinschi, Ahmad Kamal, Daniel
* Reid.
* Copyright © 2011 Gert van Valkenhoef, Ahmad Kamal, Daniel Reid, Florin
* Schimbinschi.
* Copyright © 2012 Gert van Valkenhoef, Daniel Reid, Joël Kuiper, Wouter
* Reckman.
* Copyright © 2013 Gert van Valkenhoef, Joël Kuiper.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.drugis.addis.presentation;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import org.drugis.addis.entities.Measurement;
import org.drugis.addis.entities.OutcomeMeasure;
import org.drugis.addis.entities.OutcomeMeasure.Direction;
import org.drugis.addis.entities.Study;
import org.drugis.addis.entities.relativeeffect.AxisType;
import org.drugis.addis.entities.relativeeffect.BasicRelativeEffect;
import org.drugis.addis.entities.relativeeffect.ConfidenceInterval;
import org.drugis.addis.entities.relativeeffect.RandomEffectMetaAnalysisRelativeEffect;
import org.drugis.addis.entities.relativeeffect.RelativeEffect;
import org.drugis.addis.forestplot.BinnedScale;
import org.drugis.addis.forestplot.ForestPlot;
import org.drugis.addis.forestplot.IdentityScale;
import org.drugis.addis.forestplot.LinearScale;
import org.drugis.addis.forestplot.LogScale;
import org.drugis.addis.forestplot.Scale;
import org.drugis.common.Interval;
public abstract class AbstractForestPlotPresentation implements ForestPlotPresentation {
private OutcomeMeasure d_outcomeMeasure;
private List<Study> d_studies;
private List<BasicRelativeEffect<?>> d_effects;
private RandomEffectMetaAnalysisRelativeEffect<Measurement> d_pooled;
private BinnedScale d_scale;
private double d_max = 0.0;
private AxisType d_scaleType;
protected AbstractForestPlotPresentation(
OutcomeMeasure om, List<Study> studies,
List<BasicRelativeEffect<?>> effects,
RandomEffectMetaAnalysisRelativeEffect<Measurement> pooled) {
d_outcomeMeasure = om;
d_studies = studies;
d_effects = effects;
d_pooled = pooled;
initScales();
}
protected static Interval<Double> getRange(List<ConfidenceInterval> cis, AxisType scaleType) {
double min = Double.MAX_VALUE;
double max = Double.MIN_VALUE;
for (ConfidenceInterval ci : cis) {
min = Math.min((double) ci.getLowerBound(), min);
max = Math.max((double) ci.getUpperBound(), max);
}
if (Double.isNaN(min) || Double.isNaN(max)) {
if (scaleType.equals(AxisType.LOGARITHMIC)) {
return new Interval<Double>(0.5, 2.0);
} else {
return new Interval<Double>(-1.0, 1.0);
}
}
Interval<Double> interval = new Interval<Double>(min, max);
if (scaleType == AxisType.LINEAR)
return niceIntervalLinear(interval);
if (scaleType == AxisType.LOGARITHMIC)
return niceIntervalLog(interval);
return interval;
}
protected void initScales() {
for (int i = 0; i < getNumRelativeEffects(); ++i) {
if (!isPooledRelativeEffect(i)) {
d_max = Math.max(((BasicRelativeEffect<?>)getRelativeEffectAt(i)).getSampleSize(), d_max);
}
}
if (getRelativeEffectAt(0).getAxisType() == AxisType.LINEAR) {
d_scaleType = AxisType.LINEAR;
d_scale = new BinnedScale(new LinearScale(getRange()), 1, ForestPlot.BARWIDTH);
}
if (getRelativeEffectAt(0).getAxisType() == AxisType.LOGARITHMIC) {
d_scaleType = AxisType.LOGARITHMIC;
d_scale = new BinnedScale(new LogScale(getRange()), 1, ForestPlot.BARWIDTH);
}
}
@Override
public int getNumRelativeEffects() {
return d_effects.size() + (d_pooled != null ? 1 : 0);
}
@Override
public RelativeEffect<?> getRelativeEffectAt(int i) {
if (i < d_studies.size()) {
return d_effects.get(i);
} else if (i == d_studies.size() && d_pooled != null) {
return d_pooled;
}
throw new IndexOutOfBoundsException();
}
@Override
public BinnedScale getScale() {
return d_scale;
}
@Override
public AxisType getScaleType() {
return d_scaleType;
}
@Override
public Interval<Double> getRange() {
List<ConfidenceInterval> cis = new ArrayList<ConfidenceInterval>();
for (int i = 0; i < getNumRelativeEffects(); ++i) {
cis.add(getRelativeEffectAt(i).getConfidenceInterval());
}
return getRange(cis, d_scaleType);
}
public static Interval<Double> niceIntervalLog(Interval<Double> interval) {
return niceIntervalLog(interval.getLowerBound(), interval.getUpperBound());
}
@Override
public String getStudyLabelAt(int i) {
return isPooledRelativeEffect(i) ? "Combined" : d_studies.get(i).toString();
}
public static Interval<Double> niceIntervalLog(double min, double max) {
double lowersign = Math.floor(anylog(min, 2));
double uppersign = Math.ceil(anylog(max, 2));
double minM = Math.pow(2,lowersign);
double maxM = Math.pow(2, uppersign);
return new Interval<Double>(Math.min(0.5, minM), Math.max(2, maxM));
}
public static Interval<Double> niceIntervalLinear(Interval<Double> interval) {
return niceIntervalLinear(interval.getLowerBound(), interval.getUpperBound());
}
public static Interval<Double> niceIntervalLinear(double min, double max) {
int sign = getSignificanceLevel(min, max);
double minM = Math.floor(min / Math.pow(10, sign)) * Math.pow(10, sign);
double maxM = Math.ceil(max / Math.pow(10, sign)) * Math.pow(10, sign);
double smallest = Math.pow(10, sign);
return new Interval<Double>(Math.min(-smallest, minM), Math.max(smallest, maxM));
}
private static int getSignificanceLevel(double min, double max) {
int signMax = (int) Math.floor(Math.log10(Math.abs(max)));
int signMin = (int) Math.floor(Math.log10(Math.abs(min)));
int sign = Math.max(signMax, signMin);
return sign;
}
private static double anylog(double x, double base) {
return Math.log(x) / Math.log(base);
}
public static List<Integer> getTicks(BinnedScale scale, Scale toRender) {
ArrayList<Integer> tickList = new ArrayList<Integer>();
tickList.add(scale.getBin(toRender.getMin()).bin);
tickList.add(scale.getBin(toRender instanceof LogScale ? 1 : 0).bin);
tickList.add(scale.getBin(toRender.getMax()).bin);
return tickList;
}
@Override
public String getCIlabelAt(int i) {
return new RelativeEffectPresentation(getRelativeEffectAt(i)).toString();
}
@Override
public List<Integer> getTicks() {
return getTicks(getScale(), getScale().getScale());
}
public static List<String> getTickVals(BinnedScale scale, Scale toRender) {
ArrayList<String> tickVals = new ArrayList<String>();
DecimalFormat df = new DecimalFormat("####.####");
tickVals.add(df.format(toRender.getMin()));
tickVals.add(toRender instanceof LogScale ? df.format(1D) : df.format(0D));
tickVals.add(df.format(toRender.getMax()));
return tickVals;
}
@Override
public List<String> getTickVals() {
return getTickVals(getScale(), getScale().getScale());
}
private double getWeightAt(int index) {
return (double) (((BasicRelativeEffect<?>)getRelativeEffectAt(index)).getSampleSize()) / d_max;
}
@Override
public int getDiamondSize(int index) {
BinnedScale tempbin = new BinnedScale(new IdentityScale(), 1, 10);
return isPooledRelativeEffect(index) ? 8 : tempbin.getBin(getWeightAt(index)).bin * 2 + 1;
}
@Override
public OutcomeMeasure getOutcomeMeasure() {
return d_outcomeMeasure;
}
@Override
public boolean isPooledRelativeEffect(int i) {
return i >= d_studies.size();
}
@Override
public String getHeterogeneityI2() {
DecimalFormat df = new DecimalFormat("##0.0");
final double x = d_pooled.getHeterogeneityI2();
return Double.isNaN(x) ? "N/A" : (df.format(x) + "%");
}
@Override
public String getHeterogeneity() {
DecimalFormat df = new DecimalFormat("##0.00");
return df.format(d_pooled.getHeterogeneity());
}
@Override
public boolean hasPooledRelativeEffect() {
return d_pooled != null;
}
protected abstract String getSubjectLabel();
protected abstract String getBaselineLabel();
@Override
public String getLowValueFavors() {
return (getOutcomeMeasure().getDirection().equals(Direction.HIGHER_IS_BETTER) ? getBaselineLabel() : getSubjectLabel());
}
@Override
public String getHighValueFavors() {
return (getOutcomeMeasure().getDirection().equals(Direction.HIGHER_IS_BETTER) ? getSubjectLabel() : getBaselineLabel());
}
}