Browse Source

柱图钻取设置

zhuth 6 years ago
parent
commit
073dc0e280

+ 1 - 1
package.json

@@ -10,7 +10,7 @@
   },
   "dependencies": {
     "ant-design-pro": "^2.0.0-beta.2",
-    "antd": "^3.12.4",
+    "antd": "^3.20.5",
     "canvas2image": "^1.0.5",
     "copy-to-clipboard": "^3.1.0",
     "dva": "^2.4.1",

+ 47 - 153
src/components/chartDesigner/sections/barConfigForm.jsx

@@ -1,11 +1,13 @@
-import React from 'react'
-import { Form, Select, Tag, Cascader, Dropdown, Menu, InputNumber } from 'antd'
-import { deepAssign } from '../../../utils/baseUtils'
-import { connect } from 'dva'
-import GAUGE from './gauge.json'
-import GRANULARITY from './granularity.json'
-const FormItem = Form.Item
-const { Option } = Select
+import React from 'react';
+import { Form, Select, Checkbox, InputNumber } from 'antd';
+import { deepAssign } from '../../../utils/baseUtils';
+import { connect } from 'dva';
+import XAxisItem from './xAxisItem';
+import YAxisItem from './yAxisItem';
+import DrillList from './drillList';
+import GRANULARITY from './granularity.json';
+const FormItem = Form.Item;
+const { Option } = Select;
 const formItemLayout = {
 	labelCol: { span: 8 },
 	wrapperCol: { span: 16 },
@@ -16,154 +18,16 @@ const BarConfigForm = ({ autoRefresh, chartDesigner, dispatch }) => {
 	return (
 		<Form hideRequiredMark={true}>
 			<FormItem label='横轴' {...formItemLayout}>
-				<Cascader
-					className='barconfig-yaxis'
-					value={[barConfig.xAxis.column.value, barConfig.xAxis.granularity.value]}
-					allowClear={true}
-					showSearch={{
-						filter: (inputValue, path) => {
-							let p0 = path[0].label.toLowerCase();
-							let v = inputValue.toLowerCase();
-							return p0.indexOf(v) !== -1;
-						},
-						sort: (a, b, inputValue) => {
-							return a[0].label.localeCompare(b[0].label,"zh");
-						},
-						render: (inputValue, path) => {
-							const reg = new RegExp('([+ \\- & | ! ( ) { } \\[ \\] ^ \" ~ * ? : ( ) \/])', 'g'); // 需要转义的字符
-							let v = inputValue.replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1'); // 添加转义符号
-							let label0 = (path[0].label.split(new RegExp(`(${v})`, 'i')).map((fragment, i) => {
-								return (
-									fragment.toLowerCase().replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1') === v.toLowerCase() ?
-									<span key={i} style={{fontWeight: 'bold', color: 'red'}} className="highlight">{fragment}</span> :
-									fragment
-								)
-							}))
-							return <div>{label0}{path[1] ? '>' + path[1].label : ''}</div>
-						}
-					}}
-					options={columns.filter(c =>['ordinal', 'categorical', 'time'].indexOf(c.type) !== -1).map((c, i)=>{
-						return {
-							type: c.type,
-							value: c.name,
-							label: c.label,
-							children: GRANULARITY[c.type]
-						}
-					})}
-					onChange={(value, items) => {
-						let column = {};
-						let granularity = {};
-						if(items.length > 0) {
-							column = { type: items[0].type, value: items[0].value, label: items[0].label };
-						}
-						if(items.length > 1) {
-							granularity = { value: items[1].value, label: items[1].label };
-						}
-						dispatch({ type: 'chartDesigner/changeField', name: 'barConfig', value: { ...barConfig, xAxis: { column, granularity } }, autoRefresh });
+				<XAxisItem value={barConfig.xAxis} options={columns.filter(c =>['ordinal', 'categorical', 'time'].indexOf(c.type) > -1)}
+					onChange={({ column, granularity }) => {
+						dispatch({ type: 'chartDesigner/changeField', name: 'barConfig', value: { ...barConfig, xAxis: { column, granularity }, drillList: [] }, autoRefresh });
 					}}
-					displayRender={(label, selectedOptions) => {
-						let text = '';
-						let className = 'cascader-label';
-						if(label.length > 0) {
-							className += ' full-label';
-							text += label[0];
-							if(label.length > 1) {
-								text += '(' + label[1] + ')';
-							}
-						}else {
-							className += ' empty-label';
-							text = '请选择...';
-						}
-						return <div className={className}>{text}</div>;
-					}}
-				>
-				</Cascader>
+				/>
 			</FormItem>
 			<FormItem label='纵轴' {...formItemLayout}>
-				<Cascader
-					className='gauge-item'
-					value={[barConfig.yAxis.column.value, barConfig.yAxis.gauge.value]}
-					allowClear={true}
-					showSearch={{
-						filter: (inputValue, path) => {
-							let p0 = path[0].label.toLowerCase();
-							let v = inputValue.toLowerCase();
-							return p0.indexOf(v) !== -1;
-						},
-						sort: (a, b, inputValue) => {
-							return a[0].label.localeCompare(b[0].label,"zh");
-						},
-						render: (inputValue, path) => {
-							const reg = new RegExp('([+ \\- & | ! ( ) { } \\[ \\] ^ \" ~ * ? : ( ) \/])', 'g'); // 需要转义的字符
-							let v = inputValue.replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1'); // 添加转义符号
-							let label0 = (path[0].label.split(new RegExp(`(${v})`, 'i')).map((fragment, i) => {
-								return (
-									fragment.toLowerCase().replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1') === v.toLowerCase() ?
-									<span key={i} style={{fontWeight: 'bold', color: 'red'}} className="highlight">{fragment}</span> :
-									fragment
-								)
-							}))
-							return <div>{label0}>{path[1].label}</div>
-						}
-					}}
-					options={columns.map((c, i)=>{
-						return {
-							value: c.name,
-							label: c.label,
-							type: c.type,
-							children: GAUGE[baseConfig.viewType].map(g => {
-								if(g.columnType.indexOf(c.type) !== -1) {
-									return g;
-								}else {
-									return null;
-								}
-							}).filter( g => g!==null)
-						}
-					})}
-					onChange={(value, items) => {
-						let column = {};
-						let gauge = {};
-						if(value.length > 0) {
-							column = { type: items[0].type, value: items[0].value, label: items[0].label };
-							gauge = { value: items[1].value, label: items[1].label };
-						}
-						dispatch({ type: 'chartDesigner/changeField', name: 'barConfig', value: { ...barConfig, yAxis: { column, gauge } }, autoRefresh });
-					}}
-					displayRender={(label, selectedOptions) => {
-						let menu = selectedOptions.length > 0 ? <Menu
-							selectedKeys={[barConfig.yAxis.gauge.value]}
-							selectable={true}
-						>
-							{selectedOptions[0].children.map((c, i) => {
-								return <Menu.Item  data-value={c.value} data-label={c.label} key={c.value} onClick={(e) => {
-									let value = e.domEvent.target.getAttribute('data-value');
-									let label = e.domEvent.target.getAttribute('data-label');
-									dispatch({ type: 'chartDesigner/changeField', name: 'barConfig', value: { 
-										...barConfig,
-										yAxis: {
-											column: barConfig.yAxis.column,
-											gauge: { value, label }
-										}
-									}, autoRefresh });
-									e.domEvent.stopPropagation();
-								}}>{c.label}</Menu.Item>
-							})}
-						</Menu>: [];
-						let tag = selectedOptions.length > 0 ? <Dropdown
-							trigger={['click']}
-							overlay={menu}
-						>
-							<Tag size='small' onClick={(e) => {e.stopPropagation()}}>{label[1]}</Tag>
-						</Dropdown>
-						: null;
-
-						return <div className={`cascader-label ${tag?'full' : 'empty'}-label`}>
-							{tag}
-							<span>{label[0] || '请选择...'}</span>
-						</div>
-					}}
-				>
-				</Cascader>
+				<YAxisItem value={barConfig.yAxis} options={columns} gaugeType={baseConfig.viewType} onChange={({ column, gauge }) => {
+					dispatch({ type: 'chartDesigner/changeField', name: 'barConfig', value: { ...barConfig, yAxis: { column, gauge } }, autoRefresh });
+				}}/>
 			</FormItem>
 			<FormItem label='分组' {...formItemLayout}>
 				<Select
@@ -232,6 +96,36 @@ const BarConfigForm = ({ autoRefresh, chartDesigner, dispatch }) => {
                     }}
 				/>
 			</FormItem>
+			{barConfig.xAxis.column.value && <FormItem label='钻取' {...formItemLayout}>
+				<Checkbox
+					checked={!!barConfig.drillable}
+					onChange={e => {
+						let checked = e.target.checked;
+						let drillList = [];
+						if(barConfig.xAxis.column.type === 'time') { // 如果x轴字段是时间类型,自动生成其维度钻取层级
+							const granularitys = GRANULARITY['time'];
+							let idx = granularitys.findIndex(g => g.value === barConfig.xAxis.granularity.value);
+							for(let i = idx + 1; i < granularitys.length; i++) {
+								drillList.push({
+									column: barConfig.xAxis.column,
+									granularity: granularitys[i]
+								});
+							}
+						}
+						dispatch({ type: 'chartDesigner/changeField', name: 'barConfig', value: { ...barConfig, drillable: checked, drillList }, autoRefresh });
+					}}
+				/>
+			</FormItem>}
+			{barConfig.xAxis.column.value && barConfig.drillable && <FormItem label='钻取层级' {...formItemLayout}>
+				<DrillList
+					// 可选钻取字段包括时间、类别类型,且当x轴选择的是时间类型时允许重复作为钻取字段(之后会限制年月周等维度的选择)
+					list={columns.filter(c => (barConfig.xAxis.column.type === 'time' ? true : c.name !== barConfig.xAxis.column.value) && ['categorical', 'time'].indexOf(c.type) > -1)}
+					value={barConfig.drillList || []}
+					onChange={list => {
+						dispatch({ type: 'chartDesigner/changeField', name: 'barConfig', value: { ...barConfig, drillList: list }, autoRefresh });
+					}}
+				/>
+			</FormItem>}
 		</Form>
 	);
 }

+ 40 - 0
src/components/chartDesigner/sections/drillList.jsx

@@ -0,0 +1,40 @@
+import React from 'react';
+import { Button, Row, Col, Icon, message }  from 'antd';
+import XAxisItem from './xAxisItem';
+import './drillList.less'
+
+const DrillList = ({ className, list, value, onChange }) => {
+    let newValue = value.map(v => v);
+    return <div className={`drill-list ${className || ''}`}>
+        {value.map((v, i) => {
+            const options = list.filter(l => !value.find(vi => vi.column.value !== v.column.value && vi.column.value === l.name));
+            return <Row key={i}>
+                <Col span={20}>
+                    <XAxisItem value={v} options={options} onChange={v => {
+                        newValue[i] = v;
+                        onChange(newValue);
+                    }}/>
+                </Col>
+                <Col span={4} style={{ textAlign: 'center' }}>
+                    <Icon type='delete' style={{ cursor: 'pointer' }} onClick={() => {
+                        newValue.splice(i, 1);
+                        onChange(newValue);
+                    }}/>
+                </Col>
+            </Row>
+        })}
+        <Button className='btn-add' onClick={() => {
+            if(newValue.length > 6) {
+                message.error('钻取层级最多允许添加6层');
+                return;
+            }
+            newValue.push({
+                column: {},
+                granularity: {}
+            });
+            onChange(newValue);
+        }}>添加</Button>
+    </div>
+};
+
+export default DrillList;

+ 11 - 0
src/components/chartDesigner/sections/drillList.less

@@ -0,0 +1,11 @@
+.drill-list {
+    .btn-add {
+        width: 100%;
+        background: transparent;
+        color: rgb(255, 255, 255);
+        border: 1px dashed rgb(255, 255, 255);
+        &:hover {
+            background: #808dad;
+        }
+    }
+}

+ 79 - 0
src/components/chartDesigner/sections/xAxisItem.jsx

@@ -0,0 +1,79 @@
+import React from 'react';
+import { Cascader } from 'antd';
+import GRANULARITY from './granularity.json';
+
+class XAxisItem extends React.PureComponent {
+    constructor(props) {
+        super(props);
+        this.state = {
+
+        }
+    }
+
+    render() {
+        const { value: _propsValue, options, onChange } = this.props;
+        return <Cascader
+            value={[_propsValue.column.value, _propsValue.granularity.value]}
+            allowClear={true}
+            showSearch={{
+                filter: (inputValue, path) => {
+                    let p0 = path[0].label.toLowerCase();
+                    let v = inputValue.toLowerCase();
+                    return p0.indexOf(v) !== -1;
+                },
+                sort: (a, b, inputValue) => {
+                    return a[0].label.localeCompare(b[0].label,"zh");
+                },
+                render: (inputValue, path) => {
+                    const reg = new RegExp('([+ \\- & | ! ( ) { } \\[ \\] ^ \" ~ * ? : ( ) \/])', 'g'); // 需要转义的字符
+                    let v = inputValue.replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1'); // 添加转义符号
+                    let label0 = (path[0].label.split(new RegExp(`(${v})`, 'i')).map((fragment, i) => {
+                        return (
+                            fragment.toLowerCase().replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1') === v.toLowerCase() ?
+                            <span key={i} style={{fontWeight: 'bold', color: 'red'}} className="highlight">{fragment}</span> :
+                            fragment
+                        )
+                    }))
+                    return <div>{label0}{path[1] ? '>' + path[1].label : ''}</div>
+                }
+            }}
+            options={options.map((c, i)=>{
+                return {
+                    type: c.type,
+                    value: c.name,
+                    label: c.label,
+                    children: GRANULARITY[c.type]
+                }
+            })}
+            onChange={(value, items) => {
+                let column = {};
+                let granularity = {};
+                if(items.length > 0) {
+                    column = { type: items[0].type, value: items[0].value, label: items[0].label };
+                }
+                if(items.length > 1) {
+                    granularity = { value: items[1].value, label: items[1].label };
+                }
+                onChange({ column, granularity });
+            }}
+            displayRender={(label, selectedOptions) => {
+                let text = '';
+                let className = 'cascader-label';
+                if(label.length > 0) {
+                    className += ' full-label';
+                    text += label[0];
+                    if(label.length > 1) {
+                        text += '(' + label[1] + ')';
+                    }
+                }else {
+                    className += ' empty-label';
+                    text = '请选择...';
+                }
+                return <div className={className}>{text}</div>;
+            }}
+        >
+        </Cascader> 
+    }
+}
+
+export default XAxisItem;

+ 99 - 0
src/components/chartDesigner/sections/yAxisItem.jsx

@@ -0,0 +1,99 @@
+import React from 'react';
+import { Cascader } from 'antd';
+import { Menu, Dropdown, Tag } from 'antd';
+import GAUGE from './gauge.json';
+
+class YAxisItem extends React.PureComponent {
+    constructor(props) {
+        super(props);
+        this.state = {
+
+        }
+    }
+
+    render() {
+        const { value: _propsValue, options, onChange, gaugeType } = this.props;
+        return <Cascader
+            value={[_propsValue.column.value, _propsValue.gauge.value]}
+            allowClear={true}
+            showSearch={{
+                filter: (inputValue, path) => {
+                    let p0 = path[0].label.toLowerCase();
+                    let v = inputValue.toLowerCase();
+                    return p0.indexOf(v) !== -1;
+                },
+                sort: (a, b, inputValue) => {
+                    return a[0].label.localeCompare(b[0].label,"zh");
+                },
+                render: (inputValue, path) => {
+                    const reg = new RegExp('([+ \\- & | ! ( ) { } \\[ \\] ^ \" ~ * ? : ( ) \/])', 'g'); // 需要转义的字符
+                    let v = inputValue.replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1'); // 添加转义符号
+                    let label0 = (path[0].label.split(new RegExp(`(${v})`, 'i')).map((fragment, i) => {
+                        return (
+                            fragment.toLowerCase().replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1') === v.toLowerCase() ?
+                            <span key={i} style={{fontWeight: 'bold', color: 'red'}} className="highlight">{fragment}</span> :
+                            fragment
+                        )
+                    }))
+                    return <div>{label0}{path[1] ? '>' + path[1].label : ''}</div>
+                }
+            }}
+            options={options.map((c, i)=>{
+                return {
+                    value: c.name,
+                    label: c.label,
+                    type: c.type,
+                    children: GAUGE[gaugeType].map(g => {
+                        if(g.columnType.indexOf(c.type) !== -1) {
+                            return g;
+                        }else {
+                            return null;
+                        }
+                    }).filter( g => g!==null)
+                }
+            })}
+            onChange={(value, items) => {
+                let column = {};
+                let gauge = {};
+                if(value.length > 0) {
+                    column = { type: items[0].type, value: items[0].value, label: items[0].label };
+                    gauge = { value: items[1].value, label: items[1].label };
+                }
+                onChange({ column, gauge });
+            }}
+            displayRender={(label, selectedOptions) => {
+                let menu = selectedOptions.length > 0 ? <Menu
+                    selectedKeys={[_propsValue.gauge.value]}
+                    selectable={true}
+                >
+                    {selectedOptions[0].children.map((c, i) => {
+                        return <Menu.Item  data-value={c.value} data-label={c.label} key={c.value} onClick={(e) => {
+                            let value = e.domEvent.target.getAttribute('data-value');
+                            let label = e.domEvent.target.getAttribute('data-label');
+                            onChange({
+                                column: _propsValue.column,
+                                gauge: { value, label }
+                            });
+                            e.domEvent.stopPropagation();
+                        }}>{c.label}</Menu.Item>
+                    })}
+                </Menu>: [];
+                let tag = selectedOptions.length > 0 ? <Dropdown
+                    trigger={['click']}
+                    overlay={menu}
+                >
+                    <Tag size='small' onClick={(e) => {e.stopPropagation()}}>{label[1]}</Tag>
+                </Dropdown>
+                : null;
+
+                return <div className={`cascader-label ${tag?'full' : 'empty'}-label`}>
+                    {tag}
+                    <span>{label[0] || '请选择...'}</span>
+                </div>
+            }}
+        >
+        </Cascader> 
+    }
+}
+
+export default YAxisItem;

+ 1 - 1
src/models/chart.js

@@ -256,7 +256,7 @@ export default {
                         message.error('该图表不存在');
                         return false;
                     }
-                    let chartConfig = JSON.parse(resData.chartConfig || '{ "xAxis": { "column": {}, "granularity": {} }, "yAxis": { "column": {}, "gauge": {} } }');
+                    let chartConfig = JSON.parse(resData.chartConfig || '{ "xAxis": { "column": {}, "granularity": {} }, "yAxis": { "column": {}, "gauge": {} }, "drillable": false, drillList": [] }');
                     let styleConfig = JSON.parse(resData.style || '{}');
                     let otherConfig = JSON.parse(resData.otherConfig || '{}');
                     // TODO 过渡方案

+ 3 - 3
src/models/chartDesigner.js

@@ -59,9 +59,9 @@ export default {
             baseConfig: { dataSource: { code: '' }, viewType: '' },
             aggregateTableConfig: { targetColumn: {}, statistics: [], groupBy: [] },
             dataViewConfig: { viewColumns: [], sortColumn: {key: ''}, sortType: 'asc' },
-            barConfig: { xAxis: { column: {}, granularity: {} }, yAxis: { column: {}, gauge: {} }, groupBy: {key:''}, sortTarget: 'x', sortType: 'ASC', threshold: 20 },
-            lineConfig: { xAxis: { column: {}, granularity: {} }, yAxis: { column: {}, gauge: {} }, groupBy: {key:''}, threshold: 200 },
-            pieConfig: { xAxis: { column: {}, granularity: {} }, yAxis: { column: {}, gauge: {} }, threshold: 20 },
+            barConfig: { xAxis: { column: {}, granularity: {} }, yAxis: { column: {}, gauge: {} }, groupBy: {key:''}, sortTarget: 'x', sortType: 'ASC', threshold: 20, drillable: false, drillList: [{ column: {}, granularity:{} }] },
+            lineConfig: { xAxis: { column: {}, granularity: {} }, yAxis: { column: {}, gauge: {} }, groupBy: {key:''}, threshold: 200, drillable: false, drillList: [{ column: {}, granularity:{} }] },
+            pieConfig: { xAxis: { column: {}, granularity: {} }, yAxis: { column: {}, gauge: {} }, threshold: 20, drillable: false, drillList: [{ column: {}, granularity:{} }] },
             scatterConfig: { xAxis: { column: {}, granularity: {} }, yAxis: { column: {}, gauge: {} }, groupBy: {key:''}, threshold: 1000 },
             indicatorConfig: { xAxis: { column: {} }, yAxis: { column: {}, gauge: {} }, otherColumn: [], threshold: 6 },
             theme: 'default',