def __init__(self): super().__init__() self.data = None self.input_features = None self.attrs = [] self.attr_box = gui.hBox(self.mainArea) model = VariableListModel() model.wrap(self.attrs) self.attrXCombo = gui.comboBox( self.attr_box, self, value="attrX", contentsLength=12, callback=self.change_attr, sendSelectedValue=True, valueType=str) self.attrXCombo.setModel(model) gui.widgetLabel(self.attr_box, "\u2715").\ setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.attrYCombo = gui.comboBox( self.attr_box, self, value="attrY", contentsLength=12, callback=self.change_attr, sendSelectedValue=True, valueType=str) self.attrYCombo.setModel(model) self.canvas = QGraphicsScene() self.canvasView = ViewWithPress(self.canvas, self.mainArea, handler=self.reset_selection) self.mainArea.layout().addWidget(self.canvasView) self.canvasView.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.canvasView.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) box = gui.hBox(self.mainArea) box.layout().addWidget(self.graphButton) box.layout().addWidget(self.report_button)
def __init__(self): # pylint: disable=missing-docstring super().__init__() self.data = self.discrete_data = None self.attrs = [] self.input_features = None self.areas = [] self.selection = set() self.attr_box = gui.hBox(self.mainArea) model = VariableListModel() model.wrap(self.attrs) combo_args = dict( widget=self.attr_box, master=self, contentsLength=12, callback=self.update_attr, sendSelectedValue=True, valueType=str, model=model) fixed_size = (QSizePolicy.Fixed, QSizePolicy.Fixed) self.attrXCombo = gui.comboBox(value="attrX", **combo_args) gui.widgetLabel(self.attr_box, "\u2715", sizePolicy=fixed_size) self.attrYCombo = gui.comboBox(value="attrY", **combo_args) self.vizrank, self.vizrank_button = SieveRank.add_vizrank( self.attr_box, self, "Score Combinations", self.set_attr) self.vizrank_button.setSizePolicy(*fixed_size) self.canvas = QGraphicsScene() self.canvasView = ViewWithPress( self.canvas, self.mainArea, handler=self.reset_selection) self.mainArea.layout().addWidget(self.canvasView) self.canvasView.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.canvasView.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) box = gui.hBox(self.mainArea) box.layout().addWidget(self.graphButton) box.layout().addWidget(self.report_button)
def __init__(self): super().__init__() self.data = None self.input_features = None self.attrs = [] self.attr_box = gui.hBox(self.mainArea) model = VariableListModel() model.wrap(self.attrs) self.attrXCombo = gui.comboBox(self.attr_box, self, value="attrX", contentsLength=12, callback=self.change_attr, sendSelectedValue=True, valueType=str) self.attrXCombo.setModel(model) gui.widgetLabel(self.attr_box, "\u2715").\ setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.attrYCombo = gui.comboBox(self.attr_box, self, value="attrY", contentsLength=12, callback=self.change_attr, sendSelectedValue=True, valueType=str) self.attrYCombo.setModel(model) self.canvas = QGraphicsScene() self.canvasView = ViewWithPress(self.canvas, self.mainArea, handler=self.reset_selection) self.mainArea.layout().addWidget(self.canvasView) self.canvasView.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.canvasView.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) box = gui.hBox(self.mainArea) gui.button(box, None, "&Save Graph", callback=self.save_graph, autoDefault=False) gui.button(box, None, "&Report", callback=self.show_report, autoDefault=False)
def __init__(self): # pylint: disable=missing-docstring super().__init__() self.data = self.discrete_data = None self.attrs = [] self.input_features = None self.areas = [] self.selection = set() self.attr_box = gui.hBox(self.mainArea) model = VariableListModel() model.wrap(self.attrs) combo_args = dict(widget=self.attr_box, master=self, contentsLength=12, callback=self.update_attr, sendSelectedValue=True, valueType=str, model=model) fixed_size = (QSizePolicy.Fixed, QSizePolicy.Fixed) self.attrXCombo = gui.comboBox(value="attrX", **combo_args) gui.widgetLabel(self.attr_box, "\u2715", sizePolicy=fixed_size) self.attrYCombo = gui.comboBox(value="attrY", **combo_args) self.vizrank = SieveRank(self) self.vizrank_button = gui.button(self.attr_box, self, "Score Combinations", sizePolicy=fixed_size, callback=self.vizrank.reshow, enabled=False) self.vizrank.pairSelected.connect(self.set_attr) self.canvas = QGraphicsScene() self.canvasView = ViewWithPress(self.canvas, self.mainArea, handler=self.reset_selection) self.mainArea.layout().addWidget(self.canvasView) self.canvasView.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.canvasView.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) box = gui.hBox(self.mainArea) box.layout().addWidget(self.graphButton) box.layout().addWidget(self.report_button)
class OWGeoMap(widget.OWWidget): name = "GeoMap" priority = 20000 icon = "icons/GeoMap.svg" inputs = [("Data", Table, "on_data")] outputs = [('Corpus', Corpus)] want_main_area = False selected_attr = settings.Setting('') selected_map = settings.Setting(0) regions = settings.Setting([]) def __init__(self): super().__init__() self.data = None self._create_layout() @pyqtSlot(str) def region_selected(self, regions): """Called from JavaScript""" if not regions: self.regions = [] if not regions or self.data is None: return self.send('Corpus', None) self.regions = regions.split(',') attr = self.data.domain[self.selected_attr] if attr.is_discrete: return # TODO, FIXME: make this work for discrete attrs also from Orange.data.filter import FilterRegex filter = FilterRegex(attr, r'\b{}\b'.format(r'\b|\b'.join(self.regions)), re.IGNORECASE) self.send('Corpus', self.data._filter_values(filter)) def _create_layout(self): box = gui.widgetBox(self.controlArea, orientation='horizontal') self.varmodel = VariableListModel(parent=self) self.attr_combo = gui.comboBox(box, self, 'selected_attr', orientation=Qt.Horizontal, label='Region attribute:', callback=self.on_attr_change, sendSelectedValue=True) self.attr_combo.setModel(self.varmodel) self.map_combo = gui.comboBox(box, self, 'selected_map', orientation=Qt.Horizontal, label='Map type:', callback=self.on_map_change, items=Map.all) hexpand = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.attr_combo.setSizePolicy(hexpand) self.map_combo.setSizePolicy(hexpand) url = urljoin('file:', pathname2url(os.path.join( os.path.dirname(__file__), 'resources', 'owgeomap.html'))) self.webview = gui.WebviewWidget(self.controlArea, self, url=QUrl(url)) self.controlArea.layout().addWidget(self.webview) QTimer.singleShot( 0, lambda: self.webview.evalJS('REGIONS = {};'.format({Map.WORLD: CC_WORLD, Map.EUROPE: CC_EUROPE, Map.USA: CC_USA}))) def _repopulate_attr_combo(self, data): vars = [a for a in chain(data.domain.metas, data.domain.attributes, data.domain.class_vars) if a.is_string] if data else [] self.varmodel.wrap(vars) # Select default attribute self.selected_attr = next((var.name for var in vars if var.name.lower().startswith(('country', 'location', 'region'))), vars[0].name if vars else '') def on_data(self, data): if data and not isinstance(data, Corpus): data = Corpus.from_table(data.domain, data) self.data = data self._repopulate_attr_combo(data) if not data: self.region_selected('') QTimer.singleShot(0, lambda: self.webview.evalJS('DATA = {}; renderMap();')) else: QTimer.singleShot(0, self.on_attr_change) def on_map_change(self, map_code=''): if map_code: self.map_combo.setCurrentIndex(self.map_combo.findData(map_code)) else: map_code = self.map_combo.itemData(self.selected_map) inv_cc_map, cc_map = {Map.USA: (INV_CC_USA, CC_USA), Map.WORLD: (INV_CC_WORLD, CC_WORLD), Map.EUROPE: (INV_CC_EUROPE, CC_EUROPE)}[map_code] # Set country counts for JS data = defaultdict(int) for locations in self._iter_locations(): keys = set(inv_cc_map.get(loc, loc) for loc in locations) for key in keys: if key in cc_map: data[key] += 1 # Draw the new map self.webview.evalJS('DATA = {};' 'MAP_CODE = "{}";' 'SELECTED_REGIONS = {};' 'renderMap();'.format(dict(data), map_code, self.regions)) def on_attr_change(self): if not self.selected_attr: return values = set(chain.from_iterable(self._iter_locations())) # Auto-select region map if 0 == len(values - SET_CC_USA): map_code = Map.USA elif 0 == len(values - SET_CC_EUROPE): map_code = Map.EUROPE else: map_code = Map.WORLD self.on_map_change(map_code) def _iter_locations(self): """ Iterator that yields an iterable per documents with all its's locations. """ attr = self.data.domain[self.selected_attr] for i in self.data.get_column_view(self.data.domain.index(attr))[0]: if len(i) > 3: yield map(lambda x: x.strip(), CC_NAMES.findall(i.lower())) else: yield (i, )
class OWSpiralogram(widget.OWWidget): name = 'Spiralogram' description = "Visualize time series' periodicity in a spiral heatmap." icon = 'icons/Spiralogram.svg' priority = 120 class Inputs: time_series = Input("Time series", Table) class Outputs: time_series = Output("Time series", Timeseries) settingsHandler = settings.DomainContextHandler() ax1 = settings.ContextSetting('months of year') ax2 = settings.ContextSetting('years') agg_attr = settings.ContextSetting([]) agg_func = settings.ContextSetting(0) invert_date_order = settings.Setting(False) graph_name = 'chart' class Error(widget.OWWidget.Error): no_time_variable = widget.Msg( 'Spiralogram requires time series with a time variable.') def __init__(self): self.data = None self.indices = [] box = gui.vBox(self.controlArea, 'Axes') self.combo_ax2_model = VariableListModel(parent=self) self.combo_ax1_model = VariableListModel(parent=self) for model in (self.combo_ax1_model, self.combo_ax2_model): model[:] = [_enum_str(i) for i in Spiralogram.AxesCategories] self.combo_ax2 = gui.comboBox(box, self, 'ax2', label='Y axis:', callback=self.replot, sendSelectedValue=True, orientation='horizontal', model=self.combo_ax2_model) self.combo_ax1 = gui.comboBox(box, self, 'ax1', label='Radial:', callback=self.replot, sendSelectedValue=True, orientation='horizontal', model=self.combo_ax1_model) gui.checkBox(box, self, 'invert_date_order', 'Invert Y axis order', callback=self.replot) box = gui.vBox(self.controlArea, 'Aggregation') self.combo_func = gui.comboBox(box, self, 'agg_func', label='Function:', orientation='horizontal', callback=self.replot) func_model = ListModel(AGG_FUNCTIONS, parent=self) self.combo_func.setModel(func_model) self.attrlist_model = VariableListModel(parent=self) self.attrlist = QListView(selectionMode=QListView.SingleSelection) self.attrlist.setModel(self.attrlist_model) self.attrlist.selectionModel().selectionChanged.connect( self.attrlist_selectionChanged) box.layout().addWidget(self.attrlist) gui.rubber(self.controlArea) self.chart = chart = Spiralogram(self, selection_callback=self.on_selection) self.mainArea.layout().addWidget(chart) def attrlist_selectionChanged(self): self.agg_attr = [ self.attrlist_model[i.row()] for i in self.attrlist.selectionModel().selectedIndexes() ] self.replot() @Inputs.time_series def set_data(self, data): self.Error.clear() self.data = data = None if data is None else Timeseries.from_data_table( data) if data is None: self.commit() return if self.data.time_variable is None or not isinstance( self.data.time_variable, TimeVariable): self.Error.no_time_variable() self.commit() return def init_combos(): for model in (self.combo_ax1_model, self.combo_ax2_model): model.clear() newmodel = [] if data is not None and data.time_variable is not None: for model in (self.combo_ax1_model, self.combo_ax2_model): model[:] = [ _enum_str(i) for i in Spiralogram.AxesCategories ] for var in data.domain.variables if data is not None else []: if (var.is_primitive() and (var is not data.time_variable or isinstance( var, TimeVariable) and data.time_delta is None)): newmodel.append(var) if var.is_discrete: for model in (self.combo_ax1_model, self.combo_ax2_model): model.append(var) self.attrlist_model.wrap(newmodel) init_combos() self.chart.clear() self.closeContext() self.ax2 = next((self.combo_ax2.itemText(i) for i in range(self.combo_ax2.count())), '') self.ax1 = next((self.combo_ax1.itemText(i) for i in range(1, self.combo_ax1.count())), self.ax2) self.agg_attr = [data.domain.variables[0]] if len( data.domain.variables) else [] self.agg_func = 0 if getattr(data, 'time_variable', None) is not None: self.openContext(data.domain) if self.agg_attr: self.attrlist.blockSignals(True) self.attrlist.selectionModel().clear() for attr in self.agg_attr: try: row = self.attrlist_model.indexOf(attr) except ValueError: continue self.attrlist.selectionModel().select( self.attrlist_model.index(row), QItemSelectionModel.SelectCurrent) self.attrlist.blockSignals(False) self.replot() def replot(self): if not self.combo_ax1.count() or not self.agg_attr: return self.chart.clear() vars = self.agg_attr func = AGG_FUNCTIONS[self.agg_func] if any(var.is_discrete for var in vars) and func != Mode: self.combo_func.setCurrentIndex(AGG_FUNCTIONS.index(Mode)) func = Mode try: ax1 = Spiralogram.AxesCategories[_enum_str(self.ax1, True)] except KeyError: ax1 = self.data.domain[self.ax1] # TODO: Allow having only a single (i.e. radial) axis try: ax2 = Spiralogram.AxesCategories[_enum_str(self.ax2, True)] except KeyError: ax2 = self.data.domain[self.ax2] self.chart.setSeries(self.data, vars, ax1, ax2, func) def on_selection(self, indices): self.indices = self.chart.selection_indices(indices) self.commit() def commit(self): self.Outputs.time_series.send( self.data[self.indices] if self.data else None)
class OWLineChart(widget.OWWidget): name = "折线图(Line Chart)" description = "以折线图形式观察某一列数据." icon = "icons/LineChart.svg" priority = 90 category = "可视化(Visualize)" keywords = ["zhexiantu"] class Inputs: time_series = Input("数据(Data)", Table, replaces="Data") attrs = settings.Setting({}) # Maps data.name -> [attrs] graph_name = "chart" def __init__(self): self.data = None self.plots = [] self.configs = [] self.varmodel = VariableListModel(parent=self) icon = QIcon(join(dirname(__file__), "icons", "LineChart-plus.png")) self.add_button = button = QPushButton(icon, "添加折线图", self) button.clicked.connect(self.add_plot) self.controlArea.layout().addWidget(button) self.configsArea = gui.vBox(self.controlArea) self.controlArea.layout().addStretch(1) # TODO: allow selecting ranges that are sent to output as subset table self.chart = highstock = Highstock(self, highchart="StockChart") self.mainArea.layout().addWidget(highstock) # highstock.evalJS('Highcharts.setOptions({navigator: {enabled:false}});') highstock.chart( # For some reason, these options don't work as global opts applied at Highstock init time # Disable top range selector rangeSelector_enabled=False, rangeSelector_inputEnabled=False, # Disable bottom miniview navigator (it doesn't update) navigator_enabled=False, ) QTimer.singleShot(0, self.add_plot) self.chart.add_legend() def add_plot(self): ax = self.chart.addAxis() config = PlotConfigWidget(self, ax, self.varmodel) # Connect the signals config.sigSelection.connect(self.chart.setSeries) config.sigLogarithmic.connect(self.chart.setLogarithmic) config.sigType.connect(self.chart.setType) config.sigClosed.connect(self.chart.removeAxis) config.sigClosed.connect(lambda ax, widget: widget.setParent(None)) config.sigClosed.connect( lambda ax, widget: self.add_button.setDisabled(False)) self.configs.append(config) self.add_button.setDisabled(len(self.configs) >= 5) config.sigClosed.connect(lambda ax, widget: self.remove_plot(widget)) self.configsArea.layout().addWidget(config) def remove_plot(self, plot): self.configs.remove(plot) # # self.configsArea.layout() if len(self.chart.axes) < 2: self.resize(QSize(925, 635)) @Inputs.time_series def set_data(self, data): # TODO: set xAxis resolution and tooltip time contents depending on # data.time_delta. See: http://imgur.com/yrnlgQz # If the same data is updated, short circuit to just updating the chart, # retaining all panels and list view selections ... # new_data = None if data is None else \ # Timeseries.from_data_table(data) new_data = data if (new_data is not None and self.data is not None and new_data.domain == self.data.domain): self.data = new_data for config in self.configs: config.selection_changed() return # self.data = data = None if data is None else \ # Timeseries.from_data_table(data) self.data = data if data is None: self.varmodel.clear() self.chart.clear() return # if getattr(data.time_variable, 'utc_offset', False): # offset_minutes = data.time_variable.utc_offset.total_seconds() / 60 # self.chart.evalJS('Highcharts.setOptions({global: {timezoneOffset: %d}});' % -offset_minutes) # Why is this negative? It works. # self.chart.chart() and self.chart.setXAxisType("linear") self.varmodel.wrap( [var for var in data.domain.variables if var.is_continuous])
class OWDocMap(widget.OWWidget): name = "Document Map" priority = 530 icon = "icons/DocMap.svg" replaces = ["orangecontrib.text.widgets.owgeomap.OWGeoMap"] keywords = ["GeoMap"] class Inputs: data = Input("Data", Table) class Outputs: corpus = Output("Corpus", Corpus) want_main_area = False selected_attr = settings.Setting('') selected_map = settings.Setting(0) regions = settings.Setting([]) def __init__(self): super().__init__() self.data = None self._create_layout() def region_selected(self, regions): """Called from JavaScript""" if not regions: self.regions = [] if not regions or self.data is None: return self.Outputs.corpus.send(None) self.regions = regions.split(',') attr = self.data.domain[self.selected_attr] if attr.is_discrete: return # TODO, FIXME: make this work for discrete attrs also from Orange.data.filter import FilterRegex filter = FilterRegex(attr, r'\b{}\b'.format(r'\b|\b'.join(self.regions)), re.IGNORECASE) self.Outputs.corpus.send(self.data._filter_values(filter)) def _create_layout(self): box = gui.widgetBox(self.controlArea, orientation='horizontal') self.varmodel = VariableListModel(parent=self) self.attr_combo = gui.comboBox(box, self, 'selected_attr', orientation=Qt.Horizontal, label='Region attribute:', callback=self.on_attr_change, sendSelectedValue=True) self.attr_combo.setModel(self.varmodel) self.map_combo = gui.comboBox(box, self, 'selected_map', orientation=Qt.Horizontal, label='Map type:', callback=self.on_map_change, items=Map.all) hexpand = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.attr_combo.setSizePolicy(hexpand) self.map_combo.setSizePolicy(hexpand) url = urljoin('file:', pathname2url(os.path.join( os.path.dirname(__file__), 'resources', 'owdocmap.html'))) class Bridge(QObject): @pyqtSlot(str) def region_selected(_, regions): return self.region_selected(regions) self.webview = gui.WebviewWidget(self.controlArea, Bridge(), url=QUrl(url), debug=False) self.controlArea.layout().addWidget(self.webview) QTimer.singleShot( 0, lambda: self.webview.evalJS('REGIONS = {};'.format({Map.WORLD: CC_WORLD, Map.EUROPE: CC_EUROPE, Map.USA: CC_USA}))) def _repopulate_attr_combo(self, data): vars = [a for a in chain(data.domain.metas, data.domain.attributes, data.domain.class_vars) if a.is_string] if data else [] self.varmodel.wrap(vars) # Select default attribute self.selected_attr = next((var.name for var in vars if var.name.lower().startswith(('country', 'location', 'region'))), vars[0].name if vars else '') @Inputs.data def on_data(self, data): if data and not isinstance(data, Corpus): data = Corpus.from_table(data.domain, data) self.data = data self._repopulate_attr_combo(data) if not data: self.region_selected('') QTimer.singleShot(0, lambda: self.webview.evalJS('DATA = {}; renderMap();')) else: QTimer.singleShot(0, self.on_attr_change) def on_map_change(self, map_code=''): if map_code: self.map_combo.setCurrentIndex(self.map_combo.findData(map_code)) else: map_code = self.map_combo.itemData(self.selected_map) inv_cc_map, cc_map = {Map.USA: (INV_CC_USA, CC_USA), Map.WORLD: (INV_CC_WORLD, CC_WORLD), Map.EUROPE: (INV_CC_EUROPE, CC_EUROPE)}[map_code] # Set country counts for JS data = defaultdict(int) for locations in self._iter_locations(): keys = set(inv_cc_map.get(loc, loc) for loc in locations) for key in keys: if key in cc_map: data[key] += 1 # Draw the new map self.webview.evalJS('DATA = {};' 'MAP_CODE = "{}";' 'SELECTED_REGIONS = {};' 'renderMap();'.format(dict(data), map_code, self.regions)) def on_attr_change(self): if not self.selected_attr: return values = set(chain.from_iterable(self._iter_locations())) # Auto-select region map if 0 == len(values - SET_CC_USA): map_code = Map.USA elif 0 == len(values - SET_CC_EUROPE): map_code = Map.EUROPE else: map_code = Map.WORLD self.on_map_change(map_code) def _iter_locations(self): """ Iterator that yields an iterable per documents with all its's locations. """ if self.data is not None: attr = self.data.domain[self.selected_attr] for i in self.data.get_column_view(self.data.domain.index(attr))[0]: # If string attr is None instead of empty string skip it. # This happens on data sets from WB Indicators. if i is not None: if len(i) > 3: yield map(lambda x: x.strip(), CC_NAMES.findall(i.lower())) else: yield (i, )
class OWLineChart(widget.OWWidget): name = 'Line Chart' description = "Visualize time series' sequence and progression." icon = 'icons/LineChart.svg' priority = 90 class Inputs: time_series = Input("Time series", Table) forecast = Input("Forecast", Timeseries, multiple=True) attrs = settings.Setting({}) # Maps data.name -> [attrs] graph_name = 'chart' def __init__(self): self.data = None self.plots = [] self.configs = [] self.forecasts = OrderedDict() self.varmodel = VariableListModel(parent=self) icon = QIcon(join(dirname(__file__), 'icons', 'LineChart-plus.png')) self.add_button = button = QPushButton(icon, ' &Add plot', self) button.clicked.connect(self.add_plot) self.controlArea.layout().addWidget(button) self.configsArea = gui.vBox(self.controlArea) self.controlArea.layout().addStretch(1) # TODO: allow selecting ranges that are sent to output as subset table self.chart = highstock = Highstock(self, highchart='StockChart') self.mainArea.layout().addWidget(highstock) # highstock.evalJS('Highcharts.setOptions({navigator: {enabled:false}});') highstock.chart( # For some reason, these options don't work as global opts applied at Highstock init time # Disable top range selector rangeSelector_enabled=False, rangeSelector_inputEnabled=False, # Disable bottom miniview navigator (it doesn't update) navigator_enabled=False, ) QTimer.singleShot(0, self.add_plot) def add_plot(self): ax = self.chart.addAxis() config = PlotConfigWidget(self, ax, self.varmodel) # Connect the signals config.sigSelection.connect(self.chart.setSeries) config.sigLogarithmic.connect(self.chart.setLogarithmic) config.sigType.connect(self.chart.setType) config.sigClosed.connect(self.chart.removeAxis) config.sigClosed.connect(lambda ax, widget: widget.setParent(None)) config.sigClosed.connect( lambda ax, widget: self.add_button.setDisabled(False)) self.configs.append(config) self.add_button.setDisabled(len(self.configs) >= 5) self.configsArea.layout().addWidget(config) @Inputs.time_series def set_data(self, data): # TODO: set xAxis resolution and tooltip time contents depending on # data.time_delta. See: http://imgur.com/yrnlgQz # If the same data is updated, short circuit to just updating the chart, # retaining all panels and list view selections ... if data is not None and self.data is not None and data.domain == self.data.domain: self.data = Timeseries.from_data_table(data) for config in self.configs: config.selection_changed() return self.data = data = None if data is None else Timeseries.from_data_table( data) if data is None: self.varmodel.clear() self.chart.clear() return if getattr(data.time_variable, 'utc_offset', False): offset_minutes = data.time_variable.utc_offset.total_seconds() / 60 self.chart.evalJS( 'Highcharts.setOptions({global: {timezoneOffset: %d}});' % -offset_minutes) # Why is this negative? It works. self.chart.chart() self.chart.setXAxisType('datetime' if (data.time_variable and ( getattr(data.time_variable, 'have_date', False) or getattr(data.time_variable, 'have_time', False))) else 'linear') self.varmodel.wrap([ var for var in data.domain.variables if var.is_continuous and var != data.time_variable ]) @Inputs.forecast def set_forecast(self, forecast, id): if forecast is not None: self.forecasts[id] = forecast else: self.forecasts.pop(id, None)
class OWMovingTransform(widget.OWWidget): name = 'Moving Transform' description = 'Apply rolling window functions to the time series.' icon = 'icons/MovingTransform.svg' priority = 20 class Inputs: time_series = Input("Time series", Table) class Outputs: time_series = Output("Time series", Timeseries) want_main_area = False non_overlapping = settings.Setting(False) fixed_wlen = settings.Setting(5) transformations = settings.Setting([]) autocommit = settings.Setting(False) last_win_width = settings.Setting(5) _NON_OVERLAPPING_WINDOWS = 'Non-overlapping windows' UserAdviceMessages = [ widget.Message('Get the simple moving average (SMA) of a series ' 'by setting the aggregation function to "{}".'.format(Mean), 'sma-is-mean'), widget.Message('If "{}" is checked, the rolling windows don\t ' 'overlap. Instead, they run through the series ' 'side-to-side, so the resulting transformed series is ' 'fixed-window-length-times shorter.'.format(_NON_OVERLAPPING_WINDOWS), 'non-overlapping') ] class Warning(OWWidget.Warning): no_transforms_added = Msg("At least one transform should be added.") def __init__(self): self.data = None box = gui.vBox(self.controlArea, 'Moving Transform') def _disable_fixed_wlen(): fixed_wlen.setDisabled(not self.non_overlapping) self.view.repaint() self.on_changed() gui.checkBox(box, self, 'non_overlapping', label=self._NON_OVERLAPPING_WINDOWS, callback=_disable_fixed_wlen, tooltip='If this is checked, instead of rolling windows ' 'through the series, they are applied side-to-side, ' 'so the resulting output series will be some ' 'length-of-fixed-window-times shorter.') fixed_wlen = gui.spin(box, self, 'fixed_wlen', 2, 1000, label='Fixed window width:', callback=self.on_changed) fixed_wlen.setDisabled(not self.non_overlapping) # TODO: allow the user to choose left-aligned, right-aligned, or center-aligned window class TableView(gui.TableView): def __init__(self, parent): super().__init__(parent, editTriggers=(self.SelectedClicked | self.CurrentChanged | self.DoubleClicked | self.EditKeyPressed), ) self.horizontalHeader().setStretchLastSection(False) agg_functions = ListModel(AGG_FUNCTIONS + [Cumulative_sum, Cumulative_product], parent=self) self.setItemDelegateForColumn(0, self.VariableDelegate(parent)) self.setItemDelegateForColumn(1, self.SpinDelegate(parent)) self.setItemDelegateForColumn(2, self.ComboDelegate(self, agg_functions)) class _ItemDelegate(QStyledItemDelegate): def updateEditorGeometry(self, widget, option, _index): widget.setGeometry(option.rect) class ComboDelegate(_ItemDelegate): def __init__(self, parent=None, combo_model=None): super().__init__(parent) self._parent = parent if combo_model is not None: self._combo_model = combo_model def createEditor(self, parent, _QStyleOptionViewItem, index): combo = QComboBox(parent) combo.setModel(self._combo_model) return combo def setEditorData(self, combo, index): var = index.model().data(index, Qt.EditRole) combo.setCurrentIndex(self._combo_model.indexOf(var)) def setModelData(self, combo, model, index): var = self._combo_model[combo.currentIndex()] model.setData(index, var, Qt.EditRole) class VariableDelegate(ComboDelegate): @property def _combo_model(self): return self._parent.var_model class SpinDelegate(_ItemDelegate): def paint(self, painter, option, index): # Don't paint window length if non-overlapping windows set if not self.parent().non_overlapping: super().paint(painter, option, index) def createEditor(self, parent, _QStyleOptionViewItem, _index): # Don't edit window length if non-overlapping windows set if self.parent().non_overlapping: return None spin = QSpinBox(parent, minimum=1, maximum=1000) return spin def setEditorData(self, spin, index): spin.setValue(index.model().data(index, Qt.EditRole)) def setModelData(self, spin, model, index): spin.interpretText() model.setData(index, spin.value(), Qt.EditRole) self.var_model = VariableListModel(parent=self) self.table_model = model = PyTableModel(self.transformations, parent=self, editable=True) model.setHorizontalHeaderLabels(['Series', 'Window width', 'Aggregation function']) model.dataChanged.connect(self.on_changed) self.view = view = TableView(self) view.setModel(model) box.layout().addWidget(view) hbox = gui.hBox(box) from os.path import dirname, join self.add_button = button = gui.button( hbox, self, 'Add &Transform', callback=self.on_add_transform) button.setIcon(QIcon(join(dirname(__file__), 'icons', 'LineChart-plus.png'))) self.del_button = button = gui.button( hbox, self, '&Delete Selected', callback=self.on_del_transform) QIcon.setThemeName('gnome') # Works for me button.setIcon(QIcon.fromTheme('edit-delete')) gui.auto_commit(box, self, 'autocommit', '&Apply') def sizeHint(self): return QSize(450, 600) def on_add_transform(self): if self.data is not None: self.table_model.append([self.var_model[0], self.last_win_width, AGG_FUNCTIONS[0]]) self.commit() def on_del_transform(self): for row in sorted([mi.row() for mi in self.view.selectionModel().selectedRows(0)], reverse=True): del self.table_model[row] if len(self.table_model): selection_model = self.view.selectionModel() selection_model.select(self.table_model.index(len(self.table_model) - 1, 0), selection_model.Select | selection_model.Rows) self.commit() @Inputs.time_series def set_data(self, data): self.data = data = None if data is None else Timeseries.from_data_table(data) self.add_button.setDisabled(not len(getattr(data, 'domain', ()))) self.table_model.clear() if data is not None: self.var_model.wrap([var for var in data.domain.variables if var.is_continuous and var is not data.time_variable]) self.on_changed() def on_changed(self): self.commit() def commit(self): self.Warning.no_transforms_added.clear() data = self.data if not data: self.Outputs.time_series.send(None) return if not len(self.table_model): self.Warning.no_transforms_added() self.Outputs.time_series.send(None) return ts = moving_transform(data, self.table_model, self.non_overlapping and self.fixed_wlen) self.Outputs.time_series.send(ts)
def __init__(self, parent=None, select=SELECTNONE): QWidget.__init__(self) OWComponent.__init__(self, parent) self.parent = parent self.selection_type = select self.saving_enabled = hasattr(self.parent, "save_graph") self.clear_data(init=True) self.plotview = pg.PlotWidget(background="w", viewBox=InteractiveViewBox(self)) self.plot = self.plotview.getPlotItem() self.plot.setDownsampling(auto=True, mode="peak") self.markings = [] self.vLine = pg.InfiniteLine(angle=90, movable=False) self.hLine = pg.InfiniteLine(angle=0, movable=False) self.proxy = pg.SignalProxy(self.plot.scene().sigMouseMoved, rateLimit=20, slot=self.mouseMoved, delay=0.1) self.plot.scene().sigMouseMoved.connect(self.plot.vb.mouseMovedEvent) self.plot.vb.sigRangeChanged.connect(self.resized) self.pen_mouse = pg.mkPen(color=(0, 0, 255), width=2) self.pen_normal = defaultdict(lambda: pg.mkPen(color=(200, 200, 200, 127), width=1)) self.pen_subset = defaultdict(lambda: pg.mkPen(color=(0, 0, 0, 127), width=1)) self.pen_selected = defaultdict(lambda: pg.mkPen(color=(0, 0, 0, 127), width=2, style=Qt.DotLine)) self.label = pg.TextItem("", anchor=(1, 0)) self.label.setText("", color=(0, 0, 0)) self.discrete_palette = None QPixmapCache.setCacheLimit(max(QPixmapCache.cacheLimit(), 100 * 1024)) self.curves_cont = PlotCurvesItem() self.important_decimals = 4, 4 self.MOUSE_RADIUS = 20 self.clear_graph() #interface settings self.location = True #show current position self.markclosest = True #mark self.crosshair = True self.crosshair_hidden = True self.viewtype = INDIVIDUAL layout = QVBoxLayout() self.setLayout(layout) self.layout().setContentsMargins(0, 0, 0, 0) self.layout().addWidget(self.plotview) actions = [] zoom_in = QAction( "Zoom in", self, triggered=self.plot.vb.set_mode_zooming ) zoom_in.setShortcuts([Qt.Key_Z, QKeySequence(QKeySequence.ZoomIn)]) actions.append(zoom_in) zoom_fit = QAction( "Zoom to fit", self, triggered=lambda x: (self.plot.vb.autoRange(), self.plot.vb.set_mode_panning()) ) zoom_fit.setShortcuts([Qt.Key_Backspace, QKeySequence(Qt.ControlModifier | Qt.Key_0)]) actions.append(zoom_fit) rescale_y = QAction( "Rescale Y to fit", self, shortcut=Qt.Key_D, triggered=self.rescale_current_view_y ) actions.append(rescale_y) view_individual = QAction( "Show individual", self, shortcut=Qt.Key_I, triggered=lambda x: self.show_individual() ) actions.append(view_individual) view_average = QAction( "Show averages", self, shortcut=Qt.Key_A, triggered=lambda x: self.show_average() ) actions.append(view_average) self.show_grid = False self.show_grid_a = QAction( "Show grid", self, shortcut=Qt.Key_G, checkable=True, triggered=self.grid_changed ) actions.append(self.show_grid_a) self.invertX_menu = QAction( "Invert X", self, shortcut=Qt.Key_X, checkable=True, triggered=self.invertX_changed ) actions.append(self.invertX_menu) if self.selection_type == SELECTMANY: select_curves = QAction( "Select (line)", self, triggered=self.plot.vb.set_mode_select, ) select_curves.setShortcuts([Qt.Key_S]) actions.append(select_curves) if self.saving_enabled: save_graph = QAction( "Save graph", self, triggered=self.save_graph, ) save_graph.setShortcuts([QKeySequence(Qt.ControlModifier | Qt.Key_S)]) actions.append(save_graph) range_menu = MenuFocus("Define view range", self) range_action = QWidgetAction(self) layout = QGridLayout() range_box = gui.widgetBox(self, margin=5, orientation=layout) range_box.setFocusPolicy(Qt.TabFocus) self.range_e_x1 = lineEditFloatOrNone(None, self, "range_x1", label="e") range_box.setFocusProxy(self.range_e_x1) self.range_e_x2 = lineEditFloatOrNone(None, self, "range_x2", label="e") layout.addWidget(QLabel("X"), 0, 0, Qt.AlignRight) layout.addWidget(self.range_e_x1, 0, 1) layout.addWidget(QLabel("-"), 0, 2) layout.addWidget(self.range_e_x2, 0, 3) self.range_e_y1 = lineEditFloatOrNone(None, self, "range_y1", label="e") self.range_e_y2 = lineEditFloatOrNone(None, self, "range_y2", label="e") layout.addWidget(QLabel("Y"), 1, 0, Qt.AlignRight) layout.addWidget(self.range_e_y1, 1, 1) layout.addWidget(QLabel("-"), 1, 2) layout.addWidget(self.range_e_y2, 1, 3) b = gui.button(None, self, "Apply", callback=self.set_limits) layout.addWidget(b, 2, 3, Qt.AlignRight) range_action.setDefaultWidget(range_box) range_menu.addAction(range_action) layout = QGridLayout() self.plotview.setLayout(layout) self.button = QPushButton("View", self.plotview) self.button.setAutoDefault(False) layout.setRowStretch(1, 1) layout.setColumnStretch(1, 1) layout.addWidget(self.button, 0, 0) view_menu = MenuFocus(self) self.button.setMenu(view_menu) view_menu.addActions(actions) view_menu.addMenu(range_menu) self.addActions(actions) choose_color_action = QWidgetAction(self) choose_color_box = gui.hBox(self) choose_color_box.setFocusPolicy(Qt.TabFocus) model = VariableListModel() self.attrs = [] model.wrap(self.attrs) label = gui.label(choose_color_box, self, "Color by") label.setAlignment(Qt.AlignRight | Qt.AlignVCenter) self.attrCombo = gui.comboBox( choose_color_box, self, value="color_attr", contentsLength=12, callback=self.change_color_attr) self.attrCombo.setModel(model) choose_color_box.setFocusProxy(self.attrCombo) choose_color_action.setDefaultWidget(choose_color_box) view_menu.addAction(choose_color_action) labels_action = QWidgetAction(self) layout = QGridLayout() labels_box = gui.widgetBox(self, margin=0, orientation=layout) t = gui.lineEdit(None, self, "label_title", label="Title:", callback=self.labels_changed, callbackOnType=self.labels_changed) layout.addWidget(QLabel("Title:"), 0, 0, Qt.AlignRight) layout.addWidget(t, 0, 1) t = gui.lineEdit(None, self, "label_xaxis", label="X-axis:", callback=self.labels_changed, callbackOnType=self.labels_changed) layout.addWidget(QLabel("X-axis:"), 1, 0, Qt.AlignRight) layout.addWidget(t, 1, 1) t = gui.lineEdit(None, self, "label_yaxis", label="Y-axis:", callback=self.labels_changed, callbackOnType=self.labels_changed) layout.addWidget(QLabel("Y-axis:"), 2, 0, Qt.AlignRight) layout.addWidget(t, 2, 1) labels_action.setDefaultWidget(labels_box) view_menu.addAction(labels_action) self.labels_changed() # apply saved labels self.invertX_apply() self.plot.vb.set_mode_panning() self.reports = {} # current reports self.viewhelpers_show()
class OWLineChart(OWWidget): name = 'Line Chart' description = "Visualize time series' sequence and progression." icon = 'icons/LineChart.svg' priority = 90 class Inputs: time_series = Input("Time series", Table) features = Input("Features", AttributeList) forecast = Input("Forecast", Timeseries, multiple=True) settingsHandler = LineChartContextHandler() attrs = ContextSetting([]) # Maps data.name -> [attrs] is_logit = ContextSetting([]) graph_name = 'chart' def __init__(self): self.data = None self.features = None self.configs = [] self.forecasts = OrderedDict() self.varmodel = VariableListModel(parent=self) icon = QIcon(join(dirname(__file__), 'icons', 'LineChart-plus.png')) self.add_button = button = QPushButton(icon, ' &Add plot', self) button.clicked.connect(self.add_plot) self.controlArea.layout().addWidget(button) self.configsArea = gui.vBox(self.controlArea) self.controlArea.layout().addStretch(1) # TODO: allow selecting ranges that are sent to output as subset table self.chart = highstock = Highstock(self, highchart='StockChart') self.mainArea.layout().addWidget(highstock) # highstock.evalJS('Highcharts.setOptions({navigator: {enabled:false}});') highstock.chart( # For some reason, these options don't work as global opts applied at Highstock init time # Disable top range selector rangeSelector_enabled=False, rangeSelector_inputEnabled=False, # Disable bottom miniview navigator (it doesn't update) navigator_enabled=False, ) self.add_plot() self.chart.add_legend() def add_plot(self): ax = self.chart.addAxis() config = PlotConfigWidget(self, ax, self.varmodel) # Connect the signals config.sigSelection.connect(self.chart.setSeries) config.sigLogarithmic.connect(self.chart.setLogarithmic) config.sigType.connect(self.chart.setType) config.sigClosed.connect(self.chart.removeAxis) config.sigClosed.connect(lambda ax, widget: widget.setParent(None)) config.sigClosed.connect( lambda ax, widget: self.add_button.setDisabled(False)) self.configs.append(config) self.add_button.setDisabled(len(self.configs) >= 5) config.sigClosed.connect(lambda ax, widget: self.remove_plot(widget)) self.configsArea.layout().addWidget(config) def remove_plot(self, plot): self.configs.remove(plot) # # self.configsArea.layout() if len(self.chart.axes) < 2: self.resize(QSize(925, 635)) @Inputs.time_series def set_data(self, data): # TODO: set xAxis resolution and tooltip time contents depending on # data.time_delta. See: http://imgur.com/yrnlgQz # If the same data is updated, short circuit to just updating the chart # retaining all panels and list view selections ... new_data = None if data is None else \ Timeseries.from_data_table(data) if new_data is not None and self.data is not None \ and new_data.domain == self.data.domain: self.data = new_data self._selections_changed() return self.data = data = new_data if data is None: self.varmodel.clear() self.chart.clear() return self.set_attributes() if getattr(data.time_variable, 'utc_offset', False): offset_minutes = data.time_variable.utc_offset.total_seconds() / 60 self.chart.evalJS( 'Highcharts.setOptions({global: {timezoneOffset: %d}});' % -offset_minutes) # Why is this negative? It works. self.chart.chart() self.chart.setXAxisType('datetime' if (data.time_variable and ( getattr(data.time_variable, 'have_date', False) or getattr(data.time_variable, 'have_time', False))) else 'linear') variables = [ var for var in data.domain.variables if var.is_continuous and var != data.time_variable ] self.varmodel.wrap(variables) self.update_plots() @Inputs.forecast def set_forecast(self, forecast, id): if forecast is not None: self.forecasts[id] = forecast else: self.forecasts.pop(id, None) # TODO: update currently shown plots @Inputs.features def set_features(self, features: AttributeList) -> None: if features and not self.features: # if features are on the input and they were not before # context should be saved for later when features will not be # present anymore self.closeContext() self.features = features if self.data: self.set_attributes() self.update_plots() def set_attributes(self) -> None: """ In case when features present: set shown attributes to match features In case when features not present: set default value and open context """ self.closeContext() if self.features: self.attrs = [ [f] for f in self.features if f in self.data.domain and f != self.data.time_variable ] self.is_logit = [False] * len(self.attrs) else: variables = [ var for var in self.data.domain.variables if var.is_continuous and var != self.data.time_variable ] self.attrs = [[variables[0]]] self.is_logit = [False] # context is only open when features not provided on input # when provided features defines the selection self.openContext(self.data.domain) def update_plots(self) -> None: """ Update plots when new data or new selection comes """ # remove plots if too many of them while len(self.configs) > len(self.attrs): plot = self.configs[-1] plot.sigClosed.emit(plot.ax, plot) # add plots if not enough of them while len(self.configs) < len(self.attrs): self.add_plot() assert len(self.configs) == len(self.attrs) assert len(self.attrs) == len(self.is_logit) # select correct values for config, attr, log in zip(self.configs, self.attrs, self.is_logit): config.set_logarithmic(log) config.set_selection(attr) config.view.setEnabled(not self.features) def _selections_changed(self) -> None: for config in self.configs: config.selection_changed() def closeEvent(self, event: QCloseEvent) -> None: """ When someone delets the widget closeContext must be called to gather settings. """ self.closeContext() super().closeEvent(event) def closeContext(self) -> None: """ Gather configs in contextVariables and close context. """ if not self.features: # only close in case of when features are not present if they are # feature selection is defined by the input and context should # not have impact attrs, is_logit = [], [] for config in self.configs: attrs.append(config.get_selection()) is_logit.append(config.is_logarithmic) self.attrs = attrs self.is_logit = is_logit super().closeContext()
class OWMap(widget.OWWidget): name = 'Geo Map' description = 'Show data points on a world map.' icon = "icons/Map.svg" inputs = [("Data", Table, "set_data", widget.Default), ("Data Subset", Table, "set_subset"), ("Learner", Learner, "set_learner")] outputs = [("Selected Data", Table, widget.Default), (ANNOTATED_DATA_SIGNAL_NAME, Table)] settingsHandler = settings.DomainContextHandler() want_main_area = True autocommit = settings.Setting(True) tile_provider = settings.Setting('Black and white') lat_attr = settings.ContextSetting('') lon_attr = settings.ContextSetting('') class_attr = settings.ContextSetting('(None)') color_attr = settings.ContextSetting('') label_attr = settings.ContextSetting('') shape_attr = settings.ContextSetting('') size_attr = settings.ContextSetting('') opacity = settings.Setting(100) zoom = settings.Setting(100) jittering = settings.Setting(0) cluster_points = settings.Setting(False) show_legend = settings.Setting(True) TILE_PROVIDERS = OrderedDict(( ('Black and white', 'OpenStreetMap.BlackAndWhite'), ('OpenStreetMap', 'OpenStreetMap.Mapnik'), ('Topographic', 'OpenTopoMap'), ('Topographic 2', 'Thunderforest.OpenCycleMap'), ('Topographic 3', 'Thunderforest.Outdoors'), ('Satellite', 'Esri.WorldImagery'), ('Print', 'Stamen.TonerLite'), ('Light', 'CartoDB.Positron'), ('Dark', 'CartoDB.DarkMatter'), ('Railways', 'Thunderforest.Transport'), ('Watercolor', 'Stamen.Watercolor'), )) class Error(widget.OWWidget.Error): model_error = widget.Msg("Error predicting: {}") learner_error = widget.Msg("Error modelling: {}") UserAdviceMessages = [ widget.Message( 'Select markers by holding <b><kbd>Shift</kbd></b> key and dragging ' 'a rectangle around them. Clear the selection by clicking anywhere.', 'shift-selection') ] graph_name = "map" def __init__(self): super().__init__() self.map = map = LeafletMap(self) self.mainArea.layout().addWidget(map) self.selection = None self.data = None self.learner = None def selectionChanged(indices): self.selection = self.data[indices] if self.data is not None and indices else None self._indices = indices self.commit() map.selectionChanged.connect(selectionChanged) def _set_map_provider(): map.set_map_provider(self.TILE_PROVIDERS[self.tile_provider]) box = gui.vBox(self.controlArea, 'Map') gui.comboBox(box, self, 'tile_provider', orientation=Qt.Horizontal, label='Map:', items=tuple(self.TILE_PROVIDERS.keys()), sendSelectedValue=True, callback=_set_map_provider) self._latlon_model = VariableListModel(parent=self) self._class_model = VariableListModel(parent=self) self._color_model = VariableListModel(parent=self) self._shape_model = VariableListModel(parent=self) self._size_model = VariableListModel(parent=self) self._label_model = VariableListModel(parent=self) def _set_lat_long(): self.map.set_data(self.data, self.lat_attr, self.lon_attr) self.train_model() self._combo_lat = combo = gui.comboBox( box, self, 'lat_attr', orientation=Qt.Horizontal, label='Latitude:', sendSelectedValue=True, callback=_set_lat_long) combo.setModel(self._latlon_model) self._combo_lon = combo = gui.comboBox( box, self, 'lon_attr', orientation=Qt.Horizontal, label='Longitude:', sendSelectedValue=True, callback=_set_lat_long) combo.setModel(self._latlon_model) def _toggle_legend(): self.map.toggle_legend(self.show_legend) gui.checkBox(box, self, 'show_legend', label='Show legend', callback=_toggle_legend) box = gui.vBox(self.controlArea, 'Overlay') self._combo_class = combo = gui.comboBox( box, self, 'class_attr', orientation=Qt.Horizontal, label='Target:', sendSelectedValue=True, callback=self.train_model ) self.controls.class_attr.setModel(self._class_model) self.set_learner(self.learner) box = gui.vBox(self.controlArea, 'Points') self._combo_color = combo = gui.comboBox( box, self, 'color_attr', orientation=Qt.Horizontal, label='Color:', sendSelectedValue=True, callback=lambda: self.map.set_marker_color(self.color_attr)) combo.setModel(self._color_model) self._combo_label = combo = gui.comboBox( box, self, 'label_attr', orientation=Qt.Horizontal, label='Label:', sendSelectedValue=True, callback=lambda: self.map.set_marker_label(self.label_attr)) combo.setModel(self._label_model) self._combo_shape = combo = gui.comboBox( box, self, 'shape_attr', orientation=Qt.Horizontal, label='Shape:', sendSelectedValue=True, callback=lambda: self.map.set_marker_shape(self.shape_attr)) combo.setModel(self._shape_model) self._combo_size = combo = gui.comboBox( box, self, 'size_attr', orientation=Qt.Horizontal, label='Size:', sendSelectedValue=True, callback=lambda: self.map.set_marker_size(self.size_attr)) combo.setModel(self._size_model) def _set_opacity(): map.set_marker_opacity(self.opacity) def _set_zoom(): map.set_marker_size_coefficient(self.zoom) def _set_jittering(): map.set_jittering(self.jittering) def _set_clustering(): map.set_clustering(self.cluster_points) self._opacity_slider = gui.hSlider( box, self, 'opacity', None, 1, 100, 5, label='Opacity:', labelFormat=' %d%%', callback=_set_opacity) self._zoom_slider = gui.valueSlider( box, self, 'zoom', None, values=(20, 50, 100, 200, 300, 400, 500, 700, 1000), label='Symbol size:', labelFormat=' %d%%', callback=_set_zoom) self._jittering = gui.valueSlider( box, self, 'jittering', label='Jittering:', values=(0, .5, 1, 2, 5), labelFormat=' %.1f%%', ticks=True, callback=_set_jittering) self._clustering_check = gui.checkBox( box, self, 'cluster_points', label='Cluster points', callback=_set_clustering) gui.rubber(self.controlArea) gui.auto_commit(self.controlArea, self, 'autocommit', 'Send Selection') QTimer.singleShot(0, _set_map_provider) QTimer.singleShot(0, _toggle_legend) QTimer.singleShot(0, _set_opacity) QTimer.singleShot(0, _set_zoom) QTimer.singleShot(0, _set_jittering) QTimer.singleShot(0, _set_clustering) autocommit = settings.Setting(True) def __del__(self): self.progressBarFinished(None) self.map = None def commit(self): self.send('Selected Data', self.selection) self.send(ANNOTATED_DATA_SIGNAL_NAME, create_annotated_table(self.data, self._indices)) def set_data(self, data): self.data = data self.closeContext() if data is None: return self.clear() all_vars = list(chain(self.data.domain, self.data.domain.metas)) continuous_vars = [var for var in all_vars if var.is_continuous] discrete_vars = [var for var in all_vars if var.is_discrete] primitive_vars = [var for var in all_vars if var.is_primitive()] self._latlon_model.wrap(continuous_vars) self._class_model.wrap(['(None)'] + primitive_vars) self._color_model.wrap(['(Same color)'] + primitive_vars) self._shape_model.wrap(['(Same shape)'] + discrete_vars) self._size_model.wrap(['(Same size)'] + continuous_vars) self._label_model.wrap(['(No labels)'] + all_vars) def _find_lat_lon(): lat_attr = next( (attr for attr in data.domain.variables + data.domain.metas if attr.is_continuous and attr.name.lower().startswith(('latitude', 'lat'))), None) lon_attr = next( (attr for attr in data.domain.variables + data.domain.metas if attr.is_continuous and attr.name.lower().startswith(('longitude', 'lng', 'long', 'lon'))), None) def _is_between(vals, min, max): return np.all((min <= vals) & (vals <= max)) if not lat_attr: for attr in data.domain: if attr.is_continuous: values = np.nan_to_num(data.get_column_view(attr)[0]) if _is_between(values, -90, 90): lat_attr = attr if not lon_attr: for attr in data.domain: if attr.is_continuous: values = np.nan_to_num(data.get_column_view(attr)[0]) if _is_between(values, -180, 180): lon_attr = attr return lat_attr, lon_attr lat, lon = _find_lat_lon() if lat or lon: self._combo_lat.setCurrentIndex(-1 if lat is None else continuous_vars.index(lat)) self._combo_lon.setCurrentIndex(-1 if lat is None else continuous_vars.index(lon)) self.lat_attr = lat.name self.lon_attr = lon.name self._combo_color.setCurrentIndex(0) self._combo_shape.setCurrentIndex(0) self._combo_size.setCurrentIndex(0) self._combo_label.setCurrentIndex(0) self._combo_class.setCurrentIndex(0) self.openContext(data) if self.lat_attr in self.data.domain and self.lon_attr in self.data.domain: self.map.set_data(self.data, self.lat_attr, self.lon_attr) self.map.set_marker_color(self.color_attr, update=False) self.map.set_marker_label(self.label_attr, update=False) self.map.set_marker_shape(self.shape_attr, update=False) self.map.set_marker_size(self.size_attr, update=True) def set_subset(self, subset): self.map.set_subset_ids(subset.ids if subset is not None else np.array([])) def handleNewSignals(self): super().handleNewSignals() self.train_model() def set_learner(self, learner): self.learner = learner self.controls.class_attr.setEnabled(learner is not None) self.controls.class_attr.setToolTip( 'Needs a Learner input for modelling.' if learner is None else '') def train_model(self): model = None self.Error.clear() if self.data and self.learner and self.class_attr != '(None)': domain = self.data.domain if self.lat_attr and self.lon_attr and self.class_attr in domain: domain = Domain([domain[self.lat_attr], domain[self.lon_attr]], [domain[self.class_attr]]) # I am retarded train = Table.from_table(domain, self.data) try: model = self.learner(train) except Exception as e: self.Error.learner_error(e) self.map.set_model(model) def disable_some_controls(self, disabled): tooltip = ( "Available when the zoom is close enough to have " "<{} points in the viewport.".format(self.map.N_POINTS_PER_ITER) if disabled else '') for widget in (self._combo_label, self._combo_shape, self._clustering_check): widget.setDisabled(disabled) widget.setToolTip(tooltip) def clear(self): self.map.set_data(None, '', '') self._latlon_model.wrap([]) self._class_model.wrap(['(None)']) self._color_model.wrap(['(Same color)']) self._shape_model.wrap(['(Same shape)']) self._size_model.wrap(['(Same size)']) self._label_model.wrap(['(No labels)']) self.lat_attr = self.lon_attr = self.class_attr = self.color_attr = \ self.label_attr = self.shape_attr = self.size_attr = ''
class OWSpiralogram(widget.OWWidget): name = 'Spiralogram' description = "Visualize time series' periodicity in a spiral heatmap." icon = 'icons/Spiralogram.svg' priority = 120 class Inputs: time_series = Input("Time series", Table) class Outputs: time_series = Output("Time series", Timeseries) settingsHandler = settings.DomainContextHandler() ax1 = settings.ContextSetting('months of year') ax2 = settings.ContextSetting('years') agg_attr = settings.ContextSetting([]) agg_func = settings.ContextSetting(0) invert_date_order = settings.Setting(False) graph_name = 'chart' def __init__(self): self.data = None self.indices = [] box = gui.vBox(self.controlArea, 'Axes') self.combo_ax2_model = VariableListModel(parent=self) self.combo_ax1_model = VariableListModel(parent=self) for model in (self.combo_ax1_model, self.combo_ax2_model): model[:] = [_enum_str(i) for i in Spiralogram.AxesCategories] self.combo_ax2 = gui.comboBox( box, self, 'ax2', label='Y axis:', callback=self.replot, sendSelectedValue=True, orientation='horizontal', model=self.combo_ax2_model) self.combo_ax1 = gui.comboBox( box, self, 'ax1', label='Radial:', callback=self.replot, sendSelectedValue=True, orientation='horizontal', model=self.combo_ax1_model) gui.checkBox(box, self, 'invert_date_order', 'Invert Y axis order', callback=self.replot) box = gui.vBox(self.controlArea, 'Aggregation') self.combo_func = gui.comboBox( box, self, 'agg_func', label='Function:', orientation='horizontal', callback=self.replot) func_model = ListModel(AGG_FUNCTIONS, parent=self) self.combo_func.setModel(func_model) self.attrlist_model = VariableListModel(parent=self) self.attrlist = QListView(selectionMode=QListView.SingleSelection) self.attrlist.setModel(self.attrlist_model) self.attrlist.selectionModel().selectionChanged.connect( self.attrlist_selectionChanged) box.layout().addWidget(self.attrlist) gui.rubber(self.controlArea) self.chart = chart = Spiralogram(self, selection_callback=self.on_selection) self.mainArea.layout().addWidget(chart) def attrlist_selectionChanged(self): self.agg_attr = [self.attrlist_model[i.row()] for i in self.attrlist.selectionModel().selectedIndexes()] self.replot() @Inputs.time_series def set_data(self, data): self.data = data = None if data is None else Timeseries.from_data_table(data) def init_combos(): for model in (self.combo_ax1_model, self.combo_ax2_model): model.clear() newmodel = [] if data is not None and data.time_variable is not None: for model in (self.combo_ax1_model, self.combo_ax2_model): model[:] = [_enum_str(i) for i in Spiralogram.AxesCategories] for var in data.domain.variables if data is not None else []: if (var.is_primitive() and (var is not data.time_variable or isinstance(var, TimeVariable) and data.time_delta is None)): newmodel.append(var) if var.is_discrete: for model in (self.combo_ax1_model, self.combo_ax2_model): model.append(var) self.attrlist_model.wrap(newmodel) init_combos() self.chart.clear() if data is None: self.commit() return self.closeContext() self.ax2 = next((self.combo_ax2.itemText(i) for i in range(self.combo_ax2.count())), '') self.ax1 = next((self.combo_ax1.itemText(i) for i in range(1, self.combo_ax1.count())), self.ax2) self.agg_attr = [data.domain.variables[0]] if len(data.domain.variables) else [] self.agg_func = 0 if getattr(data, 'time_variable', None) is not None: self.openContext(data.domain) if self.agg_attr: self.attrlist.blockSignals(True) self.attrlist.selectionModel().clear() for attr in self.agg_attr: try: row = self.attrlist_model.indexOf(attr) except ValueError: continue self.attrlist.selectionModel().select( self.attrlist_model.index(row), QItemSelectionModel.SelectCurrent) self.attrlist.blockSignals(False) self.replot() def replot(self): if not self.combo_ax1.count() or not self.agg_attr: return self.chart.clear() vars = self.agg_attr func = AGG_FUNCTIONS[self.agg_func] if any(var.is_discrete for var in vars) and func != Mode: self.combo_func.setCurrentIndex(AGG_FUNCTIONS.index(Mode)) func = Mode try: ax1 = Spiralogram.AxesCategories[_enum_str(self.ax1, True)] except KeyError: ax1 = self.data.domain[self.ax1] # TODO: Allow having only a sinle (i.e. radial) axis try: ax2 = Spiralogram.AxesCategories[_enum_str(self.ax2, True)] except KeyError: ax2 = self.data.domain[self.ax2] self.chart.setSeries(self.data, vars, ax1, ax2, func) def on_selection(self, indices): self.indices = self.chart.selection_indices(indices) self.commit() def commit(self): self.Outputs.time_series.send(self.data[self.indices] if self.data else None)
class OWLineChart(widget.OWWidget): name = 'Line Chart' description = "Visualize time series' sequence and progression." icon = 'icons/LineChart.svg' priority = 90 class Inputs: time_series = Input("Time series", Table) forecast = Input("Forecast", Timeseries, multiple=True) attrs = settings.Setting({}) # Maps data.name -> [attrs] graph_name = 'chart' def __init__(self): self.data = None self.plots = [] self.configs = [] self.forecasts = OrderedDict() self.varmodel = VariableListModel(parent=self) icon = QIcon(join(dirname(__file__), 'icons', 'LineChart-plus.png')) self.add_button = button = QPushButton(icon, ' &Add plot', self) button.clicked.connect(self.add_plot) self.controlArea.layout().addWidget(button) self.configsArea = gui.vBox(self.controlArea) self.controlArea.layout().addStretch(1) # TODO: allow selecting ranges that are sent to output as subset table self.chart = highstock = Highstock(self, highchart='StockChart') self.mainArea.layout().addWidget(highstock) # highstock.evalJS('Highcharts.setOptions({navigator: {enabled:false}});') highstock.chart( # For some reason, these options don't work as global opts applied at Highstock init time # Disable top range selector rangeSelector_enabled=False, rangeSelector_inputEnabled=False, # Disable bottom miniview navigator (it doesn't update) navigator_enabled=False, ) QTimer.singleShot(0, self.add_plot) def add_plot(self): ax = self.chart.addAxis() config = PlotConfigWidget(self, ax, self.varmodel) # Connect the signals config.sigSelection.connect(self.chart.setSeries) config.sigLogarithmic.connect(self.chart.setLogarithmic) config.sigType.connect(self.chart.setType) config.sigClosed.connect(self.chart.removeAxis) config.sigClosed.connect(lambda ax, widget: widget.setParent(None)) config.sigClosed.connect(lambda ax, widget: self.add_button.setDisabled(False)) self.configs.append(config) self.add_button.setDisabled(len(self.configs) >= 5) self.configsArea.layout().addWidget(config) @Inputs.time_series def set_data(self, data): # TODO: set xAxis resolution and tooltip time contents depending on # data.time_delta. See: http://imgur.com/yrnlgQz # If the same data is updated, short circuit to just updating the chart, # retaining all panels and list view selections ... if data is not None and self.data is not None and data.domain == self.data.domain: self.data = Timeseries.from_data_table(data) for config in self.configs: config.selection_changed() return self.data = data = None if data is None else Timeseries.from_data_table(data) if data is None: self.varmodel.clear() self.chart.clear() return if getattr(data.time_variable, 'utc_offset', False): offset_minutes = data.time_variable.utc_offset.total_seconds() / 60 self.chart.evalJS('Highcharts.setOptions({global: {timezoneOffset: %d}});' % -offset_minutes) # Why is this negative? It works. self.chart.chart() self.chart.setXAxisType( 'datetime' if (data.time_variable and (getattr(data.time_variable, 'have_date', False) or getattr(data.time_variable, 'have_time', False))) else 'linear') self.varmodel.wrap([var for var in data.domain.variables if var.is_continuous and var != data.time_variable]) @Inputs.forecast def set_forecast(self, forecast, id): if forecast is not None: self.forecasts[id] = forecast else: self.forecasts.pop(id, None)
class OWMap(widget.OWWidget): name = 'Map' description = 'Show data poits on a world map.' icon = "icons/Map.svg" inputs = [("Data", Table, "set_data"), ("Learner", Learner, "set_learner")] outputs = [("Selected Data", Table, widget.Default)] settingsHandler = settings.DomainContextHandler() want_main_area = True autocommit = settings.Setting(True) tile_provider = settings.Setting('Black and white') lat_attr = settings.ContextSetting('') lon_attr = settings.ContextSetting('') class_attr = settings.ContextSetting('') color_attr = settings.ContextSetting('') label_attr = settings.ContextSetting('') shape_attr = settings.ContextSetting('') size_attr = settings.ContextSetting('') opacity = settings.Setting(100) zoom = settings.Setting(100) jittering = settings.Setting(0) cluster_points = settings.Setting(False) TILE_PROVIDERS = OrderedDict(( ('Black and white', 'OpenStreetMap.BlackAndWhite'), ('OpenStreetMap', 'OpenStreetMap.DE'), ('Topographic', 'OpenTopoMap'), ('Topographic 2', 'Thunderforest.OpenCycleMap'), ('Topographic 3', 'Thunderforest.Outdoors'), ('Satellite', 'Esri.WorldImagery'), ('Print', 'Stamen.TonerLite'), ('Light', 'CartoDB.Positron'), ('Dark', 'CartoDB.DarkMatter'), ('Railways', 'Thunderforest.Transport'), ('Watercolor', 'Stamen.Watercolor'), )) class Error(widget.OWWidget.Error): model_error = widget.Msg("Model couldn't predict: {}") missing_learner = widget.Msg('No input learner to model with') learner_error = widget.Msg("Learner couldn't model: {}") UserAdviceMessages = [ widget.Message( 'Select markers by holding <b><kbd>Shift</kbd></b> key and dragging ' 'a rectangle around them. Clear the selection by clicking anywhere.', 'shift-selection') ] graph_name = "map" def __init__(self): super().__init__() self.map = map = LeafletMap(self) self.mainArea.layout().addWidget(map) self.selection = None self.data = None self.learner = None def selectionChanged(indices): self.selection = self.data[ indices] if self.data is not None and indices else None self.commit() map.selectionChanged.connect(selectionChanged) def _set_map_provider(): map.set_map_provider(self.TILE_PROVIDERS[self.tile_provider]) box = gui.vBox(self.controlArea, 'Map') gui.comboBox(box, self, 'tile_provider', orientation=Qt.Horizontal, label='Map:', items=tuple(self.TILE_PROVIDERS.keys()), sendSelectedValue=True, callback=_set_map_provider) self._latlon_model = VariableListModel(parent=self) self._class_model = VariableListModel(parent=self) self._color_model = VariableListModel(parent=self) self._shape_model = VariableListModel(parent=self) self._size_model = VariableListModel(parent=self) self._label_model = VariableListModel(parent=self) def _set_lat_long(): self.map.set_data(self.data, self.lat_attr, self.lon_attr) self.train_model() self._combo_lat = combo = gui.comboBox(box, self, 'lat_attr', orientation=Qt.Horizontal, label='Latitude:', sendSelectedValue=True, callback=_set_lat_long) combo.setModel(self._latlon_model) self._combo_lon = combo = gui.comboBox(box, self, 'lon_attr', orientation=Qt.Horizontal, label='Longitude:', sendSelectedValue=True, callback=_set_lat_long) combo.setModel(self._latlon_model) def _set_class_attr(): if not self.learner and self.class_attr != '(None)': self.Error.missing_learner() else: self.train_model() box = gui.vBox(self.controlArea, 'Heatmap') self._combo_class = combo = gui.comboBox(box, self, 'class_attr', orientation=Qt.Horizontal, label='Target:', sendSelectedValue=True, callback=_set_class_attr) combo.setModel(self._class_model) box = gui.vBox(self.controlArea, 'Points') self._combo_color = combo = gui.comboBox( box, self, 'color_attr', orientation=Qt.Horizontal, label='Color:', sendSelectedValue=True, callback=lambda: self.map.set_marker_color(self.color_attr)) combo.setModel(self._color_model) self._combo_label = combo = gui.comboBox( box, self, 'label_attr', orientation=Qt.Horizontal, label='Label:', sendSelectedValue=True, callback=lambda: self.map.set_marker_label(self.label_attr)) combo.setModel(self._label_model) self._combo_shape = combo = gui.comboBox( box, self, 'shape_attr', orientation=Qt.Horizontal, label='Shape:', sendSelectedValue=True, callback=lambda: self.map.set_marker_shape(self.shape_attr)) combo.setModel(self._shape_model) self._combo_size = combo = gui.comboBox( box, self, 'size_attr', orientation=Qt.Horizontal, label='Size:', sendSelectedValue=True, callback=lambda: self.map.set_marker_size(self.size_attr)) combo.setModel(self._size_model) def _set_opacity(): map.set_marker_opacity(self.opacity) def _set_zoom(): map.set_marker_size_coefficient(self.zoom) def _set_jittering(): map.set_jittering(self.jittering) def _set_clustering(): map.set_clustering(self.cluster_points) self._opacity_slider = gui.hSlider(box, self, 'opacity', None, 1, 100, 5, label='Opacity:', labelFormat=' %d%%', callback=_set_opacity) self._zoom_slider = gui.valueSlider(box, self, 'zoom', None, values=(20, 50, 100, 200, 500, 1000), label='Symbol size:', labelFormat=' %d%%', callback=_set_zoom) self._jittering = gui.valueSlider(box, self, 'jittering', label='Jittering:', values=(0, .5, 1, 2, 5), labelFormat=' %.1f%%', ticks=True, callback=_set_jittering) self._clustering_check = gui.checkBox(box, self, 'cluster_points', label='Cluster points', callback=_set_clustering) gui.rubber(self.controlArea) gui.auto_commit(self.controlArea, self, 'autocommit', 'Send Selection') QTimer.singleShot(0, _set_map_provider) QTimer.singleShot(0, _set_opacity) QTimer.singleShot(0, _set_zoom) QTimer.singleShot(0, _set_jittering) QTimer.singleShot(0, _set_clustering) autocommit = settings.Setting(True) def __del__(self): self.progressBarFinished(None) self.map = None def commit(self): self.send('Selected Data', self.selection) def set_data(self, data): self.data = data self.closeContext() if data is None: return self.clear() all_vars = list(chain(self.data.domain, self.data.domain.metas)) continuous_vars = [var for var in all_vars if var.is_continuous] discrete_vars = [var for var in all_vars if var.is_discrete] primitive_vars = [var for var in all_vars if var.is_primitive] self._latlon_model.wrap(continuous_vars) self._class_model.wrap(['(None)'] + continuous_vars) self._color_model.wrap(['(Same color)'] + primitive_vars) self._shape_model.wrap(['(Same shape)'] + discrete_vars) self._size_model.wrap(['(Same size)'] + continuous_vars) self._label_model.wrap(['(No labels)'] + all_vars) def _find_lat_lon(): STR_TRANSFORMS = (lambda x: x, str.capitalize, str.upper) lat_attr = next((attr for attr in data.domain if any( attr.name.startswith(transform(name)) for name in ('latitude', 'lat') for transform in STR_TRANSFORMS)), None) lon_attr = next((attr for attr in data.domain if any( attr.name.startswith(transform(name)) for name in ('longitude', 'long', 'lon') for transform in STR_TRANSFORMS)), None) return lat_attr, lon_attr lat, lon = _find_lat_lon() if lat and lon: self._combo_lat.setCurrentIndex( 0 if lat is None else continuous_vars.index(lat)) self._combo_lon.setCurrentIndex( 0 if lat is None else continuous_vars.index(lon)) self.lat_attr = lat.name self.lon_attr = lon.name self.map.set_data(self.data, lat, lon) else: self.map.set_data(None, None, None) self._combo_color.setCurrentIndex(0) self._combo_shape.setCurrentIndex(0) self._combo_size.setCurrentIndex(0) self._combo_label.setCurrentIndex(0) self.openContext(data) self.map.set_marker_color(self.color_attr, update=False) self.map.set_marker_label(self.label_attr, update=False) self.map.set_marker_shape(self.shape_attr, update=False) self.map.set_marker_size(self.size_attr, update=True) def handleNewSignals(self): super().handleNewSignals() self.train_model() def set_learner(self, learner): self.learner = learner def train_model(self): model = None self.Error.missing_learner.clear() if self.data is not None and self.learner is not None: if self.lat_attr and self.lon_attr and self.class_attr and self.class_attr != '(None)': domain = self.data.domain domain = Domain([domain[self.lat_attr], domain[self.lon_attr]], [domain[self.class_attr]]) # I am retarded train = Table.from_table(domain, self.data) try: model = self.learner(train) except Exception as e: self.Error.learner_error(e) else: self.Error.learner_error.clear() self.map.set_model(model) def disable_some_controls(self, disabled): self._combo_label.setDisabled(disabled) self._combo_shape.setDisabled(disabled) self._clustering_check.setDisabled(disabled) def clear(self): self.map.set_data(None, '', '') self._latlon_model.wrap([]) self._class_model.wrap([]) self._color_model.wrap(['(Same color)']) self._shape_model.wrap(['(Same shape)']) self._size_model.wrap(['(Same size)']) self._label_model.wrap(['(No labels)']) self.lat_attr = self.lon_attr = self.class_attr = self.color_attr = \ self.label_attr = self.shape_attr = self.size_attr = '' self.train_model()
class OWGeoMap(widget.OWWidget): name = "GeoMap" priority = 20000 icon = "icons/GeoMap.svg" inputs = [("Data", Table, "on_data")] outputs = [('Corpus', Corpus)] want_main_area = False selected_attr = settings.Setting('') selected_map = settings.Setting(0) regions = settings.Setting([]) def __init__(self): super().__init__() self.data = None self._create_layout() @QtCore.pyqtSlot(str, result=str) def region_selected(self, regions): """Called from JavaScript""" if not regions: self.regions = [] if not regions or self.data is None: return self.send('Corpus', None) self.regions = regions.split(',') attr = self.data.domain[self.selected_attr] if attr.is_discrete: return # TODO, FIXME: make this work for discrete attrs also from Orange.data.filter import FilterRegex filter = FilterRegex(attr, r'\b{}\b'.format(r'\b|\b'.join(self.regions)), re.IGNORECASE) self.send('Corpus', self.data._filter_values(filter)) def _create_layout(self): box = gui.widgetBox(self.controlArea, orientation='horizontal') self.varmodel = VariableListModel(parent=self) self.attr_combo = gui.comboBox(box, self, 'selected_attr', orientation=Qt.Horizontal, label='Region attribute:', callback=self.on_attr_change, sendSelectedValue=True) self.attr_combo.setModel(self.varmodel) self.map_combo = gui.comboBox(box, self, 'selected_map', orientation=Qt.Horizontal, label='Map type:', callback=self.on_map_change, items=Map.all) hexpand = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) self.attr_combo.setSizePolicy(hexpand) self.map_combo.setSizePolicy(hexpand) html = ''' <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <base href="{}/"/> <style> html, body, #map {{margin:0px;padding:0px;width:100%;height:100%;}} </style> <link href="resources/jquery-jvectormap-2.0.2.css" rel="stylesheet"> <script src="resources/jquery-2.1.4.min.js"></script> <script src="resources/jquery-jvectormap-2.0.2.min.js"></script> <script src="resources/jquery-jvectormap-world-mill-en.js"></script> <script src="resources/jquery-jvectormap-europe-mill-en.js"></script> <script src="resources/jquery-jvectormap-us-aea-en.js"></script> <script src="resources/geomap-script.js"></script> </head> <body> <div id="map"></div> </body> </html>'''.format(urljoin('file:', pathname2url(path.abspath(path.dirname(__file__))))) self.webview = gui.WebviewWidget(self.controlArea, self, debug=False) self.controlArea.layout().addWidget(self.webview) self.webview.setHtml(html) QTimer.singleShot( 0, lambda: self.webview.evalJS('REGIONS = {};'.format({Map.WORLD: CC_WORLD, Map.EUROPE: CC_EUROPE, Map.USA: CC_USA}))) def _repopulate_attr_combo(self, data): vars = [a for a in chain(data.domain.metas, data.domain.attributes, data.domain.class_vars) if a.is_string] if data else [] self.varmodel.wrap(vars) # Select default attribute self.selected_attr = next((var.name for var in vars if var.name.lower().startswith(('country', 'location', 'region'))), vars[0].name if vars else '') def on_data(self, data): if data and not isinstance(data, Corpus): data = Corpus.from_table(data.domain, data) self.data = data self._repopulate_attr_combo(data) if not data: self.region_selected('') QTimer.singleShot(0, lambda: self.webview.evalJS('DATA = {}; renderMap();')) else: QTimer.singleShot(0, self.on_attr_change) def on_map_change(self, map_code=''): if map_code: self.map_combo.setCurrentIndex(self.map_combo.findData(map_code)) else: map_code = self.map_combo.itemData(self.selected_map) inv_cc_map, cc_map = {Map.USA: (INV_CC_USA, CC_USA), Map.WORLD: (INV_CC_WORLD, CC_WORLD), Map.EUROPE: (INV_CC_EUROPE, CC_EUROPE)} [map_code] # Set country counts in JS data = defaultdict(int) for cc in getattr(self, 'cc_counts', ()): key = inv_cc_map.get(cc, cc) if key in cc_map: data[key] += self.cc_counts[cc] # Draw the new map self.webview.evalJS('DATA = {};' 'MAP_CODE = "{}";' 'SELECTED_REGIONS = {};' 'renderMap();'.format(dict(data), map_code, self.regions)) def on_attr_change(self): if not self.selected_attr: return attr = self.data.domain[self.selected_attr] self.cc_counts = Counter(chain.from_iterable( set(name.strip() for name in CC_NAMES.findall(i.lower())) if len(i) > 3 else (i,) for i in self.data.get_column_view(self.data.domain.index(attr))[0])) # Auto-select region map values = set(self.cc_counts) if 0 == len(values - SET_CC_USA): map_code = Map.USA elif 0 == len(values - SET_CC_EUROPE): map_code = Map.EUROPE else: map_code = Map.WORLD self.on_map_change(map_code)
class OWMovingTransform(widget.OWWidget): name = 'Moving Transform' description = 'Apply rolling window functions to the time series.' icon = 'icons/MovingTransform.svg' priority = 20 inputs = [("Time series", Table, 'set_data')] outputs = [("Time series", Timeseries)] want_main_area = False non_overlapping = settings.Setting(False) fixed_wlen = settings.Setting(5) transformations = settings.Setting([]) autocommit = settings.Setting(False) last_win_width = settings.Setting(5) _NON_OVERLAPPING_WINDOWS = 'Non-overlapping windows' UserAdviceMessages = [ widget.Message('Get the simple moving average (SMA) of a series ' 'by setting the aggregation function to "{}".'.format(Mean), 'sma-is-mean'), widget.Message('If "{}" is checked, the rolling windows don\t ' 'overlap. Instead, they run through the series ' 'side-to-side, so the resulting transformed series is ' 'fixed-window-length-times shorter.'.format(_NON_OVERLAPPING_WINDOWS), 'non-overlapping') ] def __init__(self): self.data = None box = gui.vBox(self.controlArea, 'Moving Transform') def _disable_fixed_wlen(): fixed_wlen.setDisabled(not self.non_overlapping) self.view.repaint() self.on_changed() gui.checkBox(box, self, 'non_overlapping', label=self._NON_OVERLAPPING_WINDOWS, callback=_disable_fixed_wlen, tooltip='If this is checked, instead of rolling windows ' 'through the series, they are applied side-to-side, ' 'so the resulting output series will be some ' 'length-of-fixed-window-times shorter.') fixed_wlen = gui.spin(box, self, 'fixed_wlen', 2, 1000, label='Fixed window width:', callback=self.on_changed) fixed_wlen.setDisabled(not self.non_overlapping) # TODO: allow the user to choose left-aligned, right-aligned, or center-aligned window class TableView(gui.TableView): def __init__(self, parent): super().__init__(parent, editTriggers=(self.SelectedClicked | self.CurrentChanged | self.DoubleClicked | self.EditKeyPressed), ) self.horizontalHeader().setStretchLastSection(False) agg_functions = ListModel(AGG_FUNCTIONS + [Cumulative_sum, Cumulative_product], parent=self) self.setItemDelegateForColumn(0, self.VariableDelegate(parent)) self.setItemDelegateForColumn(1, self.SpinDelegate(parent)) self.setItemDelegateForColumn(2, self.ComboDelegate(self, agg_functions)) class _ItemDelegate(QStyledItemDelegate): def updateEditorGeometry(self, widget, option, _index): widget.setGeometry(option.rect) class ComboDelegate(_ItemDelegate): def __init__(self, parent=None, combo_model=None): super().__init__(parent) self._parent = parent if combo_model is not None: self._combo_model = combo_model def createEditor(self, parent, _QStyleOptionViewItem, index): combo = QComboBox(parent) combo.setModel(self._combo_model) return combo def setEditorData(self, combo, index): var = index.model().data(index, Qt.EditRole) combo.setCurrentIndex(self._combo_model.indexOf(var)) def setModelData(self, combo, model, index): var = self._combo_model[combo.currentIndex()] model.setData(index, var, Qt.EditRole) class VariableDelegate(ComboDelegate): @property def _combo_model(self): return self._parent.var_model class SpinDelegate(_ItemDelegate): def paint(self, painter, option, index): # Don't paint window length if non-overlapping windows set if not self.parent().non_overlapping: super().paint(painter, option, index) def createEditor(self, parent, _QStyleOptionViewItem, _index): # Don't edit window length if non-overlapping windows set if self.parent().non_overlapping: return None spin = QSpinBox(parent, minimum=1, maximum=1000) return spin def setEditorData(self, spin, index): spin.setValue(index.model().data(index, Qt.EditRole)) def setModelData(self, spin, model, index): spin.interpretText() model.setData(index, spin.value(), Qt.EditRole) self.var_model = VariableListModel(parent=self) self.table_model = model = PyTableModel(self.transformations, parent=self, editable=True) model.setHorizontalHeaderLabels(['Series', 'Window width', 'Aggregation function']) model.dataChanged.connect(self.on_changed) self.view = view = TableView(self) view.setModel(model) box.layout().addWidget(view) hbox = gui.hBox(box) from os.path import dirname, join self.add_button = button = gui.button( hbox, self, 'Add &Transform', callback=self.on_add_transform) button.setIcon(QIcon(join(dirname(__file__), 'icons', 'LineChart-plus.png'))) self.del_button = button = gui.button( hbox, self, '&Delete Selected', callback=self.on_del_transform) QIcon.setThemeName('gnome') # Works for me button.setIcon(QIcon.fromTheme('edit-delete')) gui.auto_commit(box, self, 'autocommit', '&Apply') def sizeHint(self): return QSize(450, 600) def on_add_transform(self): if self.data is not None: self.table_model.append([self.var_model[0], self.last_win_width, AGG_FUNCTIONS[0]]) self.commit() def on_del_transform(self): for row in sorted([mi.row() for mi in self.view.selectionModel().selectedRows(0)], reverse=True): del self.table_model[row] if len(self.table_model): selection_model = self.view.selectionModel() selection_model.select(self.table_model.index(len(self.table_model) - 1, 0), selection_model.Select | selection_model.Rows) self.commit() def set_data(self, data): self.data = data = None if data is None else Timeseries.from_data_table(data) self.add_button.setDisabled(not len(getattr(data, 'domain', ()))) self.table_model.clear() if data is not None: self.var_model.wrap([var for var in data.domain if var.is_continuous and var is not data.time_variable]) self.on_changed() def on_changed(self): self.commit() def commit(self): data = self.data if not data: self.send(Output.TIMESERIES, None) return ts = moving_transform(data, self.table_model, self.non_overlapping and self.fixed_wlen) self.send(Output.TIMESERIES, ts)