Added support for visualizing the graphs using Stacked Line, Bar, and Stacked Bar charts in the query tool. Fixes #7486
parent
b1f6664292
commit
b92e2fcfc9
Binary file not shown.
After Width: | Height: | Size: 90 KiB |
Binary file not shown.
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 26 KiB |
Binary file not shown.
After Width: | Height: | Size: 80 KiB |
Binary file not shown.
After Width: | Height: | Size: 87 KiB |
|
@ -360,8 +360,7 @@ from the drop-down menu to select all the columns.
|
||||||
|
|
||||||
* Graph Type
|
* Graph Type
|
||||||
|
|
||||||
Choose the type of the graph that you would like to generate. Currently only
|
Choose the type of the graph that you would like to generate.
|
||||||
*Line Charts* option is there, but more charts will be added soon.
|
|
||||||
|
|
||||||
.. image:: images/query_graph_type.png
|
.. image:: images/query_graph_type.png
|
||||||
:alt: Query tool graph visualiser graph type
|
:alt: Query tool graph visualiser graph type
|
||||||
|
@ -382,7 +381,8 @@ Click the *Download* button on the button bar to download the chart.
|
||||||
Line Chart
|
Line Chart
|
||||||
==========
|
==========
|
||||||
|
|
||||||
The *Line Chart* can be generated by selecting the X-axis and the Y-axis and
|
The *Line Chart* can be generated by selecting the 'Line Chart'
|
||||||
|
from the Graph Type drop-down, selecting the X-axis and the Y-axis, and
|
||||||
clicking on the 'Generate' button. Below is an example of a chart of employee
|
clicking on the 'Generate' button. Below is an example of a chart of employee
|
||||||
names and their salaries.
|
names and their salaries.
|
||||||
|
|
||||||
|
@ -393,6 +393,40 @@ names and their salaries.
|
||||||
Set *Use different data point styles?* option to true in the :ref:`preferences`,
|
Set *Use different data point styles?* option to true in the :ref:`preferences`,
|
||||||
to show data points in a different style on each graph lines.
|
to show data points in a different style on each graph lines.
|
||||||
|
|
||||||
|
Stacked Line Chart
|
||||||
|
==================
|
||||||
|
|
||||||
|
The *Stacked Line Chart* can be generated by selecting the 'Stacked Line Chart'
|
||||||
|
from the Graph Type drop-down, selecting the X-axis and the Y-axis, and
|
||||||
|
clicking on the 'Generate' button.
|
||||||
|
|
||||||
|
.. image:: images/query_stacked_line_chart.png
|
||||||
|
:alt: Query tool graph visualiser stacked line chart
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
Bar Chart
|
||||||
|
==========
|
||||||
|
|
||||||
|
The *Bar Chart* can be generated by selecting the 'Bar Chart'
|
||||||
|
from the Graph Type drop-down, selecting the X-axis and the Y-axis, and
|
||||||
|
clicking on the 'Generate' button.
|
||||||
|
|
||||||
|
.. image:: images/query_bar_chart.png
|
||||||
|
:alt: Query tool graph visualiser bar chart
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
Stacked Bar Chart
|
||||||
|
=================
|
||||||
|
|
||||||
|
The *Stacked Bar Chart* can be generated by selecting the 'Stacked Bar Chart'
|
||||||
|
from the Graph Type drop-down, selecting the X-axis and the Y-axis, and
|
||||||
|
clicking on the 'Generate' button.
|
||||||
|
|
||||||
|
|
||||||
|
.. image:: images/query_stacked_bar_chart.png
|
||||||
|
:alt: Query tool graph visualiser stacked bar chart
|
||||||
|
:align: center
|
||||||
|
|
||||||
Connection Status
|
Connection Status
|
||||||
*****************
|
*****************
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ This release contains a number of bug fixes and new features since the release o
|
||||||
New features
|
New features
|
||||||
************
|
************
|
||||||
|
|
||||||
|
| `Issue #7486 <https://redmine.postgresql.org/issues/7486>`_ - Added support for visualizing the graphs using Stacked Line, Bar, and Stacked Bar charts in the query tool.
|
||||||
|
|
||||||
Housekeeping
|
Housekeeping
|
||||||
************
|
************
|
||||||
|
|
|
@ -21,14 +21,14 @@ export const CHART_THEME_COLORS = {
|
||||||
'#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99',
|
'#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99',
|
||||||
'#AAAA11', '#6633CC', '#E67300', '#8B0707', '#651067', '#329262',
|
'#AAAA11', '#6633CC', '#E67300', '#8B0707', '#651067', '#329262',
|
||||||
'#5574A6', '#3B3EAC', '#B77322', '#16D620', '#B91383', '#F4359E'],
|
'#5574A6', '#3B3EAC', '#B77322', '#16D620', '#B91383', '#F4359E'],
|
||||||
'dark': ['#70E000', '#FF477E', '#7DC9F1', '#2EC4B6', '#52B788', '#2A9D8F',
|
'dark': ['#7371FC', '#3A86FF', '#979DAC', '#D4A276', '#2A9D8F', '#FFEE32',
|
||||||
'#E4E487', '#DB7C74', '#8AC926', '#979DAC', '#FF8FA3', '#7371FC', '#B388EB',
|
'#70E000', '#FF477E', '#7DC9F1', '#52B788', '#E4E487', '#2EC4B6',
|
||||||
'#D4A276', '#FB5607', '#EEA236', '#FFEE32', '#EDC531', '#D4D700', '#FFFB69',
|
'#DB7C74', '#8AC926', '#FF8FA3', '#B388EB', '#FB5607', '#EEA236',
|
||||||
'#7FCC5C', '#50B0F0', '#3A86FF', '#00B4D8'],
|
'#EDC531', '#D4D700', '#FFFB69', '#7FCC5C', '#50B0F0', '#00B4D8'],
|
||||||
'high_contrast': ['#00B4D8', '#2EC4B6', '#45D48A', '#50B0F0', '#52B788',
|
'high_contrast': ['#FF6C49', '#00B4D8', '#45D48A', '#FFFB69', '#B388EB',
|
||||||
'#70E000', '#7DC9F1', '#7FCC5C', '#8AC926', '#979DAC', '#B388EB',
|
'#D4A276', '#2EC4B6', '#7DC9F1', '#50B0F0', '#52B788', '#70E000',
|
||||||
'#D4A276', '#D4D700', '#DEFF00', '#E4E487', '#EDC531', '#EEA236', '#F8845F',
|
'#7FCC5C', '#8AC926', '#979DAC', '#D4D700', '#DEFF00', '#E4E487',
|
||||||
'#FB4BF6', '#FF6C49', '#FF8FA3', '#FFEE32', '#FFFB69', '#FFFFFF']
|
'#EDC531', '#EEA236', '#F8845F', '#FB4BF6', '#FF8FA3', '#FFEE32', '#FFFFFF']
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
|
@ -79,6 +79,35 @@ const defaultOptions = {
|
||||||
color: getComputedStyle(document.documentElement).getPropertyValue('--border-color'),
|
color: getComputedStyle(document.documentElement).getPropertyValue('--border-color'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
zoom: {
|
||||||
|
pan: {
|
||||||
|
enabled: false,
|
||||||
|
mode: 'x',
|
||||||
|
modifierKey: 'ctrl',
|
||||||
|
},
|
||||||
|
zoom: {
|
||||||
|
drag: {
|
||||||
|
enabled: false,
|
||||||
|
borderColor: 'rgb(54, 162, 235)',
|
||||||
|
borderWidth: 1,
|
||||||
|
backgroundColor: 'rgba(54, 162, 235, 0.3)'
|
||||||
|
},
|
||||||
|
mode: 'x',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const stackedOptions = {
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
stacked: true,
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
stacked: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -150,8 +179,35 @@ BaseChart.propTypes = {
|
||||||
plugins: PropTypes.object,
|
plugins: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function LineChart(props) {
|
export function LineChart({stacked, options, ...props}) {
|
||||||
|
// if stacked true then merge the stacked specific options.
|
||||||
|
options = stacked ? _.merge(options, stackedOptions) : options;
|
||||||
return (
|
return (
|
||||||
<BaseChart {...props} type='line'/>
|
<BaseChart {...props} options={options} type='line'/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
LineChart.propTypes = {
|
||||||
|
options: PropTypes.object,
|
||||||
|
stacked: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
export function BarChart({stacked, options, ...props}) {
|
||||||
|
// if stacked true then merge the stacked specific options.
|
||||||
|
options = stacked ? _.merge(options, stackedOptions) : options;
|
||||||
|
return (
|
||||||
|
<BaseChart {...props} options={options} type='bar'/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
BarChart.propTypes = {
|
||||||
|
options: PropTypes.object,
|
||||||
|
stacked: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ConvertHexToRGBA(hex, opacity) {
|
||||||
|
const tempHex = hex.replace('#', '');
|
||||||
|
const r = parseInt(tempHex.substring(0, 2), 16);
|
||||||
|
const g = parseInt(tempHex.substring(2, 4), 16);
|
||||||
|
const b = parseInt(tempHex.substring(4, 6), 16);
|
||||||
|
|
||||||
|
return `rgba(${r},${g},${b},${opacity / 100})`;
|
||||||
|
}
|
||||||
|
|
|
@ -21,8 +21,8 @@ import ZoomOutMapIcon from '@material-ui/icons/ZoomOutMap';
|
||||||
import SaveAltIcon from '@material-ui/icons/SaveAlt';
|
import SaveAltIcon from '@material-ui/icons/SaveAlt';
|
||||||
import { InputSelect } from '../../../../../../static/js/components/FormComponents';
|
import { InputSelect } from '../../../../../../static/js/components/FormComponents';
|
||||||
import { DefaultButton, PgButtonGroup, PgIconButton} from '../../../../../../static/js/components/Buttons';
|
import { DefaultButton, PgButtonGroup, PgIconButton} from '../../../../../../static/js/components/Buttons';
|
||||||
import { LineChart, DATA_POINT_STYLE, DATA_POINT_SIZE,
|
import { LineChart, BarChart, DATA_POINT_STYLE, DATA_POINT_SIZE,
|
||||||
CHART_THEME_COLORS, CHART_THEME_COLORS_LENGTH} from 'sources/chartjs';
|
CHART_THEME_COLORS, CHART_THEME_COLORS_LENGTH, ConvertHexToRGBA} from 'sources/chartjs';
|
||||||
import { QueryToolEventsContext, QueryToolContext } from '../QueryToolComponent';
|
import { QueryToolEventsContext, QueryToolContext } from '../QueryToolComponent';
|
||||||
import { QUERY_TOOL_EVENTS, PANELS } from '../QueryToolConstants';
|
import { QUERY_TOOL_EVENTS, PANELS } from '../QueryToolConstants';
|
||||||
import { LayoutHelper } from '../../../../../../static/js/helpers/Layout';
|
import { LayoutHelper } from '../../../../../../static/js/helpers/Layout';
|
||||||
|
@ -72,20 +72,12 @@ function GenerateGraph({graphType, graphData, ...props}) {
|
||||||
let lineBorderWidth = queryToolCtx.preferences.graphs['graph_line_border_width'];
|
let lineBorderWidth = queryToolCtx.preferences.graphs['graph_line_border_width'];
|
||||||
|
|
||||||
// Below options are used by chartjs while rendering the graph
|
// Below options are used by chartjs while rendering the graph
|
||||||
const options = useMemo(()=>({
|
const defaultOptions = useMemo(()=>({
|
||||||
elements: {
|
|
||||||
point: {
|
|
||||||
radius: showDataPoints ? DATA_POINT_SIZE : 0,
|
|
||||||
},
|
|
||||||
line: {
|
|
||||||
borderWidth: lineBorderWidth,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: {
|
legend: {
|
||||||
display: true,
|
display: true,
|
||||||
labels: {
|
labels: {
|
||||||
usePointStyle: (showDataPoints && useDiffPointStyle) ? true : false
|
usePointStyle: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
|
@ -94,35 +86,57 @@ function GenerateGraph({graphType, graphData, ...props}) {
|
||||||
zoom: {
|
zoom: {
|
||||||
pan: {
|
pan: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
mode: 'x',
|
|
||||||
modifierKey: 'ctrl',
|
|
||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
drag: {
|
drag: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
borderColor: 'rgb(54, 162, 235)',
|
}
|
||||||
borderWidth: 1,
|
|
||||||
backgroundColor: 'rgba(54, 162, 235, 0.3)'
|
|
||||||
},
|
|
||||||
mode: 'x',
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
x: {
|
x: {
|
||||||
display: true,
|
display: true,
|
||||||
|
stacked: false,
|
||||||
ticks: {
|
ticks: {
|
||||||
display: true,
|
display: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
y: {
|
||||||
|
stacked: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const lineChartOptions = useMemo(()=>({
|
||||||
|
elements: {
|
||||||
|
point: {
|
||||||
|
radius: showDataPoints ? DATA_POINT_SIZE : 0,
|
||||||
|
},
|
||||||
|
line: {
|
||||||
|
borderWidth: lineBorderWidth,
|
||||||
|
fill: '-1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
labels: {
|
||||||
|
usePointStyle: (showDataPoints && useDiffPointStyle) ? true : false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
if (_.isEmpty(graphData.datasets))
|
if (_.isEmpty(graphData.datasets))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (graphType == 'L') {
|
if (graphType == 'L' || graphType == 'SL') {
|
||||||
return <LineChart options={options} data={graphData} {...props}/>;
|
let options = _.merge(defaultOptions, lineChartOptions);
|
||||||
|
return <LineChart options={options} data={graphData} stacked={graphType == 'SL'}
|
||||||
|
{...props}/>;
|
||||||
|
} else if (graphType == 'B' || graphType == 'SB') {
|
||||||
|
return <BarChart options={defaultOptions} data={graphData} stacked={graphType == 'SB'}
|
||||||
|
{...props}/>;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -133,7 +147,7 @@ GenerateGraph.propTypes = {
|
||||||
};
|
};
|
||||||
|
|
||||||
// This function is used to get the data set for the X axis and Y axis
|
// This function is used to get the data set for the X axis and Y axis
|
||||||
function getGraphDataSet(rows, columns, xaxis, yaxis, queryToolCtx) {
|
function getGraphDataSet(graphType, rows, columns, xaxis, yaxis, queryToolCtx) {
|
||||||
// Function is used to the find the position of the column
|
// Function is used to the find the position of the column
|
||||||
function getColumnPosition(colName) {
|
function getColumnPosition(colName) {
|
||||||
return _.find(columns, (c)=>(c.name==colName))?.pos;
|
return _.find(columns, (c)=>(c.name==colName))?.pos;
|
||||||
|
@ -167,14 +181,24 @@ function getGraphDataSet(rows, columns, xaxis, yaxis, queryToolCtx) {
|
||||||
}
|
}
|
||||||
styleIndex = styleIndex + 1;
|
styleIndex = styleIndex + 1;
|
||||||
|
|
||||||
|
if (graphType !== 'L' && graphType !== 'SL') {
|
||||||
|
return {
|
||||||
|
label: colName,
|
||||||
|
data: rows.map((r)=>r[colPosition]),
|
||||||
|
backgroundColor: color,
|
||||||
|
borderColor:color
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
label: colName,
|
label: colName,
|
||||||
data: rows.map((r)=>r[colPosition]),
|
data: rows.map((r)=>r[colPosition]),
|
||||||
backgroundColor: color,
|
backgroundColor: graphType == 'SL' ? ConvertHexToRGBA(color, 30) : color,
|
||||||
borderColor:color,
|
borderColor:color,
|
||||||
pointHitRadius: DATA_POINT_SIZE,
|
pointHitRadius: DATA_POINT_SIZE,
|
||||||
pointHoverRadius: 5,
|
pointHoverRadius: 5,
|
||||||
pointStyle: queryToolCtx.preferences.graphs['use_diff_point_style'] ? DATA_POINT_STYLE[styleIndex] : 'circle'
|
pointStyle: queryToolCtx.preferences.graphs['use_diff_point_style'] ? DATA_POINT_STYLE[styleIndex] : 'circle',
|
||||||
|
fill: graphType == 'L' ? false : 'origin',
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
@ -189,7 +213,7 @@ export function GraphVisualiser({initColumns}) {
|
||||||
const [graphType, setGraphType] = useState('L');
|
const [graphType, setGraphType] = useState('L');
|
||||||
const [xaxis, setXAxis] = useState(null);
|
const [xaxis, setXAxis] = useState(null);
|
||||||
const [yaxis, setYAxis] = useState([]);
|
const [yaxis, setYAxis] = useState([]);
|
||||||
const [graphData, setGraphData] = useState({'datasets': []});
|
const [[graphData, graphDataKey], setGraphData] = useState([{'datasets': []}, 0]);
|
||||||
const [loaderText, setLoaderText] = useState('');
|
const [loaderText, setLoaderText] = useState('');
|
||||||
const [columns, setColumns] = useState(initColumns);
|
const [columns, setColumns] = useState(initColumns);
|
||||||
const [graphHeight, setGraphHeight] = useState();
|
const [graphHeight, setGraphHeight] = useState();
|
||||||
|
@ -237,7 +261,7 @@ export function GraphVisualiser({initColumns}) {
|
||||||
contentResizeObserver.observe(contentRef.current);
|
contentResizeObserver.observe(contentRef.current);
|
||||||
|
|
||||||
const resetGraphVisualiser = (newColumns)=>{
|
const resetGraphVisualiser = (newColumns)=>{
|
||||||
setGraphData({'datasets': []});
|
setGraphData([{'datasets': []}, 0]);
|
||||||
setXAxis(null);
|
setXAxis(null);
|
||||||
setYAxis([]);
|
setYAxis([]);
|
||||||
setColumns(newColumns);
|
setColumns(newColumns);
|
||||||
|
@ -267,7 +291,7 @@ export function GraphVisualiser({initColumns}) {
|
||||||
setLoaderText(gettext('Rendering data points...'));
|
setLoaderText(gettext('Rendering data points...'));
|
||||||
// Set the Graph Data
|
// Set the Graph Data
|
||||||
setGraphData(
|
setGraphData(
|
||||||
getGraphDataSet(res.data.data.result, columns, xaxis, yaxis, queryToolCtx)
|
(prev)=> [getGraphDataSet(graphType, res.data.data.result, columns, xaxis, yaxis, queryToolCtx), prev[1] + 1]
|
||||||
);
|
);
|
||||||
|
|
||||||
setLoaderText('');
|
setLoaderText('');
|
||||||
|
@ -315,7 +339,9 @@ export function GraphVisualiser({initColumns}) {
|
||||||
<InputSelect className={classes.selectCtrl} controlProps={{allowClear: false}}
|
<InputSelect className={classes.selectCtrl} controlProps={{allowClear: false}}
|
||||||
options={[
|
options={[
|
||||||
{label: gettext('Line Chart'), value: 'L'},
|
{label: gettext('Line Chart'), value: 'L'},
|
||||||
{label: gettext('<More charts coming soon>'), value: 'L'}
|
{label: gettext('Stacked Line Chart'), value: 'SL'},
|
||||||
|
{label: gettext('Bar Chart'), value: 'B'},
|
||||||
|
{label: gettext('Stacked Bar Chart'), value: 'SB'},
|
||||||
]} onChange={(v)=>setGraphType(v)} value={graphType} />
|
]} onChange={(v)=>setGraphType(v)} value={graphType} />
|
||||||
</Box>
|
</Box>
|
||||||
<DefaultButton onClick={onGenerate} startIcon={<ShowChartRoundedIcon />}
|
<DefaultButton onClick={onGenerate} startIcon={<ShowChartRoundedIcon />}
|
||||||
|
@ -339,9 +365,9 @@ export function GraphVisualiser({initColumns}) {
|
||||||
</Box>
|
</Box>
|
||||||
<Box ref={contentRef} className={classes.graphContainer}>
|
<Box ref={contentRef} className={classes.graphContainer}>
|
||||||
<Box style={{height:`${graphHeight}px`}}>
|
<Box style={{height:`${graphHeight}px`}}>
|
||||||
<GenerateGraph graphType={graphType} graphData={graphData} onInit={(chartObj)=> {
|
{useMemo(()=> <GenerateGraph graphType={graphType} graphData={graphData} onInit={(chartObj)=> {
|
||||||
chartObjRef.current = chartObj;
|
chartObjRef.current = chartObj;
|
||||||
}} plugins={plugin}/>
|
}} plugins={plugin}/>, [graphDataKey])}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
Loading…
Reference in New Issue