/* * ARX: Powerful Data Anonymization * Copyright 2012 - 2017 Fabian Prasser, Florian Kohlmayer and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.deidentifier.arx.gui.view.impl.risk; import java.util.Arrays; import org.deidentifier.arx.ARXPopulationModel; import org.deidentifier.arx.gui.Controller; import org.deidentifier.arx.gui.model.ModelEvent; import org.deidentifier.arx.gui.model.ModelEvent.ModelPart; import org.deidentifier.arx.gui.model.ModelRisk.ViewRiskType; import org.deidentifier.arx.gui.resources.Resources; import org.deidentifier.arx.gui.view.SWTUtil; import org.deidentifier.arx.gui.view.impl.common.ComponentStatusLabelProgressProvider; import org.deidentifier.arx.gui.view.impl.common.async.Analysis; import org.deidentifier.arx.gui.view.impl.common.async.AnalysisContext; import org.deidentifier.arx.gui.view.impl.common.async.AnalysisManager; import org.deidentifier.arx.risk.RiskEstimateBuilderInterruptible; import org.deidentifier.arx.risk.RiskModelHistogram; import org.deidentifier.arx.risk.RiskModelPopulationUniqueness; import org.eclipse.nebula.widgets.nattable.util.GUIHelper; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.swtchart.Chart; import org.swtchart.IAxis; import org.swtchart.IAxisSet; import org.swtchart.ILineSeries; import org.swtchart.ILineSeries.PlotSymbolType; import org.swtchart.ISeries; import org.swtchart.ISeries.SeriesType; import org.swtchart.ISeriesSet; import org.swtchart.ITitle; import org.swtchart.Range; /** * This view displays basic risk estimates. * * @author Fabian Prasser */ public class ViewRisksPopulationUniques extends ViewRisks<AnalysisContextRisk> { /** Minimal width of a category label. */ private static final int MIN_CATEGORY_WIDTH = 10; /** Labels for the plot. */ private static final double[] POINTS = getPoints(); /** Labels for the plot. */ private static final String[] LABELS = getLabels(POINTS); /** * Creates a set of labels * @param points * @return */ private static String[] getLabels(double[] points) { String[] result = new String[points.length]; for (int i = 0; i < points.length; i++) { result[i] = SWTUtil.getPrettyString(points[i]*100d); } return result; } /** * Creates an array of points * @return */ private static double[] getPoints() { return new double[]{0.0000001d, 0.000001d, 0.00001d, 0.0001d, 0.001d, 0.01d, 0.1d, 0.2d, 0.3d, 0.4d, 0.5d, 0.6d, 0.7d, 0.8d, 0.9d}; } /** View */ private Chart chart; /** View */ private Composite root; /** Internal stuff. */ private AnalysisManager manager; /** * Creates a new instance. * * @param parent * @param controller * @param target * @param reset */ public ViewRisksPopulationUniques(final Composite parent, final Controller controller, final ModelPart target, final ModelPart reset) { super(parent, controller, target, reset); this.manager = new AnalysisManager(parent.getDisplay()); controller.addListener(ModelPart.ATTRIBUTE_TYPE, this); controller.addListener(ModelPart.POPULATION_MODEL, this); } @Override public void update(ModelEvent event) { super.update(event); if (event.part == ModelPart.ATTRIBUTE_TYPE || event.part == ModelPart.POPULATION_MODEL) { triggerUpdate(); } } /** * Creates a series * @param seriesSet * @param data * @param label * @param symbol * @param color */ private void createSeries(ISeriesSet seriesSet, double[] data, String label, PlotSymbolType symbol, Color color) { ILineSeries series = (ILineSeries) seriesSet.createSeries(SeriesType.LINE, label); //$NON-NLS-1$ series.setAntialias(SWT.ON); series.getLabel().setVisible(false); series.getLabel().setFont(chart.getFont()); series.setYSeries(data); series.setSymbolType(symbol); series.setSymbolColor(color); series.setLineColor(color); series.setXAxisId(0); series.setYAxisId(0); } /** * Convert to percentage * @param data */ private void makePercentage(double[] data) { for (int i = 0; i < data.length; i++) { data[i] = data[i] * 100d; } } /** * Resets the chart */ private void resetChart() { if (chart != null) { chart.dispose(); } chart = new Chart(root, SWT.NONE); chart.setOrientation(SWT.HORIZONTAL); // Show/Hide axis chart.addControlListener(new ControlAdapter(){ @Override public void controlResized(ControlEvent arg0) { updateCategories(); } }); // Update font FontData[] fd = chart.getFont().getFontData(); fd[0].setHeight(8); final Font font = new Font(chart.getDisplay(), fd[0]); chart.setFont(font); chart.addDisposeListener(new DisposeListener(){ public void widgetDisposed(DisposeEvent arg0) { if (font != null && !font.isDisposed()) { font.dispose(); } } }); // Update title ITitle graphTitle = chart.getTitle(); graphTitle.setText(""); //$NON-NLS-1$ graphTitle.setFont(chart.getFont()); // Set colors chart.setBackground(root.getBackground()); chart.setForeground(root.getForeground()); // OSX workaround if (System.getProperty("os.name").toLowerCase().contains("mac")){ //$NON-NLS-1$ //$NON-NLS-2$ int r = chart.getBackground().getRed()-13; int g = chart.getBackground().getGreen()-13; int b = chart.getBackground().getBlue()-13; r = r>0 ? r : 0; r = g>0 ? g : 0; r = b>0 ? b : 0; final Color background = new Color(chart.getDisplay(), r, g, b); chart.setBackground(background); chart.addDisposeListener(new DisposeListener(){ public void widgetDisposed(DisposeEvent arg0) { if (background != null && !background.isDisposed()) { background.dispose(); } } }); } // Initialize axes IAxisSet axisSet = chart.getAxisSet(); IAxis yAxis = axisSet.getYAxis(0); IAxis xAxis = axisSet.getXAxis(0); ITitle xAxisTitle = xAxis.getTitle(); xAxisTitle.setText(""); //$NON-NLS-1$ xAxis.getTitle().setFont(chart.getFont()); yAxis.getTitle().setFont(chart.getFont()); xAxis.getTick().setFont(chart.getFont()); yAxis.getTick().setFont(chart.getFont()); xAxis.getTick().setForeground(chart.getForeground()); yAxis.getTick().setForeground(chart.getForeground()); xAxis.getTitle().setForeground(chart.getForeground()); yAxis.getTitle().setForeground(chart.getForeground()); // Initialize y-axis ITitle yAxisTitle = yAxis.getTitle(); yAxisTitle.setText(Resources.getMessage("ViewRisksPlotUniquenessEstimates.0")); //$NON-NLS-1$ xAxisTitle.setText(Resources.getMessage("ViewRisksPlotUniquenessEstimates.1")); //$NON-NLS-1$ chart.setEnabled(false); updateCategories(); } /** * Makes the chart show category labels or not. */ private void updateCategories(){ if (chart != null){ IAxisSet axisSet = chart.getAxisSet(); if (axisSet != null) { IAxis xAxis = axisSet.getXAxis(0); if (xAxis != null) { String[] series = xAxis.getCategorySeries(); if (series != null) { boolean enoughSpace = chart.getPlotArea().getSize().x / series.length >= MIN_CATEGORY_WIDTH; xAxis.enableCategory(enoughSpace); xAxis.getTick().setVisible(enoughSpace); } } } } } @Override protected Control createControl(Composite parent) { this.root = new Composite(parent, SWT.NONE); this.root.setLayout(new FillLayout()); // Tool tip root.addListener(SWT.MouseMove, new Listener() { @Override public void handleEvent(Event event) { if (chart != null) { IAxisSet axisSet = chart.getAxisSet(); if (axisSet != null) { IAxis xAxis = axisSet.getXAxis(0); if (xAxis != null) { Point cursor = chart.getPlotArea().toControl(Display.getCurrent().getCursorLocation()); if (cursor.x >= 0 && cursor.x < chart.getPlotArea().getSize().x && cursor.y >= 0 && cursor.y < chart.getPlotArea().getSize().y) { String[] series = xAxis.getCategorySeries(); ISeries[] data = chart.getSeriesSet().getSeries(); if (data != null && data.length>0 && series != null) { int x = (int) Math.round(xAxis.getDataCoordinate(cursor.x)); if (x >= 0 && x < series.length) { root.setToolTipText("(Sampling fraction: "+series[x]+"%, Dankar: "+SWTUtil.getPrettyString(data[3].getYSeries()[x]) //$NON-NLS-1$ //$NON-NLS-2$ +"%, Pitman: "+SWTUtil.getPrettyString(data[2].getYSeries()[x]) //$NON-NLS-1$ +"%, Zayatz: "+SWTUtil.getPrettyString(data[1].getYSeries()[x]) //$NON-NLS-1$ +"%, SNB: "+SWTUtil.getPrettyString(data[0].getYSeries()[x]) //$NON-NLS-1$ +"%)"); //$NON-NLS-1$ return; } } } } } root.setToolTipText(null); } } }); return this.root; } @Override protected AnalysisContextRisk createViewConfig(AnalysisContext context) { return new AnalysisContextRisk(context); } @Override protected void doReset() { if (this.manager != null) { this.manager.stop(); } resetChart(); setStatusEmpty(); } @Override protected void doUpdate(final AnalysisContextRisk context) { // The statistics builder final RiskEstimateBuilderInterruptible baseBuilder = getBuilder(context); final int sampleSize = context.model.getInputConfig().getInput().getHandle().getNumRows(); // Enable/disable if (!this.isEnabled() || baseBuilder == null) { if (manager != null) { manager.stop(); } this.setStatusEmpty(); return; } // Create an analysis Analysis analysis = new Analysis() { private RiskEstimateBuilderInterruptible builder = baseBuilder; private boolean stopped = false; private double[] dataPitman; private double[] dataZayatz; private double[] dataSNB; private double[] dataDankar; private int idx; @Override public int getProgress() { return (int)Math.round(idx * 100d + (double)baseBuilder.getProgress()) / POINTS.length; } @Override public void onError() { setStatusEmpty(); } @Override public void onFinish() { if (stopped || !isEnabled()) { return; } // Update chart resetChart(); ISeriesSet seriesSet = chart.getSeriesSet(); createSeries(seriesSet, dataPitman, "Pitman", PlotSymbolType.CIRCLE, GUIHelper.COLOR_BLACK); //$NON-NLS-1$ createSeries(seriesSet, dataZayatz, "Zayatz", PlotSymbolType.CROSS, GUIHelper.COLOR_BLUE); //$NON-NLS-1$ createSeries(seriesSet, dataSNB, "SNB", PlotSymbolType.DIAMOND, GUIHelper.COLOR_RED); //$NON-NLS-1$ createSeries(seriesSet, dataDankar, "Dankar", PlotSymbolType.SQUARE, GUIHelper.COLOR_DARK_GRAY); //$NON-NLS-1$ chart.getLegend().setVisible(true); seriesSet.bringToFront("SNB"); //$NON-NLS-1$ seriesSet.bringToFront("Zayatz"); //$NON-NLS-1$ seriesSet.bringToFront("Pitman"); //$NON-NLS-1$ seriesSet.bringToFront("Dankar"); //$NON-NLS-1$ IAxisSet axisSet = chart.getAxisSet(); IAxis yAxis = axisSet.getYAxis(0); yAxis.setRange(new Range(0d, 100d)); IAxis xAxis = axisSet.getXAxis(0); xAxis.setRange(new Range(0d, LABELS.length)); xAxis.setCategorySeries(LABELS); chart.updateLayout(); chart.update(); updateCategories(); chart.layout(); chart.setRedraw(true); chart.redraw(); setStatusDone(); } @Override public void onInterrupt() { if (!isEnabled() || !isValid()) { setStatusEmpty(); } else { setStatusWorking(); } } @Override public void run() throws InterruptedException { // Timestamp long time = System.currentTimeMillis(); // Perform work dataDankar = new double[POINTS.length]; dataPitman = new double[POINTS.length]; dataZayatz = new double[POINTS.length]; dataSNB = new double[POINTS.length]; for (idx = 0; idx < POINTS.length; idx++) { if (stopped) { throw new InterruptedException(); } RiskModelHistogram histogram = builder.getEquivalenceClassModel(); ARXPopulationModel population = ARXPopulationModel.create(sampleSize, POINTS[idx]); builder = getBuilder(context, population, histogram); if (idx == 0 && builder.getSampleBasedUniquenessRisk().getFractionOfUniqueTuples() == 0.0d) { Arrays.fill(dataDankar, 0.0d); Arrays.fill(dataPitman, 0.0d); Arrays.fill(dataZayatz, 0.0d); Arrays.fill(dataSNB, 0.0d); break; } RiskModelPopulationUniqueness populationBasedModel = builder.getPopulationBasedUniquenessRisk(); dataDankar[idx] = populationBasedModel.getFractionOfUniqueTuplesDankar(); dataPitman[idx] = populationBasedModel.getFractionOfUniqueTuplesPitman(); dataZayatz[idx] = populationBasedModel.getFractionOfUniqueTuplesZayatz(); dataSNB[idx] = populationBasedModel.getFractionOfUniqueTuplesSNB(); } makePercentage(dataDankar); makePercentage(dataPitman); makePercentage(dataZayatz); makePercentage(dataSNB); // Our users are patient while (System.currentTimeMillis() - time < MINIMAL_WORKING_TIME && !stopped){ Thread.sleep(10); } } @Override public void stop() { if (baseBuilder != null) baseBuilder.interrupt(); this.stopped = true; } }; this.manager.start(analysis); } @Override protected ComponentStatusLabelProgressProvider getProgressProvider() { return new ComponentStatusLabelProgressProvider(){ public int getProgress() { if (manager == null) { return 0; } else { return manager.getProgress(); } } }; } @Override protected ViewRiskType getViewType() { return ViewRiskType.UNIQUES_ALL; } /** * Is an analysis running */ protected boolean isRunning() { return manager != null && manager.isRunning(); } }