diff options
| author | Géry Debongnie <ged@odoo.com> | 2014-12-19 13:38:37 +0100 |
|---|---|---|
| committer | Géry Debongnie <ged@odoo.com> | 2014-12-19 14:34:23 +0100 |
| commit | 2c08dc0ba610db61fe349d1602c12528ca8fc715 (patch) | |
| tree | 143cb7f61e45e027c63be321ee3d4031963df1e7 /addons/web_graph/static/src | |
| parent | 9f7a0c096f0d3deabf8baf7be33b108f5b7884b5 (diff) | |
[IMP] web: new pivot view, full rewrite of graph view
This commit is a split of the former graphview into a pivot view (the
tabular view) and graph view (the various charts). The code was fully
rewritten.
* pivot view should be faster (better underlying data structure and
better algorithm)
* better integration with the web client (use of sidebar, of menu buttons,
bettor style)
* new feature: pivot view can be sorted
* new feature: pivot view cells can be clicked to open a list view with
the corresponding domain
* web_graph addon was removed, and the graph view code has been moved
to the addon web.
* the interaction with the searchbar was changed (no more column facet)
* better and simpler code for graph view
* mary corner cases in graph charts have been corrected (for ex, pie
chart with positive and negative numbers)
* graph/pivot correctly save their state when added to dashboard/saved
(state such as current measure, chart mode, column groupbys, ...)
Diffstat (limited to 'addons/web_graph/static/src')
| -rw-r--r-- | addons/web_graph/static/src/css/graph.css | 165 | ||||
| -rw-r--r-- | addons/web_graph/static/src/css/nv.d3.css | 769 | ||||
| -rw-r--r-- | addons/web_graph/static/src/js/graph_view.js | 297 | ||||
| -rw-r--r-- | addons/web_graph/static/src/js/graph_widget.js | 875 | ||||
| -rw-r--r-- | addons/web_graph/static/src/js/pivot_table.js | 468 | ||||
| -rw-r--r-- | addons/web_graph/static/src/xml/web_graph.xml | 65 |
6 files changed, 0 insertions, 2639 deletions
diff --git a/addons/web_graph/static/src/css/graph.css b/addons/web_graph/static/src/css/graph.css deleted file mode 100644 index fd6735961e1..00000000000 --- a/addons/web_graph/static/src/css/graph.css +++ /dev/null @@ -1,165 +0,0 @@ - /* - This is a hack for IE9 ... - Attrs : width and height are not take in consideration - */ -.openerp svg:not(:root) { - overflow: visible; -} - -.graph_pivot_mode { - position:initial; -} - -.graph_chart_mode { - position:relative; -} - -.graph_main_content td { - font-size: 12px; - margin: 45px; - border-collapse: collapse; - vertical-align: middle; - border-bottom: 1px solid #ccc; - padding: 6px 8px; - text-align: right; - color: black; -} - -.graph_main_content td:first-child { - background-color: rgb(240, 238, 238); -} - -.graph_main_content table { - margin-top: 10px; -} - -.graph_measure_selection .oe_selected { - padding-left: 0; -} - -.graph_measure_selection .oe_selected:before { - content: "\f00c"; - width: 20px; - display: inline-block; - font-family: FontAwesome; -} - -.graph_measure_selection li { - margin-left: 5px; -} - -.graph_main_content th { - font-size: 13px; - font-weight: normal; - padding: 10px 8px; - vertical-align: top; - color: black; -} - -.graph_main_content tbody td{ - border: 1px solid #b8b8b8; -} - -.graph_main_content tbody { - border-top: 1px solid black; -} - -.graph_main_content thead { - background-color: rgb(240, 238, 238); - border: 1px solid #b8b8b8; -} - -.graph_main_content thead th { - border-left: 1px solid #b8b8b8; -} - -div.graph_widget { - padding:10px; -} - -.oe_dashboard div.graph_widget { - padding:0; -} - -td.graph_border { - text-align: left; - white-space: nowrap; -} - -.graph_main_content table:not(.heatmap) tr:hover td:not(:first-child) -{ - background-color: rgb(245, 245, 245); -} - -span.field-selection { - display: none; - padding: 0; -} - -.web_graph_click:hover { - cursor: pointer; -} - -.graph_widget .dropdown-menu { - font-size: 13px; - padding: 4px 0; - background: #e8e8e8 !important; - border: 1px solid rgba(0, 0, 0, 0.15); - font-color: !important black; - text-shadow: none; - background-color: #e8e8e8; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - border-radius: 3px; -} - - -.graph_widget .dropdown-menu li a, .graph_widget .dropdown-menu li a:hover, .graph_widget .dropdown-menu li a:focus { - color: black; - text-decoration: none; -} - -th.measure_row { - color: black; - font-size: 12px; - padding:0px 5px 0px 5px ; - margin:0; -} - - -.graph-dropdown-submenu { - position: relative; -} - -.graph-dropdown-submenu>.dropdown-menu { - top: 0; - left: 100%; - margin-top: -6px; - margin-left: -1px; - -webkit-border-radius: 0 6px 6px 6px; - -moz-border-radius: 0 6px 6px; - border-radius: 0 6px 6px 6px; -} - -.graph-dropdown-submenu:hover>.dropdown-menu { - display: block; -} - -.graph-dropdown-submenu>a:after { - display: block; - content: " "; - float: right; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; - border-width: 5px 0 5px 5px; - border-left-color: #ccc; - margin-top: 5px; - margin-right: -10px; -} - -.graph-dropdown-submenu:hover>a:after { - border-left-color: #fff; -} - diff --git a/addons/web_graph/static/src/css/nv.d3.css b/addons/web_graph/static/src/css/nv.d3.css deleted file mode 100644 index cae834827cc..00000000000 --- a/addons/web_graph/static/src/css/nv.d3.css +++ /dev/null @@ -1,769 +0,0 @@ - -/******************** - * HTML CSS - */ - - -.chartWrap { - margin: 0; - padding: 0; - overflow: hidden; -} - -/******************** - Box shadow and border radius styling -*/ -.nvtooltip.with-3d-shadow, .with-3d-shadow .nvtooltip { - -moz-box-shadow: 0 5px 10px rgba(0,0,0,.2); - -webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2); - box-shadow: 0 5px 10px rgba(0,0,0,.2); - - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; -} - -/******************** - * TOOLTIP CSS - */ - -.nvtooltip { - position: absolute; - background-color: rgba(255,255,255,1.0); - padding: 1px; - border: 1px solid rgba(0,0,0,.2); - z-index: 10000; - - font-family: Arial; - font-size: 13px; - text-align: left; - pointer-events: none; - - white-space: nowrap; - - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -/*Give tooltips that old fade in transition by - putting a "with-transitions" class on the container div. -*/ -.nvtooltip.with-transitions, .with-transitions .nvtooltip { - transition: opacity 250ms linear; - -moz-transition: opacity 250ms linear; - -webkit-transition: opacity 250ms linear; - - transition-delay: 250ms; - -moz-transition-delay: 250ms; - -webkit-transition-delay: 250ms; -} - -.nvtooltip.x-nvtooltip, -.nvtooltip.y-nvtooltip { - padding: 8px; -} - -.nvtooltip h3 { - margin: 0; - padding: 4px 14px; - line-height: 18px; - font-weight: normal; - background-color: rgba(247,247,247,0.75); - text-align: center; - - border-bottom: 1px solid #ebebeb; - - -webkit-border-radius: 5px 5px 0 0; - -moz-border-radius: 5px 5px 0 0; - border-radius: 5px 5px 0 0; -} - -.nvtooltip p { - margin: 0; - padding: 5px 14px; - text-align: center; -} - -.nvtooltip span { - display: inline-block; - margin: 2px 0; -} - -.nvtooltip table { - margin: 6px; - border-spacing:0; -} - - -.nvtooltip table td { - padding: 2px 9px 2px 0; - vertical-align: middle; -} - -.nvtooltip table td.key { - font-weight:normal; -} -.nvtooltip table td.value { - text-align: right; - font-weight: bold; -} - -.nvtooltip table tr.highlight td { - padding: 1px 9px 1px 0; - border-bottom-style: solid; - border-bottom-width: 1px; - border-top-style: solid; - border-top-width: 1px; -} - -.nvtooltip table td.legend-color-guide div { - width: 8px; - height: 8px; - vertical-align: middle; -} - -.nvtooltip .footer { - padding: 3px; - text-align: center; -} - - -.nvtooltip-pending-removal { - position: absolute; - pointer-events: none; -} - - -/******************** - * SVG CSS - */ - - -svg { - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - /* Trying to get SVG to act like a greedy block in all browsers */ - display: block; - width:100%; - height:100%; -} - - -svg text { - font: normal 12px Arial; -} - -svg .title { - font: bold 14px Arial; -} - -.nvd3 .nv-background { - fill: white; - fill-opacity: 0; - /* - pointer-events: none; - */ -} - -.nvd3.nv-noData { - font-size: 18px; - font-weight: bold; -} - - -/********** -* Brush -*/ - -.nv-brush .extent { - fill-opacity: .125; - shape-rendering: crispEdges; -} - - - -/********** -* Legend -*/ - -.nvd3 .nv-legend .nv-series { - cursor: pointer; -} - -.nvd3 .nv-legend .disabled circle { - fill-opacity: 0; -} - - - -/********** -* Axes -*/ -.nvd3 .nv-axis { - pointer-events:none; -} - -.nvd3 .nv-axis path { - fill: none; - stroke: #000; - stroke-opacity: .75; - shape-rendering: crispEdges; -} - -.nvd3 .nv-axis path.domain { - stroke-opacity: .75; -} - -.nvd3 .nv-axis.nv-x path.domain { - stroke-opacity: 0; -} - -.nvd3 .nv-axis line { - fill: none; - stroke: #e5e5e5; - shape-rendering: crispEdges; -} - -.nvd3 .nv-axis .zero line, -/*this selector may not be necessary*/ .nvd3 .nv-axis line.zero { - stroke-opacity: .75; -} - -.nvd3 .nv-axis .nv-axisMaxMin text { - font-weight: bold; -} - -.nvd3 .x .nv-axis .nv-axisMaxMin text, -.nvd3 .x2 .nv-axis .nv-axisMaxMin text, -.nvd3 .x3 .nv-axis .nv-axisMaxMin text { - text-anchor: middle -} - - - -/********** -* Brush -*/ - -.nv-brush .resize path { - fill: #eee; - stroke: #666; -} - - - -/********** -* Bars -*/ - -.nvd3 .nv-bars .negative rect { - zfill: brown; -} - -.nvd3 .nv-bars rect { - zfill: steelblue; - fill-opacity: .75; - - transition: fill-opacity 250ms linear; - -moz-transition: fill-opacity 250ms linear; - -webkit-transition: fill-opacity 250ms linear; -} - -.nvd3 .nv-bars rect.hover { - fill-opacity: 1; -} - -.nvd3 .nv-bars .hover rect { - fill: lightblue; -} - -.nvd3 .nv-bars text { - fill: rgba(0,0,0,0); -} - -.nvd3 .nv-bars .hover text { - fill: rgba(0,0,0,1); -} - - -/********** -* Bars -*/ - -.nvd3 .nv-multibar .nv-groups rect, -.nvd3 .nv-multibarHorizontal .nv-groups rect, -.nvd3 .nv-discretebar .nv-groups rect { - stroke-opacity: 0; - - transition: fill-opacity 250ms linear; - -moz-transition: fill-opacity 250ms linear; - -webkit-transition: fill-opacity 250ms linear; -} - -.nvd3 .nv-multibar .nv-groups rect:hover, -.nvd3 .nv-multibarHorizontal .nv-groups rect:hover, -.nvd3 .nv-discretebar .nv-groups rect:hover { - fill-opacity: 1; -} - -.nvd3 .nv-discretebar .nv-groups text, -.nvd3 .nv-multibarHorizontal .nv-groups text { - font-weight: bold; - fill: rgba(0,0,0,1); - stroke: rgba(0,0,0,0); -} - -/*********** -* Pie Chart -*/ - -.nvd3.nv-pie path { - stroke-opacity: 0; - transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear; - -moz-transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear; - -webkit-transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear; - -} - -.nvd3.nv-pie .nv-slice text { - stroke: #000; - stroke-width: 0; -} - -.nvd3.nv-pie path { - stroke: #fff; - stroke-width: 1px; - stroke-opacity: 1; -} - -.nvd3.nv-pie .hover path { - fill-opacity: .7; -} -.nvd3.nv-pie .nv-label { - pointer-events: none; -} -.nvd3.nv-pie .nv-label rect { - fill-opacity: 0; - stroke-opacity: 0; -} - -/********** -* Lines -*/ - -.nvd3 .nv-groups path.nv-line { - fill: none; - stroke-width: 1.5px; - /* - stroke-linecap: round; - shape-rendering: geometricPrecision; - - transition: stroke-width 250ms linear; - -moz-transition: stroke-width 250ms linear; - -webkit-transition: stroke-width 250ms linear; - - transition-delay: 250ms - -moz-transition-delay: 250ms; - -webkit-transition-delay: 250ms; - */ -} - -.nvd3 .nv-groups path.nv-line.nv-thin-line { - stroke-width: 1px; -} - - -.nvd3 .nv-groups path.nv-area { - stroke: none; - /* - stroke-linecap: round; - shape-rendering: geometricPrecision; - - stroke-width: 2.5px; - transition: stroke-width 250ms linear; - -moz-transition: stroke-width 250ms linear; - -webkit-transition: stroke-width 250ms linear; - - transition-delay: 250ms - -moz-transition-delay: 250ms; - -webkit-transition-delay: 250ms; - */ -} - -.nvd3 .nv-line.hover path { - stroke-width: 6px; -} - -/* -.nvd3.scatter .groups .point { - fill-opacity: 0.1; - stroke-opacity: 0.1; -} - */ - -.nvd3.nv-line .nvd3.nv-scatter .nv-groups .nv-point { - fill-opacity: 0; - stroke-opacity: 0; -} - -.nvd3.nv-scatter.nv-single-point .nv-groups .nv-point { - fill-opacity: .5 !important; - stroke-opacity: .5 !important; -} - - -.with-transitions .nvd3 .nv-groups .nv-point { - transition: stroke-width 250ms linear, stroke-opacity 250ms linear; - -moz-transition: stroke-width 250ms linear, stroke-opacity 250ms linear; - -webkit-transition: stroke-width 250ms linear, stroke-opacity 250ms linear; - -} - -.nvd3.nv-scatter .nv-groups .nv-point.hover, -.nvd3 .nv-groups .nv-point.hover { - stroke-width: 7px; - fill-opacity: .95 !important; - stroke-opacity: .95 !important; -} - - -.nvd3 .nv-point-paths path { - stroke: #aaa; - stroke-opacity: 0; - fill: #eee; - fill-opacity: 0; -} - - - -.nvd3 .nv-indexLine { - cursor: ew-resize; -} - - -/********** -* Distribution -*/ - -.nvd3 .nv-distribution { - pointer-events: none; -} - - - -/********** -* Scatter -*/ - -/* **Attempting to remove this for useVoronoi(false), need to see if it's required anywhere -.nvd3 .nv-groups .nv-point { - pointer-events: none; -} -*/ - -.nvd3 .nv-groups .nv-point.hover { - stroke-width: 20px; - stroke-opacity: .5; -} - -.nvd3 .nv-scatter .nv-point.hover { - fill-opacity: 1; -} - -/* -.nv-group.hover .nv-point { - fill-opacity: 1; -} -*/ - - -/********** -* Stacked Area -*/ - -.nvd3.nv-stackedarea path.nv-area { - fill-opacity: .7; - /* - stroke-opacity: .65; - fill-opacity: 1; - */ - stroke-opacity: 0; - - transition: fill-opacity 250ms linear, stroke-opacity 250ms linear; - -moz-transition: fill-opacity 250ms linear, stroke-opacity 250ms linear; - -webkit-transition: fill-opacity 250ms linear, stroke-opacity 250ms linear; - - /* - transition-delay: 500ms; - -moz-transition-delay: 500ms; - -webkit-transition-delay: 500ms; - */ - -} - -.nvd3.nv-stackedarea path.nv-area.hover { - fill-opacity: .9; - /* - stroke-opacity: .85; - */ -} -/* -.d3stackedarea .groups path { - stroke-opacity: 0; -} - */ - - - -.nvd3.nv-stackedarea .nv-groups .nv-point { - stroke-opacity: 0; - fill-opacity: 0; -} - -/* -.nvd3.nv-stackedarea .nv-groups .nv-point.hover { - stroke-width: 20px; - stroke-opacity: .75; - fill-opacity: 1; -}*/ - - - -/********** -* Line Plus Bar -*/ - -.nvd3.nv-linePlusBar .nv-bar rect { - fill-opacity: .75; -} - -.nvd3.nv-linePlusBar .nv-bar rect:hover { - fill-opacity: 1; -} - - -/********** -* Bullet -*/ - -.nvd3.nv-bullet { font: 10px sans-serif; } -.nvd3.nv-bullet .nv-measure { fill-opacity: .8; } -.nvd3.nv-bullet .nv-measure:hover { fill-opacity: 1; } -.nvd3.nv-bullet .nv-marker { stroke: #000; stroke-width: 2px; } -.nvd3.nv-bullet .nv-markerTriangle { stroke: #000; fill: #fff; stroke-width: 1.5px; } -.nvd3.nv-bullet .nv-tick line { stroke: #666; stroke-width: .5px; } -.nvd3.nv-bullet .nv-range.nv-s0 { fill: #eee; } -.nvd3.nv-bullet .nv-range.nv-s1 { fill: #ddd; } -.nvd3.nv-bullet .nv-range.nv-s2 { fill: #ccc; } -.nvd3.nv-bullet .nv-title { font-size: 14px; font-weight: bold; } -.nvd3.nv-bullet .nv-subtitle { fill: #999; } - - -.nvd3.nv-bullet .nv-range { - fill: #bababa; - fill-opacity: .4; -} -.nvd3.nv-bullet .nv-range:hover { - fill-opacity: .7; -} - - - -/********** -* Sparkline -*/ - -.nvd3.nv-sparkline path { - fill: none; -} - -.nvd3.nv-sparklineplus g.nv-hoverValue { - pointer-events: none; -} - -.nvd3.nv-sparklineplus .nv-hoverValue line { - stroke: #333; - stroke-width: 1.5px; - } - -.nvd3.nv-sparklineplus, -.nvd3.nv-sparklineplus g { - pointer-events: all; -} - -.nvd3 .nv-hoverArea { - fill-opacity: 0; - stroke-opacity: 0; -} - -.nvd3.nv-sparklineplus .nv-xValue, -.nvd3.nv-sparklineplus .nv-yValue { - /* - stroke: #666; - */ - stroke-width: 0; - font-size: .9em; - font-weight: normal; -} - -.nvd3.nv-sparklineplus .nv-yValue { - stroke: #f66; -} - -.nvd3.nv-sparklineplus .nv-maxValue { - stroke: #2ca02c; - fill: #2ca02c; -} - -.nvd3.nv-sparklineplus .nv-minValue { - stroke: #d62728; - fill: #d62728; -} - -.nvd3.nv-sparklineplus .nv-currentValue { - /* - stroke: #444; - fill: #000; - */ - font-weight: bold; - font-size: 1.1em; -} - -/********** -* historical stock -*/ - -.nvd3.nv-ohlcBar .nv-ticks .nv-tick { - stroke-width: 2px; -} - -.nvd3.nv-ohlcBar .nv-ticks .nv-tick.hover { - stroke-width: 4px; -} - -.nvd3.nv-ohlcBar .nv-ticks .nv-tick.positive { - stroke: #2ca02c; -} - -.nvd3.nv-ohlcBar .nv-ticks .nv-tick.negative { - stroke: #d62728; -} - -.nvd3.nv-historicalStockChart .nv-axis .nv-axislabel { - font-weight: bold; -} - -.nvd3.nv-historicalStockChart .nv-dragTarget { - fill-opacity: 0; - stroke: none; - cursor: move; -} - -.nvd3 .nv-brush .extent { - /* - cursor: ew-resize !important; - */ - fill-opacity: 0 !important; -} - -.nvd3 .nv-brushBackground rect { - stroke: #000; - stroke-width: .4; - fill: #fff; - fill-opacity: .7; -} - - - -/********** -* Indented Tree -*/ - - -/** - * TODO: the following 3 selectors are based on classes used in the example. I should either make them standard and leave them here, or move to a CSS file not included in the library - */ -.nvd3.nv-indentedtree .name { - margin-left: 5px; -} - -.nvd3.nv-indentedtree .clickable { - color: #08C; - cursor: pointer; -} - -.nvd3.nv-indentedtree span.clickable:hover { - color: #005580; - text-decoration: underline; -} - - -.nvd3.nv-indentedtree .nv-childrenCount { - display: inline-block; - margin-left: 5px; -} - -.nvd3.nv-indentedtree .nv-treeicon { - cursor: pointer; - /* - cursor: n-resize; - */ -} - -.nvd3.nv-indentedtree .nv-treeicon.nv-folded { - cursor: pointer; - /* - cursor: s-resize; - */ -} - -/********** -* Parallel Coordinates -*/ - -.nvd3 .background path { - fill: none; - stroke: #ccc; - stroke-opacity: .4; - shape-rendering: crispEdges; -} - -.nvd3 .foreground path { - fill: none; - stroke: steelblue; - stroke-opacity: .7; -} - -.nvd3 .brush .extent { - fill-opacity: .3; - stroke: #fff; - shape-rendering: crispEdges; -} - -.nvd3 .axis line, .axis path { - fill: none; - stroke: #000; - shape-rendering: crispEdges; -} - -.nvd3 .axis text { - text-shadow: 0 1px 0 #fff; -} - -/**** -Interactive Layer -*/ -.nvd3 .nv-interactiveGuideLine { - pointer-events:none; -} -.nvd3 line.nv-guideline { - stroke: #ccc; -}
\ No newline at end of file diff --git a/addons/web_graph/static/src/js/graph_view.js b/addons/web_graph/static/src/js/graph_view.js deleted file mode 100644 index acd075dcc73..00000000000 --- a/addons/web_graph/static/src/js/graph_view.js +++ /dev/null @@ -1,297 +0,0 @@ -/*--------------------------------------------------------- - * OpenERP web_graph - *---------------------------------------------------------*/ - -/* jshint undef: false */ - -openerp.web_graph = function (instance) { -'use strict'; - -var _lt = instance.web._lt; -var _t = instance.web._t; - -instance.web.views.add('graph', 'instance.web_graph.GraphView'); - -instance.web_graph.GraphView = instance.web.View.extend({ - display_name: _lt('Graph'), - view_type: 'graph', - - // ---------------------------------------------------------------------- - // Init stuff - // ---------------------------------------------------------------------- - init: function(parent, dataset, view_id, options) { - var self = this; - this._super(parent, dataset, view_id, options); - this.dataset = dataset; - this.model = new instance.web.Model(dataset.model, {group_by_no_leaf: true}); - this.search_view = parent.searchview; - this.col_search_field = { - get_context: function() { return { col_group_by: self.graph_widget.get_col_groupbys(), - measures: self.graph_widget.get_current_measures()};}, - get_domain: function () {}, - get_groupby: function () {}, - }; - this.$buttons = options.$buttons; - this.view_loaded = $.Deferred(); - }, - - start: function() { - var $buttons = instance.web.qweb.render("graph-view-buttons"); - this.$buttons.html($buttons); - return this._super(); - }, - - view_loading: function (fields_view_get) { - var self = this, - arch = fields_view_get.arch; - - this.widget_config = { - title: arch.attrs.string, - stacked : (arch.attrs.stacked === 'True'), - mode: arch.attrs.type || 'bar', - measures: [], - row_groupby: [], - col_groupby: [], - graph_view: this, - $buttons: this.$buttons - }; - - _.each(arch.children, function (field) { - var field_name = field.attrs.name; - if (_.has(field.attrs, 'interval')) { - field_name = field.attrs.name + ':' + field.attrs.interval; - } - //noinspection FallThroughInSwitchStatementJS - switch (field.attrs.type) { - case 'measure': - self.widget_config.measures.push(field_name); - break; - case 'col': - self.widget_config.col_groupby.push(field_name); - break; - default: - if ('operator' in field.attrs) { - self.widget_config.measures.push(field_name); - break; - } - case 'row': - self.widget_config.row_groupby.push(field_name); - } - }); - if (self.widget_config.measures.length === 0) { - self.widget_config.measures.push('__count'); - } - this.view_loaded.resolve(); - }, - - do_search: function (domain, context, group_by) { - var self = this; - this.view_loaded.done(function () { - self._do_search(domain, context, group_by); - }); - }, - - _do_search: function (domain, context, group_by) { - if (this.ignore_do_search) { - this.ignore_do_search = false; - return; - } - var self = this, - groupbys = this.get_groupbys_from_searchview(), - col_group_by = groupbys.col_group_by, - measures = groupbys.measures; - - if (!this.graph_widget) { - this.widget_config.context = _.clone(context); - this.widget_config.context.group_by_no_leaf = true; - if (group_by.length) { - this.widget_config.row_groupby = group_by; - } - if (col_group_by.length) { - this.widget_config.col_groupby = col_group_by; - } - if (measures.length) { - this.widget_config.measures = measures; - } - this.graph_widget = new openerp.web_graph.Graph(this, this.model, domain, this.widget_config); - this.graph_widget.appendTo(this.$el).done(function () { - - }); - return; - } - - this.graph_widget.set(domain, group_by, col_group_by, measures); - }, - - get_groupbys_from_searchview: function () { - var result = { group_by: [], col_group_by: [], measures: []}, - searchdata = this.search_view.build_search_data(); - - _.each(searchdata.groupbys, function (data) { - data = (_.isString(data)) ? py.eval(data) : data; - result.group_by = result.group_by.concat(data.group_by); - if (data.col_group_by) { - result.col_group_by = result.col_group_by.concat(data.col_group_by); - } - if (data.measures) { - result.measures = result.measures.concat(data.measures); - } - }); - - if (result.col_group_by.length) { - return result; - } - _.each(searchdata.contexts, function (context) { - if (context.col_group_by) { - result.col_group_by = result.col_group_by.concat(context.col_group_by); - } - if (context.measures) { - result.measures = result.measures.concat(context.measures); - } - }); - return result; - }, - - do_show: function () { - this.do_push_state({}); - return this._super(); - }, - - // ---------------------------------------------------------------------- - // Search view integration - // ---------------------------------------------------------------------- - - // add groupby to the search view - register_groupby: function(row_groupby, col_groupby) { - var query = this.search_view.query, - groupbys = this.get_groupbys_from_searchview(), - search_row_groupby = groupbys.group_by, - search_col_groupby = groupbys.col_group_by, - search_measures = groupbys.measures, - row_gb_changed = !_.isEqual(_.pluck(row_groupby, 'field'), search_row_groupby), - col_gb_changed = !_.isEqual(_.pluck(col_groupby, 'field'), search_col_groupby), - measures_gb_changed = !_.isEqual(_.pluck(col_groupby, 'field'), search_col_groupby); - if (!_.has(this.search_view, '_s_groupby')) { return; } - - if (!row_gb_changed && !col_gb_changed) { - return; - } - - var custom_groups = this.get_custom_filter_groupbys(); - row_groupby = row_groupby.slice(custom_groups.groupby.length); - col_groupby = col_groupby.slice(custom_groups.col_groupby.length); - - if (row_gb_changed && col_gb_changed) { - // when two changes to the search view will be done, the method do_search - // will be called twice, once with the correct groupby and incorrect col_groupby, - // and once with correct informations. This flag is necessary to prevent the - // incorrect informations to propagate and trigger useless queries - this.ignore_do_search = true; - } - - if (row_gb_changed) { - // add row groupbys - var row_facet = this.make_row_groupby_facets(row_groupby), - row_search_facet = query.findWhere({category:'GroupBy'}); - - if (row_search_facet) { - row_search_facet.values.reset(row_facet.values, {focus_input:false}); - } else { - if (row_groupby.length) { - query.add(row_facet); - } - } - } - - if (col_gb_changed) { - // add col groupbys - var col_facet = this.make_col_groupby_facets(col_groupby), - col_search_facet = query.findWhere({category:'ColGroupBy'}); - - if (col_search_facet) { - col_search_facet.values.reset(col_facet.values, {focus_input:false}); - } else { - if (col_groupby.length) { - query.add(col_facet); - } - } - } - }, - - make_row_groupby_facets: function(groupbys) { - return { - category:'GroupBy', - values: this.make_groupby_values(groupbys, 'group_by'), - icon:'fa fa-bars', - field: this.search_view._s_groupby - }; - }, - - make_col_groupby_facets: function(groupbys) { - return { - category:'ColGroupBy', - values: this.make_groupby_values(groupbys, 'col_group_by'), - icon:'fa fa-level-down', - field: this.col_search_field - }; - }, - - make_groupby_values: function (groupbys, category) { - return _.map(groupbys, function (groupby) { - var context = {}; - context[category] = groupby.field; - var value; - if (category === 'group_by' && groupby.type !== 'date' && groupby.type !== 'datetime') { - value = groupby.filter || {attrs: {domain: [], context: context}}; - } else { - value = {attrs: {domain: [], context: context}}; - } - return { - label: groupby.string, - value: value - }; - }); - }, - - get_custom_filter_groupbys: function () { - var gb = [], - col_gb = []; - - var facet = this.search_view.query.at(0); - if (facet) { - if (facet.get('category') !== 'GroupBy' && facet.get('category') !== 'ColGroupBy') { - gb = get_groupby(facet); - col_gb = get_col_groupby(facet); - } - } - return { - groupby: gb, - col_groupby: col_gb, - } - } - -}); - -function get_groupby(facet) { - var field = facet.get('field'), - result = []; - if ('get_groupby' in field) { - result = instance.web.pyeval.sync_eval_domains_and_contexts({ - group_by_seq: field.get_groupby(facet) - }).group_by; - } - return result; -} -function get_col_groupby(facet) { - var field = facet.get('field'), - result = []; - if ('get_context' in field) { - result = instance.web.pyeval.sync_eval_domains_and_contexts({ - contexts: field.get_context(facet) - }).context.col_group_by || []; - } - return result; -} - - -}; diff --git a/addons/web_graph/static/src/js/graph_widget.js b/addons/web_graph/static/src/js/graph_widget.js deleted file mode 100644 index afb691e4790..00000000000 --- a/addons/web_graph/static/src/js/graph_widget.js +++ /dev/null @@ -1,875 +0,0 @@ - -/* jshint undef: false */ - -(function () { -'use strict'; -var QWeb = openerp.web.qweb; -var _lt = openerp.web._lt; -var _t = openerp.web._t; - -nv.dev = false; // sets nvd3 library in production mode - -openerp.web_graph.Graph = openerp.web.Widget.extend({ - template: 'GraphWidget', - - // ---------------------------------------------------------------------- - // Init stuff - // ---------------------------------------------------------------------- - init: function(parent, model, domain, options) { - this._super(parent); - this.model = model; - this.domain = domain; - this.mode = options.mode || 'pivot'; // pivot, bar, pie, line - this.visible_ui = options.visible_ui || true; - this.bar_ui = options.bar_ui || 'group'; - this.graph_view = options.graph_view || null; - this.pivot_options = options; - this.title = options.title || 'Data'; - this.$buttons = options.$buttons; - }, - - start: function() { - var self = this; - this.table = $('<table>'); - this.$('.graph_main_content').append(this.table); - - this.$buttons.find('.oe-pivot-mode').click(function () { - self.set_mode.bind(self)('pivot'); - }); - this.$measure_list = this.$buttons.find('.oe-measure-list'); - - this.$buttons.find('.oe-bar-mode').click(function () { - self.set_mode.bind(self)('bar'); - }); - this.$buttons.find('.oe-line-mode').click(function () { - self.set_mode.bind(self)('line'); - }); - this.$buttons.find('.oe-pie-mode').click(function () { - self.set_mode.bind(self)('pie'); - }); - this.$buttons.find('.fa-expand').click(this.swap_axis.bind(this)); - this.$buttons.find('.fa-arrows-alt').click(function () { - self.pivot.expand_all().then(self.proxy('display_data')); - }); - this.$buttons.find('.fa-download').click(this.export_xls.bind(this)); - - var indexes = {'pivot': 0, 'bar': 1, 'line': 2, 'chart': 3}; - this.$('.graph_mode_selection label').eq(indexes[this.mode]).addClass('selected'); - - if (this.mode !== 'pivot') { - this.$('.graph_heatmap label').addClass('disabled'); - this.$('.graph_main_content').addClass('graph_chart_mode'); - } else { - this.$('.graph_main_content').addClass('graph_pivot_mode'); - } - // get search view - var parent = this.getParent(); - while (!(parent instanceof openerp.web.ViewManager)) { - parent = parent.getParent(); - } - this.search_view = parent.searchview; - - openerp.session.rpc('/web_graph/check_xlwt').then(function (result) { - self.$('.graph_options_selection label').last().toggle(result); - }); - - this.$buttons.find('button').tooltip(); - - return this.model.call('fields_get', { - context: this.graph_view.dataset.context - }).then(function (f) { - self.fields = f; - self.fields.__count = {field:'__count', type: 'integer', string:_t('Count')}; - self.groupby_fields = self.get_groupby_fields(); - self.measure_list = self.get_measures(); - self.add_measures_to_options(); - self.pivot_options.row_groupby = self.create_field_values(self.pivot_options.row_groupby || []); - self.pivot_options.col_groupby = self.create_field_values(self.pivot_options.col_groupby || []); - self.pivot_options.measures = self.create_field_values(self.pivot_options.measures || [{field:'__count', type: 'integer', string:'Count'}]); - self.pivot = new openerp.web_graph.PivotTable(self.model, self.domain, self.fields, self.pivot_options); - self.pivot.update_data().then(function () { - self.display_data(); - if (self.graph_view) { - self.graph_view.register_groupby(self.pivot.rows.groupby, self.pivot.cols.groupby); - } - }); - openerp.web.bus.on('click', self, function (event) { - if (self.dropdown) { - self.$row_clicked = $(event.target).closest('tr'); - self.dropdown.remove(); - self.dropdown = null; - } - }); - self.put_measure_checkmarks(); - }); - }, - - get_groupby_fields: function () { - var search_fields = this.get_search_fields(), - search_field_names = _.pluck(search_fields, 'field'), - other_fields = [], - groupable_types = ['many2one', 'char', 'boolean', 'selection', 'date', 'datetime']; - - _.each(this.fields, function (val, key) { - if (!_.contains(search_field_names, key) && - _.contains(groupable_types, val.type) && - val.store === true) { - other_fields.push({ - field: key, - string: val.string, - }); - } - }); - return search_fields.concat(other_fields); - }, - - // this method gets the fields that appear in the search view, under the - // 'Groupby' heading - get_search_fields: function () { - // this method is disabled for now. This requires extensive changes because the - // search view works quite differently: But the graph view is going to be split - // soon in pivot view and graph view. The pivot view will then properly handle - // groupbys. - return []; - var self = this; - - var groupbygroups = _(this.search_view.drawer.inputs).select(function (g) { - return g instanceof openerp.web.search.GroupbyGroup; - }); - - var filters = _.flatten(_.pluck(groupbygroups, 'filters'), true), - groupbys = _.flatten(_.map(filters, function (filter) { - var groupby = py.eval(filter.attrs.context).group_by; - if (!(groupby instanceof Array)) { groupby = [groupby]; } - return _.map(groupby, function(g) { - return {field: g, filter: filter}; - }); - })); - - return _.uniq(_.map(groupbys, function (groupby) { - var field = groupby.field, - filter = groupby.filter, - raw_field = field.split(':')[0], - string = (field === raw_field) ? filter.attrs.string : self.fields[raw_field].string; - - filter = (field === raw_field) ? filter : undefined; - - return { field: raw_field, string: string, filter: filter }; - }), false, function (filter) {return filter.field;}); - }, - - // Extracts the integer/float fields which are not 'id' - get_measures: function() { - return _.compact(_.map(this.fields, function (f, id) { - if (((f.type === 'integer') || (f.type === 'float')) && - (id !== 'id') && - (f.store !== false)) { - return {field:id, type: f.type, string: f.string}; - } - })); - }, - - add_measures_to_options: function() { - this.$measure_list.append( - _.map(this.measure_list, function (measure) { - return $('<li>').append($('<a>').attr('data-choice', measure.field) - .attr('href', '#') - .text(measure.string)); - })); - this.$measure_list.find('li').click(this.measure_selection.bind(this)); - }, - - // ---------------------------------------------------------------------- - // Configuration methods - // ---------------------------------------------------------------------- - set: function (domain, row_groupby, col_groupby, measures_groupby) { - if (!this.pivot) { - this.pivot_options.domain = domain; - this.pivot_options.row_groupby = row_groupby; - this.pivot_options.col_groupby = col_groupby; - this.pivot_options.measures_groupby = measures_groupby; - return; - } - var row_gbs = this.create_field_values(row_groupby), - col_gbs = this.create_field_values(col_groupby), - measures_gbs = this.create_field_values(measures_groupby), - dom_changed = !_.isEqual(this.pivot.domain, domain), - row_gb_changed = !_.isEqual(row_gbs, this.pivot.rows.groupby), - col_gb_changed = !_.isEqual(col_gbs, this.pivot.cols.groupby), - measures_gb_changed = !_.isEqual(measures_gbs, this.pivot.measures), - row_reduced = is_strict_beginning_of(row_gbs, this.pivot.rows.groupby), - col_reduced = is_strict_beginning_of(col_gbs, this.pivot.cols.groupby), - measures_reduced = is_strict_beginning_of(measures_gbs, this.pivot.measures); - - if (!dom_changed && row_reduced && !col_gb_changed && !measures_gb_changed) { - this.pivot.fold_with_depth(this.pivot.rows, row_gbs.length); - this.display_data(); - return; - } - if (!dom_changed && col_reduced && !row_gb_changed && !measures_gb_changed) { - this.pivot.fold_with_depth(this.pivot.cols, col_gbs.length); - this.display_data(); - return; - } - - if (!dom_changed && col_reduced && row_reduced && !measures_gb_changed) { - this.pivot.fold_with_depth(this.pivot.rows, row_gbs.length); - this.pivot.fold_with_depth(this.pivot.cols, col_gbs.length); - this.display_data(); - return; - } - - if (dom_changed || row_gb_changed || col_gb_changed || measures_gb_changed) { - this.pivot.set(domain, row_gbs, col_gbs, measures_gbs).then(this.proxy('display_data')); - } - - if (measures_gb_changed) { - this.put_measure_checkmarks(); - } - }, - - set_mode: function (mode) { - this.mode = mode; - - if (mode === 'pivot') { - this.$('.graph_heatmap label').removeClass('disabled'); - this.$('.graph_main_content').removeClass('graph_chart_mode').addClass('graph_pivot_mode'); - } else { - this.$('.graph_heatmap label').addClass('disabled'); - this.$('.graph_main_content').removeClass('graph_pivot_mode').addClass('graph_chart_mode'); - } - this.display_data(); - }, - - create_field_value: function (f) { - var field = (_.contains(f, ':')) ? f.split(':')[0] : f, - groupby_field = _.findWhere(this.groupby_fields, {field:field}), - string = groupby_field ? groupby_field.string : this.fields[field].string, - result = {field: f, string: string, type: this.fields[field].type }; - - if (groupby_field) { - result.filter = groupby_field.filter; - } - - return result; - }, - - create_field_values: function (field_ids) { - return _.map(field_ids, this.proxy('create_field_value')); - }, - - - get_col_groupbys: function () { - return _.pluck(this.pivot.cols.groupby, 'field'); - }, - - get_current_measures: function () { - return _.pluck(this.pivot.measures, 'field'); - }, - - // ---------------------------------------------------------------------- - // UI code - // ---------------------------------------------------------------------- - events: { - 'click .graph_mode_selection label' : 'mode_selection', - 'click .graph_measure_selection li' : 'measure_selection', - 'click .graph_options_selection label' : 'option_selection', - 'click .graph_heatmap label' : 'heatmap_mode_selection', - 'click .web_graph_click' : 'header_cell_clicked', - 'click a.field-selection' : 'field_selection', - }, - - mode_selection: function (event) { - event.preventDefault(); - var mode = event.currentTarget.getAttribute('data-mode'); - this.set_mode(mode); - }, - - measure_selection: function (event) { - event.preventDefault(); - event.stopPropagation(); - var measure_field = event.target.getAttribute('data-choice'); - var measure = { - field: measure_field, - type: this.fields[measure_field].type, - string: this.fields[measure_field].string - }; - - this.pivot.toggle_measure(measure).then(this.proxy('display_data')); - this.put_measure_checkmarks(); - }, - - put_measure_checkmarks: function () { - var self = this, - measures_li = this.$measure_list.find('li'); - measures_li.removeClass('selected'); - _.each(this.measure_list, function (measure, index) { - if (_.findWhere(self.pivot.measures, measure)) { - measures_li.eq(index).addClass('selected'); - } - }); - - }, - - option_selection: function (event) { - event.preventDefault(); - switch (event.currentTarget.getAttribute('data-choice')) { - case 'swap_axis': - this.swap_axis(); - break; - case 'expand_all': - this.pivot.expand_all().then(this.proxy('display_data')); - break; - case 'update_values': - this.pivot.update_data().then(this.proxy('display_data')); - break; - case 'export_data': - this.export_xls(); - break; - } - }, - - header_cell_clicked: function (event) { - event.preventDefault(); - event.stopPropagation(); - var id = event.target.getAttribute('data-id'), - header = this.pivot.get_header(id), - self = this; - - if (header.expanded) { - if (header.root === this.pivot.rows) { - this.fold_row(header, event); - } else { - this.fold_col(header); - } - return; - } - if (header.path.length < header.root.groupby.length) { - this.$row_clicked = $(event.target).closest('tr'); - this.expand(id); - return; - } - if (!this.groupby_fields.length) { - return; - } - - var fields = _.map(this.groupby_fields, function (field) { - return {id: field.field, value: field.string, type:self.fields[field.field.split(':')[0]].type}; - }); - if (this.dropdown) { - this.dropdown.remove(); - } - this.dropdown = $(QWeb.render('field_selection', {fields:fields, header_id:id})); - $(event.target).after(this.dropdown); - this.dropdown.css({ - position:'absolute', - left:event.originalEvent.layerX, - }); - this.$('.field-selection').next('.dropdown-menu').first().toggle(); - }, - - field_selection: function (event) { - var id = event.target.getAttribute('data-id'), - field_id = event.target.getAttribute('data-field-id'), - interval, - groupby = this.create_field_value(field_id); - event.preventDefault(); - if (this.fields[field_id].type === 'date' || this.fields[field_id].type === 'datetime') { - interval = event.target.getAttribute('data-interval'); - groupby.field = groupby.field + ':' + interval; - } - this.expand(id, groupby); - }, - - // ---------------------------------------------------------------------- - // Pivot Table integration - // ---------------------------------------------------------------------- - expand: function (header_id, groupby) { - var self = this, - header = this.pivot.get_header(header_id), - update_groupby = !!groupby; - - groupby = groupby || header.root.groupby[header.path.length]; - - this.pivot.expand(header_id, groupby).then(function () { - if (header.root === self.pivot.rows) { - // expanding rows can be done by only inserting in the dom - // console.log(event.target); - var rows = self.build_rows(header.children); - var doc_fragment = $(document.createDocumentFragment()); - rows.map(function (row) { - doc_fragment.append(self.draw_row(row, 0)); - }); - self.$row_clicked.after(doc_fragment); - } else { - // expanding cols will redraw the full table - self.display_data(); - } - if (update_groupby && self.graph_view) { - self.graph_view.register_groupby(self.pivot.rows.groupby, self.pivot.cols.groupby); - } - }); - - }, - - fold_row: function (header, event) { - var rows_before = this.pivot.rows.headers.length, - update_groupby = this.pivot.fold(header), - rows_after = this.pivot.rows.headers.length, - rows_removed = rows_before - rows_after; - - if (rows_after === 1) { - // probably faster to redraw the unique row instead of removing everything - this.display_data(); - } else { - var $row = $(event.target).parent().parent(); - $row.nextAll().slice(0,rows_removed).remove(); - } - if (update_groupby && this.graph_view) { - this.graph_view.register_groupby(this.pivot.rows.groupby, this.pivot.cols.groupby); - } - }, - - fold_col: function (header) { - var update_groupby = this.pivot.fold(header); - - this.display_data(); - if (update_groupby && this.graph_view) { - this.graph_view.register_groupby(this.pivot.rows.groupby, this.pivot.cols.groupby); - } - }, - - swap_axis: function () { - this.pivot.swap_axis(); - this.display_data(); - this.graph_view.register_groupby(this.pivot.rows.groupby, this.pivot.cols.groupby); - }, - - // ---------------------------------------------------------------------- - // Convert Pivot data structure into table structure : - // compute rows, cols, colors, cell width, cell height, ... - // ---------------------------------------------------------------------- - build_table: function(raw) { - return { - headers: this.build_headers(), - measure_row: this.build_measure_row(), - rows: this.build_rows(this.pivot.rows.headers,raw), - nbr_measures: this.pivot.measures.length, - title: this.title, - }; - }, - - build_headers: function () { - var pivot = this.pivot, - nbr_measures = pivot.measures.length, - height = _.max(_.map(pivot.cols.headers, function(g) {return g.path.length;})) + 1, - rows = []; - - _.each(pivot.cols.headers, function (col) { - var cell_width = nbr_measures * (col.expanded ? pivot.get_ancestor_leaves(col).length : 1), - cell_height = col.expanded ? 1 : height - col.path.length, - cell = {width: cell_width, height: cell_height, title: col.title, id: col.id, expanded: col.expanded}; - if (rows[col.path.length]) { - rows[col.path.length].push(cell); - } else { - rows[col.path.length] = [cell]; - } - }); - - if (pivot.get_cols_leaves().length > 1) { - rows[0].push({width: nbr_measures, height: height, title: ' ', id: pivot.main_col().id }); - } - if (pivot.cols.headers.length === 1) { - rows = [[{width: nbr_measures, height: 1, title: _t('Total'), id: pivot.main_col().id, expanded: false}]]; - } - return rows; - }, - - build_measure_row: function () { - var nbr_leaves = this.pivot.get_cols_leaves().length, - nbr_cols = nbr_leaves + ((nbr_leaves > 1) ? 1 : 0), - result = [], - add_total = this.pivot.get_cols_leaves().length > 1, - i, m; - for (i = 0; i < nbr_cols; i++) { - for (m = 0; m < this.pivot.measures.length; m++) { - result.push({ - text:this.pivot.measures[m].string, - is_bold: add_total && (i === nbr_cols - 1) - }); - } - } - return result; - }, - - make_cell: function (row, col, value, index, raw) { - var formatted_value = raw && !_.isUndefined(value) ? value : openerp.web.format_value(value, {type:this.pivot.measures[index].type}), - cell = {value:formatted_value}; - - return cell; - }, - - build_rows: function (headers, raw) { - var self = this, - pivot = this.pivot, - m, i, j, k, cell, row; - - var rows = []; - var cells, pivot_cells, values; - - var nbr_of_rows = headers.length; - var col_headers = pivot.get_cols_leaves(); - - for (i = 0; i < nbr_of_rows; i++) { - row = headers[i]; - cells = []; - pivot_cells = []; - for (j = 0; j < pivot.cells.length; j++) { - if (pivot.cells[j].x == row.id || pivot.cells[j].y == row.id) { - pivot_cells.push(pivot.cells[j]); - } - } - - for (j = 0; j < col_headers.length; j++) { - values = undefined; - for (k = 0; k < pivot_cells.length; k++) { - if (pivot_cells[k].x == col_headers[j].id || pivot_cells[k].y == col_headers[j].id) { - values = pivot_cells[k].values; - break; - } - } - if (!values) { values = new Array(pivot.measures.length);} - for (m = 0; m < pivot.measures.length; m++) { - cells.push(self.make_cell(row,col_headers[j],values[m], m, raw)); - } - } - if (col_headers.length > 1) { - var totals = pivot.get_total(row); - for (m = 0; m < pivot.measures.length; m++) { - cell = self.make_cell(row, pivot.cols.headers[0], totals[m], m, raw); - cell.is_bold = 'true'; - cells.push(cell); - } - } - rows.push({ - id: row.id, - indent: row.path.length, - title: row.title, - expanded: row.expanded, - cells: cells, - }); - } - - return rows; - }, - - // ---------------------------------------------------------------------- - // Main display method - // ---------------------------------------------------------------------- - display_data: function () { - var scroll = $(window).scrollTop(); - this.$('.graph_main_content svg').remove(); - this.$('.graph_main_content div').remove(); - this.table.empty(); - this.$('.graph_options_selection label').last().toggleClass('disabled', this.pivot.no_data); - this.width = this.$el.width(); - this.height = Math.min(Math.max(document.documentElement.clientHeight - 116 - 60, 250), Math.round(0.8*this.$el.width())); - - this.$('.graph_header').toggle(this.visible_ui); - if (this.pivot.no_data) { - this.$('.graph_main_content').append($(QWeb.render('graph_no_data'))); - } else { - if (this.mode === 'pivot') { - this.draw_table(); - $(window).scrollTop(scroll); - } else { - this.$('.graph_main_content').append($('<div><svg>')); - this.svg = this.$('.graph_main_content svg')[0]; - this[this.mode](); - } - } - }, - - // ---------------------------------------------------------------------- - // Drawing the table - // ---------------------------------------------------------------------- - draw_table: function () { - var custom_gbs = this.graph_view.get_custom_filter_groupbys(), - frozen_rows = custom_gbs.groupby.length, - frozen_cols = custom_gbs.col_groupby.length; - - var table = this.build_table(); - var doc_fragment = $(document.createDocumentFragment()); - this.draw_headers(table.headers, doc_fragment, frozen_cols); - this.draw_measure_row(table.measure_row, doc_fragment); - this.draw_rows(table.rows, doc_fragment, frozen_rows); - this.table.append(doc_fragment); - }, - - make_header_cell: function (header, frozen) { - var cell = (_.has(header, 'cells') ? $('<td>') : $('<th>')) - .addClass('graph_border') - .attr('rowspan', header.height) - .attr('colspan', header.width); - var $content = $('<span>').attr('href','#') - .text(' ' + (header.title || _t('Undefined'))) - .css('margin-left', header.indent*30 + 'px') - .attr('data-id', header.id); - if (_.has(header, 'expanded')) { - if (('indent' in header) && header.indent >= frozen) { - $content.addClass(header.expanded ? 'fa fa-minus-square' : 'fa fa-plus-square'); - $content.addClass('web_graph_click'); - } - if (!('indent' in header) && header.lvl >= frozen) { - $content.addClass(header.expanded ? 'fa fa-minus-square' : 'fa fa-plus-square'); - $content.addClass('web_graph_click'); - } - } else { - $content.css('font-weight', 'bold'); - } - return cell.append($content); - }, - - draw_headers: function (headers, doc_fragment, frozen_cols) { - var make_cell = this.make_header_cell, - $empty_cell = $('<th>').attr('rowspan', headers.length), - $thead = $('<thead>'); - - _.each(headers, function (row, lvl) { - var $row = $('<tr>'); - _.each(row, function (header) { - header.lvl = lvl; - $row.append(make_cell(header, frozen_cols)); - }); - $thead.append($row); - }); - $thead.children(':first').prepend($empty_cell); - doc_fragment.append($thead); - this.$thead = $thead; - }, - - draw_measure_row: function (measure_row) { - var $row = $('<tr>').append('<th>'); - _.each(measure_row, function (cell) { - var $cell = $('<th>').addClass('measure_row').text(cell.text); - if (cell.is_bold) {$cell.css('font-weight', 'bold');} - $row.append($cell); - }); - this.$thead.append($row); - }, - - draw_row: function (row, frozen_rows) { - var $row = $('<tr>') - .attr('data-indent', row.indent) - .append(this.make_header_cell(row, frozen_rows)); - - var cells_length = row.cells.length; - var cells_list = []; - var cell, hcell; - - for (var j = 0; j < cells_length; j++) { - cell = row.cells[j]; - hcell = '<td'; - if (cell.is_bold || cell.color) { - hcell += ' style="'; - if (cell.is_bold) hcell += 'font-weight: bold;'; - if (cell.color) hcell += 'background-color:' + $.Color(255, cell.color, cell.color) + ';'; - hcell += '"'; - } - hcell += '>' + cell.value + '</td>'; - cells_list[j] = hcell; - } - return $row.append(cells_list.join('')); - }, - - draw_rows: function (rows, doc_fragment, frozen_rows) { - var rows_length = rows.length, - $tbody = $('<tbody>'); - - doc_fragment.append($tbody); - for (var i = 0; i < rows_length; i++) { - $tbody.append(this.draw_row(rows[i], frozen_rows)); - } - }, - - // ---------------------------------------------------------------------- - // Drawing charts code - // ---------------------------------------------------------------------- - bar: function () { - var self = this, - dim_x = this.pivot.rows.groupby.length, - dim_y = this.pivot.cols.groupby.length, - show_controls = (this.width > 400 && this.height > 300 && dim_x + dim_y >=2), - data; - - // No groupby - if ((dim_x === 0) && (dim_y === 0)) { - data = [{key: _t('Total'), values:[{ - x: _t('Total'), - y: this.pivot.get_total()[0], - }]}]; - // Only column groupbys - } else if ((dim_x === 0) && (dim_y >= 1)){ - data = _.map(this.pivot.get_cols_with_depth(1), function (header) { - return { - key: header.title, - values: [{x:header.title, y: self.pivot.get_total(header)[0]}] - }; - }); - // Just 1 row groupby - } else if ((dim_x === 1) && (dim_y === 0)) { - data = _.map(this.pivot.main_row().children, function (pt) { - var value = self.pivot.get_total(pt)[0], - title = (pt.title !== undefined) ? pt.title : _t('Undefined'); - return {x: title, y: value}; - }); - data = [{key: self.pivot.measures[0].string, values:data}]; - // 1 row groupby and some col groupbys - } else if ((dim_x === 1) && (dim_y >= 1)) { - data = _.map(this.pivot.get_cols_with_depth(1), function (colhdr) { - var values = _.map(self.pivot.get_rows_with_depth(1), function (header) { - return { - x: header.title || _t('Undefined'), - y: self.pivot.get_values(header.id, colhdr.id)[0] || 0 - }; - }); - return {key: colhdr.title || _t('Undefined'), values: values}; - }); - // At least two row groupby - } else { - var keys = _.uniq(_.map(this.pivot.get_rows_with_depth(2), function (hdr) { - return hdr.title || _t('Undefined'); - })); - data = _.map(keys, function (key) { - var values = _.map(self.pivot.get_rows_with_depth(1), function (hdr) { - var subhdr = _.find(hdr.children, function (child) { - return ((child.title === key) || ((child.title === undefined) && (key === _t('Undefined')))); - }); - return { - x: hdr.title || _t('Undefined'), - y: (subhdr) ? self.pivot.get_total(subhdr)[0] : 0 - }; - }); - return {key:key, values: values}; - }); - } - - nv.addGraph(function () { - var chart = nv.models.multiBarChart() - .reduceXTicks(false) - .stacked(self.bar_ui === 'stack') - .showControls(show_controls); - - if (self.width / data[0].values.length < 80) { - chart.rotateLabels(-15); - chart.reduceXTicks(true); - chart.margin({bottom:40}); - } - - d3.select(self.svg) - .datum(data) - .attr('width', self.width) - .attr('height', self.height) - .call(chart); - - nv.utils.windowResize(chart.update); - return chart; - }); - - }, - - line: function () { - var self = this, - dim_x = this.pivot.rows.groupby.length, - dim_y = this.pivot.cols.groupby.length; - - var rows = this.pivot.get_rows_with_depth(dim_x), - labels = _.pluck(rows, 'title'); - - var data = _.map(this.pivot.get_cols_leaves(), function (col) { - var values = _.map(rows, function (row, index) { - return {x: index, y: self.pivot.get_values(row.id,col.id)[0] || 0}; - }); - var title = _.map(col.path, function (p) { - return p || _t('Undefined'); - }).join('/'); - if (dim_y === 0) { - title = self.pivot.measures[0].string; - } - return {values: values, key: title}; - }); - - nv.addGraph(function () { - var chart = nv.models.lineChart() - .x(function (d,u) { return u; }); - - chart.xAxis.tickFormat(function (d,u) {return labels[d];}); - - d3.select(self.svg) - .attr('width', self.width) - .attr('height', self.height) - .datum(data) - .call(chart); - - return chart; - }); - }, - - pie: function () { - var self = this, - dim_x = this.pivot.rows.groupby.length; - var data = _.map(this.pivot.get_rows_leaves(), function (row) { - var title = _.map(row.path, function (p) { - return p || _t('Undefined'); - }).join('/'); - if (dim_x === 0) { - title = self.measure_label; - } - return {x: title, y: self.pivot.get_total(row)[0]}; - }); - - nv.addGraph(function () { - var chart = nv.models.pieChart() - .width(self.width) - .height(self.height) - .color(d3.scale.category10().range()); - - d3.select(self.svg) - .datum(data) - .transition().duration(1200) - .attr('width', self.width) - .attr('height', self.height) - .call(chart); - - nv.utils.windowResize(chart.update); - return chart; - }); - }, - - // ---------------------------------------------------------------------- - // Controller stuff... - // ---------------------------------------------------------------------- - export_xls: function() { - var c = openerp.webclient.crashmanager; - openerp.web.blockUI(); - this.session.get_file({ - url: '/web_graph/export_xls', - data: {data: JSON.stringify(this.build_table(true))}, - complete: openerp.web.unblockUI, - error: c.rpc_error.bind(c) - }); - }, - -}); - -// Utility function: returns true if the beginning of array2 is array1 and -// if array1 is not array2 -function is_strict_beginning_of (array1, array2) { - if (array1.length >= array2.length) { return false; } - var result = true; - for (var i = 0; i < array1.length; i++) { - if (!_.isEqual(array1[i], array2[i])) { return false;} - } - return result; -} -})(); diff --git a/addons/web_graph/static/src/js/pivot_table.js b/addons/web_graph/static/src/js/pivot_table.js deleted file mode 100644 index a99bae1756f..00000000000 --- a/addons/web_graph/static/src/js/pivot_table.js +++ /dev/null @@ -1,468 +0,0 @@ - -/* jshint undef: false */ - -(function () { -'use strict'; - -var _lt = openerp.web._lt; -var _t = openerp.web._t; - -// PivotTable requires a call to update_data after initialization -openerp.web_graph.PivotTable = openerp.web.Class.extend({ - - init: function (model, domain, fields, options) { - this.cells = []; - this.domain = domain; - this.context = options.context; - this.no_data = true; - this.updating = false; - this.model = model; - this.fields = fields; - this.fields.__count = {type: 'integer', string:_t('Count')}; - this.measures = options.measures || []; - this.rows = { groupby: options.row_groupby, headers: null }; - this.cols = { groupby: options.col_groupby, headers: null }; - }, - - // ---------------------------------------------------------------------- - // Configuration methods - // ---------------------------------------------------------------------- - // this.measures: list of measure [measure], measure = {field: _, string: _, type: _} - // this.rows.groupby, this.cols.groupby : list of groupbys used for describing rows (...), - // a groupby is also {field:_, string:_, type:_} - // If its type is date/datetime, field can have the corresponding interval in its description, - // for example 'create_date:week'. - set_measures: function (measures) { - this.measures = measures; - return this.update_data(); - }, - - toggle_measure: function (measure) { - var current_measure = _.findWhere(this.measures, measure); - if (current_measure) { // remove current_measure - var index = this.measures.indexOf(current_measure); - this.measures = _.without(this.measures, current_measure); - if (this.measures.length === 0) { - this.no_data = true; - } else { - _.each(this.cells, function (cell) { - cell.values.splice(index, 1); - }); - } - return $.Deferred().resolve(); - } else { // add a new measure - this.measures.push(measure); - return this.update_data(); - } - }, - - set: function (domain, row_groupby, col_groupby, measures_groupby) { - var self = this; - if (this.updating) { - return this.updating.then(function () { - self.updating = false; - return self.set(domain, row_groupby, col_groupby, measures_groupby); - }); - } - var row_gb_changed = !_.isEqual(row_groupby, this.rows.groupby), - col_gb_changed = !_.isEqual(col_groupby, this.cols.groupby), - measures_gb_changed = !_.isEqual(measures_groupby, this.measures); - - this.domain = domain; - this.rows.groupby = row_groupby; - this.cols.groupby = col_groupby; - - if (measures_groupby.length) { this.measures = measures_groupby; } - - if (row_gb_changed) { this.rows.headers = null; } - if (col_gb_changed) { this.cols.headers = null; } - if (measures_gb_changed && measures_groupby.length) { this.set_measures(measures_groupby); } - - return this.update_data(); - }, - - // ---------------------------------------------------------------------- - // Cells manipulation methods - // ---------------------------------------------------------------------- - // cells are objects {x:_, y:_, values:_} where x < y and values is an array - // of values (one for each measure). The condition x < y might look - // unnecessary, but it makes the rest of the code simpler: headers - // don't krow if they are rows or cols, they just know their id, so - // it is useful that a call get_values(id1, id2) is the same as get_values(id2, id1) - add_cell : function (id1, id2, values) { - this.cells.push({x: Math.min(id1, id2), y: Math.max(id1, id2), values: values}); - }, - - get_values: function (id1, id2, default_values) { - var cells = this.cells, - x = Math.min(id1, id2), - y = Math.max(id1, id2); - for (var i = 0; i < cells.length; i++) { - if (cells[i].x == x && cells[i].y == y) { - return cells[i].values; - } - } - return (default_values || new Array(this.measures.length)); - }, - - // ---------------------------------------------------------------------- - // Headers/Rows/Cols manipulation methods - // ---------------------------------------------------------------------- - // this.rows.headers, this.cols.headers = [header] describe the tree structure - // of rows/cols. Headers are objects - // { - // id:_, (unique id obviously) - // path: [...], (array of all parents title, with its own title at the end) - // title:_, (name of the row/col) - // children:[_], (subrows or sub cols of this row/col) - // domain:_, (domain of data in this row/col) - // root:_ (ref to this.rows or this.cols corresponding to the header) - // expanded:_ (boolean, true if it has been expanded) - // } - is_row: function (id) { - return !!_.findWhere(this.rows.headers, {id:id}); - }, - - is_col: function (id) { - return !!_.findWhere(this.cols.headers, {id:id}); - }, - - get_header: function (id) { - return _.findWhere(this.rows.headers.concat(this.cols.headers), {id:id}); - }, - - _get_headers_with_depth: function (headers, depth) { - return _.filter(headers, function (header) { - return header.path.length === depth; - }); - }, - - // return all columns with a path length of 'depth' - get_cols_with_depth: function (depth) { - return this._get_headers_with_depth(this.cols.headers, depth); - }, - - // return all rows with a path length of 'depth' - get_rows_with_depth: function (depth) { - return this._get_headers_with_depth(this.rows.headers, depth); - }, - - get_ancestor_leaves: function (header) { - return _.where(this.get_ancestors_and_self(header), {expanded:false}); - }, - - // return all non expanded rows - get_rows_leaves: function () { - return _.where(this.rows.headers, {expanded:false}); - }, - - // return all non expanded cols - get_cols_leaves: function () { - return _.where(this.cols.headers, {expanded:false}); - }, - - get_ancestors: function (header) { - var self = this; - if (!header.children) return []; - return [].concat.apply([], _.map(header.children, function (c) { - return self.get_ancestors_and_self(c); - })); - }, - - get_ancestors_and_self: function (header) { - var self = this; - return [].concat.apply([header], _.map(header.children, function (c) { - return self.get_ancestors_and_self(c); - })); - }, - - get_total: function (header) { - return (header) ? this.get_values(header.id, this.get_other_root(header).headers[0].id) - : this.get_values(this.rows.headers[0].id, this.cols.headers[0].id); - }, - - get_other_root: function (header) { - return (header.root === this.rows) ? this.cols : this.rows; - }, - - main_row: function () { return this.rows.headers[0]; }, - - main_col: function () { return this.cols.headers[0]; }, - - // ---------------------------------------------------------------------- - // Table manipulation methods : fold/expand/swap - // ---------------------------------------------------------------------- - // return true if the folding changed the groupbys, false if not - fold: function (header) { - var ancestors = this.get_ancestors(header), - removed_ids = _.pluck(ancestors, 'id'); - - header.root.headers = _.difference(header.root.headers, ancestors); - header.children = []; - header.expanded = false; - - this.cells = _.reject(this.cells, function (cell) { - return (_.contains(removed_ids, cell.x) || _.contains(removed_ids, cell.y)); - }); - - var new_groupby_length = _.max(_.pluck(_.pluck(header.root.headers, 'path'), 'length')); - if (new_groupby_length < header.root.groupby.length) { - header.root.groupby.splice(new_groupby_length); - return true; - } - return false; - }, - - fold_with_depth: function (root, depth) { - var self = this; - _.each(this._get_headers_with_depth(root.headers, depth), function (header) { - self.fold(header); - }); - }, - - expand_all: function () { - this.rows.headers = null; - this.cols.headers = null; - return this.update_data(); - }, - - expand: function (header_id, groupby) { - var self = this, - header = this.get_header(header_id), - other_root = this.get_other_root(header), - this_gb = [groupby.field], - other_gbs = _.pluck(other_root.groupby, 'field'); - - if (header.path.length === header.root.groupby.length) { - header.root.groupby.push(groupby); - } - return this.perform_requests(this_gb, other_gbs, header.domain).then(function () { - var data = Array.prototype.slice.call(arguments).slice(other_gbs.length + 1); - _.each(data, function (data_pt) { - self.make_headers_and_cell( - data_pt, header.root.headers, other_root.headers, 1, header.path, true); - }); - header.expanded = true; - header.children.forEach(function (child) { - child.expanded = false; - child.root = header.root; - }); - }); - }, - - swap_axis: function () { - var temp = this.rows; - this.rows = this.cols; - this.cols = temp; - }, - - // ---------------------------------------------------------------------- - // Data updating methods - // ---------------------------------------------------------------------- - // update_data will try to preserve the expand/not expanded status of each - // column/row. If you want to expand all, then set this.cols.headers/this.rows.headers - // to null before calling update_data. - update_data: function () { - var self = this; - this.updating = this.perform_requests().then (function () { - var data = Array.prototype.slice.call(arguments); - self.no_data = !data[0].length; - if (self.no_data) { - return; - } - var row_headers = [], - col_headers = []; - self.cells = []; - - var dim_col = self.cols.groupby.length, - i, j, index; - - for (i = 0; i < self.rows.groupby.length + 1; i++) { - for (j = 0; j < dim_col + 1; j++) { - index = i*(dim_col + 1) + j; - self.make_headers_and_cell(data[index], row_headers, col_headers, i); - } - } - self.set_headers(row_headers, self.rows); - self.set_headers(col_headers, self.cols); - }); - return this.updating; - }, - - make_headers_and_cell: function (data_pts, row_headers, col_headers, index, prefix, expand) { - var self = this; - data_pts.forEach(function (data_pt) { - var row_value = (prefix || []).concat(data_pt.attributes.value.slice(0,index)); - var col_value = data_pt.attributes.value.slice(index); - - if (expand && !_.find(col_headers, function (hdr) {return self.isEqual(col_value, hdr.path);})) { - return; - } - var row = self.find_or_create_header(row_headers, row_value, data_pt); - var col = self.find_or_create_header(col_headers, col_value, data_pt); - - var cell_value = _.map(self.measures, function (m) { - return data_pt.attributes.aggregates[m.field]; - }); - self.cells.push({ - x: Math.min(row.id, col.id), - y: Math.max(row.id, col.id), - values: cell_value - }); - }); - }, - - make_header: function (values) { - return _.extend({ - children: [], - domain: this.domain, - expanded: undefined, - id: _.uniqueId(), - path: [], - root: undefined, - title: undefined - }, values || {}); - }, - - find_or_create_header: function (headers, path, data_pt) { - var self = this; - var hdr = _.find(headers, function (header) { - return self.isEqual(path, header.path); - }); - if (hdr) { - return hdr; - } - if (!path.length) { - hdr = this.make_header({title: _t('Total')}); - headers.push(hdr); - return hdr; - } - hdr = this.make_header({ - path:path, - domain:data_pt.model._domain, - title: _t(_.last(path)) - }); - var parent = _.find(headers, function (header) { - return self.isEqual(header.path, _.initial(path, 1)); - }); - - var previous = parent.children.length ? _.last(parent.children) : parent; - headers.splice(headers.indexOf(previous) + 1, 0, hdr); - parent.children.push(hdr); - return hdr; - }, - - perform_requests: function (group1, group2, domain) { - var self = this, - requests = [], - row_gbs = _.pluck(this.rows.groupby, 'field'), - col_gbs = _.pluck(this.cols.groupby, 'field'), - field_list = row_gbs.concat(col_gbs, _.pluck(this.measures, 'field')), - fields = field_list.map(function (f) { return self.raw_field(f); }); - - group1 = group1 || row_gbs; - group2 = group2 || col_gbs; - - var i,j, groupbys; - for (i = 0; i < group1.length + 1; i++) { - for (j = 0; j < group2.length + 1; j++) { - groupbys = group1.slice(0,i).concat(group2.slice(0,j)); - requests.push(self.get_groups(groupbys, fields, domain || self.domain)); - } - } - return $.when.apply(null, requests); - }, - - // set the 'expanded' status of new_headers more or less like root.headers, with root as root - set_headers: function(new_headers, root) { - var self = this; - if (root.headers) { - _.each(root.headers, function (header) { - var corresponding_header = _.find(new_headers, function (h) { - return self.isEqual(h.path, header.path); - }); - if (corresponding_header && header.expanded) { - corresponding_header.expanded = true; - _.each(corresponding_header.children, function (c) { - c.expanded = false; - }); - } - if (corresponding_header && (!header.expanded)) { - corresponding_header.expanded = false; - corresponding_header.children = []; - } - }); - var updated_headers = _.filter(new_headers, function (header) { - return (header.expanded !== undefined); - }); - _.each(updated_headers, function (header) { - header.root = root; - }); - root.headers = updated_headers; - } else { - root.headers = new_headers; - _.each(root.headers, function (header) { - header.root = root; - header.expanded = (header.children.length > 0); - }); - } - return new_headers; - }, - - get_groups: function (groupbys, fields, domain) { - var self = this; - return this.model.query(_.without(fields, '__count')) - .filter(domain) - .context(this.context) - .lazy(false) - .group_by(groupbys) - .then(function (groups) { - return groups.filter(function (group) { - return group.attributes.length > 0; - }).map(function (group) { - var attrs = group.attributes, - grouped_on = attrs.grouped_on instanceof Array ? attrs.grouped_on : [attrs.grouped_on], - raw_grouped_on = grouped_on.map(function (f) { - return self.raw_field(f); - }); - if (grouped_on.length === 1) { - attrs.value = [attrs.value]; - } - attrs.value = _.range(grouped_on.length).map(function (i) { - var grp = grouped_on[i], - field = self.fields[grp]; - if (attrs.value[i] === false) { - return _t('Undefined'); - } else if (attrs.value[i] instanceof Array) { - return attrs.value[i][1]; - } else if (field && field.type === 'selection') { - var selected = _.where(field.selection, {0: attrs.value[i]})[0]; - return selected ? selected[1] : attrs.value[i]; - } - return attrs.value[i]; - }); - attrs.aggregates.__count = group.attributes.length; - attrs.grouped_on = raw_grouped_on; - return group; - }); - }); - }, - - // if field is a fieldname, returns field, if field is field_id:interval, retuns field_id - raw_field: function (field) { - return field.split(':')[0]; - }, - - isEqual: function (path1, path2) { - if (path1.length !== path2.length) { return false; } - for (var i = 0; i < path1.length; i++) { - if (path1[i] !== path2[i]) { - return false; - } - } - return true; - }, - -}); - -})(); diff --git a/addons/web_graph/static/src/xml/web_graph.xml b/addons/web_graph/static/src/xml/web_graph.xml deleted file mode 100644 index 70bcf328d68..00000000000 --- a/addons/web_graph/static/src/xml/web_graph.xml +++ /dev/null @@ -1,65 +0,0 @@ -<templates> - - <t t-name="graph-view-buttons"> - <div class="btn-group btn-group-sm"> - <button class="btn btn-default fa fa-table oe-pivot-mode" title="Pivot Table"></button> - <button class="btn btn-default fa fa-bar-chart-o oe-bar-mode" title="Bar Chart"></button> - <button class="btn btn-default fa fa-line-chart oe-line-mode" title="Line Chart"></button> - <button class="btn btn-default fa fa-pie-chart oe-pie-mode" title="Pie Chart"></button> - </div> - <div class="btn-group btn-group-sm"> - <button class="btn btn-default fa fa-expand oe-pivot" title="Flip columns and rows"></button> - <button class="btn btn-default fa fa-arrows-alt oe-pivot" title="Expand"></button> - <button class="btn btn-default fa fa-download oe-pivot" title="Download Data"></button> - </div> - <div class="btn-group btn-group-sm"> - <button class="btn btn-default dropdown-toggle" data-toggle="dropdown"> - Measures <span class="caret"/> - </button> - <ul class="oe-measure-list dropdown-menu"/> - </div> - </t> - <div t-name="GraphWidget" class="graph_widget"> - <div class="graph_main_content"></div> - </div> - - <div t-name="field_selection" class="dropdown"> - <span class="btn btn-default field-selection dropdown-toggle" data-toggle="dropdown"></span> - <ul class="dropdown-menu multi-level" role="menu"> - <t t-foreach="fields" t-as="field"> - <t t-if="(field.type === 'date') || (field.type === 'datetime')"> - <li class="graph-dropdown-submenu"> - <a class="field-selection" t-att-data-field-id="field.id" t-att-data-id="header_id" t-att-data-interval="'month'" href="#"> - <t t-esc="field.value"/> - </a> - <ul class="dropdown-menu"> - <li><a href="#" class="field-selection" t-att-data-field-id="field.id" t-att-data-id="header_id" t-att-data-interval="'day'">Day</a></li> - <li><a href="#" class="field-selection" t-att-data-field-id="field.id" t-att-data-id="header_id" t-att-data-interval="'week'">Week</a></li> - <li><a href="#" class="field-selection" t-att-data-field-id="field.id" t-att-data-id="header_id" t-att-data-interval="'month'">Month</a></li> - <li><a href="#" class="field-selection" t-att-data-field-id="field.id" t-att-data-id="header_id" t-att-data-interval="'quarter'">Quarter</a></li> - <li><a href="#" class="field-selection" t-att-data-field-id="field.id" t-att-data-id="header_id" t-att-data-interval="'year'">Year</a></li> - </ul> - </li> - </t> - - <t t-if="(field.type !== 'date') && (field.type !== 'datetime')"> - <li> - <a class="field-selection" t-att-data-field-id="field.id" t-att-data-id="header_id" href="#"> - <t t-esc="field.value"/> - </a> - </li> - </t> - </t> - </ul> - </div> - - <div t-name="graph_no_data" class="oe_view_nocontent" > - <div class="oe_empty_custom_dashboard"> - <p><b>No data to display.</b></p> - <p> - No data available for this graph. Try to add some records, or make sure - that there is at least one measure and no active filter in the search bar. - </p> - </div> - </div> -</templates> |
